blob: 00f1c0108d0b5229955dcfa05d75ffbe38506074 [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.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.systemui.dagger.qualifiers.Main;
import java.util.NoSuchElementException;
import javax.inject.Inject;
/**
* 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 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() {
mHandler.post(() -> {
dismiss(UserHandle.getCallingUserId());
});
}
@Override
public void onRemoteContentReady(
@Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
if (surfacePackage != null) {
mView.setChildSurfacePackage(surfacePackage);
} else {
mHandler.post(() -> {
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);
Log.w(TAG, "Timed out waiting for secondary lockscreen content.");
},
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);
}
};
private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent,
KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
@Main Handler handler) {
mContext = context;
mHandler = handler;
mParent = parent;
mUpdateMonitor = updateMonitor;
mKeyguardCallback = callback;
mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
}
/**
* Displays the Admin security Surface view.
*/
public void show(Intent serviceIntent) {
if (mClient == null) {
mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
}
if (!mView.isAttachedToWindow()) {
mParent.addView(mView);
}
}
/**
* Hides the Admin security Surface view.
*/
public void hide() {
if (mView.isAttachedToWindow()) {
mParent.removeView(mView);
}
if (mClient != null) {
try {
mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0);
} catch (NoSuchElementException e) {
Log.w(TAG, "IKeyguardClient death recipient already released");
}
mContext.unbindService(mConnection);
mClient = null;
}
}
private void onSurfaceReady() {
try {
IBinder hostToken = mView.getHostToken();
// Should never be null when SurfaceView is attached to window.
if (hostToken != null) {
mClient.onCreateKeyguardSurface(hostToken, mCallback);
} else {
hide();
}
} catch (RemoteException e) {
Log.e(TAG, "Error in onCreateKeyguardSurface", e);
dismiss(KeyguardUpdateMonitor.getCurrentUser());
}
}
private void dismiss(int userId) {
mHandler.removeCallbacksAndMessages(null);
if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) {
hide();
if (mKeyguardCallback != null) {
mKeyguardCallback.dismiss(/* securityVerified= */ true, userId,
/* bypassSecondaryLockScreen= */true, SecurityMode.Invalid);
}
}
}
/**
* 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);
}
}
@KeyguardBouncerScope
public static class Factory {
private final Context mContext;
private final KeyguardSecurityContainer mParent;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Handler mHandler;
@Inject
public Factory(Context context, KeyguardSecurityContainer parent,
KeyguardUpdateMonitor updateMonitor, @Main Handler handler) {
mContext = context;
mParent = parent;
mUpdateMonitor = updateMonitor;
mHandler = handler;
}
public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) {
return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor,
callback, mHandler);
}
}
}