blob: 497e7b08d881ab1fe594d3808b826b93b5cc71da [file] [log] [blame]
Clara Bayarri838e36c2015-03-17 23:10:44 +00001/*
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.view;
18
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010019import android.annotation.NonNull;
Clara Bayarri838e36c2015-03-17 23:10:44 +000020import android.content.Context;
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -070021import android.graphics.Point;
Clara Bayarri838e36c2015-03-17 23:10:44 +000022import android.graphics.Rect;
23import android.view.ActionMode;
24import android.view.Menu;
25import android.view.MenuInflater;
26import android.view.MenuItem;
27import android.view.View;
Abodunrinwa Toki9e211282015-06-05 02:46:57 +010028import android.view.ViewConfiguration;
Justin Klaassen6183b722016-04-26 20:19:07 -070029import android.view.ViewGroup;
30import android.view.ViewParent;
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -070031import android.view.WindowManager;
Clara Bayarri838e36c2015-03-17 23:10:44 +000032
Abodunrinwa Toki15a6c892015-06-16 23:17:19 +010033import com.android.internal.R;
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +010034import com.android.internal.util.Preconditions;
Clara Bayarri838e36c2015-03-17 23:10:44 +000035import com.android.internal.view.menu.MenuBuilder;
36import com.android.internal.widget.FloatingToolbar;
37
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -070038import java.util.Arrays;
39
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010040public final class FloatingActionMode extends ActionMode {
Clara Bayarri838e36c2015-03-17 23:10:44 +000041
Abodunrinwa Toki9e211282015-06-05 02:46:57 +010042 private static final int MAX_HIDE_DURATION = 3000;
Abodunrinwa Tokib9acbe42015-09-16 19:47:38 +010043 private static final int MOVING_HIDE_DELAY = 50;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010044
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010045 @NonNull private final Context mContext;
46 @NonNull private final ActionMode.Callback2 mCallback;
47 @NonNull private final MenuBuilder mMenu;
48 @NonNull private final Rect mContentRect;
49 @NonNull private final Rect mContentRectOnScreen;
50 @NonNull private final Rect mPreviousContentRectOnScreen;
51 @NonNull private final int[] mViewPositionOnScreen;
52 @NonNull private final int[] mPreviousViewPositionOnScreen;
53 @NonNull private final int[] mRootViewPositionOnScreen;
54 @NonNull private final Rect mViewRectOnScreen;
55 @NonNull private final Rect mPreviousViewRectOnScreen;
56 @NonNull private final Rect mScreenRect;
57 @NonNull private final View mOriginatingView;
58 @NonNull private final Point mDisplaySize;
Abodunrinwa Toki15a6c892015-06-16 23:17:19 +010059 private final int mBottomAllowance;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010060
61 private final Runnable mMovingOff = new Runnable() {
62 public void run() {
Abodunrinwa Tokid1eb19c2016-07-21 11:10:19 +010063 if (isViewStillActive()) {
64 mFloatingToolbarVisibilityHelper.setMoving(false);
65 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
66 }
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010067 }
68 };
69
Abodunrinwa Toki9e211282015-06-05 02:46:57 +010070 private final Runnable mHideOff = new Runnable() {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010071 public void run() {
Abodunrinwa Tokid1eb19c2016-07-21 11:10:19 +010072 if (isViewStillActive()) {
73 mFloatingToolbarVisibilityHelper.setHideRequested(false);
74 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
75 }
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010076 }
77 };
78
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010079 @NonNull private FloatingToolbar mFloatingToolbar;
80 @NonNull private FloatingToolbarVisibilityHelper mFloatingToolbarVisibilityHelper;
Clara Bayarri838e36c2015-03-17 23:10:44 +000081
82 public FloatingActionMode(
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010083 Context context, ActionMode.Callback2 callback,
84 View originatingView, FloatingToolbar floatingToolbar) {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010085 mContext = Preconditions.checkNotNull(context);
86 mCallback = Preconditions.checkNotNull(callback);
Clara Bayarri838e36c2015-03-17 23:10:44 +000087 mMenu = new MenuBuilder(context).setDefaultShowAsAction(
88 MenuItem.SHOW_AS_ACTION_IF_ROOM);
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +010089 setType(ActionMode.TYPE_FLOATING);
Clara Bayarriea2d6442015-09-01 11:26:46 +010090 mMenu.setCallback(new MenuBuilder.Callback() {
91 @Override
92 public void onMenuModeChange(MenuBuilder menu) {}
93
94 @Override
95 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
96 return mCallback.onActionItemClicked(FloatingActionMode.this, item);
97 }
98 });
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +010099 mContentRect = new Rect();
Yohei Yukawabafc9082015-07-14 05:59:05 -0700100 mContentRectOnScreen = new Rect();
101 mPreviousContentRectOnScreen = new Rect();
102 mViewPositionOnScreen = new int[2];
103 mPreviousViewPositionOnScreen = new int[2];
104 mRootViewPositionOnScreen = new int[2];
105 mViewRectOnScreen = new Rect();
106 mPreviousViewRectOnScreen = new Rect();
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100107 mScreenRect = new Rect();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100108 mOriginatingView = Preconditions.checkNotNull(originatingView);
Yohei Yukawabafc9082015-07-14 05:59:05 -0700109 mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
Abodunrinwa Toki15a6c892015-06-16 23:17:19 +0100110 // Allow the content rect to overshoot a little bit beyond the
111 // bottom view bound if necessary.
112 mBottomAllowance = context.getResources()
113 .getDimensionPixelSize(R.dimen.content_rect_bottom_clip_allowance);
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -0700114 mDisplaySize = new Point();
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +0100115 setFloatingToolbar(Preconditions.checkNotNull(floatingToolbar));
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +0100116 }
117
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +0100118 private void setFloatingToolbar(FloatingToolbar floatingToolbar) {
Clara Bayarri838e36c2015-03-17 23:10:44 +0000119 mFloatingToolbar = floatingToolbar
120 .setMenu(mMenu)
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +0100121 .setOnMenuItemClickListener(item -> mMenu.performItemAction(item, 0));
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100122 mFloatingToolbarVisibilityHelper = new FloatingToolbarVisibilityHelper(mFloatingToolbar);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100123 mFloatingToolbarVisibilityHelper.activate();
Clara Bayarri838e36c2015-03-17 23:10:44 +0000124 }
125
126 @Override
127 public void setTitle(CharSequence title) {}
128
129 @Override
130 public void setTitle(int resId) {}
131
132 @Override
133 public void setSubtitle(CharSequence subtitle) {}
134
135 @Override
136 public void setSubtitle(int resId) {}
137
138 @Override
139 public void setCustomView(View view) {}
140
141 @Override
142 public void invalidate() {
143 mCallback.onPrepareActionMode(this, mMenu);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100144 invalidateContentRect(); // Will re-layout and show the toolbar if necessary.
Clara Bayarri838e36c2015-03-17 23:10:44 +0000145 }
146
147 @Override
148 public void invalidateContentRect() {
149 mCallback.onGetContentRect(this, mOriginatingView, mContentRect);
150 repositionToolbar();
151 }
152
153 public void updateViewLocationInWindow() {
Yohei Yukawabafc9082015-07-14 05:59:05 -0700154 mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
155 mOriginatingView.getRootView().getLocationOnScreen(mRootViewPositionOnScreen);
156 mOriginatingView.getGlobalVisibleRect(mViewRectOnScreen);
157 mViewRectOnScreen.offset(mRootViewPositionOnScreen[0], mRootViewPositionOnScreen[1]);
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -0700158
Yohei Yukawabafc9082015-07-14 05:59:05 -0700159 if (!Arrays.equals(mViewPositionOnScreen, mPreviousViewPositionOnScreen)
160 || !mViewRectOnScreen.equals(mPreviousViewRectOnScreen)) {
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -0700161 repositionToolbar();
Yohei Yukawabafc9082015-07-14 05:59:05 -0700162 mPreviousViewPositionOnScreen[0] = mViewPositionOnScreen[0];
163 mPreviousViewPositionOnScreen[1] = mViewPositionOnScreen[1];
164 mPreviousViewRectOnScreen.set(mViewRectOnScreen);
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -0700165 }
Clara Bayarri838e36c2015-03-17 23:10:44 +0000166 }
167
168 private void repositionToolbar() {
Yohei Yukawabafc9082015-07-14 05:59:05 -0700169 mContentRectOnScreen.set(mContentRect);
Justin Klaassen6183b722016-04-26 20:19:07 -0700170
171 // Offset the content rect into screen coordinates, taking into account any transformations
172 // that may be applied to the originating view or its ancestors.
173 final ViewParent parent = mOriginatingView.getParent();
174 if (parent instanceof ViewGroup) {
175 ((ViewGroup) parent).getChildVisibleRect(
Abodunrinwa Toki4e7a1202016-05-03 18:23:12 +0100176 mOriginatingView, mContentRectOnScreen,
177 null /* offset */, true /* forceParentCheck */);
Justin Klaassen6183b722016-04-26 20:19:07 -0700178 mContentRectOnScreen.offset(mRootViewPositionOnScreen[0], mRootViewPositionOnScreen[1]);
179 } else {
180 mContentRectOnScreen.offset(mViewPositionOnScreen[0], mViewPositionOnScreen[1]);
181 }
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100182
183 if (isContentRectWithinBounds()) {
184 mFloatingToolbarVisibilityHelper.setOutOfBounds(false);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100185 // Make sure that content rect is not out of the view's visible bounds.
Yohei Yukawabafc9082015-07-14 05:59:05 -0700186 mContentRectOnScreen.set(
187 Math.max(mContentRectOnScreen.left, mViewRectOnScreen.left),
188 Math.max(mContentRectOnScreen.top, mViewRectOnScreen.top),
189 Math.min(mContentRectOnScreen.right, mViewRectOnScreen.right),
190 Math.min(mContentRectOnScreen.bottom,
191 mViewRectOnScreen.bottom + mBottomAllowance));
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100192
Yohei Yukawabafc9082015-07-14 05:59:05 -0700193 if (!mContentRectOnScreen.equals(mPreviousContentRectOnScreen)) {
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100194 // Content rect is moving.
195 mOriginatingView.removeCallbacks(mMovingOff);
196 mFloatingToolbarVisibilityHelper.setMoving(true);
197 mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY);
198
Yohei Yukawabafc9082015-07-14 05:59:05 -0700199 mFloatingToolbar.setContentRect(mContentRectOnScreen);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100200 mFloatingToolbar.updateLayout();
201 }
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100202 } else {
203 mFloatingToolbarVisibilityHelper.setOutOfBounds(true);
Yohei Yukawabafc9082015-07-14 05:59:05 -0700204 mContentRectOnScreen.setEmpty();
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100205 }
Abodunrinwa Tokib9acbe42015-09-16 19:47:38 +0100206 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100207
Yohei Yukawabafc9082015-07-14 05:59:05 -0700208 mPreviousContentRectOnScreen.set(mContentRectOnScreen);
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100209 }
210
211 private boolean isContentRectWithinBounds() {
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -0700212 mContext.getSystemService(WindowManager.class)
213 .getDefaultDisplay().getRealSize(mDisplaySize);
214 mScreenRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100215
Clara Bayarrif95ed102015-08-12 19:46:47 +0100216 return intersectsClosed(mContentRectOnScreen, mScreenRect)
217 && intersectsClosed(mContentRectOnScreen, mViewRectOnScreen);
218 }
219
220 /*
221 * Same as Rect.intersects, but includes cases where the rectangles touch.
222 */
223 private static boolean intersectsClosed(Rect a, Rect b) {
224 return a.left <= b.right && b.left <= a.right
225 && a.top <= b.bottom && b.top <= a.bottom;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100226 }
227
228 @Override
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100229 public void hide(long duration) {
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100230 if (duration == ActionMode.DEFAULT_HIDE_DURATION) {
231 duration = ViewConfiguration.getDefaultActionModeHideDuration();
232 }
233 duration = Math.min(MAX_HIDE_DURATION, duration);
234 mOriginatingView.removeCallbacks(mHideOff);
235 if (duration <= 0) {
236 mHideOff.run();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100237 } else {
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100238 mFloatingToolbarVisibilityHelper.setHideRequested(true);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100239 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100240 mOriginatingView.postDelayed(mHideOff, duration);
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100241 }
242 }
243
Clara Bayarri838e36c2015-03-17 23:10:44 +0000244 @Override
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100245 public void onWindowFocusChanged(boolean hasWindowFocus) {
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100246 mFloatingToolbarVisibilityHelper.setWindowFocused(hasWindowFocus);
247 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
248 }
249
250 @Override
Clara Bayarri838e36c2015-03-17 23:10:44 +0000251 public void finish() {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100252 reset();
Clara Bayarri838e36c2015-03-17 23:10:44 +0000253 mCallback.onDestroyActionMode(this);
254 }
255
256 @Override
257 public Menu getMenu() {
258 return mMenu;
259 }
260
261 @Override
262 public CharSequence getTitle() {
263 return null;
264 }
265
266 @Override
267 public CharSequence getSubtitle() {
268 return null;
269 }
270
271 @Override
272 public View getCustomView() {
273 return null;
274 }
275
276 @Override
277 public MenuInflater getMenuInflater() {
278 return new MenuInflater(mContext);
279 }
280
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100281 private void reset() {
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100282 mFloatingToolbar.dismiss();
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100283 mFloatingToolbarVisibilityHelper.deactivate();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100284 mOriginatingView.removeCallbacks(mMovingOff);
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100285 mOriginatingView.removeCallbacks(mHideOff);
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100286 }
287
Abodunrinwa Tokid1eb19c2016-07-21 11:10:19 +0100288 private boolean isViewStillActive() {
289 return mOriginatingView.getWindowVisibility() == View.VISIBLE
290 && mOriginatingView.isShown();
291 }
292
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100293 /**
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100294 * A helper for showing/hiding the floating toolbar depending on certain states.
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100295 */
296 private static final class FloatingToolbarVisibilityHelper {
297
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100298 private static final long MIN_SHOW_DURATION_FOR_MOVE_HIDE = 500;
299
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100300 private final FloatingToolbar mToolbar;
301
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100302 private boolean mHideRequested;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100303 private boolean mMoving;
304 private boolean mOutOfBounds;
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100305 private boolean mWindowFocused = true;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100306
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100307 private boolean mActive;
308
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100309 private long mLastShowTime;
310
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100311 public FloatingToolbarVisibilityHelper(FloatingToolbar toolbar) {
312 mToolbar = Preconditions.checkNotNull(toolbar);
313 }
314
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100315 public void activate() {
316 mHideRequested = false;
317 mMoving = false;
318 mOutOfBounds = false;
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100319 mWindowFocused = true;
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100320
321 mActive = true;
322 }
323
324 public void deactivate() {
325 mActive = false;
326 mToolbar.dismiss();
327 }
328
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100329 public void setHideRequested(boolean hide) {
330 mHideRequested = hide;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100331 }
332
333 public void setMoving(boolean moving) {
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100334 // Avoid unintended flickering by allowing the toolbar to show long enough before
335 // triggering the 'moving' flag - which signals a hide.
336 final boolean showingLongEnough =
337 System.currentTimeMillis() - mLastShowTime > MIN_SHOW_DURATION_FOR_MOVE_HIDE;
338 if (!moving || showingLongEnough) {
339 mMoving = moving;
340 }
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100341 }
342
343 public void setOutOfBounds(boolean outOfBounds) {
344 mOutOfBounds = outOfBounds;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100345 }
346
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100347 public void setWindowFocused(boolean windowFocused) {
348 mWindowFocused = windowFocused;
349 }
350
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100351 public void updateToolbarVisibility() {
352 if (!mActive) {
353 return;
354 }
355
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100356 if (mHideRequested || mMoving || mOutOfBounds || !mWindowFocused) {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100357 mToolbar.hide();
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100358 } else {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100359 mToolbar.show();
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100360 mLastShowTime = System.currentTimeMillis();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100361 }
362 }
363 }
Clara Bayarri838e36c2015-03-17 23:10:44 +0000364}