DividerSnapAlgorithm.java revision 3e8747414520ee348cf4b9c4a6afd9ff80b5a8f8
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.policy;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.Rect;
22
23import java.util.ArrayList;
24
25/**
26 * Calculates the snap targets and the snap position given a position and a velocity. All positions
27 * here are to be interpreted as the left/top edge of the divider rectangle.
28 *
29 * @hide
30 */
31public class DividerSnapAlgorithm {
32
33    /**
34     * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
35     */
36    private static final int SNAP_MODE_16_9 = 0;
37
38    /**
39     * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
40     */
41    private static final int SNAP_FIXED_RATIO = 1;
42
43    /**
44     * 1 snap target: 1:1
45     */
46    private static final int SNAP_ONLY_1_1 = 2;
47
48    private final float mMinFlingVelocityPxPerSecond;
49    private final int mDisplayWidth;
50    private final int mDisplayHeight;
51    private final int mDividerSize;
52    private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
53    private final Rect mInsets = new Rect();
54    private final int mSnapMode;
55    private final float mFixedRatio;
56
57    /** The first target which is still splitting the screen */
58    private final SnapTarget mFirstSplitTarget;
59
60    /** The last target which is still splitting the screen */
61    private final SnapTarget mLastSplitTarget;
62
63    private final SnapTarget mDismissStartTarget;
64    private final SnapTarget mDismissEndTarget;
65    private final SnapTarget mMiddleTarget;
66
67    public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond,
68            int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision,
69            Rect insets) {
70        mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond;
71        mDividerSize = dividerSize;
72        mDisplayWidth = displayWidth;
73        mDisplayHeight = displayHeight;
74        mInsets.set(insets);
75        mSnapMode = res.getInteger(
76                com.android.internal.R.integer.config_dockedStackDividerSnapMode);
77        mFixedRatio = res.getFraction(
78                com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
79        calculateTargets(isHorizontalDivision);
80        mFirstSplitTarget = mTargets.get(1);
81        mLastSplitTarget = mTargets.get(mTargets.size() - 2);
82        mDismissStartTarget = mTargets.get(0);
83        mDismissEndTarget = mTargets.get(mTargets.size() - 1);
84        mMiddleTarget = mTargets.get(mTargets.size() / 2);
85    }
86
87    public SnapTarget calculateSnapTarget(int position, float velocity) {
88        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
89            return snap(position);
90        }
91        if (position < mFirstSplitTarget.position && velocity < 0) {
92            return mDismissStartTarget;
93        }
94        if (position > mLastSplitTarget.position && velocity > 0) {
95            return mDismissEndTarget;
96        }
97        if (velocity < 0) {
98            return mFirstSplitTarget;
99        } else {
100            return mLastSplitTarget;
101        }
102    }
103
104    public SnapTarget calculateNonDismissingSnapTarget(int position) {
105        SnapTarget target = snap(position);
106        if (target == mDismissStartTarget) {
107            return mFirstSplitTarget;
108        } else if (target == mDismissEndTarget) {
109            return mLastSplitTarget;
110        } else {
111            return target;
112        }
113    }
114
115    public float calculateDismissingFraction(int position) {
116        if (position < mFirstSplitTarget.position) {
117            return 1f - (float) position / mFirstSplitTarget.position;
118        } else if (position > mLastSplitTarget.position) {
119            return (float) (position - mLastSplitTarget.position)
120                    / (mDismissEndTarget.position - mLastSplitTarget.position);
121        }
122        return 0f;
123    }
124
125    public SnapTarget getClosestDismissTarget(int position) {
126        if (position - mDismissStartTarget.position < mDismissEndTarget.position - position) {
127            return mDismissStartTarget;
128        } else {
129            return mDismissEndTarget;
130        }
131    }
132
133    public SnapTarget getFirstSplitTarget() {
134        return mFirstSplitTarget;
135    }
136
137    public SnapTarget getLastSplitTarget() {
138        return mLastSplitTarget;
139    }
140
141    public SnapTarget getDismissStartTarget() {
142        return mDismissStartTarget;
143    }
144
145    public SnapTarget getDismissEndTarget() {
146        return mDismissEndTarget;
147    }
148
149    private SnapTarget snap(int position) {
150        int minIndex = -1;
151        int minDistance = Integer.MAX_VALUE;
152        int size = mTargets.size();
153        for (int i = 0; i < size; i++) {
154            int distance = Math.abs(position - mTargets.get(i).position);
155            if (distance < minDistance) {
156                minIndex = i;
157                minDistance = distance;
158            }
159        }
160        return mTargets.get(minIndex);
161    }
162
163    private void calculateTargets(boolean isHorizontalDivision) {
164        mTargets.clear();
165        int dividerMax = isHorizontalDivision
166                ? mDisplayHeight
167                : mDisplayWidth;
168        mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
169        switch (mSnapMode) {
170            case SNAP_MODE_16_9:
171                addRatio16_9Targets(isHorizontalDivision);
172                break;
173            case SNAP_FIXED_RATIO:
174                addFixedDivisionTargets(isHorizontalDivision);
175                break;
176            case SNAP_ONLY_1_1:
177                addMiddleTarget(isHorizontalDivision);
178                break;
179        }
180        mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
181    }
182
183    private void addFixedDivisionTargets(boolean isHorizontalDivision) {
184        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
185        int end = isHorizontalDivision
186                ? mDisplayHeight - mInsets.bottom
187                : mDisplayWidth - mInsets.right;
188        mTargets.add(new SnapTarget((int) (start + mFixedRatio * (end - start)) - mDividerSize / 2,
189                SnapTarget.FLAG_NONE));
190        addMiddleTarget(isHorizontalDivision);
191        mTargets.add(new SnapTarget((int) (start + (1 - mFixedRatio) * (end - start))
192                - mDividerSize / 2, SnapTarget.FLAG_NONE));
193    }
194
195    private void addRatio16_9Targets(boolean isHorizontalDivision) {
196        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
197        int end = isHorizontalDivision
198                ? mDisplayHeight - mInsets.bottom
199                : mDisplayWidth - mInsets.right;
200        int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
201        int endOther = isHorizontalDivision
202                ? mDisplayWidth - mInsets.right
203                : mDisplayHeight - mInsets.bottom;
204        float size = 9.0f / 16.0f * (endOther - startOther);
205        int sizeInt = (int) Math.floor(size);
206        mTargets.add(new SnapTarget(start + sizeInt, SnapTarget.FLAG_NONE));
207        addMiddleTarget(isHorizontalDivision);
208        mTargets.add(new SnapTarget(end - sizeInt - mDividerSize, SnapTarget.FLAG_NONE));
209    }
210
211    private void addMiddleTarget(boolean isHorizontalDivision) {
212        mTargets.add(new SnapTarget(DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
213                mInsets, mDisplayWidth, mDisplayHeight, mDividerSize), SnapTarget.FLAG_NONE));
214    }
215
216    public SnapTarget getMiddleTarget() {
217        return mMiddleTarget;
218    }
219
220    /**
221     * Represents a snap target for the divider.
222     */
223    public static class SnapTarget {
224        public static final int FLAG_NONE = 0;
225
226        /** If the divider reaches this value, the left/top task should be dismissed. */
227        public static final int FLAG_DISMISS_START = 1;
228
229        /** If the divider reaches this value, the right/bottom task should be dismissed */
230        public static final int FLAG_DISMISS_END = 2;
231
232        public final int position;
233        public final int flag;
234
235        public SnapTarget(int position, int flag) {
236            this.position = position;
237            this.flag = flag;
238        }
239    }
240}
241