11fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi/*
21fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * Copyright (C) 2015 The Android Open Source Project
31fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi *
41fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * Licensed under the Apache License, Version 2.0 (the "License");
51fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * you may not use this file except in compliance with the License.
61fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * You may obtain a copy of the License at
71fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi *
81fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi *      http://www.apache.org/licenses/LICENSE-2.0
91fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi *
101fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * Unless required by applicable law or agreed to in writing, software
111fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * distributed under the License is distributed on an "AS IS" BASIS,
121fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * See the License for the specific language governing permissions and
141fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * limitations under the License.
151fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi */
161fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
17737af724eb31f513386e91ee5510cc6991350937Jorim Jaggipackage com.android.internal.policy;
181fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
191fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggiimport android.content.Context;
2019cf2972582198484816ac15ba83a4f46946082bJorim Jaggiimport android.content.res.Configuration;
21737af724eb31f513386e91ee5510cc6991350937Jorim Jaggiimport android.content.res.Resources;
2281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggiimport android.graphics.Rect;
2319cf2972582198484816ac15ba83a4f46946082bJorim Jaggiimport android.hardware.display.DisplayManager;
24df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggiimport android.util.Log;
2519cf2972582198484816ac15ba83a4f46946082bJorim Jaggiimport android.view.Display;
2619cf2972582198484816ac15ba83a4f46946082bJorim Jaggiimport android.view.DisplayInfo;
271fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
281fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggiimport java.util.ArrayList;
291fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
301fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi/**
311fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * Calculates the snap targets and the snap position given a position and a velocity. All positions
321fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi * here are to be interpreted as the left/top edge of the divider rectangle.
33737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi *
34737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi * @hide
351fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi */
361fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggipublic class DividerSnapAlgorithm {
371fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
38df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    private static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
39df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    private static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
40df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi
4181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    /**
4281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi     * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
4381fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi     */
4481fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private static final int SNAP_MODE_16_9 = 0;
4581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi
4681fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    /**
4781fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi     * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
4881fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi     */
4981fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private static final int SNAP_FIXED_RATIO = 1;
5081fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi
5181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    /**
5281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi     * 1 snap target: 1:1
5381fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi     */
5481fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private static final int SNAP_ONLY_1_1 = 2;
5581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi
56737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi    private final float mMinFlingVelocityPxPerSecond;
57df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    private final float mMinDismissVelocityPxPerSecond;
5881fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private final int mDisplayWidth;
5981fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private final int mDisplayHeight;
601fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    private final int mDividerSize;
6181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
6281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private final Rect mInsets = new Rect();
6381fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private final int mSnapMode;
6419cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    private final int mMinimalSizeResizableTask;
6581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private final float mFixedRatio;
661b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi    private boolean mIsHorizontalDivision;
671fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
681fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    /** The first target which is still splitting the screen */
691fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    private final SnapTarget mFirstSplitTarget;
701fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
711fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    /** The last target which is still splitting the screen */
721fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    private final SnapTarget mLastSplitTarget;
731fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
741fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    private final SnapTarget mDismissStartTarget;
751fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    private final SnapTarget mDismissEndTarget;
76d434dcbfc9407baad28b6b40fea75b1b6050ad7eJorim Jaggi    private final SnapTarget mMiddleTarget;
771fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
7819cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    public static DividerSnapAlgorithm create(Context ctx, Rect insets) {
7919cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        DisplayInfo displayInfo = new DisplayInfo();
8019cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        ctx.getSystemService(DisplayManager.class).getDisplay(
8119cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                Display.DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
8219cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int dividerWindowWidth = ctx.getResources().getDimensionPixelSize(
8319cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                com.android.internal.R.dimen.docked_stack_divider_thickness);
8419cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int dividerInsets = ctx.getResources().getDimensionPixelSize(
8519cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                com.android.internal.R.dimen.docked_stack_divider_insets);
8619cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        return new DividerSnapAlgorithm(ctx.getResources(),
8719cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                displayInfo.logicalWidth, displayInfo.logicalHeight,
8819cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                dividerWindowWidth - 2 * dividerInsets,
899832f8f463a7d5448af2294bce05eac85f4dbef0Winson                ctx.getApplicationContext().getResources().getConfiguration().orientation
9019cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                        == Configuration.ORIENTATION_PORTRAIT,
9119cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                insets);
9219cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    }
9319cf2972582198484816ac15ba83a4f46946082bJorim Jaggi
94df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
95df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi            boolean isHorizontalDivision, Rect insets) {
96df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        mMinFlingVelocityPxPerSecond =
97df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi                MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
98df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        mMinDismissVelocityPxPerSecond =
99df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi                MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
1001fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        mDividerSize = dividerSize;
10181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        mDisplayWidth = displayWidth;
10281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        mDisplayHeight = displayHeight;
1031b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi        mIsHorizontalDivision = isHorizontalDivision;
10481fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        mInsets.set(insets);
105737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi        mSnapMode = res.getInteger(
10681fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                com.android.internal.R.integer.config_dockedStackDividerSnapMode);
107737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi        mFixedRatio = res.getFraction(
10881fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
10919cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        mMinimalSizeResizableTask = res.getDimensionPixelSize(
11019cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                com.android.internal.R.dimen.default_minimal_size_resizable_task);
11181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        calculateTargets(isHorizontalDivision);
1121fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        mFirstSplitTarget = mTargets.get(1);
1131fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        mLastSplitTarget = mTargets.get(mTargets.size() - 2);
1141fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        mDismissStartTarget = mTargets.get(0);
1151fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        mDismissEndTarget = mTargets.get(mTargets.size() - 1);
116d434dcbfc9407baad28b6b40fea75b1b6050ad7eJorim Jaggi        mMiddleTarget = mTargets.get(mTargets.size() / 2);
1171fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    }
1181fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
11919cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    /**
12019cf2972582198484816ac15ba83a4f46946082bJorim Jaggi     * @return whether it's feasible to enable split screen in the current configuration, i.e. when
12119cf2972582198484816ac15ba83a4f46946082bJorim Jaggi     *         snapping in the middle both tasks are larger than the minimal task size.
12219cf2972582198484816ac15ba83a4f46946082bJorim Jaggi     */
12319cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    public boolean isSplitScreenFeasible() {
12419cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int statusBarSize = mInsets.top;
12519cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
12619cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int size = mIsHorizontalDivision
12719cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                ? mDisplayHeight
12819cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                : mDisplayWidth;
12919cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
13019cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        return availableSpace / 2 >= mMinimalSizeResizableTask;
13119cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    }
13219cf2972582198484816ac15ba83a4f46946082bJorim Jaggi
1331fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    public SnapTarget calculateSnapTarget(int position, float velocity) {
134df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        return calculateSnapTarget(position, velocity, true /* hardDismiss */);
135df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    }
136df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi
137df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    /**
138df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi     * @param position the top/left position of the divider
139df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi     * @param velocity current dragging velocity
140df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi     * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
141df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi     */
142df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
143df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
1441fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            return mDismissStartTarget;
1451fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        }
146df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) {
1471fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            return mDismissEndTarget;
1481fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        }
149df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
150df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi            return snap(position, hardDismiss);
151df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        }
1521fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        if (velocity < 0) {
1531fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            return mFirstSplitTarget;
1541fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        } else {
1551fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            return mLastSplitTarget;
1561fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        }
1571fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    }
1581fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
159737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi    public SnapTarget calculateNonDismissingSnapTarget(int position) {
160df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        SnapTarget target = snap(position, false /* hardDismiss */);
161737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi        if (target == mDismissStartTarget) {
162737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi            return mFirstSplitTarget;
163737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi        } else if (target == mDismissEndTarget) {
164737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi            return mLastSplitTarget;
165737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi        } else {
166737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi            return target;
167737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi        }
168737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi    }
169737af724eb31f513386e91ee5510cc6991350937Jorim Jaggi
1705098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi    public float calculateDismissingFraction(int position) {
1715098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi        if (position < mFirstSplitTarget.position) {
1721b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi            return 1f - (float) (position - getStartInset())
1731b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi                    / (mFirstSplitTarget.position - getStartInset());
1745098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi        } else if (position > mLastSplitTarget.position) {
1755098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi            return (float) (position - mLastSplitTarget.position)
17681ba11eccbc2519338782100c13cf4a5909ad6beJorim Jaggi                    / (mDismissEndTarget.position - mLastSplitTarget.position - mDividerSize);
1775098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi        }
1785098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi        return 0f;
1795098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi    }
1805098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi
1815098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi    public SnapTarget getClosestDismissTarget(int position) {
1828f8155ba8a1c66d539101e55f2d06ae0422682c0Jorim Jaggi        if (position < mFirstSplitTarget.position) {
1838f8155ba8a1c66d539101e55f2d06ae0422682c0Jorim Jaggi            return mDismissStartTarget;
1848f8155ba8a1c66d539101e55f2d06ae0422682c0Jorim Jaggi        } else if (position > mLastSplitTarget.position) {
1858f8155ba8a1c66d539101e55f2d06ae0422682c0Jorim Jaggi            return mDismissEndTarget;
1868f8155ba8a1c66d539101e55f2d06ae0422682c0Jorim Jaggi        } else if (position - mDismissStartTarget.position
1878f8155ba8a1c66d539101e55f2d06ae0422682c0Jorim Jaggi                < mDismissEndTarget.position - position) {
1885098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi            return mDismissStartTarget;
1895098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi        } else {
1905098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi            return mDismissEndTarget;
1915098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi        }
1925098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi    }
1935098159ae31bc59aa3857fecb1847f8d7bb73e54Jorim Jaggi
194e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    public SnapTarget getFirstSplitTarget() {
195e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi        return mFirstSplitTarget;
196e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    }
197e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi
198e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    public SnapTarget getLastSplitTarget() {
199e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi        return mLastSplitTarget;
200e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    }
201e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi
202e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    public SnapTarget getDismissStartTarget() {
203e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi        return mDismissStartTarget;
204e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    }
205e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi
206e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    public SnapTarget getDismissEndTarget() {
207e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi        return mDismissEndTarget;
208e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi    }
209e435e982fa43832b183bac2d00d9415ac58ac06eJorim Jaggi
2101b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi    private int getStartInset() {
2111b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi        if (mIsHorizontalDivision) {
2121b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi            return mInsets.top;
2131b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi        } else {
2141b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi            return mInsets.left;
2151b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi        }
2161b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi    }
2171b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi
2181b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi    private int getEndInset() {
2191b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi        if (mIsHorizontalDivision) {
2201b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi            return mInsets.bottom;
2211b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi        } else {
2221b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi            return mInsets.right;
2231b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi        }
2241b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi    }
2251b12ef55b74f47eedde8afe22b997d40c3668a31Jorim Jaggi
226df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi    private SnapTarget snap(int position, boolean hardDismiss) {
2271fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        int minIndex = -1;
228df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        float minDistance = Float.MAX_VALUE;
2291fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        int size = mTargets.size();
2301fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        for (int i = 0; i < size; i++) {
231df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi            SnapTarget target = mTargets.get(i);
232df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi            float distance = Math.abs(position - target.position);
233df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi            if (hardDismiss) {
234df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi                distance /= target.distanceMultiplier;
235df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi            }
2361fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            if (distance < minDistance) {
2371fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi                minIndex = i;
2381fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi                minDistance = distance;
2391fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            }
2401fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        }
2411fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        return mTargets.get(minIndex);
2421fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    }
2431fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
24481fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private void calculateTargets(boolean isHorizontalDivision) {
24581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        mTargets.clear();
2461fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        int dividerMax = isHorizontalDivision
24781fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                ? mDisplayHeight
24881fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                : mDisplayWidth;
2498563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        mTargets.add(new SnapTarget(-mDividerSize, -mDividerSize, SnapTarget.FLAG_DISMISS_START,
2508563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi                0.35f));
25181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        switch (mSnapMode) {
25281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi            case SNAP_MODE_16_9:
25319cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                addRatio16_9Targets(isHorizontalDivision, dividerMax);
25481fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                break;
25581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi            case SNAP_FIXED_RATIO:
25619cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                addFixedDivisionTargets(isHorizontalDivision, dividerMax);
25781fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                break;
25881fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi            case SNAP_ONLY_1_1:
25981fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                addMiddleTarget(isHorizontalDivision);
26081fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                break;
26181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        }
26281ba11eccbc2519338782100c13cf4a5909ad6beJorim Jaggi        int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
2638563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
2648563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi                SnapTarget.FLAG_DISMISS_END, 0.35f));
26581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    }
2661fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
26719cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
26819cf2972582198484816ac15ba83a4f46946082bJorim Jaggi            int bottomPosition, int dividerMax) {
26919cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        maybeAddTarget(topPosition, topPosition - mInsets.top);
27019cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        addMiddleTarget(isHorizontalDivision);
27119cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        maybeAddTarget(bottomPosition, dividerMax - mInsets.bottom
27219cf2972582198484816ac15ba83a4f46946082bJorim Jaggi                - (bottomPosition + mDividerSize));
27319cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    }
2748563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi
27519cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
27681fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
27781fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        int end = isHorizontalDivision
27881fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                ? mDisplayHeight - mInsets.bottom
27981fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                : mDisplayWidth - mInsets.right;
2808563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
2818563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        int topPosition = start + size;
2828563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        int bottomPosition = end - size - mDividerSize;
28319cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
28481fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    }
28581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi
28619cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    private void addRatio16_9Targets(boolean isHorizontalDivision, int dividerMax) {
28781fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
28881fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        int end = isHorizontalDivision
28981fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                ? mDisplayHeight - mInsets.bottom
29081fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                : mDisplayWidth - mInsets.right;
29181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
29281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        int endOther = isHorizontalDivision
29381fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                ? mDisplayWidth - mInsets.right
29481fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi                : mDisplayHeight - mInsets.bottom;
29581fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        float size = 9.0f / 16.0f * (endOther - startOther);
29681fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi        int sizeInt = (int) Math.floor(size);
29719cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int topPosition = start + sizeInt;
29819cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        int bottomPosition = end - sizeInt - mDividerSize;
29919cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
30019cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    }
30119cf2972582198484816ac15ba83a4f46946082bJorim Jaggi
30219cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    /**
30319cf2972582198484816ac15ba83a4f46946082bJorim Jaggi     * Adds a target at {@param position} but only if the area with size of {@param smallerSize}
30419cf2972582198484816ac15ba83a4f46946082bJorim Jaggi     * meets the minimal size requirement.
30519cf2972582198484816ac15ba83a4f46946082bJorim Jaggi     */
30619cf2972582198484816ac15ba83a4f46946082bJorim Jaggi    private void maybeAddTarget(int position, int smallerSize) {
30719cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        if (smallerSize >= mMinimalSizeResizableTask) {
3088563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi            mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
30919cf2972582198484816ac15ba83a4f46946082bJorim Jaggi        }
31081fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    }
31181fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi
31281fe2d1f0adc9e752d7f1a410d66af6a326fd6e2Jorim Jaggi    private void addMiddleTarget(boolean isHorizontalDivision) {
3138563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
3148563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi                mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
3158563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
3161fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    }
3171fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
318d434dcbfc9407baad28b6b40fea75b1b6050ad7eJorim Jaggi    public SnapTarget getMiddleTarget() {
319d434dcbfc9407baad28b6b40fea75b1b6050ad7eJorim Jaggi        return mMiddleTarget;
320d434dcbfc9407baad28b6b40fea75b1b6050ad7eJorim Jaggi    }
321d434dcbfc9407baad28b6b40fea75b1b6050ad7eJorim Jaggi
32250cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi    public SnapTarget getNextTarget(SnapTarget snapTarget) {
32350cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        int index = mTargets.indexOf(snapTarget);
32450cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        if (index != -1 && index < mTargets.size() - 1) {
32550cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi            return mTargets.get(index + 1);
32650cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        }
32750cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        return snapTarget;
32850cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi    }
32950cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi
33050cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi    public SnapTarget getPreviousTarget(SnapTarget snapTarget) {
33150cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        int index = mTargets.indexOf(snapTarget);
33250cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        if (index != -1 && index > 0) {
33350cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi            return mTargets.get(index - 1);
33450cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        }
33550cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi        return snapTarget;
33650cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi    }
33750cd6361d7cb50bdc0ee199f42307885abc65f0bJorim Jaggi
338ce9630da86342c7bf30f00d1ced34a1d051c55baWinson    public boolean isFirstSplitTargetAvailable() {
339ce9630da86342c7bf30f00d1ced34a1d051c55baWinson        return mFirstSplitTarget != mMiddleTarget;
340ce9630da86342c7bf30f00d1ced34a1d051c55baWinson    }
341ce9630da86342c7bf30f00d1ced34a1d051c55baWinson
342ce9630da86342c7bf30f00d1ced34a1d051c55baWinson    public boolean isLastSplitTargetAvailable() {
343ce9630da86342c7bf30f00d1ced34a1d051c55baWinson        return mLastSplitTarget != mMiddleTarget;
344ce9630da86342c7bf30f00d1ced34a1d051c55baWinson    }
345ce9630da86342c7bf30f00d1ced34a1d051c55baWinson
3461fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    /**
347a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li     * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
348a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li     * if {@param increment} is negative and moves right otherwise.
349a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li     */
350a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li    public SnapTarget cycleNonDismissTarget(SnapTarget snapTarget, int increment) {
351a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li        int index = mTargets.indexOf(snapTarget);
352a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li        if (index != -1) {
353a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li            SnapTarget newTarget = mTargets.get((index + mTargets.size() + increment)
354a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li                    % mTargets.size());
355a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li            if (newTarget == mDismissStartTarget) {
356a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li                return mLastSplitTarget;
357a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li            } else if (newTarget == mDismissEndTarget) {
358a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li                return mFirstSplitTarget;
359a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li            } else {
360a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li                return newTarget;
361a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li            }
362a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li        }
363a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li        return snapTarget;
364a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li    }
365a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li
366a212999f245032f033e6a0993fd2be9832a9b9ccMuyuan Li    /**
3671fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi     * Represents a snap target for the divider.
3681fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi     */
3691fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    public static class SnapTarget {
3701fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        public static final int FLAG_NONE = 0;
3711fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
3721fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        /** If the divider reaches this value, the left/top task should be dismissed. */
3731fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        public static final int FLAG_DISMISS_START = 1;
3741fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
3751fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        /** If the divider reaches this value, the right/bottom task should be dismissed */
3761fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        public static final int FLAG_DISMISS_END = 2;
3771fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
3788563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        /** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
3791fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        public final int position;
3808563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi
3818563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        /**
3828563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi         * Like {@link #position}, but used to calculate the task bounds which might be different
3838563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi         * from the stack bounds.
3848563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi         */
3858563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        public final int taskPosition;
3868563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi
3871fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        public final int flag;
3881fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi
389df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        /**
390df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi         * Multiplier used to calculate distance to snap position. The lower this value, the harder
391df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi         * it's to snap on this target
392df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi         */
393df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        private final float distanceMultiplier;
394df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi
3958563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        public SnapTarget(int position, int taskPosition, int flag) {
3968563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi            this(position, taskPosition, flag, 1f);
397df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi        }
398df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi
3998563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi        public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
4001fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            this.position = position;
4018563943bd39107b5a7ff9ea475592d0040423ba1Jorim Jaggi            this.taskPosition = taskPosition;
4021fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi            this.flag = flag;
403df012d5102735412d9f38513c103aa53df4bcab9Jorim Jaggi            this.distanceMultiplier = distanceMultiplier;
4041fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi        }
4051fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi    }
4061fcbab6ae5c99acab70eacc015d194e2c6ddd4e2Jorim Jaggi}
407