1 /* 2 * Copyright (C) 2017 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.launcher3; 18 19 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION; 20 import static com.android.launcher3.util.FlagDebugUtils.appendFlag; 21 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; 22 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; 23 24 import static java.lang.annotation.RetentionPolicy.SOURCE; 25 26 import android.app.Activity; 27 import android.content.Context; 28 import android.content.ContextWrapper; 29 import android.content.Intent; 30 import android.content.res.Configuration; 31 import android.os.Bundle; 32 import android.util.Log; 33 import android.view.ActionMode; 34 import android.view.View; 35 import android.window.OnBackInvokedDispatcher; 36 37 import androidx.annotation.IntDef; 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.lifecycle.Lifecycle; 41 import androidx.lifecycle.LifecycleRegistry; 42 import androidx.savedstate.SavedStateRegistry; 43 import androidx.savedstate.SavedStateRegistryController; 44 45 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 46 import com.android.launcher3.logging.StatsLogManager; 47 import com.android.launcher3.model.data.ItemInfo; 48 import com.android.launcher3.testing.TestLogging; 49 import com.android.launcher3.testing.shared.TestProtocol; 50 import com.android.launcher3.util.ActivityOptionsWrapper; 51 import com.android.launcher3.util.DisplayController; 52 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; 53 import com.android.launcher3.util.DisplayController.Info; 54 import com.android.launcher3.util.LifecycleHelper; 55 import com.android.launcher3.util.RunnableList; 56 import com.android.launcher3.util.SystemUiController; 57 import com.android.launcher3.util.ViewCache; 58 import com.android.launcher3.util.WeakCleanupSet; 59 import com.android.launcher3.util.WindowBounds; 60 import com.android.launcher3.views.ActivityContext; 61 import com.android.launcher3.views.ScrimView; 62 63 import java.io.PrintWriter; 64 import java.lang.annotation.Retention; 65 import java.util.ArrayList; 66 import java.util.List; 67 import java.util.StringJoiner; 68 69 /** 70 * Launcher BaseActivity 71 */ 72 public abstract class BaseActivity extends Activity implements ActivityContext, 73 DisplayInfoChangeListener { 74 75 private static final String TAG = "BaseActivity"; 76 static final boolean DEBUG = false; 77 78 public static final int INVISIBLE_BY_STATE_HANDLER = 1 << 0; 79 public static final int INVISIBLE_BY_APP_TRANSITIONS = 1 << 1; 80 public static final int INVISIBLE_BY_PENDING_FLAGS = 1 << 2; 81 82 // This is not treated as invisibility flag, but adds as a hint for an incomplete transition. 83 // When the wallpaper animation runs, it replaces this flag with a proper invisibility 84 // flag, INVISIBLE_BY_PENDING_FLAGS only for the duration of that animation. 85 public static final int PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION = 1 << 3; 86 87 private static final int INVISIBLE_FLAGS = 88 INVISIBLE_BY_STATE_HANDLER | INVISIBLE_BY_APP_TRANSITIONS | INVISIBLE_BY_PENDING_FLAGS; 89 public static final int STATE_HANDLER_INVISIBILITY_FLAGS = 90 INVISIBLE_BY_STATE_HANDLER | PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; 91 public static final int INVISIBLE_ALL = 92 INVISIBLE_FLAGS | PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; 93 94 @Retention(SOURCE) 95 @IntDef( 96 flag = true, 97 value = {INVISIBLE_BY_STATE_HANDLER, INVISIBLE_BY_APP_TRANSITIONS, 98 INVISIBLE_BY_PENDING_FLAGS, PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION}) 99 public @interface InvisibilityFlags { 100 } 101 102 private final ArrayList<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>(); 103 private final ArrayList<MultiWindowModeChangedListener> mMultiWindowModeChangedListeners = 104 new ArrayList<>(); 105 106 private final SavedStateRegistryController mSavedStateRegistryController = 107 SavedStateRegistryController.create(this); 108 private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); 109 private final WeakCleanupSet mCleanupSet = new WeakCleanupSet(this); 110 111 protected DeviceProfile mDeviceProfile; 112 protected SystemUiController mSystemUiController; 113 private StatsLogManager mStatsLogManager; 114 115 public static final int ACTIVITY_STATE_STARTED = 1 << 0; 116 public static final int ACTIVITY_STATE_RESUMED = 1 << 1; 117 118 /** 119 * State flags indicating that the activity has received one frame after resume, and was 120 * not immediately paused. 121 */ 122 public static final int ACTIVITY_STATE_DEFERRED_RESUMED = 1 << 2; 123 124 public static final int ACTIVITY_STATE_WINDOW_FOCUSED = 1 << 3; 125 126 /** 127 * State flag indicating if the user is active or the activity when to background as a result 128 * of user action. 129 * 130 * @see #isUserActive() 131 */ 132 public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4; 133 134 /** 135 * State flag indicating that a state transition is in progress 136 */ 137 public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 6; 138 139 @Retention(SOURCE) 140 @IntDef( 141 flag = true, 142 value = {ACTIVITY_STATE_STARTED, 143 ACTIVITY_STATE_RESUMED, 144 ACTIVITY_STATE_DEFERRED_RESUMED, 145 ACTIVITY_STATE_WINDOW_FOCUSED, 146 ACTIVITY_STATE_USER_ACTIVE, 147 ACTIVITY_STATE_TRANSITION_ACTIVE}) 148 public @interface ActivityFlags { 149 } 150 151 // When starting an action mode, setting this tag will cause the action mode to be cancelled 152 // automatically when user interacts with the launcher. 153 public static final Object AUTO_CANCEL_ACTION_MODE = new Object(); 154 155 /** Returns a human-readable string for the specified {@link ActivityFlags}. */ getActivityStateString(@ctivityFlags int flags)156 public static String getActivityStateString(@ActivityFlags int flags) { 157 StringJoiner result = new StringJoiner("|"); 158 appendFlag(result, flags, ACTIVITY_STATE_STARTED, "state_started"); 159 appendFlag(result, flags, ACTIVITY_STATE_RESUMED, "state_resumed"); 160 appendFlag(result, flags, ACTIVITY_STATE_DEFERRED_RESUMED, "state_deferred_resumed"); 161 appendFlag(result, flags, ACTIVITY_STATE_WINDOW_FOCUSED, "state_window_focused"); 162 appendFlag(result, flags, ACTIVITY_STATE_USER_ACTIVE, "state_user_active"); 163 appendFlag(result, flags, ACTIVITY_STATE_TRANSITION_ACTIVE, "state_transition_active"); 164 return result.toString(); 165 } 166 167 @ActivityFlags 168 private int mActivityFlags; 169 170 // When the recents animation is running, the visibility of the Launcher is managed by the 171 // animation 172 @InvisibilityFlags 173 private int mForceInvisible; 174 175 private final ViewCache mViewCache = new ViewCache(); 176 177 @Retention(SOURCE) 178 @IntDef({EVENT_STARTED, EVENT_RESUMED, EVENT_STOPPED, EVENT_DESTROYED}) 179 public @interface ActivityEvent { } 180 public static final int EVENT_STARTED = 0; 181 public static final int EVENT_RESUMED = 1; 182 public static final int EVENT_STOPPED = 2; 183 public static final int EVENT_DESTROYED = 3; 184 185 // Callback array that corresponds to events defined in @ActivityEvent 186 private final RunnableList[] mEventCallbacks = 187 {new RunnableList(), new RunnableList(), new RunnableList(), new RunnableList()}; 188 189 private ActionMode mCurrentActionMode; 190 BaseActivity()191 public BaseActivity() { 192 mSavedStateRegistryController.performAttach(); 193 registerActivityLifecycleCallbacks( 194 new LifecycleHelper(this, mSavedStateRegistryController, mLifecycleRegistry)); 195 } 196 197 @Override getViewCache()198 public ViewCache getViewCache() { 199 return mViewCache; 200 } 201 202 @Override getDeviceProfile()203 public DeviceProfile getDeviceProfile() { 204 return mDeviceProfile; 205 } 206 207 @Override getOnDeviceProfileChangeListeners()208 public List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() { 209 return mDPChangeListeners; 210 } 211 212 /** 213 * Returns {@link StatsLogManager} for user event logging. 214 */ 215 @Override getStatsLogManager()216 public StatsLogManager getStatsLogManager() { 217 if (mStatsLogManager == null) { 218 mStatsLogManager = StatsLogManager.newInstance(this); 219 } 220 return mStatsLogManager; 221 } 222 getSystemUiController()223 public SystemUiController getSystemUiController() { 224 if (mSystemUiController == null) { 225 mSystemUiController = new SystemUiController(getWindow().getDecorView()); 226 } 227 return mSystemUiController; 228 } 229 getScrimView()230 public ScrimView getScrimView() { 231 return null; 232 } 233 234 @Override onActivityResult(int requestCode, int resultCode, Intent data)235 public void onActivityResult(int requestCode, int resultCode, Intent data) { 236 super.onActivityResult(requestCode, resultCode, data); 237 } 238 239 @Override onCreate(Bundle savedInstanceState)240 protected void onCreate(Bundle savedInstanceState) { 241 super.onCreate(savedInstanceState); 242 registerBackDispatcher(); 243 DisplayController.INSTANCE.get(this).addChangeListener(this); 244 } 245 246 @Override onStart()247 protected void onStart() { 248 addActivityFlags(ACTIVITY_STATE_STARTED); 249 super.onStart(); 250 mEventCallbacks[EVENT_STARTED].executeAllAndClear(); 251 } 252 253 @Override onResume()254 protected void onResume() { 255 setResumed(); 256 super.onResume(); 257 mEventCallbacks[EVENT_RESUMED].executeAllAndClear(); 258 } 259 260 @Override onUserLeaveHint()261 protected void onUserLeaveHint() { 262 removeActivityFlags(ACTIVITY_STATE_USER_ACTIVE); 263 super.onUserLeaveHint(); 264 } 265 266 @Override onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig)267 public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) { 268 super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig); 269 for (int i = mMultiWindowModeChangedListeners.size() - 1; i >= 0; i--) { 270 mMultiWindowModeChangedListeners.get(i).onMultiWindowModeChanged(isInMultiWindowMode); 271 } 272 } 273 274 @Override onStop()275 protected void onStop() { 276 removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE); 277 mForceInvisible = 0; 278 super.onStop(); 279 mEventCallbacks[EVENT_STOPPED].executeAllAndClear(); 280 281 282 // Reset the overridden sysui flags used for the task-swipe launch animation, this is a 283 // catch all for if we do not get resumed (and therefore not paused below) 284 getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0); 285 } 286 287 @Override onDestroy()288 protected void onDestroy() { 289 super.onDestroy(); 290 mEventCallbacks[EVENT_DESTROYED].executeAllAndClear(); 291 DisplayController.INSTANCE.get(this).removeChangeListener(this); 292 } 293 294 @Override onPause()295 protected void onPause() { 296 setPaused(); 297 super.onPause(); 298 299 // Reset the overridden sysui flags used for the task-swipe launch animation, we do this 300 // here instead of at the end of the animation because the start of the new activity does 301 // not happen immediately, which would cause us to reset to launcher's sysui flags and then 302 // back to the new app (causing a flash) 303 getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0); 304 } 305 306 @Override onWindowFocusChanged(boolean hasFocus)307 public void onWindowFocusChanged(boolean hasFocus) { 308 super.onWindowFocusChanged(hasFocus); 309 if (hasFocus) { 310 addActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED); 311 } else { 312 removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED); 313 } 314 } 315 registerBackDispatcher()316 protected void registerBackDispatcher() { 317 if (Utilities.ATLEAST_T) { 318 getOnBackInvokedDispatcher().registerOnBackInvokedCallback( 319 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 320 () -> { 321 onBackPressed(); 322 TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked"); 323 }); 324 } 325 } 326 isStarted()327 public boolean isStarted() { 328 return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0; 329 } 330 331 /** 332 * isResumed in already defined as a hidden final method in Activity.java 333 */ hasBeenResumed()334 public boolean hasBeenResumed() { 335 return (mActivityFlags & ACTIVITY_STATE_RESUMED) != 0; 336 } 337 338 /** 339 * Sets the activity to appear as paused. 340 */ setPaused()341 public void setPaused() { 342 removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED); 343 } 344 345 /** 346 * Sets the activity to appear as resumed. 347 */ setResumed()348 public void setResumed() { 349 addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE); 350 } 351 isUserActive()352 public boolean isUserActive() { 353 return (mActivityFlags & ACTIVITY_STATE_USER_ACTIVE) != 0; 354 } 355 getActivityFlags()356 public int getActivityFlags() { 357 return mActivityFlags; 358 } 359 addActivityFlags(int toAdd)360 protected void addActivityFlags(int toAdd) { 361 final int oldFlags = mActivityFlags; 362 mActivityFlags |= toAdd; 363 if (DEBUG) { 364 Log.d(TAG, "Launcher flags updated: " + formatFlagChange(mActivityFlags, oldFlags, 365 BaseActivity::getActivityStateString)); 366 } 367 onActivityFlagsChanged(toAdd); 368 } 369 removeActivityFlags(int toRemove)370 protected void removeActivityFlags(int toRemove) { 371 final int oldFlags = mActivityFlags; 372 mActivityFlags &= ~toRemove; 373 if (DEBUG) { 374 Log.d(TAG, "Launcher flags updated: " + formatFlagChange(mActivityFlags, oldFlags, 375 BaseActivity::getActivityStateString)); 376 } 377 378 onActivityFlagsChanged(toRemove); 379 } 380 onActivityFlagsChanged(int changeBits)381 protected void onActivityFlagsChanged(int changeBits) { 382 } 383 addMultiWindowModeChangedListener(MultiWindowModeChangedListener listener)384 public void addMultiWindowModeChangedListener(MultiWindowModeChangedListener listener) { 385 mMultiWindowModeChangedListeners.add(listener); 386 } 387 removeMultiWindowModeChangedListener(MultiWindowModeChangedListener listener)388 public void removeMultiWindowModeChangedListener(MultiWindowModeChangedListener listener) { 389 mMultiWindowModeChangedListeners.remove(listener); 390 } 391 392 /** 393 * Used to set the override visibility state, used only to handle the transition home with the 394 * recents animation. 395 * 396 * @see QuickstepTransitionManager#createWallpaperOpenRunner 397 */ addForceInvisibleFlag(@nvisibilityFlags int flag)398 public void addForceInvisibleFlag(@InvisibilityFlags int flag) { 399 mForceInvisible |= flag; 400 } 401 clearForceInvisibleFlag(@nvisibilityFlags int flag)402 public void clearForceInvisibleFlag(@InvisibilityFlags int flag) { 403 mForceInvisible &= ~flag; 404 } 405 406 /** 407 * @return Wether this activity should be considered invisible regardless of actual visibility. 408 */ isForceInvisible()409 public boolean isForceInvisible() { 410 return hasSomeInvisibleFlag(INVISIBLE_FLAGS); 411 } 412 hasSomeInvisibleFlag(int mask)413 public boolean hasSomeInvisibleFlag(int mask) { 414 return (mForceInvisible & mask) != 0; 415 } 416 417 /** 418 * Adds a callback for the provided activity event 419 */ addEventCallback(@ctivityEvent int event, Runnable callback)420 public void addEventCallback(@ActivityEvent int event, Runnable callback) { 421 mEventCallbacks[event].add(callback); 422 } 423 424 /** Removes a previously added callback */ removeEventCallback(@ctivityEvent int event, Runnable callback)425 public void removeEventCallback(@ActivityEvent int event, Runnable callback) { 426 mEventCallbacks[event].remove(callback); 427 } 428 429 public interface MultiWindowModeChangedListener { onMultiWindowModeChanged(boolean isInMultiWindowMode)430 void onMultiWindowModeChanged(boolean isInMultiWindowMode); 431 } 432 dumpMisc(String prefix, PrintWriter writer)433 protected void dumpMisc(String prefix, PrintWriter writer) { 434 writer.println(prefix + "deviceProfile isTransposed=" 435 + getDeviceProfile().isVerticalBarLayout()); 436 writer.println(prefix + "orientation=" + getResources().getConfiguration().orientation); 437 writer.println(prefix + "mSystemUiController: " + mSystemUiController); 438 writer.println(prefix + "mActivityFlags: " + getActivityStateString(mActivityFlags)); 439 writer.println(prefix + "mForceInvisible: " + mForceInvisible); 440 } 441 442 443 @Override onActionModeStarted(ActionMode mode)444 public void onActionModeStarted(ActionMode mode) { 445 super.onActionModeStarted(mode); 446 mCurrentActionMode = mode; 447 } 448 449 @Override onActionModeFinished(ActionMode mode)450 public void onActionModeFinished(ActionMode mode) { 451 super.onActionModeFinished(mode); 452 mCurrentActionMode = null; 453 } 454 isInAutoCancelActionMode()455 protected boolean isInAutoCancelActionMode() { 456 return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag(); 457 } 458 459 @Override finishAutoCancelActionMode()460 public boolean finishAutoCancelActionMode() { 461 if (isInAutoCancelActionMode()) { 462 mCurrentActionMode.finish(); 463 return true; 464 } 465 return false; 466 } 467 468 @Override 469 @NonNull getActivityLaunchOptions(View v, @Nullable ItemInfo item)470 public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { 471 ActivityOptionsWrapper wrapper = ActivityContext.super.getActivityLaunchOptions(v, item); 472 addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy); 473 return wrapper; 474 } 475 476 @Override makeDefaultActivityOptions(int splashScreenStyle)477 public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) { 478 ActivityOptionsWrapper wrapper = 479 ActivityContext.super.makeDefaultActivityOptions(splashScreenStyle); 480 addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy); 481 return wrapper; 482 } 483 getMultiWindowDisplaySize()484 protected WindowBounds getMultiWindowDisplaySize() { 485 return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics()); 486 } 487 488 @Override onDisplayInfoChanged(Context context, Info info, int flags)489 public void onDisplayInfoChanged(Context context, Info info, int flags) { 490 if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) { 491 reapplyUi(); 492 } 493 } 494 reapplyUi()495 protected void reapplyUi() {} 496 497 @NonNull 498 @Override getSavedStateRegistry()499 public SavedStateRegistry getSavedStateRegistry() { 500 return mSavedStateRegistryController.getSavedStateRegistry(); 501 } 502 503 @NonNull 504 @Override getLifecycle()505 public Lifecycle getLifecycle() { 506 return mLifecycleRegistry; 507 } 508 509 @Override getOwnerCleanupSet()510 public WeakCleanupSet getOwnerCleanupSet() { 511 return mCleanupSet; 512 } 513 fromContext(Context context)514 public static <T extends BaseActivity> T fromContext(Context context) { 515 if (context instanceof BaseActivity) { 516 return (T) context; 517 } else if (context instanceof ContextWrapper cw) { 518 return fromContext(cw.getBaseContext()); 519 } else { 520 throw new IllegalArgumentException("Cannot find BaseActivity in parent tree"); 521 } 522 } 523 } 524