1 /* 2 * Copyright (C) 2016 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.wm.shell.legacysplitscreen; 18 19 20 import static com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity.EXTRA_FORCED_RESIZEABLE_REASON; 21 22 import android.app.ActivityOptions; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.UserHandle; 26 import android.util.ArraySet; 27 import android.widget.Toast; 28 29 import com.android.wm.shell.R; 30 import com.android.wm.shell.common.ShellExecutor; 31 32 import java.util.function.Consumer; 33 34 /** 35 * Controller that decides when to show the {@link ForcedResizableInfoActivity}. 36 */ 37 final class ForcedResizableInfoActivityController implements DividerView.DividerCallbacks { 38 39 private static final String SELF_PACKAGE_NAME = "com.android.systemui"; 40 41 private static final int TIMEOUT = 1000; 42 private final Context mContext; 43 private final ShellExecutor mMainExecutor; 44 private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>(); 45 private final ArraySet<String> mPackagesShownInSession = new ArraySet<>(); 46 private boolean mDividerDragging; 47 48 private final Runnable mTimeoutRunnable = this::showPending; 49 50 private final Consumer<Boolean> mDockedStackExistsListener = exists -> { 51 if (!exists) { 52 mPackagesShownInSession.clear(); 53 } 54 }; 55 56 /** Record of force resized task that's pending to be handled. */ 57 private class PendingTaskRecord { 58 int mTaskId; 59 /** 60 * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or 61 * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY} 62 */ 63 int mReason; 64 PendingTaskRecord(int taskId, int reason)65 PendingTaskRecord(int taskId, int reason) { 66 this.mTaskId = taskId; 67 this.mReason = reason; 68 } 69 } 70 ForcedResizableInfoActivityController(Context context, LegacySplitScreenController splitScreenController, ShellExecutor mainExecutor)71 ForcedResizableInfoActivityController(Context context, 72 LegacySplitScreenController splitScreenController, 73 ShellExecutor mainExecutor) { 74 mContext = context; 75 mMainExecutor = mainExecutor; 76 splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener); 77 } 78 79 @Override onDraggingStart()80 public void onDraggingStart() { 81 mDividerDragging = true; 82 mMainExecutor.removeCallbacks(mTimeoutRunnable); 83 } 84 85 @Override onDraggingEnd()86 public void onDraggingEnd() { 87 mDividerDragging = false; 88 showPending(); 89 } 90 onAppTransitionFinished()91 void onAppTransitionFinished() { 92 if (!mDividerDragging) { 93 showPending(); 94 } 95 } 96 activityForcedResizable(String packageName, int taskId, int reason)97 void activityForcedResizable(String packageName, int taskId, int reason) { 98 if (debounce(packageName)) { 99 return; 100 } 101 mPendingTasks.add(new PendingTaskRecord(taskId, reason)); 102 postTimeout(); 103 } 104 activityDismissingSplitScreen()105 void activityDismissingSplitScreen() { 106 Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, 107 Toast.LENGTH_SHORT).show(); 108 } 109 activityLaunchOnSecondaryDisplayFailed()110 void activityLaunchOnSecondaryDisplayFailed() { 111 Toast.makeText(mContext, R.string.activity_launch_on_secondary_display_failed_text, 112 Toast.LENGTH_SHORT).show(); 113 } 114 showPending()115 private void showPending() { 116 mMainExecutor.removeCallbacks(mTimeoutRunnable); 117 for (int i = mPendingTasks.size() - 1; i >= 0; i--) { 118 PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i); 119 Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class); 120 ActivityOptions options = ActivityOptions.makeBasic(); 121 options.setLaunchTaskId(pendingRecord.mTaskId); 122 // Set as task overlay and allow to resume, so that when an app enters split-screen and 123 // becomes paused, the overlay will still be shown. 124 options.setTaskOverlay(true, true /* canResume */); 125 intent.putExtra(EXTRA_FORCED_RESIZEABLE_REASON, pendingRecord.mReason); 126 mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); 127 } 128 mPendingTasks.clear(); 129 } 130 postTimeout()131 private void postTimeout() { 132 mMainExecutor.removeCallbacks(mTimeoutRunnable); 133 mMainExecutor.executeDelayed(mTimeoutRunnable, TIMEOUT); 134 } 135 debounce(String packageName)136 private boolean debounce(String packageName) { 137 if (packageName == null) { 138 return false; 139 } 140 141 // We launch ForcedResizableInfoActivity into a task that was forced resizable, so that 142 // triggers another notification. So ignore our own activity. 143 if (SELF_PACKAGE_NAME.equals(packageName)) { 144 return true; 145 } 146 boolean debounce = mPackagesShownInSession.contains(packageName); 147 mPackagesShownInSession.add(packageName); 148 return debounce; 149 } 150 } 151