• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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