1 /*
2  * Copyright (C) 2013 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 package com.android.keyguard;
17 
18 import static android.view.Display.DEFAULT_DISPLAY;
19 
20 import android.app.Presentation;
21 import android.content.Context;
22 import android.graphics.Color;
23 import android.graphics.Point;
24 import android.hardware.display.DisplayManager;
25 import android.media.MediaRouter;
26 import android.media.MediaRouter.RouteInfo;
27 import android.os.Bundle;
28 import android.util.Log;
29 import android.util.SparseArray;
30 import android.view.Display;
31 import android.view.DisplayInfo;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.WindowManager;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.systemui.Dependency;
38 import com.android.systemui.statusbar.NavigationBarController;
39 import com.android.systemui.statusbar.phone.NavigationBarView;
40 import com.android.systemui.util.InjectionInflationController;
41 
42 public class KeyguardDisplayManager {
43     protected static final String TAG = "KeyguardDisplayManager";
44     private static boolean DEBUG = KeyguardConstants.DEBUG;
45 
46     private final MediaRouter mMediaRouter;
47     private final DisplayManager mDisplayService;
48     private final InjectionInflationController mInjectableInflater;
49     private final Context mContext;
50 
51     private boolean mShowing;
52     private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
53 
54     private final SparseArray<Presentation> mPresentations = new SparseArray<>();
55 
56     private final NavigationBarController mNavBarController =
57             Dependency.get(NavigationBarController.class);
58 
59     private final DisplayManager.DisplayListener mDisplayListener =
60             new DisplayManager.DisplayListener() {
61 
62         @Override
63         public void onDisplayAdded(int displayId) {
64             final Display display = mDisplayService.getDisplay(displayId);
65             if (mShowing) {
66                 updateNavigationBarVisibility(displayId, false /* navBarVisible */);
67                 showPresentation(display);
68             }
69         }
70 
71         @Override
72         public void onDisplayChanged(int displayId) {
73             if (displayId == DEFAULT_DISPLAY) return;
74             final Display display = mDisplayService.getDisplay(displayId);
75             if (display != null && mShowing) {
76                 final Presentation presentation = mPresentations.get(displayId);
77                 if (presentation != null && !presentation.getDisplay().equals(display)) {
78                     hidePresentation(displayId);
79                     showPresentation(display);
80                 }
81             }
82         }
83 
84         @Override
85         public void onDisplayRemoved(int displayId) {
86             hidePresentation(displayId);
87         }
88     };
89 
KeyguardDisplayManager(Context context, InjectionInflationController injectableInflater)90     public KeyguardDisplayManager(Context context,
91             InjectionInflationController injectableInflater) {
92         mContext = context;
93         mInjectableInflater = injectableInflater;
94         mMediaRouter = mContext.getSystemService(MediaRouter.class);
95         mDisplayService = mContext.getSystemService(DisplayManager.class);
96         mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */);
97     }
98 
isKeyguardShowable(Display display)99     private boolean isKeyguardShowable(Display display) {
100         if (display == null) {
101             if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
102             return false;
103         }
104         if (display.getDisplayId() == DEFAULT_DISPLAY) {
105             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
106             return false;
107         }
108         display.getDisplayInfo(mTmpDisplayInfo);
109         if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) {
110             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display");
111             return false;
112         }
113         return true;
114     }
115     /**
116      * @param display The display to show the presentation on.
117      * @return {@code true} if a presentation was added.
118      *         {@code false} if the presentation cannot be added on that display or the presentation
119      *         was already there.
120      */
showPresentation(Display display)121     private boolean showPresentation(Display display) {
122         if (!isKeyguardShowable(display)) return false;
123         if (DEBUG) Log.i(TAG, "Keyguard enabled on display: " + display);
124         final int displayId = display.getDisplayId();
125         Presentation presentation = mPresentations.get(displayId);
126         if (presentation == null) {
127             presentation = new KeyguardPresentation(mContext, display, mInjectableInflater);
128             presentation.setOnDismissListener(dialog -> {
129                 if (null != mPresentations.get(displayId)) {
130                     mPresentations.remove(displayId);
131                 }
132             });
133             try {
134                 presentation.show();
135             } catch (WindowManager.InvalidDisplayException ex) {
136                 Log.w(TAG, "Invalid display:", ex);
137                 presentation = null;
138             }
139             if (presentation != null) {
140                 mPresentations.append(displayId, presentation);
141                 return true;
142             }
143         }
144         return false;
145     }
146 
147     /**
148      * @param displayId The id of the display to hide the presentation off.
149      */
hidePresentation(int displayId)150     private void hidePresentation(int displayId) {
151         final Presentation presentation = mPresentations.get(displayId);
152         if (presentation != null) {
153             presentation.dismiss();
154             mPresentations.remove(displayId);
155         }
156     }
157 
show()158     public void show() {
159         if (!mShowing) {
160             if (DEBUG) Log.v(TAG, "show");
161             mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
162                     mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
163             updateDisplays(true /* showing */);
164         }
165         mShowing = true;
166     }
167 
hide()168     public void hide() {
169         if (mShowing) {
170             if (DEBUG) Log.v(TAG, "hide");
171             mMediaRouter.removeCallback(mMediaRouterCallback);
172             updateDisplays(false /* showing */);
173         }
174         mShowing = false;
175     }
176 
177     private final MediaRouter.SimpleCallback mMediaRouterCallback =
178             new MediaRouter.SimpleCallback() {
179         @Override
180         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
181             if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info);
182             updateDisplays(mShowing);
183         }
184 
185         @Override
186         public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
187             if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
188             updateDisplays(mShowing);
189         }
190 
191         @Override
192         public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
193             if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info);
194             updateDisplays(mShowing);
195         }
196     };
197 
updateDisplays(boolean showing)198     protected boolean updateDisplays(boolean showing) {
199         boolean changed = false;
200         if (showing) {
201             final Display[] displays = mDisplayService.getDisplays();
202             for (Display display : displays) {
203                 int displayId = display.getDisplayId();
204                 updateNavigationBarVisibility(displayId, false /* navBarVisible */);
205                 changed |= showPresentation(display);
206             }
207         } else {
208             changed = mPresentations.size() > 0;
209             for (int i = mPresentations.size() - 1; i >= 0; i--) {
210                 int displayId = mPresentations.keyAt(i);
211                 updateNavigationBarVisibility(displayId, true /* navBarVisible */);
212                 mPresentations.valueAt(i).dismiss();
213             }
214             mPresentations.clear();
215         }
216         return changed;
217     }
218 
219     // TODO(b/127878649): this logic is from
220     //  {@link StatusBarKeyguardViewManager#updateNavigationBarVisibility}. Try to revisit a long
221     //  term solution in R.
updateNavigationBarVisibility(int displayId, boolean navBarVisible)222     private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) {
223         // Leave this task to {@link StatusBarKeyguardViewManager}
224         if (displayId == DEFAULT_DISPLAY) return;
225 
226         NavigationBarView navBarView = mNavBarController.getNavigationBarView(displayId);
227         // We may not have nav bar on a display.
228         if (navBarView == null) return;
229 
230         if (navBarVisible) {
231             navBarView.getRootView().setVisibility(View.VISIBLE);
232         } else {
233             navBarView.getRootView().setVisibility(View.GONE);
234         }
235 
236     }
237 
238     @VisibleForTesting
239     static final class KeyguardPresentation extends Presentation {
240         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
241         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
242         private final InjectionInflationController mInjectableInflater;
243         private View mClock;
244         private int mUsableWidth;
245         private int mUsableHeight;
246         private int mMarginTop;
247         private int mMarginLeft;
248         Runnable mMoveTextRunnable = new Runnable() {
249             @Override
250             public void run() {
251                 int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth()));
252                 int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight()));
253                 mClock.setTranslationX(x);
254                 mClock.setTranslationY(y);
255                 mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT);
256             }
257         };
258 
KeyguardPresentation(Context context, Display display, InjectionInflationController injectionInflater)259         KeyguardPresentation(Context context, Display display,
260                 InjectionInflationController injectionInflater) {
261             super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
262             mInjectableInflater = injectionInflater;
263             getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
264             setCancelable(false);
265         }
266 
267         @Override
onDetachedFromWindow()268         public void onDetachedFromWindow() {
269             mClock.removeCallbacks(mMoveTextRunnable);
270         }
271 
272         @Override
onCreate(Bundle savedInstanceState)273         protected void onCreate(Bundle savedInstanceState) {
274             super.onCreate(savedInstanceState);
275 
276             Point p = new Point();
277             getDisplay().getSize(p);
278             mUsableWidth = VIDEO_SAFE_REGION * p.x/100;
279             mUsableHeight = VIDEO_SAFE_REGION * p.y/100;
280             mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
281             mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
282 
283             LayoutInflater inflater = mInjectableInflater.injectable(
284                     LayoutInflater.from(getContext()));
285             setContentView(inflater.inflate(R.layout.keyguard_presentation, null));
286 
287             // Logic to make the lock screen fullscreen
288             getWindow().getDecorView().setSystemUiVisibility(
289                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
290                             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
291                             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
292             getWindow().setNavigationBarContrastEnforced(false);
293             getWindow().setNavigationBarColor(Color.TRANSPARENT);
294 
295             mClock = findViewById(R.id.clock);
296 
297             // Avoid screen burn in
298             mClock.post(mMoveTextRunnable);
299         }
300     }
301 }
302