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