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 com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; 19 20 import android.annotation.NonNull; 21 import android.app.Presentation; 22 import android.content.Context; 23 import android.hardware.devicestate.DeviceState; 24 import android.hardware.devicestate.DeviceStateManager; 25 import android.hardware.display.DisplayManager; 26 import android.media.MediaRouter; 27 import android.media.MediaRouter.RouteInfo; 28 import android.os.Trace; 29 import android.util.Log; 30 import android.util.SparseArray; 31 import android.view.Display; 32 import android.view.DisplayInfo; 33 import android.view.View; 34 import android.view.WindowManager; 35 36 import androidx.annotation.Nullable; 37 38 import com.android.systemui.dagger.SysUISingleton; 39 import com.android.systemui.dagger.qualifiers.Application; 40 import com.android.systemui.dagger.qualifiers.Main; 41 import com.android.systemui.dagger.qualifiers.UiBackground; 42 import com.android.systemui.navigationbar.NavigationBarController; 43 import com.android.systemui.navigationbar.views.NavigationBarView; 44 import com.android.systemui.settings.DisplayTracker; 45 import com.android.systemui.shade.ShadeDisplayAware; 46 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository; 47 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; 48 import com.android.systemui.statusbar.policy.KeyguardStateController; 49 50 import dagger.Lazy; 51 52 import kotlinx.coroutines.CoroutineScope; 53 54 import java.util.concurrent.Executor; 55 56 import javax.inject.Inject; 57 import javax.inject.Provider; 58 59 /** 60 * Manages Keyguard Presentations for non-primary display(s). 61 */ 62 @SysUISingleton 63 public class KeyguardDisplayManager { 64 protected static final String TAG = "KeyguardDisplayManager"; 65 private static final boolean DEBUG = KeyguardConstants.DEBUG; 66 67 private MediaRouter mMediaRouter = null; 68 private final DisplayManager mDisplayService; 69 private final DisplayTracker mDisplayTracker; 70 private final Lazy<NavigationBarController> mNavigationBarControllerLazy; 71 private final Provider<ShadeDisplaysRepository> mShadePositionRepositoryProvider; 72 private final ConnectedDisplayKeyguardPresentation.Factory 73 mConnectedDisplayKeyguardPresentationFactory; 74 private final Context mContext; 75 76 private boolean mShowing; 77 private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); 78 79 private final DeviceStateHelper mDeviceStateHelper; 80 private final KeyguardStateController mKeyguardStateController; 81 82 private final SparseArray<Presentation> mPresentations = new SparseArray<>(); 83 84 private final DisplayTracker.Callback mDisplayCallback = 85 new DisplayTracker.Callback() { 86 @Override 87 public void onDisplayAdded(int displayId) { 88 Trace.beginSection( 89 "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")"); 90 final Display display = mDisplayService.getDisplay(displayId); 91 if (mShowing) { 92 updateNavigationBarVisibility(displayId, false /* navBarVisible */); 93 showPresentation(display); 94 } 95 Trace.endSection(); 96 } 97 98 @Override 99 public void onDisplayRemoved(int displayId) { 100 Trace.beginSection( 101 "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")"); 102 hidePresentation(displayId); 103 Trace.endSection(); 104 } 105 }; 106 107 @Inject KeyguardDisplayManager( @hadeDisplayAware Context context, Lazy<NavigationBarController> navigationBarControllerLazy, DisplayTracker displayTracker, @Main Executor mainExecutor, @UiBackground Executor uiBgExecutor, DeviceStateHelper deviceStateHelper, KeyguardStateController keyguardStateController, ConnectedDisplayKeyguardPresentation.Factory connectedDisplayKeyguardPresentationFactory, Provider<ShadeDisplaysRepository> shadePositionRepositoryProvider, @Application CoroutineScope appScope)108 public KeyguardDisplayManager( 109 @ShadeDisplayAware Context context, 110 Lazy<NavigationBarController> navigationBarControllerLazy, 111 DisplayTracker displayTracker, 112 @Main Executor mainExecutor, 113 @UiBackground Executor uiBgExecutor, 114 DeviceStateHelper deviceStateHelper, 115 KeyguardStateController keyguardStateController, 116 ConnectedDisplayKeyguardPresentation.Factory 117 connectedDisplayKeyguardPresentationFactory, 118 Provider<ShadeDisplaysRepository> shadePositionRepositoryProvider, 119 @Application CoroutineScope appScope) { 120 mContext = context; 121 mNavigationBarControllerLazy = navigationBarControllerLazy; 122 mShadePositionRepositoryProvider = shadePositionRepositoryProvider; 123 uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class)); 124 mDisplayService = mContext.getSystemService(DisplayManager.class); 125 mDisplayTracker = displayTracker; 126 mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor); 127 mDeviceStateHelper = deviceStateHelper; 128 mKeyguardStateController = keyguardStateController; 129 mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory; 130 if (ShadeWindowGoesAround.isEnabled()) { 131 collectFlow(appScope, shadePositionRepositoryProvider.get().getDisplayId(), 132 (id) -> onShadeWindowMovedToDisplayId(id)); 133 } 134 } 135 onShadeWindowMovedToDisplayId(int shadeDisplayId)136 private void onShadeWindowMovedToDisplayId(int shadeDisplayId) { 137 if (mShowing) { 138 hidePresentation(shadeDisplayId); 139 updateDisplays(/* showing= */ true); 140 } 141 } 142 isKeyguardShowable(Display display)143 private boolean isKeyguardShowable(Display display) { 144 if (display == null) { 145 Log.i(TAG, "Cannot show Keyguard on null display"); 146 return false; 147 } 148 if (ShadeWindowGoesAround.isEnabled()) { 149 int shadeDisplayId = mShadePositionRepositoryProvider.get().getDisplayId().getValue(); 150 if (display.getDisplayId() == shadeDisplayId) { 151 Log.i(TAG, "Do not show KeyguardPresentation on the shade window display"); 152 return false; 153 } 154 } else { 155 if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) { 156 Log.i(TAG, "Do not show KeyguardPresentation on the default display"); 157 return false; 158 } 159 } 160 display.getDisplayInfo(mTmpDisplayInfo); 161 if ((mTmpDisplayInfo.flags & Display.FLAG_PRIVATE) != 0) { 162 Log.i(TAG, "Do not show KeyguardPresentation on a private display"); 163 return false; 164 } 165 if ((mTmpDisplayInfo.flags & Display.FLAG_ALWAYS_UNLOCKED) != 0) { 166 Log.i(TAG, "Do not show KeyguardPresentation on an unlocked display"); 167 return false; 168 } 169 170 final boolean deviceStateOccludesKeyguard = 171 mDeviceStateHelper.isConcurrentDisplayActive(display) 172 || mDeviceStateHelper.isRearDisplayOuterDefaultActive(display); 173 if (mKeyguardStateController.isOccluded() && deviceStateOccludesKeyguard) { 174 // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the Keyguard 175 // state becomes "occluded". In this case, we should not show the KeyguardPresentation, 176 // since the activity is presenting content onto the non-default display. 177 Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent or rear" 178 + " display is active"); 179 return false; 180 } 181 182 return true; 183 } 184 /** 185 * @param display The display to show the presentation on. 186 * @return {@code true} if a presentation was added. 187 * {@code false} if the presentation cannot be added on that display or the presentation 188 * was already there. 189 */ showPresentation(Display display)190 private boolean showPresentation(Display display) { 191 if (!isKeyguardShowable(display)) return false; 192 Log.i(TAG, "Keyguard enabled on display: " + display); 193 final int displayId = display.getDisplayId(); 194 Presentation presentation = mPresentations.get(displayId); 195 if (presentation == null) { 196 final Presentation newPresentation = createPresentation(display); 197 newPresentation.setOnDismissListener(dialog -> { 198 if (newPresentation.equals(mPresentations.get(displayId))) { 199 mPresentations.remove(displayId); 200 } 201 }); 202 presentation = newPresentation; 203 try { 204 presentation.show(); 205 } catch (WindowManager.InvalidDisplayException ex) { 206 Log.w(TAG, "Invalid display:", ex); 207 presentation = null; 208 } 209 if (presentation != null) { 210 mPresentations.append(displayId, presentation); 211 return true; 212 } 213 } 214 return false; 215 } 216 createPresentation(Display display)217 Presentation createPresentation(Display display) { 218 return mConnectedDisplayKeyguardPresentationFactory.create(display); 219 } 220 221 /** 222 * @param displayId The id of the display to hide the presentation off. 223 */ hidePresentation(int displayId)224 private void hidePresentation(int displayId) { 225 final Presentation presentation = mPresentations.get(displayId); 226 if (presentation != null) { 227 presentation.dismiss(); 228 mPresentations.remove(displayId); 229 } 230 } 231 show()232 public void show() { 233 if (!mShowing) { 234 Log.v(TAG, "show"); 235 if (mMediaRouter != null) { 236 mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, 237 mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); 238 } else { 239 Log.w(TAG, "MediaRouter not yet initialized"); 240 } 241 updateDisplays(true /* showing */); 242 } 243 mShowing = true; 244 } 245 hide()246 public void hide() { 247 if (mShowing) { 248 Log.v(TAG, "hide"); 249 if (mMediaRouter != null) { 250 mMediaRouter.removeCallback(mMediaRouterCallback); 251 } 252 updateDisplays(false /* showing */); 253 } 254 mShowing = false; 255 } 256 257 private final MediaRouter.SimpleCallback mMediaRouterCallback = 258 new MediaRouter.SimpleCallback() { 259 @Override 260 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { 261 if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); 262 updateDisplays(mShowing); 263 } 264 265 @Override 266 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { 267 if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); 268 updateDisplays(mShowing); 269 } 270 271 @Override 272 public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { 273 if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); 274 updateDisplays(mShowing); 275 } 276 }; 277 updateDisplays(boolean showing)278 protected boolean updateDisplays(boolean showing) { 279 boolean changed = false; 280 if (showing) { 281 final Display[] displays = mDisplayTracker.getAllDisplays(); 282 for (Display display : displays) { 283 int displayId = display.getDisplayId(); 284 updateNavigationBarVisibility(displayId, false /* navBarVisible */); 285 changed |= showPresentation(display); 286 } 287 } else { 288 changed = mPresentations.size() > 0; 289 for (int i = mPresentations.size() - 1; i >= 0; i--) { 290 int displayId = mPresentations.keyAt(i); 291 updateNavigationBarVisibility(displayId, true /* navBarVisible */); 292 mPresentations.valueAt(i).dismiss(); 293 } 294 mPresentations.clear(); 295 } 296 return changed; 297 } 298 299 // TODO(b/127878649): this logic is from 300 // {@link StatusBarKeyguardViewManager#updateNavigationBarVisibility}. Try to revisit a long 301 // term solution in R. updateNavigationBarVisibility(int displayId, boolean navBarVisible)302 private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) { 303 // Leave this task to {@link StatusBarKeyguardViewManager} 304 if (displayId == mDisplayTracker.getDefaultDisplayId()) return; 305 306 NavigationBarView navBarView = mNavigationBarControllerLazy.get() 307 .getNavigationBarView(displayId); 308 // We may not have nav bar on a display. 309 if (navBarView == null) return; 310 311 if (navBarVisible) { 312 navBarView.getRootView().setVisibility(View.VISIBLE); 313 } else { 314 navBarView.getRootView().setVisibility(View.GONE); 315 } 316 317 } 318 319 /** 320 * Helper used to receive device state info from {@link DeviceStateManager}. 321 */ 322 public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback { 323 324 @Nullable 325 private DeviceState mDeviceState; 326 327 @Inject DeviceStateHelper( DeviceStateManager deviceStateManager, @Main Executor mainExecutor)328 DeviceStateHelper( 329 DeviceStateManager deviceStateManager, 330 @Main Executor mainExecutor) { 331 deviceStateManager.registerCallback(mainExecutor, this); 332 } 333 334 @Override onDeviceStateChanged(@onNull DeviceState state)335 public void onDeviceStateChanged(@NonNull DeviceState state) { 336 // When dual display or rear display mode ends, the display also turns off. This is 337 // enforced in various ExtensionRearDisplayPresentationTest CTS tests. So, we don't need 338 // to invoke hide() since that will happen through the onDisplayRemoved callback. 339 mDeviceState = state; 340 } 341 342 /** 343 * @return true if the device is in Dual Display mode, and the specified display is the 344 * rear facing (outer) display. 345 */ isConcurrentDisplayActive(@onNull Display display)346 boolean isConcurrentDisplayActive(@NonNull Display display) { 347 return mDeviceState != null 348 && mDeviceState.hasProperty( 349 DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT) 350 && (display.getFlags() & Display.FLAG_REAR) != 0; 351 } 352 353 /** 354 * @return true if the device is the updated Rear Display mode, and the specified display is 355 * the inner display. See {@link DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT}. 356 * Note that in this state, the outer display is the default display, while the inner 357 * display is the "rear" display. 358 */ isRearDisplayOuterDefaultActive(@onNull Display display)359 boolean isRearDisplayOuterDefaultActive(@NonNull Display display) { 360 return mDeviceState != null 361 && mDeviceState.hasProperty( 362 DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT) 363 && (display.getFlags() & Display.FLAG_REAR) != 0; 364 } 365 } 366 } 367