1 /*
2  * Copyright (C) 2019 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.quickstep;
18 
19 import static android.content.Intent.ACTION_PACKAGE_ADDED;
20 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
21 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
22 
23 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
24 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
25 
26 import android.content.ActivityNotFoundException;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.os.Bundle;
34 import android.util.Log;
35 import android.util.SparseIntArray;
36 
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 
40 import com.android.launcher3.R;
41 import com.android.launcher3.util.SimpleBroadcastReceiver;
42 import com.android.quickstep.util.ActiveGestureLog;
43 import com.android.systemui.shared.system.PackageManagerWrapper;
44 
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.Objects;
48 import java.util.function.Consumer;
49 
50 /**
51  * Class to keep track of the current overview component based off user preferences and app updates
52  * and provide callers the relevant classes.
53  */
54 public final class OverviewComponentObserver {
55     private static final String TAG = "OverviewComponentObserver";
56 
57     private final SimpleBroadcastReceiver mUserPreferenceChangeReceiver =
58             new SimpleBroadcastReceiver(this::updateOverviewTargets);
59     private final SimpleBroadcastReceiver mOtherHomeAppUpdateReceiver =
60             new SimpleBroadcastReceiver(this::updateOverviewTargets);
61 
62     private final Context mContext;
63     private final RecentsAnimationDeviceState mDeviceState;
64     private final Intent mCurrentHomeIntent;
65     private final Intent mMyHomeIntent;
66     private final Intent mFallbackIntent;
67     private final SparseIntArray mConfigChangesMap = new SparseIntArray();
68     private final String mSetupWizardPkg;
69 
70     private Consumer<Boolean> mOverviewChangeListener = b -> { };
71 
72     private String mUpdateRegisteredPackage;
73     private BaseActivityInterface mActivityInterface;
74     private Intent mOverviewIntent;
75     private boolean mIsHomeAndOverviewSame;
76     private boolean mIsDefaultHome;
77     private boolean mIsHomeDisabled;
78 
79 
OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState)80     public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
81         mContext = context;
82         mDeviceState = deviceState;
83         mCurrentHomeIntent = createHomeIntent();
84         mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
85         ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
86         ComponentName myHomeComponent =
87                 new ComponentName(context.getPackageName(), info.activityInfo.name);
88         mMyHomeIntent.setComponent(myHomeComponent);
89         mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
90         mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
91 
92         ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
93         mFallbackIntent = new Intent(Intent.ACTION_MAIN)
94                 .addCategory(Intent.CATEGORY_DEFAULT)
95                 .setComponent(fallbackComponent)
96                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
97 
98         try {
99             ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
100                     mFallbackIntent.getComponent(), 0 /* flags */);
101             mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
102         } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
103 
104         mUserPreferenceChangeReceiver.register(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
105         updateOverviewTargets();
106     }
107 
108     /**
109      * Sets a listener for changes in {@link #isHomeAndOverviewSame()}
110      */
setOverviewChangeListener(Consumer<Boolean> overviewChangeListener)111     public void setOverviewChangeListener(Consumer<Boolean> overviewChangeListener) {
112         // TODO(b/337861962): This method should be able to support multiple listeners instead of
113         // one so that we can reuse the same instance of this class across multiple places
114         mOverviewChangeListener = overviewChangeListener;
115     }
116 
onSystemUiStateChanged()117     public void onSystemUiStateChanged() {
118         if (mDeviceState.isHomeDisabled() != mIsHomeDisabled) {
119             updateOverviewTargets();
120         }
121     }
122 
updateOverviewTargets(Intent unused)123     private void updateOverviewTargets(Intent unused) {
124         updateOverviewTargets();
125     }
126 
127     /**
128      * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
129      * component.
130      */
updateOverviewTargets()131     private void updateOverviewTargets() {
132         ComponentName defaultHome = PackageManagerWrapper.getInstance()
133                 .getHomeActivities(new ArrayList<>());
134         if (defaultHome != null && defaultHome.getPackageName().equals(mSetupWizardPkg)) {
135             // Treat setup wizard as null default home, because there is a period between setup and
136             // launcher being default home where it is briefly null. Otherwise, it would appear as
137             // if overview targets are changing twice, giving the listener an incorrect signal.
138             defaultHome = null;
139         }
140 
141         mIsHomeDisabled = mDeviceState.isHomeDisabled();
142         mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
143 
144         // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
145         // launcher made invisible become visible again before the new activity control helper
146         // becomes active.
147         if (mActivityInterface != null) {
148             mActivityInterface.onAssistantVisibilityChanged(0.f);
149         }
150 
151         if (SEPARATE_RECENTS_ACTIVITY.get()) {
152             mIsDefaultHome = false;
153             if (defaultHome == null) {
154                 defaultHome = mMyHomeIntent.getComponent();
155             }
156         }
157 
158         // TODO(b/258022658): Remove temporary logging.
159         Log.i(TAG, "updateOverviewTargets: mIsHomeDisabled=" + mIsHomeDisabled
160                 + ", isDefaultHomeNull=" + (defaultHome == null)
161                 + ", mIsDefaultHome=" + mIsDefaultHome);
162 
163         if (!mIsHomeDisabled && (defaultHome == null || mIsDefaultHome)) {
164             // User default home is same as out home app. Use Overview integrated in Launcher.
165             mActivityInterface = LauncherActivityInterface.INSTANCE;
166             mIsHomeAndOverviewSame = true;
167             mOverviewIntent = mMyHomeIntent;
168             mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
169 
170             // Remove any update listener as we don't care about other packages.
171             unregisterOtherHomeAppUpdateReceiver();
172         } else {
173             // The default home app is a different launcher. Use the fallback Overview instead.
174 
175             mActivityInterface = FallbackActivityInterface.INSTANCE;
176             mIsHomeAndOverviewSame = false;
177             mOverviewIntent = mFallbackIntent;
178             mCurrentHomeIntent.setComponent(defaultHome);
179 
180             // User's default home app can change as a result of package updates of this app (such
181             // as uninstalling the app or removing the "Launcher" feature in an update).
182             // Listen for package updates of this app (and remove any previously attached
183             // package listener).
184             if (defaultHome == null) {
185                 unregisterOtherHomeAppUpdateReceiver();
186             } else if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
187                 unregisterOtherHomeAppUpdateReceiver();
188 
189                 mUpdateRegisteredPackage = defaultHome.getPackageName();
190                 mOtherHomeAppUpdateReceiver.registerPkgActions(mContext, mUpdateRegisteredPackage,
191                         ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
192             }
193         }
194         mOverviewChangeListener.accept(mIsHomeAndOverviewSame);
195     }
196 
197     /**
198      * Clean up any registered receivers.
199      */
onDestroy()200     public void onDestroy() {
201         mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
202         unregisterOtherHomeAppUpdateReceiver();
203     }
204 
unregisterOtherHomeAppUpdateReceiver()205     private void unregisterOtherHomeAppUpdateReceiver() {
206         if (mUpdateRegisteredPackage != null) {
207             mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
208             mUpdateRegisteredPackage = null;
209         }
210     }
211 
212     /**
213      * @return {@code true} if the overview component is able to handle the configuration changes.
214      */
canHandleConfigChanges(ComponentName component, int changes)215     boolean canHandleConfigChanges(ComponentName component, int changes) {
216         final int orientationChange =
217                 ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE;
218         if ((changes & orientationChange) == orientationChange) {
219             // This is just an approximate guess for simple orientation change because the changes
220             // may contain non-public bits (e.g. window configuration).
221             return true;
222         }
223 
224         int configMask = mConfigChangesMap.get(component.hashCode());
225         return configMask != 0 && (~configMask & changes) == 0;
226     }
227 
228     /**
229      * Get the intent for overview activity. It is used when lockscreen is shown and home was died
230      * in background, we still want to restart the one that will be used after unlock.
231      *
232      * @return the overview intent
233      */
getOverviewIntentIgnoreSysUiState()234     Intent getOverviewIntentIgnoreSysUiState() {
235         return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
236     }
237 
238     /**
239      * Get the current intent for going to the overview activity.
240      *
241      * @return the overview intent
242      */
getOverviewIntent()243     public Intent getOverviewIntent() {
244         return mOverviewIntent;
245     }
246 
247     /**
248      * Get the current intent for going to the home activity.
249      */
getHomeIntent()250     public Intent getHomeIntent() {
251         return mCurrentHomeIntent;
252     }
253 
254     /**
255      * Returns true if home and overview are same activity.
256      */
isHomeAndOverviewSame()257     public boolean isHomeAndOverviewSame() {
258         return mIsHomeAndOverviewSame;
259     }
260 
261     /**
262      * Get the current activity control helper for managing interactions to the overview activity.
263      *
264      * @return the current activity control helper
265      */
getActivityInterface()266     public BaseActivityInterface getActivityInterface() {
267         return mActivityInterface;
268     }
269 
dump(PrintWriter pw)270     public void dump(PrintWriter pw) {
271         pw.println("OverviewComponentObserver:");
272         pw.println("  isDefaultHome=" + mIsDefaultHome);
273         pw.println("  isHomeDisabled=" + mIsHomeDisabled);
274         pw.println("  homeAndOverviewSame=" + mIsHomeAndOverviewSame);
275         pw.println("  overviewIntent=" + mOverviewIntent);
276         pw.println("  homeIntent=" + mCurrentHomeIntent);
277     }
278 
279     /**
280      * Starts the intent for the current home activity.
281      */
startHomeIntentSafely(@onNull Context context, @Nullable Bundle options, @NonNull String reason)282     public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options,
283             @NonNull String reason) {
284         RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
285         OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
286         Intent intent = observer.getHomeIntent();
287         observer.onDestroy();
288         deviceState.destroy();
289         startHomeIntentSafely(context, intent, options, reason);
290     }
291 
292     /**
293      * Starts the intent for the current home activity.
294      */
startHomeIntentSafely( @onNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options, @NonNull String reason)295     public static void startHomeIntentSafely(
296             @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options,
297             @NonNull String reason) {
298         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
299                 "OverviewComponentObserver.startHomeIntent: ").append(reason));
300         try {
301             context.startActivity(homeIntent, options);
302         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
303             context.startActivity(createHomeIntent(), options);
304         }
305     }
306 
createHomeIntent()307     private static Intent createHomeIntent() {
308         return new Intent(Intent.ACTION_MAIN)
309                 .addCategory(Intent.CATEGORY_HOME)
310                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
311     }
312 }
313