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 17 package com.android.quickstep; 18 19 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; 20 21 import android.content.ComponentName; 22 import android.content.Intent; 23 import android.graphics.Bitmap; 24 import android.graphics.Color; 25 import android.graphics.Rect; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.RemoteException; 29 import android.os.UserHandle; 30 import android.util.Log; 31 import android.view.View; 32 33 import com.android.launcher3.AbstractFloatingView; 34 import com.android.launcher3.BaseDraggingActivity; 35 import com.android.launcher3.DeviceProfile; 36 import com.android.launcher3.ItemInfo; 37 import com.android.launcher3.R; 38 import com.android.launcher3.ShortcutInfo; 39 import com.android.launcher3.popup.SystemShortcut; 40 import com.android.launcher3.userevent.nano.LauncherLogProto; 41 import com.android.launcher3.util.InstantAppResolver; 42 import com.android.quickstep.views.RecentsView; 43 import com.android.quickstep.views.TaskThumbnailView; 44 import com.android.quickstep.views.TaskView; 45 import com.android.systemui.shared.recents.ISystemUiProxy; 46 import com.android.systemui.shared.recents.model.Task; 47 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat; 48 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture; 49 import com.android.systemui.shared.recents.view.RecentsTransition; 50 import com.android.systemui.shared.system.ActivityManagerWrapper; 51 import com.android.systemui.shared.system.ActivityOptionsCompat; 52 import com.android.systemui.shared.system.WindowManagerWrapper; 53 54 import java.util.Collections; 55 import java.util.List; 56 import java.util.function.Consumer; 57 58 /** 59 * Represents a system shortcut that can be shown for a recent task. 60 */ 61 public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut { 62 63 private static final String TAG = "TaskSystemShortcut"; 64 65 protected T mSystemShortcut; 66 67 protected TaskSystemShortcut(T systemShortcut) { 68 super(systemShortcut.iconResId, systemShortcut.labelResId); 69 mSystemShortcut = systemShortcut; 70 } 71 72 protected TaskSystemShortcut(int iconResId, int labelResId) { 73 super(iconResId, labelResId); 74 } 75 76 @Override 77 public View.OnClickListener getOnClickListener( 78 BaseDraggingActivity activity, ItemInfo itemInfo) { 79 return null; 80 } 81 82 public View.OnClickListener getOnClickListener(BaseDraggingActivity activity, TaskView view) { 83 Task task = view.getTask(); 84 85 ShortcutInfo dummyInfo = new ShortcutInfo(); 86 dummyInfo.intent = new Intent(); 87 ComponentName component = task.getTopComponent(); 88 dummyInfo.intent.setComponent(component); 89 dummyInfo.user = UserHandle.of(task.key.userId); 90 dummyInfo.title = TaskUtils.getTitle(activity, task); 91 92 return getOnClickListenerForTask(activity, task, dummyInfo); 93 } 94 95 protected View.OnClickListener getOnClickListenerForTask( 96 BaseDraggingActivity activity, Task task, ItemInfo dummyInfo) { 97 return mSystemShortcut.getOnClickListener(activity, dummyInfo); 98 } 99 100 public static class AppInfo extends TaskSystemShortcut<SystemShortcut.AppInfo> { 101 public AppInfo() { 102 super(new SystemShortcut.AppInfo()); 103 } 104 } 105 106 public static class SplitScreen extends TaskSystemShortcut { 107 108 private Handler mHandler; 109 110 public SplitScreen() { 111 super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen); 112 mHandler = new Handler(Looper.getMainLooper()); 113 } 114 115 @Override 116 public View.OnClickListener getOnClickListener( 117 BaseDraggingActivity activity, TaskView taskView) { 118 if (activity.getDeviceProfile().isMultiWindowMode) { 119 return null; 120 } 121 final Task task = taskView.getTask(); 122 final int taskId = task.key.id; 123 if (!task.isDockable) { 124 return null; 125 } 126 final RecentsView recentsView = activity.getOverviewPanel(); 127 128 final TaskThumbnailView thumbnailView = taskView.getThumbnail(); 129 return (v -> { 130 final View.OnLayoutChangeListener onLayoutChangeListener = 131 new View.OnLayoutChangeListener() { 132 @Override 133 public void onLayoutChange(View v, int l, int t, int r, int b, 134 int oldL, int oldT, int oldR, int oldB) { 135 taskView.getRootView().removeOnLayoutChangeListener(this); 136 recentsView.removeIgnoreResetTask(taskView); 137 138 // Start animating in the side pages once launcher has been resized 139 recentsView.dismissTask(taskView, false, false); 140 } 141 }; 142 143 final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener = 144 new DeviceProfile.OnDeviceProfileChangeListener() { 145 @Override 146 public void onDeviceProfileChanged(DeviceProfile dp) { 147 activity.removeOnDeviceProfileChangeListener(this); 148 if (dp.isMultiWindowMode) { 149 taskView.getRootView().addOnLayoutChangeListener( 150 onLayoutChangeListener); 151 } 152 } 153 }; 154 155 AbstractFloatingView.closeOpenViews(activity, true, 156 AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); 157 158 final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(); 159 if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) { 160 return; 161 } 162 boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT; 163 if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, 164 ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft))) { 165 ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy(); 166 try { 167 sysUiProxy.onSplitScreenInvoked(); 168 } catch (RemoteException e) { 169 Log.w(TAG, "Failed to notify SysUI of split screen: ", e); 170 return; 171 } 172 activity.getUserEventDispatcher().logActionOnControl(TAP, 173 LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET); 174 // Add a device profile change listener to kick off animating the side tasks 175 // once we enter multiwindow mode and relayout 176 activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener); 177 178 final Runnable animStartedListener = () -> { 179 // Hide the task view and wait for the window to be resized 180 // TODO: Consider animating in launcher and do an in-place start activity 181 // afterwards 182 recentsView.addIgnoreResetTask(taskView); 183 taskView.setAlpha(0f); 184 }; 185 186 final int[] position = new int[2]; 187 thumbnailView.getLocationOnScreen(position); 188 final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX()); 189 final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY()); 190 final Rect taskBounds = new Rect(position[0], position[1], 191 position[0] + width, position[1] + height); 192 193 Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap( 194 taskBounds.width(), taskBounds.height(), thumbnailView, 1f, 195 Color.BLACK); 196 AppTransitionAnimationSpecsFuture future = 197 new AppTransitionAnimationSpecsFuture(mHandler) { 198 @Override 199 public List<AppTransitionAnimationSpecCompat> composeSpecs() { 200 return Collections.singletonList(new AppTransitionAnimationSpecCompat( 201 taskId, thumbnail, taskBounds)); 202 } 203 }; 204 WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( 205 future, animStartedListener, mHandler, true /* scaleUp */); 206 } 207 }); 208 } 209 } 210 211 public static class Pin extends TaskSystemShortcut { 212 213 private static final String TAG = Pin.class.getSimpleName(); 214 215 private Handler mHandler; 216 217 public Pin() { 218 super(R.drawable.ic_pin, R.string.recent_task_option_pin); 219 mHandler = new Handler(Looper.getMainLooper()); 220 } 221 222 @Override 223 public View.OnClickListener getOnClickListener( 224 BaseDraggingActivity activity, TaskView taskView) { 225 ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy(); 226 if (sysUiProxy == null) { 227 return null; 228 } 229 if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { 230 return null; 231 } 232 if (ActivityManagerWrapper.getInstance().isLockToAppActive()) { 233 // We shouldn't be able to pin while an app is locked. 234 return null; 235 } 236 return view -> { 237 Consumer<Boolean> resultCallback = success -> { 238 if (success) { 239 try { 240 sysUiProxy.startScreenPinning(taskView.getTask().key.id); 241 } catch (RemoteException e) { 242 Log.w(TAG, "Failed to start screen pinning: ", e); 243 } 244 } else { 245 taskView.notifyTaskLaunchFailed(TAG); 246 } 247 }; 248 taskView.launchTask(true, resultCallback, mHandler); 249 }; 250 } 251 } 252 253 public static class Install extends TaskSystemShortcut<SystemShortcut.Install> { 254 public Install() { 255 super(new SystemShortcut.Install()); 256 } 257 258 @Override 259 protected View.OnClickListener getOnClickListenerForTask( 260 BaseDraggingActivity activity, Task task, ItemInfo itemInfo) { 261 if (InstantAppResolver.newInstance(activity).isInstantApp(activity, 262 task.getTopComponent().getPackageName())) { 263 return mSystemShortcut.createOnClickListener(activity, itemInfo); 264 } 265 return null; 266 } 267 } 268 } 269