/* * 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 java.util.NoSuchElementException; /** * 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); } }; public AdminSecondaryLockScreenController(Context context, ViewGroup parent, KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback, 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); } } } /** * 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); } } }