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