blob: 42d40fee7d8c57f0063b7ff23604e61ea6382310 [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.messaging.ui;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import com.android.messaging.R;
import com.android.messaging.util.BugleActivityUtil;
import com.android.messaging.util.ImeUtil;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.UiUtils;
import java.util.HashSet;
import java.util.Set;
/**
* Base class for app activities that use an action bar. Responsible for logging/telemetry/other
* needs that will be common for all activities. We can break out the common code if/when we need
* a version that doesn't use an actionbar.
*/
public class BugleActionBarActivity extends AppCompatActivity implements ImeUtil.ImeStateHost {
// Tracks the list of observers opting in for IME state change.
private final Set<ImeUtil.ImeStateObserver> mImeStateObservers = new HashSet<>();
// Tracks the soft keyboard display state
private boolean mImeOpen;
// The ActionMode that represents the modal contextual action bar, using our own implementation
// rather than the built in contextual action bar to reduce jank
private CustomActionMode mActionMode;
// The menu for the action bar
private Menu mActionBarMenu;
// Used to determine if a onDisplayHeightChanged was due to the IME opening or rotation of the
// device
private int mLastScreenHeight;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
return;
}
mLastScreenHeight = getResources().getDisplayMetrics().heightPixels;
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate");
}
}
@Override
protected void onStart() {
super.onStart();
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStart");
}
}
@Override
protected void onRestart() {
super.onStop();
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onRestart");
}
}
@Override
protected void onResume() {
super.onResume();
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
}
BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this);
}
@Override
protected void onPause() {
super.onPause();
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
}
}
@Override
protected void onStop() {
super.onStop();
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStop");
}
}
private boolean mDestroyed;
@Override
protected void onDestroy() {
super.onDestroy();
mDestroyed = true;
}
public boolean getIsDestroyed() {
return mDestroyed;
}
@Override
public void onDisplayHeightChanged(final int heightSpecification) {
int screenHeight = getResources().getDisplayMetrics().heightPixels;
if (screenHeight != mLastScreenHeight) {
// Appears to be an orientation change, don't fire ime updates
mLastScreenHeight = screenHeight;
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
" screenHeight: " + screenHeight + " lastScreenHeight: " + mLastScreenHeight +
" Skipped, appears to be orientation change.");
return;
}
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null && actionBar.isShowing()) {
screenHeight -= actionBar.getHeight();
}
final int height = View.MeasureSpec.getSize(heightSpecification);
final boolean imeWasOpen = mImeOpen;
mImeOpen = screenHeight - height > 100;
if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
"imeWasOpen: " + imeWasOpen + " mImeOpen: " + mImeOpen + " screenHeight: " +
screenHeight + " height: " + height);
}
if (imeWasOpen != mImeOpen) {
for (final ImeUtil.ImeStateObserver observer : mImeStateObservers) {
observer.onImeStateChanged(mImeOpen);
}
}
}
@Override
public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) {
mImeStateObservers.add(observer);
}
@Override
public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) {
mImeStateObservers.remove(observer);
}
@Override
public boolean isImeOpen() {
return mImeOpen;
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
mActionBarMenu = menu;
if (mActionMode != null &&
mActionMode.getCallback().onCreateActionMode(mActionMode, menu)) {
return true;
}
return false;
}
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
mActionBarMenu = menu;
if (mActionMode != null &&
mActionMode.getCallback().onPrepareActionMode(mActionMode, menu)) {
return true;
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem menuItem) {
if (mActionMode != null &&
mActionMode.getCallback().onActionItemClicked(mActionMode, menuItem)) {
return true;
}
switch (menuItem.getItemId()) {
case android.R.id.home:
if (mActionMode != null) {
dismissActionMode();
return true;
}
}
return super.onOptionsItemSelected(menuItem);
}
@Override
public ActionMode startActionMode(final ActionMode.Callback callback) {
mActionMode = new CustomActionMode(callback);
supportInvalidateOptionsMenu();
invalidateActionBar();
return mActionMode;
}
public void dismissActionMode() {
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
invalidateActionBar();
}
}
public ActionMode getActionMode() {
return mActionMode;
}
protected ActionMode.Callback getActionModeCallback() {
if (mActionMode == null) {
return null;
}
return mActionMode.getCallback();
}
/**
* Receives and handles action bar invalidation request from sub-components of this activity.
*
* <p>Normally actions have sole control over the action bar, but in order to support seamless
* transitions for components such as the full screen media picker, we have to let it take over
* the action bar and then restore its state afterwards</p>
*
* <p>If a fragment does anything that may change the action bar, it should call this method
* and then it is this method's responsibility to figure out which component "controls" the
* action bar and delegate the updating of the action bar to that component</p>
*/
public final void invalidateActionBar() {
if (mActionMode != null) {
mActionMode.updateActionBar(getSupportActionBar());
} else {
updateActionBar(getSupportActionBar());
}
}
protected void updateActionBar(final ActionBar actionBar) {
actionBar.setHomeAsUpIndicator(null);
}
/**
* Custom ActionMode implementation which allows us to just replace the contents of the main
* action bar rather than overlay over it
*/
private class CustomActionMode extends ActionMode {
private CharSequence mTitle;
private CharSequence mSubtitle;
private View mCustomView;
private final Callback mCallback;
public CustomActionMode(final Callback callback) {
mCallback = callback;
}
@Override
public void setTitle(final CharSequence title) {
mTitle = title;
}
@Override
public void setTitle(final int resId) {
mTitle = getResources().getString(resId);
}
@Override
public void setSubtitle(final CharSequence subtitle) {
mSubtitle = subtitle;
}
@Override
public void setSubtitle(final int resId) {
mSubtitle = getResources().getString(resId);
}
@Override
public void setCustomView(final View view) {
mCustomView = view;
}
@Override
public void invalidate() {
invalidateActionBar();
}
@Override
public void finish() {
mActionMode = null;
mCallback.onDestroyActionMode(this);
supportInvalidateOptionsMenu();
invalidateActionBar();
}
@Override
public Menu getMenu() {
return mActionBarMenu;
}
@Override
public CharSequence getTitle() {
return mTitle;
}
@Override
public CharSequence getSubtitle() {
return mSubtitle;
}
@Override
public View getCustomView() {
return mCustomView;
}
@Override
public MenuInflater getMenuInflater() {
return BugleActionBarActivity.this.getMenuInflater();
}
public Callback getCallback() {
return mCallback;
}
public void updateActionBar(final ActionBar actionBar) {
actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(false);
mActionMode.getCallback().onPrepareActionMode(mActionMode, mActionBarMenu);
actionBar.setBackgroundDrawable(new ColorDrawable(
getResources().getColor(R.color.contextual_action_bar_background_color)));
actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_small_dark);
actionBar.show();
}
}
}