From 838e36cda7af884233c06c5b2b5e43ffbed4715b Mon Sep 17 00:00:00 2001 From: Clara Bayarri Date: Tue, 17 Mar 2015 23:10:44 +0000 Subject: Floating toolbar: Create the FloatingActionMode and use it in DecorView. This CL - Creates a new FloatingActionMode - Uses it for action mode requests of type floating - Plumbs in the positioning invalidation logic Change-Id: I379de5b0a87b256946d0a6d8014299cfb78e6734 --- core/java/android/view/PhoneWindow.java | 109 ++++++++++++++-- .../android/internal/view/FloatingActionMode.java | 141 +++++++++++++++++++++ 2 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 core/java/com/android/internal/view/FloatingActionMode.java diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java index cb32697..38f4d1c 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/android/view/PhoneWindow.java @@ -29,6 +29,7 @@ import android.os.UserHandle; import com.android.internal.R; import com.android.internal.util.ScreenShapeHelper; +import com.android.internal.view.FloatingActionMode; import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.StandaloneActionMode; import com.android.internal.view.menu.ContextMenuBuilder; @@ -41,6 +42,7 @@ import com.android.internal.view.menu.MenuView; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.DecorContentParent; +import com.android.internal.widget.FloatingToolbar; import com.android.internal.widget.SwipeDismissLayout; import android.app.ActivityManager; @@ -2179,6 +2181,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ActionBarContextView mPrimaryActionModeView; private PopupWindow mPrimaryActionModePopup; private Runnable mShowPrimaryActionModePopup; + private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; + private View mFloatingActionModeOriginatingView; + private FloatingToolbar mFloatingToolbar; // View added at runtime to draw under the status bar area private View mStatusGuard; @@ -2703,18 +2708,18 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mode.getType() == ActionMode.TYPE_PRIMARY) { cleanupPrimaryActionMode(); mPrimaryActionMode = mode; - } else { + } else if (mode.getType() == ActionMode.TYPE_FLOATING) { + if (mFloatingActionMode != null) { + mFloatingActionMode.finish(); + } mFloatingActionMode = mode; } } else { - if (type == ActionMode.TYPE_PRIMARY) { - cleanupPrimaryActionMode(); - mode = createStandaloneActionMode(wrappedCallback); - if (mode != null && callback.onCreateActionMode(mode, mode.getMenu())) { - setHandledPrimaryActionMode(mode); - } else { - mode = null; - } + mode = createActionMode(type, wrappedCallback, originatingView); + if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { + setHandledActionMode(mode); + } else { + mode = null; } } if (mode != null && getCallback() != null && !isDestroyed()) { @@ -2737,6 +2742,21 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } + private void cleanupFloatingActionModeViews() { + if (mFloatingToolbar != null) { + mFloatingToolbar.dismiss(); + mFloatingToolbar = null; + } + if (mFloatingActionModeOriginatingView != null) { + if (mFloatingToolbarPreDrawListener != null) { + mFloatingActionModeOriginatingView.getViewTreeObserver() + .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); + mFloatingToolbarPreDrawListener = null; + } + mFloatingActionModeOriginatingView = null; + } + } + public void startChanging() { mChanging = true; } @@ -3128,6 +3148,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (cb != null && !isDestroyed() && mFeatureId < 0) { cb.onWindowFocusChanged(hasWindowFocus); } + + if (mFloatingToolbar != null) { + if (hasWindowFocus) { + mFloatingToolbar.show(); + } else { + mFloatingToolbar.dismiss(); + } + } } void updateWindowResizeState() { @@ -3179,6 +3207,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } mPrimaryActionModePopup = null; } + if (mFloatingToolbar != null) { + mFloatingToolbar.dismiss(); + mFloatingToolbar = null; + } PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); if (st != null && st.menu != null && mFeatureId < 0) { @@ -3220,7 +3252,27 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { updateColorViewTranslations(); } + private ActionMode createActionMode( + int type, ActionMode.Callback2 callback, View originatingView) { + switch (type) { + case ActionMode.TYPE_PRIMARY: + default: + return createStandaloneActionMode(callback); + case ActionMode.TYPE_FLOATING: + return createFloatingActionMode(originatingView, callback); + } + } + + private void setHandledActionMode(ActionMode mode) { + if (mode.getType() == ActionMode.TYPE_PRIMARY) { + setHandledPrimaryActionMode(mode); + } else if (mode.getType() == ActionMode.TYPE_FLOATING) { + setHandledFloatingActionMode(mode); + } + } + private ActionMode createStandaloneActionMode(ActionMode.Callback callback) { + cleanupPrimaryActionMode(); if (mPrimaryActionModeView == null) { if (isFloating()) { // Use the action bar theme. @@ -3291,6 +3343,35 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + private ActionMode createFloatingActionMode( + View originatingView, ActionMode.Callback2 callback) { + if (mFloatingActionMode != null) { + mFloatingActionMode.finish(); + } + cleanupFloatingActionModeViews(); + mFloatingToolbar = new FloatingToolbar(mContext, PhoneWindow.this); + final FloatingActionMode mode = new FloatingActionMode( + mContext, callback, originatingView, mFloatingToolbar); + mFloatingActionModeOriginatingView = originatingView; + mFloatingToolbarPreDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mode.updateViewLocationInWindow(); + return true; + } + }; + return mode; + } + + private void setHandledFloatingActionMode(ActionMode mode) { + mFloatingActionMode = mode; + mFloatingActionMode.invalidate(); + mFloatingToolbar.show(); + mFloatingActionModeOriginatingView.getViewTreeObserver() + .addOnPreDrawListener(mFloatingToolbarPreDrawListener); + } + /** * Clears out internal references when the action mode is destroyed. */ @@ -3328,6 +3409,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } mPrimaryActionMode = null; } else if (mode == mFloatingActionMode) { + cleanupFloatingActionModeViews(); mFloatingActionMode = null; } if (getCallback() != null && !isDestroyed()) { @@ -3339,6 +3421,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } requestFitSystemWindows(); } + + @Override + public void onGetContentRect(ActionMode mode, View view, Rect outRect) { + if (mWrapped instanceof ActionMode.Callback2) { + ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); + } else { + super.onGetContentRect(mode, view, outRect); + } + } } } diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java new file mode 100644 index 0000000..aacdb34 --- /dev/null +++ b/core/java/com/android/internal/view/FloatingActionMode.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view; + +import android.content.Context; +import android.graphics.Rect; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.widget.FloatingToolbar; + +public class FloatingActionMode extends ActionMode { + + private final Context mContext; + private final ActionMode.Callback2 mCallback; + private final MenuBuilder mMenu; + private final FloatingToolbar mFloatingToolbar; + private final Rect mContentRect; + private final Rect mContentRectOnWindow; + private final Rect mPreviousContentRectOnWindow; + private final int[] mViewPosition; + private final View mOriginatingView; + + public FloatingActionMode( + Context context, ActionMode.Callback2 callback, View originatingView, + FloatingToolbar floatingToolbar) { + mContext = context; + mCallback = callback; + mMenu = new MenuBuilder(context).setDefaultShowAsAction( + MenuItem.SHOW_AS_ACTION_IF_ROOM); + mFloatingToolbar = floatingToolbar + .setMenu(mMenu) + .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return mCallback.onActionItemClicked(FloatingActionMode.this, item); + } + }); + setType(ActionMode.TYPE_FLOATING); + mContentRect = new Rect(); + mContentRectOnWindow = new Rect(); + mPreviousContentRectOnWindow = new Rect(); + mViewPosition = new int[2]; + mOriginatingView = originatingView; + } + + @Override + public void setTitle(CharSequence title) {} + + @Override + public void setTitle(int resId) {} + + @Override + public void setSubtitle(CharSequence subtitle) {} + + @Override + public void setSubtitle(int resId) {} + + @Override + public void setCustomView(View view) {} + + @Override + public void invalidate() { + mCallback.onPrepareActionMode(this, mMenu); + mFloatingToolbar.updateLayout(); + invalidateContentRect(); + } + + @Override + public void invalidateContentRect() { + mCallback.onGetContentRect(this, mOriginatingView, mContentRect); + repositionToolbar(); + } + + public void updateViewLocationInWindow() { + mOriginatingView.getLocationInWindow(mViewPosition); + repositionToolbar(); + } + + private void repositionToolbar() { + mContentRectOnWindow.set( + mContentRect.left + mViewPosition[0], + mContentRect.top + mViewPosition[1], + mContentRect.right + mViewPosition[0], + mContentRect.bottom + mViewPosition[1]); + if (!mContentRectOnWindow.equals(mPreviousContentRectOnWindow)) { + mFloatingToolbar.setContentRect(mContentRectOnWindow); + mFloatingToolbar.updateLayout(); + } + mPreviousContentRectOnWindow.set(mContentRectOnWindow); + } + + @Override + public void finish() { + mCallback.onDestroyActionMode(this); + } + + @Override + public Menu getMenu() { + return mMenu; + } + + @Override + public CharSequence getTitle() { + return null; + } + + @Override + public CharSequence getSubtitle() { + return null; + } + + @Override + public View getCustomView() { + return null; + } + + @Override + public MenuInflater getMenuInflater() { + return new MenuInflater(mContext); + } + +} -- cgit v1.1