Home | History | Annotate | Download | only in quickstep
      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