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.pip.phone;
18
19import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
20import static android.view.Display.DEFAULT_DISPLAY;
21
22import android.app.ActivityManager;
23import android.app.ActivityManager.StackInfo;
24import android.app.IActivityManager;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.pm.ParceledListSlice;
28import android.content.res.Configuration;
29import android.graphics.Rect;
30import android.os.Handler;
31import android.os.RemoteException;
32import android.util.Log;
33import android.view.IPinnedStackController;
34import android.view.IPinnedStackListener;
35import android.view.IWindowManager;
36import android.view.WindowManagerGlobal;
37
38import com.android.systemui.pip.BasePipManager;
39import com.android.systemui.recents.events.EventBus;
40import com.android.systemui.recents.events.component.ExpandPipEvent;
41import com.android.systemui.recents.misc.SystemServicesProxy;
42import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
43import com.android.systemui.statusbar.CommandQueue;
44
45import java.io.PrintWriter;
46
47/**
48 * Manages the picture-in-picture (PIP) UI and states for Phones.
49 */
50public class PipManager implements BasePipManager {
51    private static final String TAG = "PipManager";
52
53    private static PipManager sPipController;
54
55    private Context mContext;
56    private IActivityManager mActivityManager;
57    private IWindowManager mWindowManager;
58    private Handler mHandler = new Handler();
59
60    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
61
62    private InputConsumerController mInputConsumerController;
63    private PipMenuActivityController mMenuController;
64    private PipMediaController mMediaController;
65    private PipNotificationController mNotificationController;
66    private PipTouchHandler mTouchHandler;
67
68    /**
69     * Handler for system task stack changes.
70     */
71    TaskStackListener mTaskStackListener = new TaskStackListener() {
72        @Override
73        public void onActivityPinned(String packageName, int taskId) {
74            if (!checkCurrentUserId(mContext, false /* debug */)) {
75                return;
76            }
77
78            mTouchHandler.onActivityPinned();
79            mMediaController.onActivityPinned();
80            mMenuController.onActivityPinned();
81            mNotificationController.onActivityPinned(packageName,
82                    true /* deferUntilAnimationEnds */);
83
84            SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
85        }
86
87        @Override
88        public void onActivityUnpinned() {
89            if (!checkCurrentUserId(mContext, false /* debug */)) {
90                return;
91            }
92
93            ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext,
94                    mActivityManager);
95            mMenuController.hideMenu();
96            mNotificationController.onActivityUnpinned(topPipActivity);
97
98            SystemServicesProxy.getInstance(mContext).setPipVisibility(topPipActivity != null);
99        }
100
101        @Override
102        public void onPinnedStackAnimationStarted() {
103            // Disable touches while the animation is running
104            mTouchHandler.setTouchEnabled(false);
105        }
106
107        @Override
108        public void onPinnedStackAnimationEnded() {
109            // Re-enable touches after the animation completes
110            mTouchHandler.setTouchEnabled(true);
111            mTouchHandler.onPinnedStackAnimationEnded();
112            mMenuController.onPinnedStackAnimationEnded();
113            mNotificationController.onPinnedStackAnimationEnded();
114        }
115
116        @Override
117        public void onPinnedActivityRestartAttempt(boolean clearedTask) {
118            if (!checkCurrentUserId(mContext, false /* debug */)) {
119                return;
120            }
121
122            mTouchHandler.getMotionHelper().expandPip(clearedTask /* skipAnimation */);
123        }
124    };
125
126    /**
127     * Handler for messages from the PIP controller.
128     */
129    private class PinnedStackListener extends IPinnedStackListener.Stub {
130
131        @Override
132        public void onListenerRegistered(IPinnedStackController controller) {
133            mHandler.post(() -> {
134                mTouchHandler.setPinnedStackController(controller);
135            });
136        }
137
138        @Override
139        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
140            mHandler.post(() -> {
141                mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
142            });
143        }
144
145        @Override
146        public void onMinimizedStateChanged(boolean isMinimized) {
147            mHandler.post(() -> {
148                mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
149            });
150        }
151
152        @Override
153        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
154                Rect animatingBounds, boolean fromImeAdjustement, int displayRotation) {
155            mHandler.post(() -> {
156                mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
157                        fromImeAdjustement, displayRotation);
158            });
159        }
160
161        @Override
162        public void onActionsChanged(ParceledListSlice actions) {
163            mHandler.post(() -> {
164                mMenuController.setAppActions(actions);
165            });
166        }
167    }
168
169    private PipManager() {}
170
171    /**
172     * Initializes {@link PipManager}.
173     */
174    public void initialize(Context context) {
175        mContext = context;
176        mActivityManager = ActivityManager.getService();
177        mWindowManager = WindowManagerGlobal.getWindowManagerService();
178
179        try {
180            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
181        } catch (RemoteException e) {
182            Log.e(TAG, "Failed to register pinned stack listener", e);
183        }
184        SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
185
186        mInputConsumerController = new InputConsumerController(mWindowManager);
187        mMediaController = new PipMediaController(context, mActivityManager);
188        mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
189                mInputConsumerController);
190        mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
191                mInputConsumerController);
192        mNotificationController = new PipNotificationController(context, mActivityManager,
193                mTouchHandler.getMotionHelper());
194        EventBus.getDefault().register(this);
195    }
196
197    /**
198     * Updates the PIP per configuration changed.
199     */
200    public void onConfigurationChanged(Configuration newConfig) {
201        mTouchHandler.onConfigurationChanged();
202    }
203
204    /**
205     * Expands the PIP.
206     */
207    public final void onBusEvent(ExpandPipEvent event) {
208        if (event.clearThumbnailWindows) {
209            try {
210                StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
211                if (stackInfo != null && stackInfo.taskIds != null) {
212                    SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
213                    for (int taskId : stackInfo.taskIds) {
214                        ssp.cancelThumbnailTransition(taskId);
215                    }
216                }
217            } catch (RemoteException e) {
218                // Do nothing
219            }
220        }
221        mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */);
222    }
223
224    /**
225     * Sent from KEYCODE_WINDOW handler in PhoneWindowManager, to request the menu to be shown.
226     */
227    public void showPictureInPictureMenu() {
228        mTouchHandler.showPictureInPictureMenu();
229    }
230
231    /**
232     * Gets an instance of {@link PipManager}.
233     */
234    public static PipManager getInstance() {
235        if (sPipController == null) {
236            sPipController = new PipManager();
237        }
238        return sPipController;
239    }
240
241    public void dump(PrintWriter pw) {
242        final String innerPrefix = "  ";
243        pw.println(TAG);
244        mInputConsumerController.dump(pw, innerPrefix);
245        mMenuController.dump(pw, innerPrefix);
246        mTouchHandler.dump(pw, innerPrefix);
247    }
248}
249