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.tv.pip;
18 
19 import android.content.Context;
20 import android.graphics.PixelFormat;
21 import android.graphics.Rect;
22 import android.util.Log;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.view.WindowManager;
26 import android.view.WindowManager.LayoutParams;
27 import android.view.accessibility.AccessibilityEvent;
28 
29 import com.android.systemui.R;
30 import com.android.systemui.recents.misc.SystemServicesProxy;
31 
32 import static android.view.Gravity.CENTER_HORIZONTAL;
33 import static android.view.Gravity.TOP;
34 import static android.view.View.MeasureSpec.UNSPECIFIED;
35 import static com.android.systemui.tv.pip.PipManager.STATE_PIP_OVERLAY;
36 import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS;
37 import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS_FOCUSED;
38 
39 public class PipRecentsOverlayManager {
40     private static final String TAG = "PipRecentsOverlayManager";
41 
42     public interface Callback {
onClosed()43         void onClosed();
onBackPressed()44         void onBackPressed();
onRecentsFocused()45         void onRecentsFocused();
46     }
47 
48     private final PipManager mPipManager = PipManager.getInstance();
49     private final WindowManager mWindowManager;
50     private final SystemServicesProxy mSystemServicesProxy;
51     private View mOverlayView;
52     private PipRecentsControlsView mPipControlsView;
53     private View mRecentsView;
54     private boolean mTalkBackEnabled;
55 
56     private LayoutParams mPipRecentsControlsViewLayoutParams;
57     private LayoutParams mPipRecentsControlsViewFocusedLayoutParams;
58 
59     private boolean mHasFocusableInRecents;
60     private boolean mIsPipRecentsOverlayShown;
61     private boolean mIsRecentsShown;
62     private boolean mIsPipFocusedInRecent;
63     private Callback mCallback;
64     private PipRecentsControlsView.Listener mPipControlsViewListener =
65             new PipRecentsControlsView.Listener() {
66                 @Override
67                 public void onClosed() {
68                     if (mCallback != null) {
69                         mCallback.onClosed();
70                     }
71                 }
72 
73                 @Override
74                 public void onBackPressed() {
75                     if (mCallback != null) {
76                         mCallback.onBackPressed();
77                     }
78                 }
79             };
80 
PipRecentsOverlayManager(Context context)81     PipRecentsOverlayManager(Context context) {
82         mWindowManager = (WindowManager) context.getSystemService(WindowManager.class);
83         mSystemServicesProxy = SystemServicesProxy.getInstance(context);
84         initViews(context);
85     }
86 
initViews(Context context)87     private void initViews(Context context) {
88         LayoutInflater inflater = (LayoutInflater) context
89                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
90         mOverlayView = inflater.inflate(R.layout.tv_pip_recents_overlay, null);
91         mPipControlsView = (PipRecentsControlsView) mOverlayView.findViewById(R.id.pip_controls);
92         mRecentsView = mOverlayView.findViewById(R.id.recents);
93         mRecentsView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
94             @Override
95             public void onFocusChange(View v, boolean hasFocus) {
96                 if (hasFocus) {
97                     clearFocus();
98                 }
99             }
100         });
101 
102         mOverlayView.measure(UNSPECIFIED, UNSPECIFIED);
103         mPipRecentsControlsViewLayoutParams = new WindowManager.LayoutParams(
104                 mOverlayView.getMeasuredWidth(), mOverlayView.getMeasuredHeight(),
105                 LayoutParams.TYPE_SYSTEM_DIALOG,
106                 LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE,
107                 PixelFormat.TRANSLUCENT);
108         mPipRecentsControlsViewLayoutParams.gravity = TOP | CENTER_HORIZONTAL;
109         mPipRecentsControlsViewFocusedLayoutParams = new WindowManager.LayoutParams(
110                 mOverlayView.getMeasuredWidth(), mOverlayView.getMeasuredHeight(),
111                 LayoutParams.TYPE_SYSTEM_DIALOG,
112                 0,
113                 PixelFormat.TRANSLUCENT);
114         mPipRecentsControlsViewFocusedLayoutParams.gravity = TOP | CENTER_HORIZONTAL;
115     }
116 
117     /**
118      * Add Recents overlay view.
119      * This is expected to be called after the PIP animation is over.
120      */
addPipRecentsOverlayView()121     void addPipRecentsOverlayView() {
122         if (mIsPipRecentsOverlayShown) {
123             return;
124         }
125         mTalkBackEnabled = mSystemServicesProxy.isTouchExplorationEnabled();
126         mRecentsView.setVisibility(mTalkBackEnabled ? View.VISIBLE : View.GONE);
127         mIsPipRecentsOverlayShown = true;
128         mIsPipFocusedInRecent = true;
129         mWindowManager.addView(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams);
130     }
131 
132     /**
133      * Remove Recents overlay view.
134      * This should be called when Recents or PIP is closed.
135      */
removePipRecentsOverlayView()136     public void removePipRecentsOverlayView() {
137         if (!mIsPipRecentsOverlayShown) {
138             return;
139         }
140         mWindowManager.removeView(mOverlayView);
141         // Resets the controls view when its removed.
142         // If not, changing focus in reset will be show animation when Recents is resumed.
143         mPipControlsView.reset();
144         mIsPipRecentsOverlayShown = false;
145     }
146 
147     /**
148      * Request focus to the PIP Recents overlay.
149      * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}.
150      * @param hasFocusableInRecents {@code true} if Recents can have focus. (i.e. Has a recent task)
151      */
requestFocus(boolean hasFocusableInRecents)152     public void requestFocus(boolean hasFocusableInRecents) {
153         mHasFocusableInRecents = hasFocusableInRecents;
154         if (!mIsPipRecentsOverlayShown || !mIsRecentsShown || mIsPipFocusedInRecent
155                 || !mPipManager.isPipShown()) {
156             return;
157         }
158         mIsPipFocusedInRecent = true;
159         mPipControlsView.startFocusGainAnimation();
160         mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams);
161         mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED);
162         if (mTalkBackEnabled) {
163             mPipControlsView.requestFocus();
164             mPipControlsView.sendAccessibilityEvent(
165                     AccessibilityEvent.TYPE_VIEW_FOCUSED);
166         }
167     }
168 
169     /**
170      * Request focus to the PIP Recents overlay.
171      */
clearFocus()172     public void clearFocus() {
173         if (!mIsPipRecentsOverlayShown || !mIsRecentsShown || !mIsPipFocusedInRecent
174                 || !mPipManager.isPipShown() || !mHasFocusableInRecents) {
175             return;
176         }
177         mIsPipFocusedInRecent = false;
178         mPipControlsView.startFocusLossAnimation();
179         mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewLayoutParams);
180         mPipManager.resizePinnedStack(STATE_PIP_RECENTS);
181         if (mCallback != null) {
182             mCallback.onRecentsFocused();
183         }
184     }
185 
setCallback(Callback listener)186     public void setCallback(Callback listener) {
187         mCallback = listener;
188         mPipControlsView.setListener(mCallback != null ? mPipControlsViewListener : null);
189     }
190 
191     /**
192      * Called when Recents is resumed.
193      * PIPed activity will be resized accordingly and overlay will show available buttons.
194      */
onRecentsResumed()195     public void onRecentsResumed() {
196         if (!mPipManager.isPipShown()) {
197             return;
198         }
199         mIsRecentsShown = true;
200         mIsPipFocusedInRecent = true;
201         mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED);
202         // Overlay view will be added after the resize animation ends, if any.
203     }
204 
205     /**
206      * Called when Recents is paused.
207      * PIPed activity will be resized accordingly and overlay will hide available buttons.
208      */
onRecentsPaused()209     public void onRecentsPaused() {
210         mIsRecentsShown = false;
211         mIsPipFocusedInRecent = false;
212         removePipRecentsOverlayView();
213 
214         if (mPipManager.isPipShown()) {
215             mPipManager.resizePinnedStack(STATE_PIP_OVERLAY);
216         }
217     }
218 
219     /**
220      * Returns {@code true} if recents is shown.
221      */
isRecentsShown()222     boolean isRecentsShown() {
223         return mIsRecentsShown;
224     }
225 
226     /**
227      * Updates the PIP per configuration changed.
228      */
onConfigurationChanged(Context context)229     void onConfigurationChanged(Context context) {
230         if (mIsRecentsShown) {
231             Log.w(TAG, "Configuration is changed while Recents is shown");
232         }
233         initViews(context);
234     }
235 }
236