• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static android.view.Display.DEFAULT_DISPLAY;
23 
24 import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
25 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
26 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
27 import static com.android.quickstep.fallback.window.RecentsWindowFlags.enableFallbackOverviewInWindow;
28 import static com.android.quickstep.fallback.window.RecentsWindowFlags.enableLauncherOverviewInWindow;
29 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
30 
31 import android.content.ActivityNotFoundException;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.ActivityInfo;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.os.Bundle;
39 import android.util.Log;
40 import android.util.SparseIntArray;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 import androidx.annotation.UiThread;
45 
46 import com.android.launcher3.R;
47 import com.android.launcher3.dagger.ApplicationContext;
48 import com.android.launcher3.dagger.LauncherAppComponent;
49 import com.android.launcher3.dagger.LauncherAppSingleton;
50 import com.android.launcher3.util.DaggerSingletonObject;
51 import com.android.launcher3.util.DaggerSingletonTracker;
52 import com.android.launcher3.util.SimpleBroadcastReceiver;
53 import com.android.quickstep.fallback.window.RecentsDisplayModel;
54 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
55 import com.android.systemui.shared.system.PackageManagerWrapper;
56 
57 import java.io.PrintWriter;
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.Objects;
61 import java.util.concurrent.CopyOnWriteArrayList;
62 
63 import javax.inject.Inject;
64 
65 /**
66  * Class to keep track of the current overview component based off user preferences and app updates
67  * and provide callers the relevant classes.
68  */
69 @LauncherAppSingleton
70 public final class OverviewComponentObserver {
71     private static final String TAG = "OverviewComponentObserver";
72 
73     public static final DaggerSingletonObject<OverviewComponentObserver> INSTANCE =
74             new DaggerSingletonObject<>(LauncherAppComponent::getOverviewComponentObserver);
75 
76     // We register broadcast receivers on main thread to avoid missing updates.
77     private final SimpleBroadcastReceiver mUserPreferenceChangeReceiver;
78     private final SimpleBroadcastReceiver mOtherHomeAppUpdateReceiver;
79 
80     private final RecentsDisplayModel mRecentsDisplayModel;
81 
82     private final Intent mCurrentHomeIntent;
83     private final Intent mMyHomeIntent;
84     private final Intent mFallbackIntent;
85     private final SparseIntArray mConfigChangesMap = new SparseIntArray();
86     private final String mSetupWizardPkg;
87 
88     private final List<OverviewChangeListener> mOverviewChangeListeners =
89             new CopyOnWriteArrayList<>();
90 
91     private String mUpdateRegisteredPackage;
92     private BaseContainerInterface mDefaultDisplayContainerInterface;
93     private Intent mOverviewIntent;
94     private boolean mIsHomeAndOverviewSame;
95     private boolean mIsDefaultHome;
96     private boolean mIsHomeDisabled;
97 
98     @Inject
OverviewComponentObserver( @pplicationContext Context context, RecentsDisplayModel recentsDisplayModel, DaggerSingletonTracker lifecycleTracker)99     public OverviewComponentObserver(
100             @ApplicationContext Context context,
101             RecentsDisplayModel recentsDisplayModel,
102             DaggerSingletonTracker lifecycleTracker) {
103         mUserPreferenceChangeReceiver =
104                 new SimpleBroadcastReceiver(context, MAIN_EXECUTOR, this::updateOverviewTargets);
105         mOtherHomeAppUpdateReceiver =
106                 new SimpleBroadcastReceiver(context, MAIN_EXECUTOR, this::updateOverviewTargets);
107         mRecentsDisplayModel = recentsDisplayModel;
108         mCurrentHomeIntent = createHomeIntent();
109         mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(context.getPackageName());
110         ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
111         ComponentName myHomeComponent =
112                 new ComponentName(context.getPackageName(), info.activityInfo.name);
113         mMyHomeIntent.setComponent(myHomeComponent);
114         mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
115         mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
116 
117         ComponentName fallbackComponent = new ComponentName(context, RecentsActivity.class);
118         mFallbackIntent = new Intent(Intent.ACTION_MAIN)
119                 .addCategory(Intent.CATEGORY_DEFAULT)
120                 .setComponent(fallbackComponent)
121                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
122 
123         try {
124             ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
125                     mFallbackIntent.getComponent(), 0 /* flags */);
126             mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
127         } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
128 
129         mUserPreferenceChangeReceiver.register(ACTION_PREFERRED_ACTIVITY_CHANGED);
130         updateOverviewTargets();
131 
132         lifecycleTracker.addCloseable(this::onDestroy);
133     }
134 
135     /** Adds a listener for changes in {@link #isHomeAndOverviewSame()} */
addOverviewChangeListener(OverviewChangeListener overviewChangeListener)136     public void addOverviewChangeListener(OverviewChangeListener overviewChangeListener) {
137         mOverviewChangeListeners.add(overviewChangeListener);
138     }
139 
140     /** Removes a previously added listener */
removeOverviewChangeListener(OverviewChangeListener overviewChangeListener)141     public void removeOverviewChangeListener(OverviewChangeListener overviewChangeListener) {
142         mOverviewChangeListeners.remove(overviewChangeListener);
143     }
144 
145     /**
146      * Called to set home enabled/disabled state via systemUI
147      * @param isHomeDisabled
148      */
setHomeDisabled(boolean isHomeDisabled)149     public void setHomeDisabled(boolean isHomeDisabled) {
150         if (isHomeDisabled != mIsHomeDisabled) {
151             mIsHomeDisabled = isHomeDisabled;
152             updateOverviewTargets();
153         }
154     }
155 
updateOverviewTargets(Intent unused)156     private void updateOverviewTargets(Intent unused) {
157         updateOverviewTargets();
158     }
159 
160     /**
161      * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
162      * component.
163      */
164     @UiThread
updateOverviewTargets()165     private void updateOverviewTargets() {
166         ComponentName defaultHome = PackageManagerWrapper.getInstance()
167                 .getHomeActivities(new ArrayList<>());
168         if (defaultHome != null && defaultHome.getPackageName().equals(mSetupWizardPkg)) {
169             // Treat setup wizard as null default home, because there is a period between setup and
170             // launcher being default home where it is briefly null. Otherwise, it would appear as
171             // if overview targets are changing twice, giving the listener an incorrect signal.
172             defaultHome = null;
173         }
174 
175         mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
176 
177         // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
178         // launcher made invisible become visible again before the new activity control helper
179         // becomes active.
180         if (mDefaultDisplayContainerInterface != null) {
181             mDefaultDisplayContainerInterface.onAssistantVisibilityChanged(0.f);
182         }
183 
184         if (SEPARATE_RECENTS_ACTIVITY.get()) {
185             mIsDefaultHome = false;
186             if (defaultHome == null) {
187                 defaultHome = mMyHomeIntent.getComponent();
188             }
189         }
190 
191         // TODO(b/258022658): Remove temporary logging.
192         Log.i(TAG, "updateOverviewTargets: mIsHomeDisabled=" + mIsHomeDisabled
193                 + ", isDefaultHomeNull=" + (defaultHome == null)
194                 + ", mIsDefaultHome=" + mIsDefaultHome);
195 
196         if (!mIsHomeDisabled && (defaultHome == null || mIsDefaultHome)) {
197             // User default home is same as our home app. Use Overview integrated in Launcher.
198             if (enableLauncherOverviewInWindow.isTrue()) {
199                 mDefaultDisplayContainerInterface =
200                         mRecentsDisplayModel.getFallbackWindowInterface(DEFAULT_DISPLAY);
201             } else {
202                 mDefaultDisplayContainerInterface = LauncherActivityInterface.INSTANCE;
203             }
204             mIsHomeAndOverviewSame = true;
205             mOverviewIntent = mMyHomeIntent;
206             mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
207 
208             // Remove any update listener as we don't care about other packages.
209             unregisterOtherHomeAppUpdateReceiver();
210         } else {
211             // The default home app is a different launcher. Use the fallback Overview instead.
212             if (enableFallbackOverviewInWindow.isTrue()) {
213                 mDefaultDisplayContainerInterface =
214                         mRecentsDisplayModel.getFallbackWindowInterface(DEFAULT_DISPLAY);
215             } else {
216                 mDefaultDisplayContainerInterface = FallbackActivityInterface.INSTANCE;
217             }
218             mIsHomeAndOverviewSame = false;
219             mOverviewIntent = mFallbackIntent;
220             mCurrentHomeIntent.setComponent(defaultHome);
221 
222             // User's default home app can change as a result of package updates of this app (such
223             // as uninstalling the app or removing the "Launcher" feature in an update).
224             // Listen for package updates of this app (and remove any previously attached
225             // package listener).
226             if (defaultHome == null) {
227                 unregisterOtherHomeAppUpdateReceiver();
228             } else if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
229                 unregisterOtherHomeAppUpdateReceiver();
230 
231                 mUpdateRegisteredPackage = defaultHome.getPackageName();
232                 mOtherHomeAppUpdateReceiver.registerPkgActions(
233                         mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED,
234                         ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
235             }
236         }
237         mOverviewChangeListeners.forEach(l -> l.onOverviewTargetChange(mIsHomeAndOverviewSame));
238     }
239 
240     /**
241      * Clean up any registered receivers.
242      */
onDestroy()243     private void onDestroy() {
244         mUserPreferenceChangeReceiver.unregisterReceiverSafely();
245         unregisterOtherHomeAppUpdateReceiver();
246     }
247 
unregisterOtherHomeAppUpdateReceiver()248     private void unregisterOtherHomeAppUpdateReceiver() {
249         if (mUpdateRegisteredPackage != null) {
250             mOtherHomeAppUpdateReceiver.unregisterReceiverSafely();
251             mUpdateRegisteredPackage = null;
252         }
253     }
254 
255     /**
256      * @return {@code true} if the overview component is able to handle the configuration changes.
257      */
canHandleConfigChanges(ComponentName component, int changes)258     boolean canHandleConfigChanges(ComponentName component, int changes) {
259         final int orientationChange =
260                 ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE;
261         if ((changes & orientationChange) == orientationChange) {
262             // This is just an approximate guess for simple orientation change because the changes
263             // may contain non-public bits (e.g. window configuration).
264             return true;
265         }
266 
267         int configMask = mConfigChangesMap.get(component.hashCode());
268         return configMask != 0 && (~configMask & changes) == 0;
269     }
270 
271     /**
272      * Get the intent for overview activity. It is used when lockscreen is shown and home was died
273      * in background, we still want to restart the one that will be used after unlock.
274      *
275      * @return the overview intent
276      */
getOverviewIntentIgnoreSysUiState()277     public Intent getOverviewIntentIgnoreSysUiState() {
278         return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
279     }
280 
281     /**
282      * Get the current intent for going to the overview activity.
283      *
284      * @return the overview intent
285      */
getOverviewIntent()286     public Intent getOverviewIntent() {
287         return mOverviewIntent;
288     }
289 
290     /**
291      * Get the current intent for going to the home activity.
292      */
getHomeIntent()293     public Intent getHomeIntent() {
294         return mCurrentHomeIntent;
295     }
296 
297     /**
298      * Returns true if home and overview are same process.
299      */
isHomeAndOverviewSame()300     public boolean isHomeAndOverviewSame() {
301         return mIsHomeAndOverviewSame;
302     }
303 
isHomeAndOverviewSameActivity()304     public boolean isHomeAndOverviewSameActivity() {
305         return isHomeAndOverviewSame() && !enableLauncherOverviewInWindow.isTrue();
306     }
307 
308     /**
309      * Get the current control helper for managing interactions to the overview container for
310      * the given displayId.
311      *
312      * @param displayId The display id
313      * @return the control helper for the given display
314      */
getContainerInterface(int displayId)315     public BaseContainerInterface<?, ?> getContainerInterface(int displayId) {
316         return (enableOverviewOnConnectedDisplays() && displayId != DEFAULT_DISPLAY)
317                 ? mRecentsDisplayModel.getFallbackWindowInterface(displayId)
318                 : mDefaultDisplayContainerInterface;
319     }
320 
dump(PrintWriter pw)321     public void dump(PrintWriter pw) {
322         pw.println("OverviewComponentObserver:");
323         pw.println("  isDefaultHome=" + mIsDefaultHome);
324         pw.println("  isHomeDisabled=" + mIsHomeDisabled);
325         pw.println("  homeAndOverviewSame=" + mIsHomeAndOverviewSame);
326         pw.println("  overviewIntent=" + mOverviewIntent);
327         pw.println("  homeIntent=" + mCurrentHomeIntent);
328     }
329 
330     /**
331      * Starts the intent for the current home activity.
332      */
startHomeIntentSafely(@onNull Context context, @Nullable Bundle options, @NonNull String reason)333     public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options,
334             @NonNull String reason) {
335         Intent intent = OverviewComponentObserver.INSTANCE.get(context).getHomeIntent();
336         startHomeIntentSafely(context, intent, options, reason);
337     }
338 
339     /**
340      * Starts the intent for the current home activity.
341      */
startHomeIntentSafely( @onNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options, @NonNull String reason)342     public static void startHomeIntentSafely(
343             @NonNull Context context,
344             @NonNull Intent homeIntent,
345             @Nullable Bundle options,
346             @NonNull String reason) {
347         ActiveGestureProtoLogProxy.logStartHomeIntent(reason);
348         try {
349             context.startActivity(homeIntent, options);
350         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
351             context.startActivity(createHomeIntent(), options);
352         }
353     }
354 
355     /**
356      * Interface for listening to overview changes
357      */
358     public interface OverviewChangeListener {
359 
360         /**
361          * Called when the overview target changes
362          */
onOverviewTargetChange(boolean isHomeAndOverviewSame)363         void onOverviewTargetChange(boolean isHomeAndOverviewSame);
364     }
365 
createHomeIntent()366     private static Intent createHomeIntent() {
367         return new Intent(Intent.ACTION_MAIN)
368                 .addCategory(Intent.CATEGORY_HOME)
369                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
370     }
371 }
372