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