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