DividerSnapAlgorithm.java revision 737af724eb31f513386e91ee5510cc6991350937
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
66    public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond,
67            int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision,
68            Rect insets) {
69        mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond;
70        mDividerSize = dividerSize;
71        mDisplayWidth = displayWidth;
72        mDisplayHeight = displayHeight;
73        mInsets.set(insets);
74        mSnapMode = res.getInteger(
75                com.android.internal.R.integer.config_dockedStackDividerSnapMode);
76        mFixedRatio = res.getFraction(
77                com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
78        calculateTargets(isHorizontalDivision);
79        mFirstSplitTarget = mTargets.get(1);
80        mLastSplitTarget = mTargets.get(mTargets.size() - 2);
81        mDismissStartTarget = mTargets.get(0);
82        mDismissEndTarget = mTargets.get(mTargets.size() - 1);
83    }
84
85    public SnapTarget calculateSnapTarget(int position, float velocity) {
86        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
87            return snap(position);
88        }
89        if (position < mFirstSplitTarget.position && velocity < 0) {
90            return mDismissStartTarget;
91        }
92        if (position > mLastSplitTarget.position && velocity > 0) {
93            return mDismissEndTarget;
94        }
95        if (velocity < 0) {
96            return mFirstSplitTarget;
97        } else {
98            return mLastSplitTarget;
99        }
100    }
101
102    public SnapTarget calculateNonDismissingSnapTarget(int position) {
103        SnapTarget target = snap(position);
104        if (target == mDismissStartTarget) {
105            return mFirstSplitTarget;
106        } else if (target == mDismissEndTarget) {
107            return mLastSplitTarget;
108        } else {
109            return target;
110        }
111    }
112
113    public float calculateDismissingFraction(int position) {
114        if (position < mFirstSplitTarget.position) {
115            return 1f - (float) position / mFirstSplitTarget.position;
116        } else if (position > mLastSplitTarget.position) {
117            return (float) (position - mLastSplitTarget.position)
118                    / (mDismissEndTarget.position - mLastSplitTarget.position);
119        }
120        return 0f;
121    }
122
123    public SnapTarget getClosestDismissTarget(int position) {
124        if (position - mDismissStartTarget.position < mDismissEndTarget.position - position) {
125            return mDismissStartTarget;
126        } else {
127            return mDismissEndTarget;
128        }
129    }
130
131    public SnapTarget getFirstSplitTarget() {
132        return mFirstSplitTarget;
133    }
134
135    public SnapTarget getLastSplitTarget() {
136        return mLastSplitTarget;
137    }
138
139    public SnapTarget getDismissStartTarget() {
140        return mDismissStartTarget;
141    }
142
143    public SnapTarget getDismissEndTarget() {
144        return mDismissEndTarget;
145    }
146
147    private SnapTarget snap(int position) {
148        int minIndex = -1;
149        int minDistance = Integer.MAX_VALUE;
150        int size = mTargets.size();
151        for (int i = 0; i < size; i++) {
152            int distance = Math.abs(position - mTargets.get(i).position);
153            if (distance < minDistance) {
154                minIndex = i;
155                minDistance = distance;
156            }
157        }
158        return mTargets.get(minIndex);
159    }
160
161    private void calculateTargets(boolean isHorizontalDivision) {
162        mTargets.clear();
163        int dividerMax = isHorizontalDivision
164                ? mDisplayHeight
165                : mDisplayWidth;
166        mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
167        switch (mSnapMode) {
168            case SNAP_MODE_16_9:
169                addRatio16_9Targets(isHorizontalDivision);
170                break;
171            case SNAP_FIXED_RATIO:
172                addFixedDivisionTargets(isHorizontalDivision);
173                break;
174            case SNAP_ONLY_1_1:
175                addMiddleTarget(isHorizontalDivision);
176                break;
177        }
178        mTargets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
179    }
180
181    private void addFixedDivisionTargets(boolean isHorizontalDivision) {
182        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
183        int end = isHorizontalDivision
184                ? mDisplayHeight - mInsets.bottom
185                : mDisplayWidth - mInsets.right;
186        mTargets.add(new SnapTarget((int) (start + mFixedRatio * (end - start)) - mDividerSize / 2,
187                SnapTarget.FLAG_NONE));
188        addMiddleTarget(isHorizontalDivision);
189        mTargets.add(new SnapTarget((int) (start + (1 - mFixedRatio) * (end - start))
190                - mDividerSize / 2, SnapTarget.FLAG_NONE));
191    }
192
193    private void addRatio16_9Targets(boolean isHorizontalDivision) {
194        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
195        int end = isHorizontalDivision
196                ? mDisplayHeight - mInsets.bottom
197                : mDisplayWidth - mInsets.right;
198        int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
199        int endOther = isHorizontalDivision
200                ? mDisplayWidth - mInsets.right
201                : mDisplayHeight - mInsets.bottom;
202        float size = 9.0f / 16.0f * (endOther - startOther);
203        int sizeInt = (int) Math.floor(size);
204        mTargets.add(new SnapTarget(start + sizeInt, SnapTarget.FLAG_NONE));
205        addMiddleTarget(isHorizontalDivision);
206        mTargets.add(new SnapTarget(end - sizeInt - mDividerSize, SnapTarget.FLAG_NONE));
207    }
208
209    private void addMiddleTarget(boolean isHorizontalDivision) {
210        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
211        int end = isHorizontalDivision
212                ? mDisplayHeight - mInsets.bottom
213                : mDisplayWidth - mInsets.right;
214        mTargets.add(new SnapTarget(start + (end - start) / 2 - mDividerSize / 2,
215                SnapTarget.FLAG_NONE));
216    }
217
218    /**
219     * Represents a snap target for the divider.
220     */
221    public static class SnapTarget {
222        public static final int FLAG_NONE = 0;
223
224        /** If the divider reaches this value, the left/top task should be dismissed. */
225        public static final int FLAG_DISMISS_START = 1;
226
227        /** If the divider reaches this value, the right/bottom task should be dismissed */
228        public static final int FLAG_DISMISS_END = 2;
229
230        public final int position;
231        public final int flag;
232
233        public SnapTarget(int position, int flag) {
234            this.position = position;
235            this.flag = flag;
236        }
237    }
238}
239