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