1 /* 2 * Copyright (C) 2018 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 package com.android.quickstep; 17 18 import static android.content.Intent.ACTION_PACKAGE_ADDED; 19 import static android.content.Intent.ACTION_PACKAGE_CHANGED; 20 import static android.content.Intent.ACTION_PACKAGE_REMOVED; 21 22 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 23 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; 24 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL; 25 import static com.android.systemui.shared.system.ActivityManagerWrapper 26 .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; 27 import static com.android.systemui.shared.system.PackageManagerWrapper 28 .ACTION_PREFERRED_ACTIVITY_CHANGED; 29 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; 30 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; 31 32 import android.animation.Animator; 33 import android.animation.AnimatorSet; 34 import android.animation.ValueAnimator; 35 import android.annotation.TargetApi; 36 import android.content.BroadcastReceiver; 37 import android.content.ComponentName; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.content.IntentFilter; 41 import android.content.pm.ResolveInfo; 42 import android.graphics.Rect; 43 import android.os.Build; 44 import android.os.PatternMatcher; 45 import android.os.SystemClock; 46 import android.util.Log; 47 import android.view.View; 48 import android.view.ViewConfiguration; 49 50 import com.android.launcher3.AbstractFloatingView; 51 import com.android.launcher3.BaseDraggingActivity; 52 import com.android.launcher3.InvariantDeviceProfile; 53 import com.android.launcher3.MainThreadExecutor; 54 import com.android.launcher3.anim.AnimationSuccessListener; 55 import com.android.launcher3.logging.UserEventDispatcher; 56 import com.android.launcher3.userevent.nano.LauncherLogProto.Action; 57 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 58 import com.android.quickstep.ActivityControlHelper.ActivityInitListener; 59 import com.android.quickstep.ActivityControlHelper.AnimationFactory; 60 import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper; 61 import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper; 62 import com.android.quickstep.util.ClipAnimationHelper; 63 import com.android.quickstep.util.TransformedRect; 64 import com.android.quickstep.util.RemoteAnimationTargetSet; 65 import com.android.quickstep.views.RecentsView; 66 import com.android.systemui.shared.system.ActivityManagerWrapper; 67 import com.android.systemui.shared.system.LatencyTrackerCompat; 68 import com.android.systemui.shared.system.PackageManagerWrapper; 69 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 70 import com.android.systemui.shared.system.TransactionCompat; 71 72 import java.util.ArrayList; 73 74 /** 75 * Helper class to handle various atomic commands for switching between Overview. 76 */ 77 @TargetApi(Build.VERSION_CODES.P) 78 public class OverviewCommandHelper { 79 80 private static final long RECENTS_LAUNCH_DURATION = 250; 81 82 private static final String TAG = "OverviewCommandHelper"; 83 84 private final Context mContext; 85 private final ActivityManagerWrapper mAM; 86 private final RecentsModel mRecentsModel; 87 private final MainThreadExecutor mMainThreadExecutor; 88 private final ComponentName mMyHomeComponent; 89 90 private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() { 91 @Override 92 public void onReceive(Context context, Intent intent) { 93 initOverviewTargets(); 94 } 95 }; 96 private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() { 97 @Override 98 public void onReceive(Context context, Intent intent) { 99 initOverviewTargets(); 100 } 101 }; 102 private String mUpdateRegisteredPackage; 103 104 public Intent overviewIntent; 105 public ComponentName overviewComponent; 106 private ActivityControlHelper mActivityControlHelper; 107 108 private long mLastToggleTime; 109 110 public OverviewCommandHelper(Context context) { 111 mContext = context; 112 mAM = ActivityManagerWrapper.getInstance(); 113 mMainThreadExecutor = new MainThreadExecutor(); 114 mRecentsModel = RecentsModel.getInstance(mContext); 115 116 Intent myHomeIntent = new Intent(Intent.ACTION_MAIN) 117 .addCategory(Intent.CATEGORY_HOME) 118 .setPackage(mContext.getPackageName()); 119 ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0); 120 mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name); 121 122 mContext.registerReceiver(mUserPreferenceChangeReceiver, 123 new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED)); 124 initOverviewTargets(); 125 } 126 127 private void initOverviewTargets() { 128 ComponentName defaultHome = PackageManagerWrapper.getInstance() 129 .getHomeActivities(new ArrayList<>()); 130 131 final String overviewIntentCategory; 132 if (defaultHome == null || mMyHomeComponent.equals(defaultHome)) { 133 // User default home is same as out home app. Use Overview integrated in Launcher. 134 overviewComponent = mMyHomeComponent; 135 mActivityControlHelper = new LauncherActivityControllerHelper(); 136 overviewIntentCategory = Intent.CATEGORY_HOME; 137 138 if (mUpdateRegisteredPackage != null) { 139 // Remove any update listener as we don't care about other packages. 140 mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver); 141 mUpdateRegisteredPackage = null; 142 } 143 } else { 144 // The default home app is a different launcher. Use the fallback Overview instead. 145 overviewComponent = new ComponentName(mContext, RecentsActivity.class); 146 mActivityControlHelper = new FallbackActivityControllerHelper(defaultHome); 147 overviewIntentCategory = Intent.CATEGORY_DEFAULT; 148 149 // User's default home app can change as a result of package updates of this app (such 150 // as uninstalling the app or removing the "Launcher" feature in an update). 151 // Listen for package updates of this app (and remove any previously attached 152 // package listener). 153 if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) { 154 if (mUpdateRegisteredPackage != null) { 155 mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver); 156 } 157 158 mUpdateRegisteredPackage = defaultHome.getPackageName(); 159 IntentFilter updateReceiver = new IntentFilter(ACTION_PACKAGE_ADDED); 160 updateReceiver.addAction(ACTION_PACKAGE_CHANGED); 161 updateReceiver.addAction(ACTION_PACKAGE_REMOVED); 162 updateReceiver.addDataScheme("package"); 163 updateReceiver.addDataSchemeSpecificPart(mUpdateRegisteredPackage, 164 PatternMatcher.PATTERN_LITERAL); 165 mContext.registerReceiver(mOtherHomeAppUpdateReceiver, updateReceiver); 166 } 167 } 168 169 overviewIntent = new Intent(Intent.ACTION_MAIN) 170 .addCategory(overviewIntentCategory) 171 .setComponent(overviewComponent) 172 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 173 } 174 175 public void onDestroy() { 176 mContext.unregisterReceiver(mUserPreferenceChangeReceiver); 177 178 if (mUpdateRegisteredPackage != null) { 179 mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver); 180 mUpdateRegisteredPackage = null; 181 } 182 } 183 184 public void onOverviewToggle() { 185 // If currently screen pinning, do not enter overview 186 if (mAM.isScreenPinningActive()) { 187 return; 188 } 189 190 mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 191 mMainThreadExecutor.execute(new RecentsActivityCommand<>()); 192 } 193 194 public void onOverviewShown() { 195 mMainThreadExecutor.execute(new ShowRecentsCommand()); 196 } 197 198 public void onTip(int actionType, int viewType) { 199 mMainThreadExecutor.execute(new Runnable() { 200 @Override 201 public void run() { 202 UserEventDispatcher.newInstance(mContext, 203 new InvariantDeviceProfile(mContext).getDeviceProfile(mContext)) 204 .logActionTip(actionType, viewType); 205 } 206 }); 207 } 208 209 public ActivityControlHelper getActivityControlHelper() { 210 return mActivityControlHelper; 211 } 212 213 private class ShowRecentsCommand extends RecentsActivityCommand { 214 215 @Override 216 protected boolean handleCommand(long elapsedTime) { 217 return mHelper.getVisibleRecentsView() != null; 218 } 219 } 220 221 private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable { 222 223 protected final ActivityControlHelper<T> mHelper; 224 private final long mCreateTime; 225 private final int mRunningTaskId; 226 227 private ActivityInitListener mListener; 228 private T mActivity; 229 private RecentsView mRecentsView; 230 private final long mToggleClickedTime = SystemClock.uptimeMillis(); 231 private boolean mUserEventLogged; 232 233 public RecentsActivityCommand() { 234 mHelper = getActivityControlHelper(); 235 mCreateTime = SystemClock.elapsedRealtime(); 236 mRunningTaskId = mAM.getRunningTask().id; 237 238 // Preload the plan 239 mRecentsModel.loadTasks(mRunningTaskId, null); 240 } 241 242 @Override 243 public void run() { 244 long elapsedTime = mCreateTime - mLastToggleTime; 245 mLastToggleTime = mCreateTime; 246 247 if (!handleCommand(elapsedTime)) { 248 // Start overview 249 if (!mHelper.switchToRecentsIfVisible(true)) { 250 mListener = mHelper.createActivityInitListener(this::onActivityReady); 251 mListener.registerAndStartActivity(overviewIntent, this::createWindowAnimation, 252 mContext, mMainThreadExecutor.getHandler(), RECENTS_LAUNCH_DURATION); 253 } 254 } 255 } 256 257 protected boolean handleCommand(long elapsedTime) { 258 // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows 259 // the menu activity which takes window focus, preventing the right condition from 260 // being run below 261 RecentsView recents = mHelper.getVisibleRecentsView(); 262 if (recents != null) { 263 // Launch the next task 264 recents.showNextTask(); 265 return true; 266 } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) { 267 // The user tried to launch back into overview too quickly, either after 268 // launching an app, or before overview has actually shown, just ignore for now 269 return true; 270 } 271 return false; 272 } 273 274 private boolean onActivityReady(T activity, Boolean wasVisible) { 275 activity.<RecentsView>getOverviewPanel().setCurrentTask(mRunningTaskId); 276 AbstractFloatingView.closeAllOpenViews(activity, wasVisible); 277 AnimationFactory factory = mHelper.prepareRecentsUI(activity, wasVisible, 278 (controller) -> { 279 controller.dispatchOnStart(); 280 ValueAnimator anim = controller.getAnimationPlayer() 281 .setDuration(RECENTS_LAUNCH_DURATION); 282 anim.setInterpolator(FAST_OUT_SLOW_IN); 283 anim.start(); 284 }); 285 factory.onRemoteAnimationReceived(null); 286 if (wasVisible) { 287 factory.createActivityController(RECENTS_LAUNCH_DURATION, INTERACTION_NORMAL); 288 } 289 mActivity = activity; 290 mRecentsView = mActivity.getOverviewPanel(); 291 mRecentsView.setRunningTaskIconScaledDown(true /* isScaledDown */, false /* animate */); 292 if (!mUserEventLogged) { 293 activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON, 294 mHelper.getContainerType(), ContainerType.TASKSWITCHER); 295 mUserEventLogged = true; 296 } 297 return false; 298 } 299 300 private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) { 301 if (LatencyTrackerCompat.isEnabled(mContext)) { 302 LatencyTrackerCompat.logToggleRecents( 303 (int) (SystemClock.uptimeMillis() - mToggleClickedTime)); 304 } 305 306 if (mListener != null) { 307 mListener.unregister(); 308 } 309 AnimatorSet anim = new AnimatorSet(); 310 anim.addListener(new AnimationSuccessListener() { 311 @Override 312 public void onAnimationSuccess(Animator animator) { 313 if (mRecentsView != null) { 314 mRecentsView.setRunningTaskIconScaledDown(false /* isScaledDown */, 315 true /* animate */); 316 } 317 } 318 }); 319 if (mActivity == null) { 320 Log.e(TAG, "Animation created, before activity"); 321 anim.play(ValueAnimator.ofInt(0, 1).setDuration(100)); 322 return anim; 323 } 324 325 RemoteAnimationTargetSet targetSet = 326 new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING); 327 328 // Use the top closing app to determine the insets for the animation 329 RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId); 330 if (runningTaskTarget == null) { 331 Log.e(TAG, "No closing app"); 332 anim.play(ValueAnimator.ofInt(0, 1).setDuration(100)); 333 return anim; 334 } 335 336 final ClipAnimationHelper clipHelper = new ClipAnimationHelper(); 337 338 // At this point, the activity is already started and laid-out. Get the home-bounds 339 // relative to the screen using the rootView of the activity. 340 int loc[] = new int[2]; 341 View rootView = mActivity.getRootView(); 342 rootView.getLocationOnScreen(loc); 343 Rect homeBounds = new Rect(loc[0], loc[1], 344 loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight()); 345 clipHelper.updateSource(homeBounds, runningTaskTarget); 346 347 TransformedRect targetRect = new TransformedRect(); 348 mHelper.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity, 349 INTERACTION_NORMAL, targetRect); 350 clipHelper.updateTargetRect(targetRect); 351 clipHelper.prepareAnimation(false /* isOpening */); 352 353 ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); 354 valueAnimator.setDuration(RECENTS_LAUNCH_DURATION); 355 valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); 356 valueAnimator.addUpdateListener((v) -> 357 clipHelper.applyTransform(targetSet, (float) v.getAnimatedValue())); 358 359 if (targetSet.isAnimatingHome()) { 360 // If we are animating home, fade in the opening targets 361 RemoteAnimationTargetSet openingSet = 362 new RemoteAnimationTargetSet(targetCompats, MODE_OPENING); 363 364 TransactionCompat transaction = new TransactionCompat(); 365 valueAnimator.addUpdateListener((v) -> { 366 for (RemoteAnimationTargetCompat app : openingSet.apps) { 367 transaction.setAlpha(app.leash, (float) v.getAnimatedValue()); 368 } 369 transaction.apply(); 370 }); 371 } 372 anim.play(valueAnimator); 373 return anim; 374 } 375 } 376 } 377