| /* |
| * 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(); |
| } |
| } |
| } |