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.model.WidgetsModel.GO_DISABLE_WIDGETS;
20 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
21 
22 import static java.lang.annotation.RetentionPolicy.SOURCE;
23 
24 import android.app.Activity;
25 import android.content.Context;
26 import android.content.ContextWrapper;
27 import android.content.Intent;
28 import android.content.pm.LauncherApps;
29 import android.content.res.Configuration;
30 import android.graphics.Rect;
31 import android.os.Bundle;
32 import android.os.UserHandle;
33 import android.util.Log;
34 
35 import androidx.annotation.IntDef;
36 
37 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
38 import com.android.launcher3.logging.StatsLogManager;
39 import com.android.launcher3.util.SystemUiController;
40 import com.android.launcher3.util.ViewCache;
41 import com.android.launcher3.views.ActivityContext;
42 import com.android.launcher3.views.ScrimView;
43 
44 import java.io.PrintWriter;
45 import java.lang.annotation.Retention;
46 import java.util.ArrayList;
47 
48 /**
49  * Launcher BaseActivity
50  */
51 public abstract class BaseActivity extends Activity implements ActivityContext {
52 
53     private static final String TAG = "BaseActivity";
54 
55     public static final int INVISIBLE_BY_STATE_HANDLER = 1 << 0;
56     public static final int INVISIBLE_BY_APP_TRANSITIONS = 1 << 1;
57     public static final int INVISIBLE_BY_PENDING_FLAGS = 1 << 2;
58 
59     // This is not treated as invisibility flag, but adds as a hint for an incomplete transition.
60     // When the wallpaper animation runs, it replaces this flag with a proper invisibility
61     // flag, INVISIBLE_BY_PENDING_FLAGS only for the duration of that animation.
62     public static final int PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION = 1 << 3;
63 
64     private static final int INVISIBLE_FLAGS =
65             INVISIBLE_BY_STATE_HANDLER | INVISIBLE_BY_APP_TRANSITIONS | INVISIBLE_BY_PENDING_FLAGS;
66     public static final int STATE_HANDLER_INVISIBILITY_FLAGS =
67             INVISIBLE_BY_STATE_HANDLER | PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
68     public static final int INVISIBLE_ALL =
69             INVISIBLE_FLAGS | PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
70 
71     @Retention(SOURCE)
72     @IntDef(
73             flag = true,
74             value = {INVISIBLE_BY_STATE_HANDLER, INVISIBLE_BY_APP_TRANSITIONS,
75                     INVISIBLE_BY_PENDING_FLAGS, PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION})
76     public @interface InvisibilityFlags{}
77 
78     private final ArrayList<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
79     private final ArrayList<MultiWindowModeChangedListener> mMultiWindowModeChangedListeners =
80             new ArrayList<>();
81 
82     protected DeviceProfile mDeviceProfile;
83     protected StatsLogManager mStatsLogManager;
84     protected SystemUiController mSystemUiController;
85 
86 
87     public static final int ACTIVITY_STATE_STARTED = 1 << 0;
88     public static final int ACTIVITY_STATE_RESUMED = 1 << 1;
89 
90     /**
91      * State flags indicating that the activity has received one frame after resume, and was
92      * not immediately paused.
93      */
94     public static final int ACTIVITY_STATE_DEFERRED_RESUMED = 1 << 2;
95 
96     public static final int ACTIVITY_STATE_WINDOW_FOCUSED = 1 << 3;
97 
98     /**
99      * State flag indicating if the user is active or the activity when to background as a result
100      * of user action.
101      * @see #isUserActive()
102      */
103     public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4;
104 
105     /**
106      * State flag indicating if the user will be active shortly.
107      */
108     public static final int ACTIVITY_STATE_USER_WILL_BE_ACTIVE = 1 << 5;
109 
110     /**
111      * State flag indicating that a state transition is in progress
112      */
113     public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 6;
114 
115     @Retention(SOURCE)
116     @IntDef(
117             flag = true,
118             value = {ACTIVITY_STATE_STARTED,
119                     ACTIVITY_STATE_RESUMED,
120                     ACTIVITY_STATE_DEFERRED_RESUMED,
121                     ACTIVITY_STATE_WINDOW_FOCUSED,
122                     ACTIVITY_STATE_USER_ACTIVE,
123                     ACTIVITY_STATE_TRANSITION_ACTIVE})
124     public @interface ActivityFlags{}
125 
126     @ActivityFlags
127     private int mActivityFlags;
128 
129     // When the recents animation is running, the visibility of the Launcher is managed by the
130     // animation
131     @InvisibilityFlags private int mForceInvisible;
132 
133     private final ViewCache mViewCache = new ViewCache();
134 
135     @Override
getViewCache()136     public ViewCache getViewCache() {
137         return mViewCache;
138     }
139 
140     @Override
getDeviceProfile()141     public DeviceProfile getDeviceProfile() {
142         return mDeviceProfile;
143     }
144 
145     /**
146      * Returns {@link StatsLogManager} for user event logging.
147      */
148     @Override
getStatsLogManager()149     public StatsLogManager getStatsLogManager() {
150         if (mStatsLogManager == null) {
151             mStatsLogManager = StatsLogManager.newInstance(this);
152         }
153         return mStatsLogManager;
154     }
155 
getSystemUiController()156     public SystemUiController getSystemUiController() {
157         if (mSystemUiController == null) {
158             mSystemUiController = new SystemUiController(getWindow());
159         }
160         return mSystemUiController;
161     }
162 
getScrimView()163     public ScrimView getScrimView() {
164         return null;
165     }
166 
167     @Override
onActivityResult(int requestCode, int resultCode, Intent data)168     public void onActivityResult(int requestCode, int resultCode, Intent data) {
169         super.onActivityResult(requestCode, resultCode, data);
170     }
171 
172     @Override
onStart()173     protected void onStart() {
174         addActivityFlags(ACTIVITY_STATE_STARTED);
175         super.onStart();
176     }
177 
178     @Override
onResume()179     protected void onResume() {
180         addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
181         removeActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
182         super.onResume();
183     }
184 
185     @Override
onUserLeaveHint()186     protected void onUserLeaveHint() {
187         removeActivityFlags(ACTIVITY_STATE_USER_ACTIVE);
188         super.onUserLeaveHint();
189     }
190 
191     @Override
onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig)192     public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
193         super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
194         for (int i = mMultiWindowModeChangedListeners.size() - 1; i >= 0; i--) {
195             mMultiWindowModeChangedListeners.get(i).onMultiWindowModeChanged(isInMultiWindowMode);
196         }
197     }
198 
199     @Override
onStop()200     protected void onStop() {
201         removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE);
202         mForceInvisible = 0;
203         super.onStop();
204 
205         // Reset the overridden sysui flags used for the task-swipe launch animation, this is a
206         // catch all for if we do not get resumed (and therefore not paused below)
207         getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
208     }
209 
210     @Override
onPause()211     protected void onPause() {
212         removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED);
213         super.onPause();
214 
215         // Reset the overridden sysui flags used for the task-swipe launch animation, we do this
216         // here instead of at the end of the animation because the start of the new activity does
217         // not happen immediately, which would cause us to reset to launcher's sysui flags and then
218         // back to the new app (causing a flash)
219         getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0);
220     }
221 
222     @Override
onWindowFocusChanged(boolean hasFocus)223     public void onWindowFocusChanged(boolean hasFocus) {
224         super.onWindowFocusChanged(hasFocus);
225         if (hasFocus) {
226             addActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
227         } else {
228             removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
229         }
230 
231     }
232 
isStarted()233     public boolean isStarted() {
234         return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0;
235     }
236 
237     /**
238      * isResumed in already defined as a hidden final method in Activity.java
239      */
hasBeenResumed()240     public boolean hasBeenResumed() {
241         return (mActivityFlags & ACTIVITY_STATE_RESUMED) != 0;
242     }
243 
isUserActive()244     public boolean isUserActive() {
245         return (mActivityFlags & ACTIVITY_STATE_USER_ACTIVE) != 0;
246     }
247 
getActivityFlags()248     public int getActivityFlags() {
249         return mActivityFlags;
250     }
251 
addActivityFlags(int flags)252     protected void addActivityFlags(int flags) {
253         mActivityFlags |= flags;
254         onActivityFlagsChanged(flags);
255     }
256 
removeActivityFlags(int flags)257     protected void removeActivityFlags(int flags) {
258         mActivityFlags &= ~flags;
259         onActivityFlagsChanged(flags);
260     }
261 
onActivityFlagsChanged(int changeBits)262     protected void onActivityFlagsChanged(int changeBits) { }
263 
addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener)264     public void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
265         mDPChangeListeners.add(listener);
266     }
267 
removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener)268     public void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
269         mDPChangeListeners.remove(listener);
270     }
271 
dispatchDeviceProfileChanged()272     protected void dispatchDeviceProfileChanged() {
273         for (int i = mDPChangeListeners.size() - 1; i >= 0; i--) {
274             mDPChangeListeners.get(i).onDeviceProfileChanged(mDeviceProfile);
275         }
276     }
277 
addMultiWindowModeChangedListener(MultiWindowModeChangedListener listener)278     public void addMultiWindowModeChangedListener(MultiWindowModeChangedListener listener) {
279         mMultiWindowModeChangedListeners.add(listener);
280     }
281 
removeMultiWindowModeChangedListener(MultiWindowModeChangedListener listener)282     public void removeMultiWindowModeChangedListener(MultiWindowModeChangedListener listener) {
283         mMultiWindowModeChangedListeners.remove(listener);
284     }
285 
286     /**
287      * Used to set the override visibility state, used only to handle the transition home with the
288      * recents animation.
289      * @see QuickstepTransitionManager#createWallpaperOpenRunner
290      */
addForceInvisibleFlag(@nvisibilityFlags int flag)291     public void addForceInvisibleFlag(@InvisibilityFlags int flag) {
292         mForceInvisible |= flag;
293     }
294 
clearForceInvisibleFlag(@nvisibilityFlags int flag)295     public void clearForceInvisibleFlag(@InvisibilityFlags int flag) {
296         mForceInvisible &= ~flag;
297     }
298 
299     /**
300      * @return Wether this activity should be considered invisible regardless of actual visibility.
301      */
isForceInvisible()302     public boolean isForceInvisible() {
303         return hasSomeInvisibleFlag(INVISIBLE_FLAGS);
304     }
305 
hasSomeInvisibleFlag(int mask)306     public boolean hasSomeInvisibleFlag(int mask) {
307         return (mForceInvisible & mask) != 0;
308     }
309 
310     public interface MultiWindowModeChangedListener {
onMultiWindowModeChanged(boolean isInMultiWindowMode)311         void onMultiWindowModeChanged(boolean isInMultiWindowMode);
312     }
313 
dumpMisc(String prefix, PrintWriter writer)314     protected void dumpMisc(String prefix, PrintWriter writer) {
315         writer.println(prefix + "deviceProfile isTransposed="
316                 + getDeviceProfile().isVerticalBarLayout());
317         writer.println(prefix + "orientation=" + getResources().getConfiguration().orientation);
318         writer.println(prefix + "mSystemUiController: " + mSystemUiController);
319         writer.println(prefix + "mActivityFlags: " + mActivityFlags);
320         writer.println(prefix + "mForceInvisible: " + mForceInvisible);
321     }
322 
323     /**
324      * A wrapper around the platform method with Launcher specific checks
325      */
startShortcut(String packageName, String id, Rect sourceBounds, Bundle startActivityOptions, UserHandle user)326     public void startShortcut(String packageName, String id, Rect sourceBounds,
327             Bundle startActivityOptions, UserHandle user) {
328         if (GO_DISABLE_WIDGETS) {
329             return;
330         }
331         try {
332             getSystemService(LauncherApps.class).startShortcut(packageName, id, sourceBounds,
333                     startActivityOptions, user);
334         } catch (SecurityException | IllegalStateException e) {
335             Log.e(TAG, "Failed to start shortcut", e);
336         }
337     }
338 
fromContext(Context context)339     public static <T extends BaseActivity> T fromContext(Context context) {
340         if (context instanceof BaseActivity) {
341             return (T) context;
342         } else if (context instanceof ContextWrapper) {
343             return fromContext(((ContextWrapper) context).getBaseContext());
344         } else {
345             throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
346         }
347     }
348 }
349