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.tv.pip;
18
19import android.content.Context;
20import android.graphics.PixelFormat;
21import android.graphics.Rect;
22import android.util.Log;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.WindowManager;
26import android.view.WindowManager.LayoutParams;
27import android.view.accessibility.AccessibilityEvent;
28
29import com.android.systemui.R;
30import com.android.systemui.recents.misc.SystemServicesProxy;
31
32import static android.view.Gravity.CENTER_HORIZONTAL;
33import static android.view.Gravity.TOP;
34import static android.view.View.MeasureSpec.UNSPECIFIED;
35import static com.android.systemui.tv.pip.PipManager.STATE_PIP_OVERLAY;
36import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS;
37import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS_FOCUSED;
38
39public class PipRecentsOverlayManager {
40    private static final String TAG = "PipRecentsOverlayManager";
41
42    public interface Callback {
43        void onClosed();
44        void onBackPressed();
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
81    PipRecentsOverlayManager(Context context) {
82        mWindowManager = (WindowManager) context.getSystemService(WindowManager.class);
83        mSystemServicesProxy = SystemServicesProxy.getInstance(context);
84        initViews(context);
85    }
86
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
222    boolean isRecentsShown() {
223        return mIsRecentsShown;
224    }
225
226    /**
227     * Updates the PIP per configuration changed.
228     */
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