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