/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.keyguard; import static android.view.Display.DEFAULT_DISPLAY; import android.annotation.Nullable; import android.app.Presentation; import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.os.Bundle; import android.util.Log; import android.util.SparseArray; import android.view.Display; import android.view.View; import android.view.WindowManager; import java.util.function.BooleanSupplier; // TODO(multi-display): Support multiple external displays public class KeyguardDisplayManager { protected static final String TAG = "KeyguardDisplayManager"; private static boolean DEBUG = KeyguardConstants.DEBUG; private final ViewMediatorCallback mCallback; private final MediaRouter mMediaRouter; private final DisplayManager mDisplayService; private final Context mContext; private boolean mShowing; private final SparseArray mPresentations = new SparseArray<>(); private final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { @Override public void onDisplayAdded(int displayId) { final Display display = mDisplayService.getDisplay(displayId); if (mShowing) { notifyIfChanged(() -> showPresentation(display)); } } @Override public void onDisplayChanged(int displayId) { if (displayId == DEFAULT_DISPLAY) return; final Display display = mDisplayService.getDisplay(displayId); if (display != null && mShowing) { final Presentation presentation = mPresentations.get(displayId); if (presentation != null && !presentation.getDisplay().equals(display)) { hidePresentation(displayId); showPresentation(display); } } } @Override public void onDisplayRemoved(int displayId) { notifyIfChanged(() -> hidePresentation(displayId)); } }; public KeyguardDisplayManager(Context context, ViewMediatorCallback callback) { mContext = context; mCallback = callback; mMediaRouter = mContext.getSystemService(MediaRouter.class); mDisplayService = mContext.getSystemService(DisplayManager.class); mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */); } /** * @param display The display to show the presentation on. * @return {@code true} if a presentation was added. * {@code false} if the presentation cannot be added on that display or the presentation * was already there. */ private boolean showPresentation(Display display) { if (display == null || display.getDisplayId() == DEFAULT_DISPLAY) return false; if (DEBUG) Log.i(TAG, "Keyguard enabled on display: " + display); final int displayId = display.getDisplayId(); Presentation presentation = mPresentations.get(displayId); if (presentation == null) { presentation = new KeyguardPresentation(mContext, display); presentation.setOnDismissListener(dialog -> { if (null != mPresentations.get(displayId)) { mPresentations.remove(displayId); } }); try { presentation.show(); } catch (WindowManager.InvalidDisplayException ex) { Log.w(TAG, "Invalid display:", ex); presentation = null; } if (presentation != null) { mPresentations.append(displayId, presentation); return true; } } return false; } /** * @param displayId The id of the display to hide the presentation off. * @return {@code true} if the a presentation was removed. * {@code false} if the presentation was not added before. */ private boolean hidePresentation(int displayId) { final Presentation presentation = mPresentations.get(displayId); if (presentation != null) { presentation.dismiss(); mPresentations.remove(displayId); return true; } return false; } private void notifyIfChanged(BooleanSupplier updateMethod) { if (updateMethod.getAsBoolean()) { final int[] displayList = getPresentationDisplayIds(); mCallback.onSecondaryDisplayShowingChanged(displayList); } } /** * @return An array of displayId's on which a {@link KeyguardPresentation} is showing on. */ @Nullable private int[] getPresentationDisplayIds() { final int size = mPresentations.size(); if (size == 0) return null; final int[] displayIds = new int[size]; for (int i = mPresentations.size() - 1; i >= 0; i--) { final Presentation presentation = mPresentations.valueAt(i); if (presentation != null) { displayIds[i] = presentation.getDisplay().getDisplayId(); } } return displayIds; } public void show() { if (!mShowing) { if (DEBUG) Log.v(TAG, "show"); mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); notifyIfChanged(() -> updateDisplays(true /* showing */)); } mShowing = true; } public void hide() { if (mShowing) { if (DEBUG) Log.v(TAG, "hide"); mMediaRouter.removeCallback(mMediaRouterCallback); notifyIfChanged(() -> updateDisplays(false /* showing */)); } mShowing = false; } private final MediaRouter.SimpleCallback mMediaRouterCallback = new MediaRouter.SimpleCallback() { @Override public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { if (DEBUG) Log.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); notifyIfChanged(() -> updateDisplays(mShowing)); } @Override public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { if (DEBUG) Log.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); notifyIfChanged(() -> updateDisplays(mShowing)); } @Override public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { if (DEBUG) Log.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); notifyIfChanged(() -> updateDisplays(mShowing)); } }; protected boolean updateDisplays(boolean showing) { boolean changed = false; if (showing) { final Display[] displays = mDisplayService.getDisplays(); for (Display display : displays) { changed |= showPresentation(display); } } else { changed = mPresentations.size() > 0; for (int i = mPresentations.size() - 1; i >= 0; i--) { mPresentations.valueAt(i).dismiss(); } mPresentations.clear(); } return changed; } private final static class KeyguardPresentation extends Presentation { private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s private View mClock; private int mUsableWidth; private int mUsableHeight; private int mMarginTop; private int mMarginLeft; Runnable mMoveTextRunnable = new Runnable() { @Override public void run() { int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth())); int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight())); mClock.setTranslationX(x); mClock.setTranslationY(y); mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT); } }; KeyguardPresentation(Context context, Display display) { super(context, display, R.style.keyguard_presentation_theme); getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); setCancelable(false); } @Override public void onDetachedFromWindow() { mClock.removeCallbacks(mMoveTextRunnable); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Point p = new Point(); getDisplay().getSize(p); mUsableWidth = VIDEO_SAFE_REGION * p.x/100; mUsableHeight = VIDEO_SAFE_REGION * p.y/100; mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200; mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200; setContentView(R.layout.keyguard_presentation); mClock = findViewById(R.id.clock); // Avoid screen burn in mClock.post(mMoveTextRunnable); } } }