/* * 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 static androidx.constraintlayout.widget.ConstraintSet.BOTTOM; import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; import static androidx.constraintlayout.widget.ConstraintSet.START; import static androidx.constraintlayout.widget.ConstraintSet.TOP; 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.View; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; 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 com.android.systemui.user.domain.interactor.SelectedUserInteractor; 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 ConstraintLayout mParent; private AdminSecurityView mView; private Handler mHandler; private IKeyguardClient mClient; private KeyguardSecurityCallback mKeyguardCallback; private SelectedUserInteractor mSelectedUserInteractor; 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(mSelectedUserInteractor.getSelectedUserId()); } } } @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(mSelectedUserInteractor.getSelectedUserId()); }); } } }; 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 = mSelectedUserInteractor.getSelectedUserId(); 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, SelectedUserInteractor selectedUserInteractor) { mContext = context; mHandler = handler; mParent = parent; mUpdateMonitor = updateMonitor; mKeyguardCallback = callback; mView = new AdminSecurityView(mContext, mSurfaceHolderCallback); mView.setId(View.generateViewId()); mSelectedUserInteractor = selectedUserInteractor; } /** * 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); ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(mParent); constraintSet.connect(mView.getId(), TOP, PARENT_ID, TOP); constraintSet.connect(mView.getId(), START, PARENT_ID, START); constraintSet.connect(mView.getId(), END, PARENT_ID, END); constraintSet.connect(mView.getId(), BOTTOM, PARENT_ID, BOTTOM); constraintSet.constrainHeight(mView.getId(), ConstraintSet.MATCH_CONSTRAINT); constraintSet.constrainWidth(mView.getId(), ConstraintSet.MATCH_CONSTRAINT); constraintSet.applyTo(mParent); } } /** * 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(mSelectedUserInteractor.getSelectedUserId()); } } private void dismiss(int userId) { mHandler.removeCallbacksAndMessages(null); if (mView.isAttachedToWindow() && userId == mSelectedUserInteractor.getSelectedUserId()) { 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; private final SelectedUserInteractor mSelectedUserInteractor; @Inject public Factory(Context context, KeyguardSecurityContainer parent, KeyguardUpdateMonitor updateMonitor, @Main Handler handler, SelectedUserInteractor selectedUserInteractor) { mContext = context; mParent = parent; mUpdateMonitor = updateMonitor; mHandler = handler; mSelectedUserInteractor = selectedUserInteractor; } public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) { return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor, callback, mHandler, mSelectedUserInteractor); } } }