Home | History | Annotate | Download | only in ui
      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.messaging.ui;
     18 
     19 import android.graphics.drawable.ColorDrawable;
     20 import android.os.Bundle;
     21 import android.support.v7.app.ActionBar;
     22 import android.support.v7.app.AppCompatActivity;
     23 import android.view.ActionMode;
     24 import android.view.Menu;
     25 import android.view.MenuInflater;
     26 import android.view.MenuItem;
     27 import android.view.View;
     28 
     29 import com.android.messaging.R;
     30 import com.android.messaging.util.BugleActivityUtil;
     31 import com.android.messaging.util.ImeUtil;
     32 import com.android.messaging.util.LogUtil;
     33 import com.android.messaging.util.UiUtils;
     34 
     35 import java.util.HashSet;
     36 import java.util.Set;
     37 
     38 /**
     39  * Base class for app activities that use an action bar. Responsible for logging/telemetry/other
     40  * needs that will be common for all activities.  We can break out the common code if/when we need
     41  * a version that doesn't use an actionbar.
     42  */
     43 public class BugleActionBarActivity extends AppCompatActivity implements ImeUtil.ImeStateHost {
     44     // Tracks the list of observers opting in for IME state change.
     45     private final Set<ImeUtil.ImeStateObserver> mImeStateObservers = new HashSet<>();
     46 
     47     // Tracks the soft keyboard display state
     48     private boolean mImeOpen;
     49 
     50     // The ActionMode that represents the modal contextual action bar, using our own implementation
     51     // rather than the built in contextual action bar to reduce jank
     52     private CustomActionMode mActionMode;
     53 
     54     // The menu for the action bar
     55     private Menu mActionBarMenu;
     56 
     57     // Used to determine if a onDisplayHeightChanged was due to the IME opening or rotation of the
     58     // device
     59     private int mLastScreenHeight;
     60 
     61     @Override
     62     protected void onCreate(final Bundle savedInstanceState) {
     63         super.onCreate(savedInstanceState);
     64         if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
     65             return;
     66         }
     67 
     68         mLastScreenHeight = getResources().getDisplayMetrics().heightPixels;
     69         if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
     70             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate");
     71         }
     72     }
     73 
     74     @Override
     75     protected void onStart() {
     76         super.onStart();
     77         if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
     78             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStart");
     79         }
     80     }
     81 
     82     @Override
     83     protected void onRestart() {
     84         super.onStop();
     85         if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
     86             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onRestart");
     87         }
     88     }
     89 
     90     @Override
     91     protected void onResume() {
     92         super.onResume();
     93         if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
     94             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
     95         }
     96         BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this);
     97     }
     98 
     99     @Override
    100     protected void onPause() {
    101         super.onPause();
    102         if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
    103             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
    104         }
    105     }
    106 
    107     @Override
    108     protected void onStop() {
    109         super.onStop();
    110         if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
    111             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStop");
    112         }
    113     }
    114 
    115     private boolean mDestroyed;
    116 
    117     @Override
    118     protected void onDestroy() {
    119         super.onDestroy();
    120         mDestroyed = true;
    121     }
    122 
    123     public boolean getIsDestroyed() {
    124         return mDestroyed;
    125     }
    126 
    127     @Override
    128     public void onDisplayHeightChanged(final int heightSpecification) {
    129         int screenHeight = getResources().getDisplayMetrics().heightPixels;
    130 
    131         if (screenHeight != mLastScreenHeight) {
    132             // Appears to be an orientation change, don't fire ime updates
    133             mLastScreenHeight = screenHeight;
    134             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
    135                     " screenHeight: " + screenHeight + " lastScreenHeight: " + mLastScreenHeight +
    136                     " Skipped, appears to be orientation change.");
    137             return;
    138         }
    139         final ActionBar actionBar = getSupportActionBar();
    140         if (actionBar != null && actionBar.isShowing()) {
    141             screenHeight -= actionBar.getHeight();
    142         }
    143         final int height = View.MeasureSpec.getSize(heightSpecification);
    144 
    145         final boolean imeWasOpen = mImeOpen;
    146         mImeOpen = screenHeight - height > 100;
    147 
    148         if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
    149             LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
    150                     "imeWasOpen: " + imeWasOpen + " mImeOpen: " + mImeOpen + " screenHeight: " +
    151                     screenHeight + " height: " + height);
    152         }
    153 
    154         if (imeWasOpen != mImeOpen) {
    155             for (final ImeUtil.ImeStateObserver observer : mImeStateObservers) {
    156                 observer.onImeStateChanged(mImeOpen);
    157             }
    158         }
    159     }
    160 
    161     @Override
    162     public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) {
    163         mImeStateObservers.add(observer);
    164     }
    165 
    166     @Override
    167     public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) {
    168         mImeStateObservers.remove(observer);
    169     }
    170 
    171     @Override
    172     public boolean isImeOpen() {
    173         return mImeOpen;
    174     }
    175 
    176     @Override
    177     public boolean onCreateOptionsMenu(final Menu menu) {
    178         mActionBarMenu = menu;
    179         if (mActionMode != null &&
    180                 mActionMode.getCallback().onCreateActionMode(mActionMode, menu)) {
    181             return true;
    182         }
    183         return false;
    184     }
    185 
    186     @Override
    187     public boolean onPrepareOptionsMenu(final Menu menu) {
    188         mActionBarMenu = menu;
    189         if (mActionMode != null &&
    190                 mActionMode.getCallback().onPrepareActionMode(mActionMode, menu)) {
    191             return true;
    192         }
    193         return super.onPrepareOptionsMenu(menu);
    194     }
    195 
    196     @Override
    197     public boolean onOptionsItemSelected(final MenuItem menuItem) {
    198         if (mActionMode != null &&
    199                 mActionMode.getCallback().onActionItemClicked(mActionMode, menuItem)) {
    200             return true;
    201         }
    202 
    203         switch (menuItem.getItemId()) {
    204             case android.R.id.home:
    205                 if (mActionMode != null) {
    206                     dismissActionMode();
    207                     return true;
    208                 }
    209         }
    210         return super.onOptionsItemSelected(menuItem);
    211     }
    212 
    213     @Override
    214     public ActionMode startActionMode(final ActionMode.Callback callback) {
    215         mActionMode = new CustomActionMode(callback);
    216         supportInvalidateOptionsMenu();
    217         invalidateActionBar();
    218         return mActionMode;
    219     }
    220 
    221     public void dismissActionMode() {
    222         if (mActionMode != null) {
    223             mActionMode.finish();
    224             mActionMode = null;
    225             invalidateActionBar();
    226         }
    227     }
    228 
    229     public ActionMode getActionMode() {
    230         return mActionMode;
    231     }
    232 
    233     protected ActionMode.Callback getActionModeCallback() {
    234         if (mActionMode == null) {
    235             return null;
    236         }
    237 
    238         return mActionMode.getCallback();
    239     }
    240 
    241     /**
    242      * Receives and handles action bar invalidation request from sub-components of this activity.
    243      *
    244      * <p>Normally actions have sole control over the action bar, but in order to support seamless
    245      * transitions for components such as the full screen media picker, we have to let it take over
    246      * the action bar and then restore its state afterwards</p>
    247      *
    248      * <p>If a fragment does anything that may change the action bar, it should call this method
    249      * and then it is this method's responsibility to figure out which component "controls" the
    250      * action bar and delegate the updating of the action bar to that component</p>
    251      */
    252     public final void invalidateActionBar() {
    253         if (mActionMode != null) {
    254             mActionMode.updateActionBar(getSupportActionBar());
    255         } else {
    256             updateActionBar(getSupportActionBar());
    257         }
    258     }
    259 
    260     protected void updateActionBar(final ActionBar actionBar) {
    261         actionBar.setHomeAsUpIndicator(null);
    262     }
    263 
    264     /**
    265      * Custom ActionMode implementation which allows us to just replace the contents of the main
    266      * action bar rather than overlay over it
    267      */
    268     private class CustomActionMode extends ActionMode {
    269         private CharSequence mTitle;
    270         private CharSequence mSubtitle;
    271         private View mCustomView;
    272         private final Callback mCallback;
    273 
    274         public CustomActionMode(final Callback callback) {
    275             mCallback = callback;
    276         }
    277 
    278         @Override
    279         public void setTitle(final CharSequence title) {
    280             mTitle = title;
    281         }
    282 
    283         @Override
    284         public void setTitle(final int resId) {
    285             mTitle = getResources().getString(resId);
    286         }
    287 
    288         @Override
    289         public void setSubtitle(final CharSequence subtitle) {
    290             mSubtitle = subtitle;
    291         }
    292 
    293         @Override
    294         public void setSubtitle(final int resId) {
    295             mSubtitle = getResources().getString(resId);
    296         }
    297 
    298         @Override
    299         public void setCustomView(final View view) {
    300             mCustomView = view;
    301         }
    302 
    303         @Override
    304         public void invalidate() {
    305             invalidateActionBar();
    306         }
    307 
    308         @Override
    309         public void finish() {
    310             mActionMode = null;
    311             mCallback.onDestroyActionMode(this);
    312             supportInvalidateOptionsMenu();
    313             invalidateActionBar();
    314         }
    315 
    316         @Override
    317         public Menu getMenu() {
    318             return mActionBarMenu;
    319         }
    320 
    321         @Override
    322         public CharSequence getTitle() {
    323             return mTitle;
    324         }
    325 
    326         @Override
    327         public CharSequence getSubtitle() {
    328             return mSubtitle;
    329         }
    330 
    331         @Override
    332         public View getCustomView() {
    333             return mCustomView;
    334         }
    335 
    336         @Override
    337         public MenuInflater getMenuInflater() {
    338             return BugleActionBarActivity.this.getMenuInflater();
    339         }
    340 
    341         public Callback getCallback() {
    342             return mCallback;
    343         }
    344 
    345         public void updateActionBar(final ActionBar actionBar) {
    346             actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP);
    347             actionBar.setDisplayShowTitleEnabled(false);
    348             actionBar.setDisplayShowCustomEnabled(false);
    349             mActionMode.getCallback().onPrepareActionMode(mActionMode, mActionBarMenu);
    350             actionBar.setBackgroundDrawable(new ColorDrawable(
    351                     getResources().getColor(R.color.contextual_action_bar_background_color)));
    352             actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_small_dark);
    353             actionBar.show();
    354         }
    355     }
    356 }
    357