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