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.devicestate.DeviceStateManager; 23 import android.hardware.display.DisplayManager; 24 import android.media.MediaRouter; 25 import android.media.MediaRouter.RouteInfo; 26 import android.os.Bundle; 27 import android.os.Trace; 28 import android.text.TextUtils; 29 import android.util.Log; 30 import android.util.SparseArray; 31 import android.view.Display; 32 import android.view.DisplayAddress; 33 import android.view.DisplayInfo; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.WindowManager; 37 38 import androidx.annotation.Nullable; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.keyguard.dagger.KeyguardStatusViewComponent; 42 import com.android.systemui.R; 43 import com.android.systemui.dagger.SysUISingleton; 44 import com.android.systemui.dagger.qualifiers.Main; 45 import com.android.systemui.dagger.qualifiers.UiBackground; 46 import com.android.systemui.flags.FeatureFlags; 47 import com.android.systemui.flags.Flags; 48 import com.android.systemui.navigationbar.NavigationBarController; 49 import com.android.systemui.navigationbar.NavigationBarView; 50 import com.android.systemui.settings.DisplayTracker; 51 import com.android.systemui.statusbar.policy.KeyguardStateController; 52 53 import dagger.Lazy; 54 55 import java.util.concurrent.Executor; 56 57 import javax.inject.Inject; 58 59 60 @SysUISingleton 61 public class KeyguardDisplayManager { 62 protected static final String TAG = "KeyguardDisplayManager"; 63 private static final boolean DEBUG = KeyguardConstants.DEBUG; 64 65 private MediaRouter mMediaRouter = null; 66 private final DisplayManager mDisplayService; 67 private final DisplayTracker mDisplayTracker; 68 private final Lazy<NavigationBarController> mNavigationBarControllerLazy; 69 private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; 70 private final ConnectedDisplayKeyguardPresentation.Factory 71 mConnectedDisplayKeyguardPresentationFactory; 72 private final FeatureFlags mFeatureFlags; 73 private final Context mContext; 74 75 private boolean mShowing; 76 private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); 77 78 private final DeviceStateHelper mDeviceStateHelper; 79 private final KeyguardStateController mKeyguardStateController; 80 81 private final SparseArray<Presentation> mPresentations = new SparseArray<>(); 82 83 private final DisplayTracker.Callback mDisplayCallback = 84 new DisplayTracker.Callback() { 85 @Override 86 public void onDisplayAdded(int displayId) { 87 Trace.beginSection( 88 "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")"); 89 final Display display = mDisplayService.getDisplay(displayId); 90 if (mShowing) { 91 updateNavigationBarVisibility(displayId, false /* navBarVisible */); 92 showPresentation(display); 93 } 94 Trace.endSection(); 95 } 96 97 @Override 98 public void onDisplayRemoved(int displayId) { 99 Trace.beginSection( 100 "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")"); 101 hidePresentation(displayId); 102 Trace.endSection(); 103 } 104 }; 105 106 @Inject KeyguardDisplayManager(Context context, Lazy<NavigationBarController> navigationBarControllerLazy, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, DisplayTracker displayTracker, @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, DeviceStateHelper deviceStateHelper, KeyguardStateController keyguardStateController, ConnectedDisplayKeyguardPresentation.Factory connectedDisplayKeyguardPresentationFactory, FeatureFlags featureFlags)107 public KeyguardDisplayManager(Context context, 108 Lazy<NavigationBarController> navigationBarControllerLazy, 109 KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory, 110 DisplayTracker displayTracker, 111 @Main Executor mainExecutor, 112 @UiBackground Executor uiBgExecutor, 113 DeviceStateHelper deviceStateHelper, 114 KeyguardStateController keyguardStateController, 115 ConnectedDisplayKeyguardPresentation.Factory 116 connectedDisplayKeyguardPresentationFactory, 117 FeatureFlags featureFlags) { 118 mContext = context; 119 mNavigationBarControllerLazy = navigationBarControllerLazy; 120 mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; 121 uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class)); 122 mDisplayService = mContext.getSystemService(DisplayManager.class); 123 mDisplayTracker = displayTracker; 124 mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); 125 mDeviceStateHelper = deviceStateHelper; 126 mKeyguardStateController = keyguardStateController; 127 mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory; 128 mFeatureFlags = featureFlags; 129 } 130 isKeyguardShowable(Display display)131 private boolean isKeyguardShowable(Display display) { 132 if (display == null) { 133 if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display"); 134 return false; 135 } 136 if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) { 137 if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display"); 138 return false; 139 } 140 display.getDisplayInfo(mTmpDisplayInfo); 141 if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) { 142 if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display"); 143 return false; 144 } 145 if ((mTmpDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) { 146 if (DEBUG) { 147 Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display"); 148 } 149 return false; 150 } 151 if (mKeyguardStateController.isOccluded() 152 && mDeviceStateHelper.isConcurrentDisplayActive(display)) { 153 if (DEBUG) { 154 // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the 155 // Keyguard state becomes "occluded". In this case, we should not show the 156 // KeyguardPresentation, since the activity is presenting content onto the 157 // non-default display. 158 Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent" 159 + " display is active"); 160 } 161 return false; 162 } 163 164 return true; 165 } 166 /** 167 * @param display The display to show the presentation on. 168 * @return {@code true} if a presentation was added. 169 * {@code false} if the presentation cannot be added on that display or the presentation 170 * was already there. 171 */ showPresentation(Display display)172 private boolean showPresentation(Display display) { 173 if (!isKeyguardShowable(display)) return false; 174 if (DEBUG) Log.i(TAG, "Keyguard enabled on display: " + display); 175 final int displayId = display.getDisplayId(); 176 Presentation presentation = mPresentations.get(displayId); 177 if (presentation == null) { 178 final Presentation newPresentation = createPresentation(display); 179 newPresentation.setOnDismissListener(dialog -> { 180 if (newPresentation.equals(mPresentations.get(displayId))) { 181 mPresentations.remove(displayId); 182 } 183 }); 184 presentation = newPresentation; 185 try { 186 presentation.show(); 187 } catch (WindowManager.InvalidDisplayException ex) { 188 Log.w(TAG, "Invalid display:", ex); 189 presentation = null; 190 } 191 if (presentation != null) { 192 mPresentations.append(displayId, presentation); 193 return true; 194 } 195 } 196 return false; 197 } 198 createPresentation(Display display)199 Presentation createPresentation(Display display) { 200 if (mFeatureFlags.isEnabled(Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION)) { 201 return mConnectedDisplayKeyguardPresentationFactory.create(display); 202 } else { 203 return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory); 204 } 205 } 206 207 /** 208 * @param displayId The id of the display to hide the presentation off. 209 */ hidePresentation(int displayId)210 private void hidePresentation(int displayId) { 211 final Presentation presentation = mPresentations.get(displayId); 212 if (presentation != null) { 213 presentation.dismiss(); 214 mPresentations.remove(displayId); 215 } 216 } 217 show()218 public void show() { 219 if (!mShowing) { 220 if (DEBUG) Log.v(TAG, "show"); 221 if (mMediaRouter != null) { 222 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, 223 mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); 224 } else { 225 Log.w(TAG, "MediaRouter not yet initialized"); 226 } 227 updateDisplays(true /* showing */); 228 } 229 mShowing = true; 230 } 231 hide()232 public void hide() { 233 if (mShowing) { 234 if (DEBUG) Log.v(TAG, "hide"); 235 if (mMediaRouter != null) { 236 mMediaRouter.removeCallback(mMediaRouterCallback); 237 } 238 updateDisplays(false /* showing */); 239 } 240 mShowing = false; 241 } 242 243 private final MediaRouter.SimpleCallback mMediaRouterCallback = 244 new MediaRouter.SimpleCallback() { 245 @Override 246 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { 247 if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); 248 updateDisplays(mShowing); 249 } 250 251 @Override 252 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { 253 if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); 254 updateDisplays(mShowing); 255 } 256 257 @Override 258 public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { 259 if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); 260 updateDisplays(mShowing); 261 } 262 }; 263 updateDisplays(boolean showing)264 protected boolean updateDisplays(boolean showing) { 265 boolean changed = false; 266 if (showing) { 267 final Display[] displays = mDisplayTracker.getAllDisplays(); 268 for (Display display : displays) { 269 int displayId = display.getDisplayId(); 270 updateNavigationBarVisibility(displayId, false /* navBarVisible */); 271 changed |= showPresentation(display); 272 } 273 } else { 274 changed = mPresentations.size() > 0; 275 for (int i = mPresentations.size() - 1; i >= 0; i--) { 276 int displayId = mPresentations.keyAt(i); 277 updateNavigationBarVisibility(displayId, true /* navBarVisible */); 278 mPresentations.valueAt(i).dismiss(); 279 } 280 mPresentations.clear(); 281 } 282 return changed; 283 } 284 285 // TODO(b/127878649): this logic is from 286 // {@link StatusBarKeyguardViewManager#updateNavigationBarVisibility}. Try to revisit a long 287 // term solution in R. updateNavigationBarVisibility(int displayId, boolean navBarVisible)288 private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) { 289 // Leave this task to {@link StatusBarKeyguardViewManager} 290 if (displayId == mDisplayTracker.getDefaultDisplayId()) return; 291 292 NavigationBarView navBarView = mNavigationBarControllerLazy.get() 293 .getNavigationBarView(displayId); 294 // We may not have nav bar on a display. 295 if (navBarView == null) return; 296 297 if (navBarVisible) { 298 navBarView.getRootView().setVisibility(View.VISIBLE); 299 } else { 300 navBarView.getRootView().setVisibility(View.GONE); 301 } 302 303 } 304 305 /** 306 * Helper used to receive device state info from {@link DeviceStateManager}. 307 */ 308 static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback { 309 310 @Nullable 311 private final DisplayAddress.Physical mRearDisplayPhysicalAddress; 312 313 // TODO(b/271317597): These device states should be defined in DeviceStateManager 314 private final int mConcurrentState; 315 private boolean mIsInConcurrentDisplayState; 316 317 @Inject DeviceStateHelper(Context context, DeviceStateManager deviceStateManager, @Main Executor mainExecutor)318 DeviceStateHelper(Context context, 319 DeviceStateManager deviceStateManager, 320 @Main Executor mainExecutor) { 321 322 final String rearDisplayPhysicalAddress = context.getResources().getString( 323 com.android.internal.R.string.config_rearDisplayPhysicalAddress); 324 if (TextUtils.isEmpty(rearDisplayPhysicalAddress)) { 325 mRearDisplayPhysicalAddress = null; 326 } else { 327 mRearDisplayPhysicalAddress = DisplayAddress 328 .fromPhysicalDisplayId(Long.parseLong(rearDisplayPhysicalAddress)); 329 } 330 331 mConcurrentState = context.getResources().getInteger( 332 com.android.internal.R.integer.config_deviceStateConcurrentRearDisplay); 333 deviceStateManager.registerCallback(mainExecutor, this); 334 } 335 336 @Override onStateChanged(int state)337 public void onStateChanged(int state) { 338 // When concurrent state ends, the display also turns off. This is enforced in various 339 // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke 340 // hide() since that will happen through the onDisplayRemoved callback. 341 mIsInConcurrentDisplayState = state == mConcurrentState; 342 } 343 isConcurrentDisplayActive(Display display)344 boolean isConcurrentDisplayActive(Display display) { 345 return mIsInConcurrentDisplayState 346 && mRearDisplayPhysicalAddress != null 347 && mRearDisplayPhysicalAddress.equals(display.getAddress()); 348 } 349 } 350 351 352 @VisibleForTesting 353 static final class KeyguardPresentation extends Presentation { 354 private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height 355 private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s 356 private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; 357 private KeyguardClockSwitchController mKeyguardClockSwitchController; 358 private View mClock; 359 private int mUsableWidth; 360 private int mUsableHeight; 361 private int mMarginTop; 362 private int mMarginLeft; 363 Runnable mMoveTextRunnable = new Runnable() { 364 @Override 365 public void run() { 366 int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth())); 367 int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight())); 368 mClock.setTranslationX(x); 369 mClock.setTranslationY(y); 370 mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT); 371 } 372 }; 373 KeyguardPresentation(Context context, Display display, KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory)374 KeyguardPresentation(Context context, Display display, 375 KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) { 376 super(context, display, R.style.Theme_SystemUI_KeyguardPresentation, 377 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 378 mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; 379 setCancelable(false); 380 } 381 382 @Override cancel()383 public void cancel() { 384 // Do not allow anything to cancel KeyguardPresentation except KeyguardDisplayManager. 385 } 386 387 @Override onDetachedFromWindow()388 public void onDetachedFromWindow() { 389 mClock.removeCallbacks(mMoveTextRunnable); 390 } 391 392 @Override onDisplayChanged()393 public void onDisplayChanged() { 394 updateBounds(); 395 getWindow().getDecorView().requestLayout(); 396 } 397 398 @Override onCreate(Bundle savedInstanceState)399 protected void onCreate(Bundle savedInstanceState) { 400 super.onCreate(savedInstanceState); 401 402 updateBounds(); 403 404 setContentView(LayoutInflater.from(getContext()) 405 .inflate(R.layout.keyguard_presentation, null)); 406 407 // Logic to make the lock screen fullscreen 408 getWindow().getDecorView().setSystemUiVisibility( 409 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 410 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 411 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 412 getWindow().getAttributes().setFitInsetsTypes(0 /* types */); 413 getWindow().setNavigationBarContrastEnforced(false); 414 getWindow().setNavigationBarColor(Color.TRANSPARENT); 415 416 mClock = findViewById(R.id.clock); 417 418 // Avoid screen burn in 419 mClock.post(mMoveTextRunnable); 420 421 mKeyguardClockSwitchController = mKeyguardStatusViewComponentFactory 422 .build(findViewById(R.id.clock)) 423 .getKeyguardClockSwitchController(); 424 425 mKeyguardClockSwitchController.setOnlyClock(true); 426 mKeyguardClockSwitchController.init(); 427 } 428 updateBounds()429 private void updateBounds() { 430 final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics() 431 .getBounds(); 432 mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100; 433 mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100; 434 mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200; 435 mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200; 436 } 437 } 438 } 439