| /* |
| * 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); |
| } |
| } |
| } |