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