blob: b2423b9bf252152f5383026a90e69d7149d85020 [file] [log] [blame]
/*
* Copyright (C) 2020 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 android.annotation.Nullable;
import android.app.admin.IKeyguardCallback;
import android.app.admin.IKeyguardClient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
/**
* Encapsulates all logic for secondary lockscreen state management.
*/
public class AdminSecondaryLockScreenController {
private static final String TAG = "AdminSecondaryLockScreenController";
private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Context mContext;
private final ViewGroup mParent;
private AdminSecurityView mView;
private Handler mHandler;
private IKeyguardClient mClient;
private KeyguardSecurityCallback mKeyguardCallback;
private SurfaceControl.Transaction mTransaction;
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mClient = IKeyguardClient.Stub.asInterface(service);
if (mView.isAttachedToWindow() && mClient != null) {
onSurfaceReady();
try {
service.linkToDeath(mKeyguardClientDeathRecipient, 0);
} catch (RemoteException e) {
// Failed to link to death, just dismiss and unbind the service for now.
Log.e(TAG, "Lost connection to secondary lockscreen service", e);
dismiss(KeyguardUpdateMonitor.getCurrentUser());
}
}
}
@Override
public void onServiceDisconnected(ComponentName className) {
mClient = null;
}
};
private final IBinder.DeathRecipient mKeyguardClientDeathRecipient = () -> {
hide(); // hide also takes care of unlinking to death.
Log.d(TAG, "KeyguardClient service died");
};
private final IKeyguardCallback mCallback = new IKeyguardCallback.Stub() {
@Override
public void onDismiss() {
dismiss(UserHandle.getCallingUserId());
}
@Override
public void onSurfaceControlCreated(@Nullable SurfaceControl remoteSurfaceControl) {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
if (remoteSurfaceControl != null) {
mTransaction.reparent(remoteSurfaceControl, mView.getSurfaceControl())
.apply();
} else {
dismiss(KeyguardUpdateMonitor.getCurrentUser());
}
}
};
private final KeyguardUpdateMonitorCallback mUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
public void onSecondaryLockscreenRequirementChanged(int userId) {
Intent newIntent = mUpdateMonitor.getSecondaryLockscreenRequirement(userId);
if (newIntent == null) {
dismiss(userId);
}
}
};
@VisibleForTesting
protected SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
final int userId = KeyguardUpdateMonitor.getCurrentUser();
mUpdateMonitor.registerCallback(mUpdateCallback);
if (mClient != null) {
onSurfaceReady();
}
mHandler.postDelayed(
() -> {
// If the remote content is not readied within the timeout period,
// move on without the secondary lockscreen.
dismiss(userId);
},
REMOTE_CONTENT_READY_TIMEOUT_MILLIS);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mUpdateMonitor.removeCallback(mUpdateCallback);
}
};
public AdminSecondaryLockScreenController(Context context, ViewGroup parent,
KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
Handler handler, SurfaceControl.Transaction transaction) {
mContext = context;
mHandler = handler;
mParent = parent;
mTransaction = transaction;
mUpdateMonitor = updateMonitor;
mKeyguardCallback = callback;
mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
}
/**
* Displays the Admin security Surface view.
*/
public void show(Intent serviceIntent) {
mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
mParent.addView(mView);
}
/**
* Hides the Admin security Surface view.
*/
public void hide() {
if (mView.isAttachedToWindow()) {
mParent.removeView(mView);
}
if (mClient != null) {
mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0);
mContext.unbindService(mConnection);
mClient = null;
}
}
private void onSurfaceReady() {
try {
mClient.onSurfaceReady(mView.getHostToken(), mCallback);
} catch (RemoteException e) {
Log.e(TAG, "Error in onSurfaceReady", e);
dismiss(KeyguardUpdateMonitor.getCurrentUser());
}
}
private void dismiss(int userId) {
mHandler.removeCallbacksAndMessages(null);
if (mView != null && mView.isAttachedToWindow()
&& userId == KeyguardUpdateMonitor.getCurrentUser()) {
hide();
mKeyguardCallback.dismiss(true, userId);
}
}
/**
* Custom {@link SurfaceView} used to allow a device admin to present an additional security
* screen.
*/
private class AdminSecurityView extends SurfaceView {
private SurfaceHolder.Callback mSurfaceHolderCallback;
AdminSecurityView(Context context, SurfaceHolder.Callback surfaceHolderCallback) {
super(context);
mSurfaceHolderCallback = surfaceHolderCallback;
setZOrderOnTop(true);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getHolder().addCallback(mSurfaceHolderCallback);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getHolder().removeCallback(mSurfaceHolderCallback);
}
}
}