blob: 54dede6753e4131254d64082fc4f88af2240bb86 [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;
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +010020import android.annotation.Nullable;
Clara Bayarri838e36c2015-03-17 23:10:44 +000021import android.content.Context;
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -070022import android.graphics.Point;
Clara Bayarri838e36c2015-03-17 23:10:44 +000023import android.graphics.Rect;
24import android.view.ActionMode;
25import android.view.Menu;
26import android.view.MenuInflater;
27import android.view.MenuItem;
28import android.view.View;
Abodunrinwa Toki9e211282015-06-05 02:46:57 +010029import android.view.ViewConfiguration;
Justin Klaassen6183b722016-04-26 20:19:07 -070030import android.view.ViewGroup;
31import android.view.ViewParent;
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -070032import android.view.WindowManager;
Clara Bayarri838e36c2015-03-17 23:10:44 +000033
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +010034import android.widget.PopupWindow;
Abodunrinwa Toki15a6c892015-06-16 23:17:19 +010035import com.android.internal.R;
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +010036import com.android.internal.util.Preconditions;
Clara Bayarri838e36c2015-03-17 23:10:44 +000037import com.android.internal.view.menu.MenuBuilder;
38import com.android.internal.widget.FloatingToolbar;
39
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -070040import java.util.Arrays;
41
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010042public final class FloatingActionMode extends ActionMode {
Clara Bayarri838e36c2015-03-17 23:10:44 +000043
Abodunrinwa Toki9e211282015-06-05 02:46:57 +010044 private static final int MAX_HIDE_DURATION = 3000;
Abodunrinwa Tokib9acbe42015-09-16 19:47:38 +010045 private static final int MOVING_HIDE_DELAY = 50;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010046
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010047 @NonNull private final Context mContext;
48 @NonNull private final ActionMode.Callback2 mCallback;
49 @NonNull private final MenuBuilder mMenu;
50 @NonNull private final Rect mContentRect;
51 @NonNull private final Rect mContentRectOnScreen;
52 @NonNull private final Rect mPreviousContentRectOnScreen;
53 @NonNull private final int[] mViewPositionOnScreen;
54 @NonNull private final int[] mPreviousViewPositionOnScreen;
55 @NonNull private final int[] mRootViewPositionOnScreen;
56 @NonNull private final Rect mViewRectOnScreen;
57 @NonNull private final Rect mPreviousViewRectOnScreen;
58 @NonNull private final Rect mScreenRect;
59 @NonNull private final View mOriginatingView;
60 @NonNull private final Point mDisplaySize;
Abodunrinwa Toki15a6c892015-06-16 23:17:19 +010061 private final int mBottomAllowance;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010062
63 private final Runnable mMovingOff = new Runnable() {
64 public void run() {
Abodunrinwa Tokid1eb19c2016-07-21 11:10:19 +010065 if (isViewStillActive()) {
66 mFloatingToolbarVisibilityHelper.setMoving(false);
67 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
68 }
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010069 }
70 };
71
Abodunrinwa Toki9e211282015-06-05 02:46:57 +010072 private final Runnable mHideOff = new Runnable() {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010073 public void run() {
Abodunrinwa Tokid1eb19c2016-07-21 11:10:19 +010074 if (isViewStillActive()) {
75 mFloatingToolbarVisibilityHelper.setHideRequested(false);
76 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
77 }
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010078 }
79 };
80
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010081 @NonNull private FloatingToolbar mFloatingToolbar;
82 @NonNull private FloatingToolbarVisibilityHelper mFloatingToolbarVisibilityHelper;
Clara Bayarri838e36c2015-03-17 23:10:44 +000083
84 public FloatingActionMode(
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +010085 Context context, ActionMode.Callback2 callback,
86 View originatingView, FloatingToolbar floatingToolbar) {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +010087 mContext = Preconditions.checkNotNull(context);
88 mCallback = Preconditions.checkNotNull(callback);
Clara Bayarri838e36c2015-03-17 23:10:44 +000089 mMenu = new MenuBuilder(context).setDefaultShowAsAction(
90 MenuItem.SHOW_AS_ACTION_IF_ROOM);
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +010091 setType(ActionMode.TYPE_FLOATING);
Clara Bayarriea2d6442015-09-01 11:26:46 +010092 mMenu.setCallback(new MenuBuilder.Callback() {
93 @Override
94 public void onMenuModeChange(MenuBuilder menu) {}
95
96 @Override
97 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
98 return mCallback.onActionItemClicked(FloatingActionMode.this, item);
99 }
100 });
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +0100101 mContentRect = new Rect();
Yohei Yukawabafc9082015-07-14 05:59:05 -0700102 mContentRectOnScreen = new Rect();
103 mPreviousContentRectOnScreen = new Rect();
104 mViewPositionOnScreen = new int[2];
105 mPreviousViewPositionOnScreen = new int[2];
106 mRootViewPositionOnScreen = new int[2];
107 mViewRectOnScreen = new Rect();
108 mPreviousViewRectOnScreen = new Rect();
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100109 mScreenRect = new Rect();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100110 mOriginatingView = Preconditions.checkNotNull(originatingView);
Yohei Yukawabafc9082015-07-14 05:59:05 -0700111 mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
Abodunrinwa Toki15a6c892015-06-16 23:17:19 +0100112 // Allow the content rect to overshoot a little bit beyond the
113 // bottom view bound if necessary.
114 mBottomAllowance = context.getResources()
115 .getDimensionPixelSize(R.dimen.content_rect_bottom_clip_allowance);
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -0700116 mDisplaySize = new Point();
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +0100117 setFloatingToolbar(Preconditions.checkNotNull(floatingToolbar));
Clara Bayarri0d7d4ef2015-05-13 15:12:12 +0100118 }
119
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +0100120 private void setFloatingToolbar(FloatingToolbar floatingToolbar) {
Clara Bayarri838e36c2015-03-17 23:10:44 +0000121 mFloatingToolbar = floatingToolbar
122 .setMenu(mMenu)
Abodunrinwa Toki17293cc2017-05-22 14:16:04 +0100123 .setOnMenuItemClickListener(item -> mMenu.performItemAction(item, 0));
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100124 mFloatingToolbarVisibilityHelper = new FloatingToolbarVisibilityHelper(mFloatingToolbar);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100125 mFloatingToolbarVisibilityHelper.activate();
Clara Bayarri838e36c2015-03-17 23:10:44 +0000126 }
127
128 @Override
129 public void setTitle(CharSequence title) {}
130
131 @Override
132 public void setTitle(int resId) {}
133
134 @Override
135 public void setSubtitle(CharSequence subtitle) {}
136
137 @Override
138 public void setSubtitle(int resId) {}
139
140 @Override
141 public void setCustomView(View view) {}
142
143 @Override
144 public void invalidate() {
145 mCallback.onPrepareActionMode(this, mMenu);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100146 invalidateContentRect(); // Will re-layout and show the toolbar if necessary.
Clara Bayarri838e36c2015-03-17 23:10:44 +0000147 }
148
149 @Override
150 public void invalidateContentRect() {
151 mCallback.onGetContentRect(this, mOriginatingView, mContentRect);
152 repositionToolbar();
153 }
154
155 public void updateViewLocationInWindow() {
Yohei Yukawabafc9082015-07-14 05:59:05 -0700156 mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
157 mOriginatingView.getRootView().getLocationOnScreen(mRootViewPositionOnScreen);
158 mOriginatingView.getGlobalVisibleRect(mViewRectOnScreen);
159 mViewRectOnScreen.offset(mRootViewPositionOnScreen[0], mRootViewPositionOnScreen[1]);
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -0700160
Yohei Yukawabafc9082015-07-14 05:59:05 -0700161 if (!Arrays.equals(mViewPositionOnScreen, mPreviousViewPositionOnScreen)
162 || !mViewRectOnScreen.equals(mPreviousViewRectOnScreen)) {
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -0700163 repositionToolbar();
Yohei Yukawabafc9082015-07-14 05:59:05 -0700164 mPreviousViewPositionOnScreen[0] = mViewPositionOnScreen[0];
165 mPreviousViewPositionOnScreen[1] = mViewPositionOnScreen[1];
166 mPreviousViewRectOnScreen.set(mViewRectOnScreen);
Abodunrinwa Tokic107b0e2015-06-25 21:33:51 -0700167 }
Clara Bayarri838e36c2015-03-17 23:10:44 +0000168 }
169
170 private void repositionToolbar() {
Yohei Yukawabafc9082015-07-14 05:59:05 -0700171 mContentRectOnScreen.set(mContentRect);
Justin Klaassen6183b722016-04-26 20:19:07 -0700172
173 // Offset the content rect into screen coordinates, taking into account any transformations
174 // that may be applied to the originating view or its ancestors.
175 final ViewParent parent = mOriginatingView.getParent();
176 if (parent instanceof ViewGroup) {
177 ((ViewGroup) parent).getChildVisibleRect(
Abodunrinwa Toki4e7a1202016-05-03 18:23:12 +0100178 mOriginatingView, mContentRectOnScreen,
179 null /* offset */, true /* forceParentCheck */);
Justin Klaassen6183b722016-04-26 20:19:07 -0700180 mContentRectOnScreen.offset(mRootViewPositionOnScreen[0], mRootViewPositionOnScreen[1]);
181 } else {
182 mContentRectOnScreen.offset(mViewPositionOnScreen[0], mViewPositionOnScreen[1]);
183 }
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100184
185 if (isContentRectWithinBounds()) {
186 mFloatingToolbarVisibilityHelper.setOutOfBounds(false);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100187 // Make sure that content rect is not out of the view's visible bounds.
Yohei Yukawabafc9082015-07-14 05:59:05 -0700188 mContentRectOnScreen.set(
189 Math.max(mContentRectOnScreen.left, mViewRectOnScreen.left),
190 Math.max(mContentRectOnScreen.top, mViewRectOnScreen.top),
191 Math.min(mContentRectOnScreen.right, mViewRectOnScreen.right),
192 Math.min(mContentRectOnScreen.bottom,
193 mViewRectOnScreen.bottom + mBottomAllowance));
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100194
Yohei Yukawabafc9082015-07-14 05:59:05 -0700195 if (!mContentRectOnScreen.equals(mPreviousContentRectOnScreen)) {
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100196 // Content rect is moving.
197 mOriginatingView.removeCallbacks(mMovingOff);
198 mFloatingToolbarVisibilityHelper.setMoving(true);
199 mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY);
200
Yohei Yukawabafc9082015-07-14 05:59:05 -0700201 mFloatingToolbar.setContentRect(mContentRectOnScreen);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100202 mFloatingToolbar.updateLayout();
203 }
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100204 } else {
205 mFloatingToolbarVisibilityHelper.setOutOfBounds(true);
Yohei Yukawabafc9082015-07-14 05:59:05 -0700206 mContentRectOnScreen.setEmpty();
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100207 }
Abodunrinwa Tokib9acbe42015-09-16 19:47:38 +0100208 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100209
Yohei Yukawabafc9082015-07-14 05:59:05 -0700210 mPreviousContentRectOnScreen.set(mContentRectOnScreen);
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100211 }
212
213 private boolean isContentRectWithinBounds() {
Abodunrinwa Toki49f1a762016-08-03 20:07:15 -0700214 mContext.getSystemService(WindowManager.class)
215 .getDefaultDisplay().getRealSize(mDisplaySize);
216 mScreenRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
Abodunrinwa Tokia8151e42015-05-06 18:57:06 +0100217
Clara Bayarrif95ed102015-08-12 19:46:47 +0100218 return intersectsClosed(mContentRectOnScreen, mScreenRect)
219 && intersectsClosed(mContentRectOnScreen, mViewRectOnScreen);
220 }
221
222 /*
223 * Same as Rect.intersects, but includes cases where the rectangles touch.
224 */
225 private static boolean intersectsClosed(Rect a, Rect b) {
226 return a.left <= b.right && b.left <= a.right
227 && a.top <= b.bottom && b.top <= a.bottom;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100228 }
229
230 @Override
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100231 public void hide(long duration) {
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100232 if (duration == ActionMode.DEFAULT_HIDE_DURATION) {
233 duration = ViewConfiguration.getDefaultActionModeHideDuration();
234 }
235 duration = Math.min(MAX_HIDE_DURATION, duration);
236 mOriginatingView.removeCallbacks(mHideOff);
237 if (duration <= 0) {
238 mHideOff.run();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100239 } else {
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100240 mFloatingToolbarVisibilityHelper.setHideRequested(true);
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100241 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100242 mOriginatingView.postDelayed(mHideOff, duration);
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100243 }
244 }
245
Abodunrinwa Toki29cb7682018-04-11 21:24:20 +0100246 /**
247 * If this is set to true, the action mode view will dismiss itself on touch events outside of
248 * its window. This only makes sense if the action mode view is a PopupWindow that is touchable
249 * but not focusable, which means touches outside of the window will be delivered to the window
250 * behind. The default is false.
251 *
252 * This is for internal use only and the approach to this may change.
253 * @hide
254 *
255 * @param outsideTouchable whether or not this action mode is "outside touchable"
256 * @param onDismiss optional. Sets a callback for when this action mode popup dismisses itself
257 */
258 public void setOutsideTouchable(
259 boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
260 mFloatingToolbar.setOutsideTouchable(outsideTouchable, onDismiss);
261 }
262
Clara Bayarri838e36c2015-03-17 23:10:44 +0000263 @Override
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100264 public void onWindowFocusChanged(boolean hasWindowFocus) {
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100265 mFloatingToolbarVisibilityHelper.setWindowFocused(hasWindowFocus);
266 mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
267 }
268
269 @Override
Clara Bayarri838e36c2015-03-17 23:10:44 +0000270 public void finish() {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100271 reset();
Clara Bayarri838e36c2015-03-17 23:10:44 +0000272 mCallback.onDestroyActionMode(this);
273 }
274
275 @Override
276 public Menu getMenu() {
277 return mMenu;
278 }
279
280 @Override
281 public CharSequence getTitle() {
282 return null;
283 }
284
285 @Override
286 public CharSequence getSubtitle() {
287 return null;
288 }
289
290 @Override
291 public View getCustomView() {
292 return null;
293 }
294
295 @Override
296 public MenuInflater getMenuInflater() {
297 return new MenuInflater(mContext);
298 }
299
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100300 private void reset() {
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100301 mFloatingToolbar.dismiss();
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100302 mFloatingToolbarVisibilityHelper.deactivate();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100303 mOriginatingView.removeCallbacks(mMovingOff);
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100304 mOriginatingView.removeCallbacks(mHideOff);
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100305 }
306
Abodunrinwa Tokid1eb19c2016-07-21 11:10:19 +0100307 private boolean isViewStillActive() {
308 return mOriginatingView.getWindowVisibility() == View.VISIBLE
309 && mOriginatingView.isShown();
310 }
311
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100312 /**
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100313 * A helper for showing/hiding the floating toolbar depending on certain states.
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100314 */
315 private static final class FloatingToolbarVisibilityHelper {
316
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100317 private static final long MIN_SHOW_DURATION_FOR_MOVE_HIDE = 500;
318
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100319 private final FloatingToolbar mToolbar;
320
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100321 private boolean mHideRequested;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100322 private boolean mMoving;
323 private boolean mOutOfBounds;
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100324 private boolean mWindowFocused = true;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100325
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100326 private boolean mActive;
327
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100328 private long mLastShowTime;
329
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100330 public FloatingToolbarVisibilityHelper(FloatingToolbar toolbar) {
331 mToolbar = Preconditions.checkNotNull(toolbar);
332 }
333
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100334 public void activate() {
335 mHideRequested = false;
336 mMoving = false;
337 mOutOfBounds = false;
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100338 mWindowFocused = true;
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100339
340 mActive = true;
341 }
342
343 public void deactivate() {
344 mActive = false;
345 mToolbar.dismiss();
346 }
347
Abodunrinwa Toki9e211282015-06-05 02:46:57 +0100348 public void setHideRequested(boolean hide) {
349 mHideRequested = hide;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100350 }
351
352 public void setMoving(boolean moving) {
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100353 // Avoid unintended flickering by allowing the toolbar to show long enough before
354 // triggering the 'moving' flag - which signals a hide.
355 final boolean showingLongEnough =
356 System.currentTimeMillis() - mLastShowTime > MIN_SHOW_DURATION_FOR_MOVE_HIDE;
357 if (!moving || showingLongEnough) {
358 mMoving = moving;
359 }
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100360 }
361
362 public void setOutOfBounds(boolean outOfBounds) {
363 mOutOfBounds = outOfBounds;
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100364 }
365
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100366 public void setWindowFocused(boolean windowFocused) {
367 mWindowFocused = windowFocused;
368 }
369
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100370 public void updateToolbarVisibility() {
371 if (!mActive) {
372 return;
373 }
374
Abodunrinwa Toki972ab4f2015-06-17 18:04:23 +0100375 if (mHideRequested || mMoving || mOutOfBounds || !mWindowFocused) {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100376 mToolbar.hide();
Abodunrinwa Tokif444b5c2015-06-16 15:47:04 +0100377 } else {
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100378 mToolbar.show();
Abodunrinwa Toki4a7aeb32017-07-13 02:06:56 +0100379 mLastShowTime = System.currentTimeMillis();
Abodunrinwa Tokifd3a3a12015-05-05 20:04:34 +0100380 }
381 }
382 }
Clara Bayarri838e36c2015-03-17 23:10:44 +0000383}