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 androidx.appcompat.app.ActionBar; 22 import androidx.appcompat.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 onCreate(final Bundle savedInstanceState)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 onStart()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 onRestart()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 onResume()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 onPause()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 onStop()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 onDestroy()118 protected void onDestroy() { 119 super.onDestroy(); 120 mDestroyed = true; 121 } 122 getIsDestroyed()123 public boolean getIsDestroyed() { 124 return mDestroyed; 125 } 126 127 @Override onDisplayHeightChanged(final int heightSpecification)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 registerImeStateObserver(final ImeUtil.ImeStateObserver observer)162 public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) { 163 mImeStateObservers.add(observer); 164 } 165 166 @Override unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer)167 public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) { 168 mImeStateObservers.remove(observer); 169 } 170 171 @Override isImeOpen()172 public boolean isImeOpen() { 173 return mImeOpen; 174 } 175 176 @Override onCreateOptionsMenu(final Menu menu)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 onPrepareOptionsMenu(final Menu menu)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 onOptionsItemSelected(final MenuItem menuItem)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 startActionMode(final ActionMode.Callback callback)214 public ActionMode startActionMode(final ActionMode.Callback callback) { 215 mActionMode = new CustomActionMode(callback); 216 supportInvalidateOptionsMenu(); 217 invalidateActionBar(); 218 return mActionMode; 219 } 220 dismissActionMode()221 public void dismissActionMode() { 222 if (mActionMode != null) { 223 mActionMode.finish(); 224 mActionMode = null; 225 invalidateActionBar(); 226 } 227 } 228 getActionMode()229 public ActionMode getActionMode() { 230 return mActionMode; 231 } 232 getActionModeCallback()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 */ invalidateActionBar()252 public final void invalidateActionBar() { 253 if (mActionMode != null) { 254 mActionMode.updateActionBar(getSupportActionBar()); 255 } else { 256 updateActionBar(getSupportActionBar()); 257 } 258 } 259 updateActionBar(final ActionBar actionBar)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 CustomActionMode(final Callback callback)274 public CustomActionMode(final Callback callback) { 275 mCallback = callback; 276 } 277 278 @Override setTitle(final CharSequence title)279 public void setTitle(final CharSequence title) { 280 mTitle = title; 281 } 282 283 @Override setTitle(final int resId)284 public void setTitle(final int resId) { 285 mTitle = getResources().getString(resId); 286 } 287 288 @Override setSubtitle(final CharSequence subtitle)289 public void setSubtitle(final CharSequence subtitle) { 290 mSubtitle = subtitle; 291 } 292 293 @Override setSubtitle(final int resId)294 public void setSubtitle(final int resId) { 295 mSubtitle = getResources().getString(resId); 296 } 297 298 @Override setCustomView(final View view)299 public void setCustomView(final View view) { 300 mCustomView = view; 301 } 302 303 @Override invalidate()304 public void invalidate() { 305 invalidateActionBar(); 306 } 307 308 @Override finish()309 public void finish() { 310 mActionMode = null; 311 mCallback.onDestroyActionMode(this); 312 supportInvalidateOptionsMenu(); 313 invalidateActionBar(); 314 } 315 316 @Override getMenu()317 public Menu getMenu() { 318 return mActionBarMenu; 319 } 320 321 @Override getTitle()322 public CharSequence getTitle() { 323 return mTitle; 324 } 325 326 @Override getSubtitle()327 public CharSequence getSubtitle() { 328 return mSubtitle; 329 } 330 331 @Override getCustomView()332 public View getCustomView() { 333 return mCustomView; 334 } 335 336 @Override getMenuInflater()337 public MenuInflater getMenuInflater() { 338 return BugleActionBarActivity.this.getMenuInflater(); 339 } 340 getCallback()341 public Callback getCallback() { 342 return mCallback; 343 } 344 updateActionBar(final ActionBar actionBar)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