• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.internal.view;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.view.ActionMode;
22 import android.view.Menu;
23 import android.view.MenuInflater;
24 import android.view.MenuItem;
25 import android.view.View;
26 import android.view.ViewConfiguration;
27 import android.view.ViewGroup;
28 import android.view.ViewParent;
29 import android.util.DisplayMetrics;
30 
31 import com.android.internal.R;
32 import com.android.internal.util.Preconditions;
33 import com.android.internal.view.menu.MenuBuilder;
34 import com.android.internal.widget.FloatingToolbar;
35 
36 import java.util.Arrays;
37 
38 public class FloatingActionMode extends ActionMode {
39 
40     private static final int MAX_HIDE_DURATION = 3000;
41     private static final int MOVING_HIDE_DELAY = 50;
42 
43     private final Context mContext;
44     private final ActionMode.Callback2 mCallback;
45     private final MenuBuilder mMenu;
46     private final Rect mContentRect;
47     private final Rect mContentRectOnScreen;
48     private final Rect mPreviousContentRectOnScreen;
49     private final int[] mViewPositionOnScreen;
50     private final int[] mPreviousViewPositionOnScreen;
51     private final int[] mRootViewPositionOnScreen;
52     private final Rect mViewRectOnScreen;
53     private final Rect mPreviousViewRectOnScreen;
54     private final Rect mScreenRect;
55     private final View mOriginatingView;
56     private final int mBottomAllowance;
57 
58     private final Runnable mMovingOff = new Runnable() {
59         public void run() {
60             mFloatingToolbarVisibilityHelper.setMoving(false);
61             mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
62         }
63     };
64 
65     private final Runnable mHideOff = new Runnable() {
66         public void run() {
67             mFloatingToolbarVisibilityHelper.setHideRequested(false);
68             mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
69         }
70     };
71 
72     private FloatingToolbar mFloatingToolbar;
73     private FloatingToolbarVisibilityHelper mFloatingToolbarVisibilityHelper;
74 
FloatingActionMode( Context context, ActionMode.Callback2 callback, View originatingView)75     public FloatingActionMode(
76             Context context, ActionMode.Callback2 callback, View originatingView) {
77         mContext = Preconditions.checkNotNull(context);
78         mCallback = Preconditions.checkNotNull(callback);
79         mMenu = new MenuBuilder(context).setDefaultShowAsAction(
80                 MenuItem.SHOW_AS_ACTION_IF_ROOM);
81         setType(ActionMode.TYPE_FLOATING);
82         mMenu.setCallback(new MenuBuilder.Callback() {
83             @Override
84             public void onMenuModeChange(MenuBuilder menu) {}
85 
86             @Override
87             public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
88                 return mCallback.onActionItemClicked(FloatingActionMode.this, item);
89             }
90         });
91         mContentRect = new Rect();
92         mContentRectOnScreen = new Rect();
93         mPreviousContentRectOnScreen = new Rect();
94         mViewPositionOnScreen = new int[2];
95         mPreviousViewPositionOnScreen = new int[2];
96         mRootViewPositionOnScreen = new int[2];
97         mViewRectOnScreen = new Rect();
98         mPreviousViewRectOnScreen = new Rect();
99         mScreenRect = new Rect();
100         mOriginatingView = Preconditions.checkNotNull(originatingView);
101         mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
102         // Allow the content rect to overshoot a little bit beyond the
103         // bottom view bound if necessary.
104         mBottomAllowance = context.getResources()
105                 .getDimensionPixelSize(R.dimen.content_rect_bottom_clip_allowance);
106     }
107 
setFloatingToolbar(FloatingToolbar floatingToolbar)108     public void setFloatingToolbar(FloatingToolbar floatingToolbar) {
109         mFloatingToolbar = floatingToolbar
110                 .setMenu(mMenu)
111                 .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
112                         @Override
113                     public boolean onMenuItemClick(MenuItem item) {
114                         return mMenu.performItemAction(item, 0);
115                     }
116                 });
117         mFloatingToolbarVisibilityHelper = new FloatingToolbarVisibilityHelper(mFloatingToolbar);
118         mFloatingToolbarVisibilityHelper.activate();
119     }
120 
121     @Override
setTitle(CharSequence title)122     public void setTitle(CharSequence title) {}
123 
124     @Override
setTitle(int resId)125     public void setTitle(int resId) {}
126 
127     @Override
setSubtitle(CharSequence subtitle)128     public void setSubtitle(CharSequence subtitle) {}
129 
130     @Override
setSubtitle(int resId)131     public void setSubtitle(int resId) {}
132 
133     @Override
setCustomView(View view)134     public void setCustomView(View view) {}
135 
136     @Override
invalidate()137     public void invalidate() {
138         checkToolbarInitialized();
139         mCallback.onPrepareActionMode(this, mMenu);
140         invalidateContentRect();  // Will re-layout and show the toolbar if necessary.
141     }
142 
143     @Override
invalidateContentRect()144     public void invalidateContentRect() {
145         checkToolbarInitialized();
146         mCallback.onGetContentRect(this, mOriginatingView, mContentRect);
147         repositionToolbar();
148     }
149 
updateViewLocationInWindow()150     public void updateViewLocationInWindow() {
151         checkToolbarInitialized();
152 
153         mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
154         mOriginatingView.getRootView().getLocationOnScreen(mRootViewPositionOnScreen);
155         mOriginatingView.getGlobalVisibleRect(mViewRectOnScreen);
156         mViewRectOnScreen.offset(mRootViewPositionOnScreen[0], mRootViewPositionOnScreen[1]);
157 
158         if (!Arrays.equals(mViewPositionOnScreen, mPreviousViewPositionOnScreen)
159                 || !mViewRectOnScreen.equals(mPreviousViewRectOnScreen)) {
160             repositionToolbar();
161             mPreviousViewPositionOnScreen[0] = mViewPositionOnScreen[0];
162             mPreviousViewPositionOnScreen[1] = mViewPositionOnScreen[1];
163             mPreviousViewRectOnScreen.set(mViewRectOnScreen);
164         }
165     }
166 
repositionToolbar()167     private void repositionToolbar() {
168         checkToolbarInitialized();
169 
170         mContentRectOnScreen.set(mContentRect);
171 
172         // Offset the content rect into screen coordinates, taking into account any transformations
173         // that may be applied to the originating view or its ancestors.
174         final ViewParent parent = mOriginatingView.getParent();
175         if (parent instanceof ViewGroup) {
176             ((ViewGroup) parent).getChildVisibleRect(
177                     mOriginatingView, mContentRectOnScreen,
178                     null /* offset */, true /* forceParentCheck */);
179             mContentRectOnScreen.offset(mRootViewPositionOnScreen[0], mRootViewPositionOnScreen[1]);
180         } else {
181             mContentRectOnScreen.offset(mViewPositionOnScreen[0], mViewPositionOnScreen[1]);
182         }
183 
184         if (isContentRectWithinBounds()) {
185             mFloatingToolbarVisibilityHelper.setOutOfBounds(false);
186             // Make sure that content rect is not out of the view's visible bounds.
187             mContentRectOnScreen.set(
188                     Math.max(mContentRectOnScreen.left, mViewRectOnScreen.left),
189                     Math.max(mContentRectOnScreen.top, mViewRectOnScreen.top),
190                     Math.min(mContentRectOnScreen.right, mViewRectOnScreen.right),
191                     Math.min(mContentRectOnScreen.bottom,
192                             mViewRectOnScreen.bottom + mBottomAllowance));
193 
194             if (!mContentRectOnScreen.equals(mPreviousContentRectOnScreen)) {
195                 // Content rect is moving.
196                 mOriginatingView.removeCallbacks(mMovingOff);
197                 mFloatingToolbarVisibilityHelper.setMoving(true);
198                 mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY);
199 
200                 mFloatingToolbar.setContentRect(mContentRectOnScreen);
201                 mFloatingToolbar.updateLayout();
202             }
203         } else {
204             mFloatingToolbarVisibilityHelper.setOutOfBounds(true);
205             mContentRectOnScreen.setEmpty();
206         }
207         mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
208 
209         mPreviousContentRectOnScreen.set(mContentRectOnScreen);
210     }
211 
isContentRectWithinBounds()212     private boolean isContentRectWithinBounds() {
213         DisplayMetrics metrics = mContext.getApplicationContext()
214                 .getResources().getDisplayMetrics();
215         mScreenRect.set(0, 0, metrics.widthPixels, metrics.heightPixels);
216 
217         return intersectsClosed(mContentRectOnScreen, mScreenRect)
218             && intersectsClosed(mContentRectOnScreen, mViewRectOnScreen);
219     }
220 
221     /*
222      * Same as Rect.intersects, but includes cases where the rectangles touch.
223     */
intersectsClosed(Rect a, Rect b)224     private static boolean intersectsClosed(Rect a, Rect b) {
225          return a.left <= b.right && b.left <= a.right
226                  && a.top <= b.bottom && b.top <= a.bottom;
227     }
228 
229     @Override
hide(long duration)230     public void hide(long duration) {
231         checkToolbarInitialized();
232 
233         if (duration == ActionMode.DEFAULT_HIDE_DURATION) {
234             duration = ViewConfiguration.getDefaultActionModeHideDuration();
235         }
236         duration = Math.min(MAX_HIDE_DURATION, duration);
237         mOriginatingView.removeCallbacks(mHideOff);
238         if (duration <= 0) {
239             mHideOff.run();
240         } else {
241             mFloatingToolbarVisibilityHelper.setHideRequested(true);
242             mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
243             mOriginatingView.postDelayed(mHideOff, duration);
244         }
245     }
246 
247     @Override
onWindowFocusChanged(boolean hasWindowFocus)248     public void onWindowFocusChanged(boolean hasWindowFocus) {
249         checkToolbarInitialized();
250         mFloatingToolbarVisibilityHelper.setWindowFocused(hasWindowFocus);
251         mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
252     }
253 
254     @Override
finish()255     public void finish() {
256         checkToolbarInitialized();
257         reset();
258         mCallback.onDestroyActionMode(this);
259     }
260 
261     @Override
getMenu()262     public Menu getMenu() {
263         return mMenu;
264     }
265 
266     @Override
getTitle()267     public CharSequence getTitle() {
268         return null;
269     }
270 
271     @Override
getSubtitle()272     public CharSequence getSubtitle() {
273         return null;
274     }
275 
276     @Override
getCustomView()277     public View getCustomView() {
278         return null;
279     }
280 
281     @Override
getMenuInflater()282     public MenuInflater getMenuInflater() {
283         return new MenuInflater(mContext);
284     }
285 
286     /**
287      * @throws IllegalStateException
288      */
checkToolbarInitialized()289     private void checkToolbarInitialized() {
290         Preconditions.checkState(mFloatingToolbar != null);
291         Preconditions.checkState(mFloatingToolbarVisibilityHelper != null);
292     }
293 
reset()294     private void reset() {
295         mFloatingToolbar.dismiss();
296         mFloatingToolbarVisibilityHelper.deactivate();
297         mOriginatingView.removeCallbacks(mMovingOff);
298         mOriginatingView.removeCallbacks(mHideOff);
299     }
300 
301     /**
302      * A helper for showing/hiding the floating toolbar depending on certain states.
303      */
304     private static final class FloatingToolbarVisibilityHelper {
305 
306         private final FloatingToolbar mToolbar;
307 
308         private boolean mHideRequested;
309         private boolean mMoving;
310         private boolean mOutOfBounds;
311         private boolean mWindowFocused = true;
312 
313         private boolean mActive;
314 
FloatingToolbarVisibilityHelper(FloatingToolbar toolbar)315         public FloatingToolbarVisibilityHelper(FloatingToolbar toolbar) {
316             mToolbar = Preconditions.checkNotNull(toolbar);
317         }
318 
activate()319         public void activate() {
320             mHideRequested = false;
321             mMoving = false;
322             mOutOfBounds = false;
323             mWindowFocused = true;
324 
325             mActive = true;
326         }
327 
deactivate()328         public void deactivate() {
329             mActive = false;
330             mToolbar.dismiss();
331         }
332 
setHideRequested(boolean hide)333         public void setHideRequested(boolean hide) {
334             mHideRequested = hide;
335         }
336 
setMoving(boolean moving)337         public void setMoving(boolean moving) {
338             mMoving = moving;
339         }
340 
setOutOfBounds(boolean outOfBounds)341         public void setOutOfBounds(boolean outOfBounds) {
342             mOutOfBounds = outOfBounds;
343         }
344 
setWindowFocused(boolean windowFocused)345         public void setWindowFocused(boolean windowFocused) {
346             mWindowFocused = windowFocused;
347         }
348 
updateToolbarVisibility()349         public void updateToolbarVisibility() {
350             if (!mActive) {
351                 return;
352             }
353 
354             if (mHideRequested || mMoving || mOutOfBounds || !mWindowFocused) {
355                 mToolbar.hide();
356             } else {
357                 mToolbar.show();
358             }
359         }
360     }
361 }
362