blob: c869722abfd4d4b6088146af2c13289f80dbfac1 [file] [log] [blame]
/*
* 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 android.view.ViewConfiguration;
import com.android.internal.R;
import com.android.internal.util.Preconditions;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.FloatingToolbar;
import java.util.Arrays;
public class FloatingActionMode extends ActionMode {
private static final int MAX_HIDE_DURATION = 3000;
private static final int MOVING_HIDE_DELAY = 300;
private final Context mContext;
private final ActionMode.Callback2 mCallback;
private final MenuBuilder mMenu;
private final Rect mContentRect;
private final Rect mContentRectOnWindow;
private final Rect mPreviousContentRectOnWindow;
private final int[] mViewPosition;
private final int[] mPreviousViewPosition;
private final Rect mViewRect;
private final Rect mPreviousViewRect;
private final Rect mScreenRect;
private final View mOriginatingView;
private final int mBottomAllowance;
private final Runnable mMovingOff = new Runnable() {
public void run() {
mFloatingToolbarVisibilityHelper.setMoving(false);
mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
}
};
private final Runnable mHideOff = new Runnable() {
public void run() {
mFloatingToolbarVisibilityHelper.setHideRequested(false);
mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
}
};
private FloatingToolbar mFloatingToolbar;
private FloatingToolbarVisibilityHelper mFloatingToolbarVisibilityHelper;
public FloatingActionMode(
Context context, ActionMode.Callback2 callback, View originatingView) {
mContext = Preconditions.checkNotNull(context);
mCallback = Preconditions.checkNotNull(callback);
mMenu = new MenuBuilder(context).setDefaultShowAsAction(
MenuItem.SHOW_AS_ACTION_IF_ROOM);
setType(ActionMode.TYPE_FLOATING);
mContentRect = new Rect();
mContentRectOnWindow = new Rect();
mPreviousContentRectOnWindow = new Rect();
mViewPosition = new int[2];
mPreviousViewPosition = new int[2];
mViewRect = new Rect();
mPreviousViewRect = new Rect();
mScreenRect = new Rect();
mOriginatingView = Preconditions.checkNotNull(originatingView);
mOriginatingView.getLocationInWindow(mViewPosition);
// Allow the content rect to overshoot a little bit beyond the
// bottom view bound if necessary.
mBottomAllowance = context.getResources()
.getDimensionPixelSize(R.dimen.content_rect_bottom_clip_allowance);
}
public void setFloatingToolbar(FloatingToolbar floatingToolbar) {
mFloatingToolbar = floatingToolbar
.setMenu(mMenu)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
return mCallback.onActionItemClicked(FloatingActionMode.this, item);
}
});
mFloatingToolbarVisibilityHelper = new FloatingToolbarVisibilityHelper(mFloatingToolbar);
mFloatingToolbarVisibilityHelper.activate();
}
@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() {
checkToolbarInitialized();
mCallback.onPrepareActionMode(this, mMenu);
invalidateContentRect(); // Will re-layout and show the toolbar if necessary.
}
@Override
public void invalidateContentRect() {
checkToolbarInitialized();
mCallback.onGetContentRect(this, mOriginatingView, mContentRect);
repositionToolbar();
}
public void updateViewLocationInWindow() {
checkToolbarInitialized();
mOriginatingView.getLocationInWindow(mViewPosition);
mOriginatingView.getGlobalVisibleRect(mViewRect);
if (!Arrays.equals(mViewPosition, mPreviousViewPosition)
|| !mViewRect.equals(mPreviousViewRect)) {
repositionToolbar();
mPreviousViewPosition[0] = mViewPosition[0];
mPreviousViewPosition[1] = mViewPosition[1];
mPreviousViewRect.set(mViewRect);
}
}
private void repositionToolbar() {
checkToolbarInitialized();
mContentRectOnWindow.set(mContentRect);
mContentRectOnWindow.offset(mViewPosition[0], mViewPosition[1]);
if (isContentRectWithinBounds()) {
mFloatingToolbarVisibilityHelper.setOutOfBounds(false);
// Make sure that content rect is not out of the view's visible bounds.
mContentRectOnWindow.set(
Math.max(mContentRectOnWindow.left, mViewRect.left),
Math.max(mContentRectOnWindow.top, mViewRect.top),
Math.min(mContentRectOnWindow.right, mViewRect.right),
Math.min(mContentRectOnWindow.bottom, mViewRect.bottom + mBottomAllowance));
if (!mContentRectOnWindow.equals(mPreviousContentRectOnWindow)) {
// Content rect is moving.
mOriginatingView.removeCallbacks(mMovingOff);
mFloatingToolbarVisibilityHelper.setMoving(true);
mOriginatingView.postDelayed(mMovingOff, MOVING_HIDE_DELAY);
mFloatingToolbar.setContentRect(mContentRectOnWindow);
mFloatingToolbar.updateLayout();
}
} else {
mFloatingToolbarVisibilityHelper.setOutOfBounds(true);
mContentRectOnWindow.setEmpty();
}
mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
mPreviousContentRectOnWindow.set(mContentRectOnWindow);
}
private boolean isContentRectWithinBounds() {
mScreenRect.set(
0,
0,
mContext.getResources().getDisplayMetrics().widthPixels,
mContext.getResources().getDisplayMetrics().heightPixels);
return Rect.intersects(mContentRectOnWindow, mScreenRect)
&& Rect.intersects(mContentRectOnWindow, mViewRect);
}
@Override
public void hide(long duration) {
checkToolbarInitialized();
if (duration == ActionMode.DEFAULT_HIDE_DURATION) {
duration = ViewConfiguration.getDefaultActionModeHideDuration();
}
duration = Math.min(MAX_HIDE_DURATION, duration);
mOriginatingView.removeCallbacks(mHideOff);
if (duration <= 0) {
mHideOff.run();
} else {
mFloatingToolbarVisibilityHelper.setHideRequested(true);
mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
mOriginatingView.postDelayed(mHideOff, duration);
}
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
checkToolbarInitialized();
mFloatingToolbarVisibilityHelper.setWindowFocused(hasWindowFocus);
mFloatingToolbarVisibilityHelper.updateToolbarVisibility();
}
@Override
public void finish() {
checkToolbarInitialized();
reset();
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);
}
/**
* @throws IllegalStateException
*/
private void checkToolbarInitialized() {
Preconditions.checkState(mFloatingToolbar != null);
Preconditions.checkState(mFloatingToolbarVisibilityHelper != null);
}
private void reset() {
mFloatingToolbar.dismiss();
mFloatingToolbarVisibilityHelper.deactivate();
mOriginatingView.removeCallbacks(mMovingOff);
mOriginatingView.removeCallbacks(mHideOff);
}
/**
* A helper for showing/hiding the floating toolbar depending on certain states.
*/
private static final class FloatingToolbarVisibilityHelper {
private final FloatingToolbar mToolbar;
private boolean mHideRequested;
private boolean mMoving;
private boolean mOutOfBounds;
private boolean mWindowFocused = true;
private boolean mActive;
public FloatingToolbarVisibilityHelper(FloatingToolbar toolbar) {
mToolbar = Preconditions.checkNotNull(toolbar);
}
public void activate() {
mHideRequested = false;
mMoving = false;
mOutOfBounds = false;
mWindowFocused = true;
mActive = true;
}
public void deactivate() {
mActive = false;
mToolbar.dismiss();
}
public void setHideRequested(boolean hide) {
mHideRequested = hide;
}
public void setMoving(boolean moving) {
mMoving = moving;
}
public void setOutOfBounds(boolean outOfBounds) {
mOutOfBounds = outOfBounds;
}
public void setWindowFocused(boolean windowFocused) {
mWindowFocused = windowFocused;
}
public void updateToolbarVisibility() {
if (!mActive) {
return;
}
if (mHideRequested || mMoving || mOutOfBounds || !mWindowFocused) {
mToolbar.hide();
} else {
mToolbar.show();
}
}
}
}