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