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
17package com.android.systemui.stackdivider;
18
19import static com.android.systemui.stackdivider.ForcedResizableInfoActivity
20        .EXTRA_FORCED_RESIZEABLE_REASON;
21
22import android.app.ActivityOptions;
23import android.content.Context;
24import android.content.Intent;
25import android.os.Handler;
26import android.os.UserHandle;
27import android.util.ArraySet;
28import android.widget.Toast;
29
30import com.android.systemui.R;
31import com.android.systemui.recents.events.EventBus;
32import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
33import com.android.systemui.recents.events.component.ShowUserToastEvent;
34import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
35import com.android.systemui.recents.misc.SystemServicesProxy;
36import com.android.systemui.shared.system.ActivityManagerWrapper;
37import com.android.systemui.stackdivider.events.StartedDragingEvent;
38import com.android.systemui.stackdivider.events.StoppedDragingEvent;
39
40/**
41 * Controller that decides when to show the {@link ForcedResizableInfoActivity}.
42 */
43public class ForcedResizableInfoActivityController {
44
45    private static final String SELF_PACKAGE_NAME = "com.android.systemui";
46
47    private static final int TIMEOUT = 1000;
48    private final Context mContext;
49    private final Handler mHandler = new Handler();
50    private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>();
51    private final ArraySet<String> mPackagesShownInSession = new ArraySet<>();
52    private boolean mDividerDraging;
53
54    private final Runnable mTimeoutRunnable = new Runnable() {
55        @Override
56        public void run() {
57            showPending();
58        }
59    };
60
61    /** Record of force resized task that's pending to be handled. */
62    private class PendingTaskRecord {
63        int taskId;
64        /**
65         * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or
66         * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY}
67         */
68        int reason;
69
70        PendingTaskRecord(int taskId, int reason) {
71            this.taskId = taskId;
72            this.reason = reason;
73        }
74    }
75
76    public ForcedResizableInfoActivityController(Context context) {
77        mContext = context;
78        EventBus.getDefault().register(this);
79        ActivityManagerWrapper.getInstance().registerTaskStackListener(
80                new SysUiTaskStackChangeListener() {
81                    @Override
82                    public void onActivityForcedResizable(String packageName, int taskId,
83                            int reason) {
84                        activityForcedResizable(packageName, taskId, reason);
85                    }
86
87                    @Override
88                    public void onActivityDismissingDockedStack() {
89                        activityDismissingDockedStack();
90                    }
91
92                    @Override
93                    public void onActivityLaunchOnSecondaryDisplayFailed() {
94                        activityLaunchOnSecondaryDisplayFailed();
95                    }
96                });
97    }
98
99    public void notifyDockedStackExistsChanged(boolean exists) {
100        if (!exists) {
101            mPackagesShownInSession.clear();
102        }
103    }
104
105    public final void onBusEvent(AppTransitionFinishedEvent event) {
106        if (!mDividerDraging) {
107            showPending();
108        }
109    }
110
111    public final void onBusEvent(StartedDragingEvent event) {
112        mDividerDraging = true;
113        mHandler.removeCallbacks(mTimeoutRunnable);
114    }
115
116    public final void onBusEvent(StoppedDragingEvent event) {
117        mDividerDraging = false;
118        showPending();
119    }
120
121    private void activityForcedResizable(String packageName, int taskId, int reason) {
122        if (debounce(packageName)) {
123            return;
124        }
125        mPendingTasks.add(new PendingTaskRecord(taskId, reason));
126        postTimeout();
127    }
128
129    private void activityDismissingDockedStack() {
130        EventBus.getDefault().send(new ShowUserToastEvent(
131                R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT));
132    }
133
134    private void activityLaunchOnSecondaryDisplayFailed() {
135        EventBus.getDefault().send(new ShowUserToastEvent(
136                R.string.activity_launch_on_secondary_display_failed_text, Toast.LENGTH_SHORT));
137    }
138
139    private void showPending() {
140        mHandler.removeCallbacks(mTimeoutRunnable);
141        for (int i = mPendingTasks.size() - 1; i >= 0; i--) {
142            PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i);
143            Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class);
144            ActivityOptions options = ActivityOptions.makeBasic();
145            options.setLaunchTaskId(pendingRecord.taskId);
146            // Set as task overlay and allow to resume, so that when an app enters split-screen and
147            // becomes paused, the overlay will still be shown.
148            options.setTaskOverlay(true, true /* canResume */);
149            intent.putExtra(EXTRA_FORCED_RESIZEABLE_REASON, pendingRecord.reason);
150            mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
151        }
152        mPendingTasks.clear();
153    }
154
155    private void postTimeout() {
156        mHandler.removeCallbacks(mTimeoutRunnable);
157        mHandler.postDelayed(mTimeoutRunnable, TIMEOUT);
158    }
159
160    private boolean debounce(String packageName) {
161        if (packageName == null) {
162            return false;
163        }
164
165        // We launch ForcedResizableInfoActivity into a task that was forced resizable, so that
166        // triggers another notification. So ignore our own activity.
167        if (SELF_PACKAGE_NAME.equals(packageName)) {
168            return true;
169        }
170        boolean debounce = mPackagesShownInSession.contains(packageName);
171        mPackagesShownInSession.add(packageName);
172        return debounce;
173    }
174}
175