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