blob: e051317b96b6fb4aaef44b7a57077a14dcc90a6d [file] [log] [blame]
/*
* 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<Presentation> 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);
}
}
}