1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.remotelockscreenvalidation;
18 
19 import static android.service.remotelockscreenvalidation.RemoteLockscreenValidationService.SERVICE_INTERFACE;
20 
21 import android.Manifest;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ServiceInfo;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.RemoteException;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import java.util.ArrayDeque;
38 import java.util.ArrayList;
39 import java.util.Queue;
40 import java.util.concurrent.Executor;
41 
42 /**
43  * Implements {@link RemoteLockscreenValidationClient}.
44  *
45  * @hide
46  */
47 public class RemoteLockscreenValidationClientImpl implements RemoteLockscreenValidationClient,
48         ServiceConnection {
49 
50     private static final String TAG = RemoteLockscreenValidationClientImpl.class.getSimpleName();
51     private final Handler mHandler;
52     private final Context mContext;
53     private final Queue<Call> mRequestQueue;
54     private final Executor mLifecycleExecutor;
55     private final boolean mIsServiceAvailable;
56     private boolean mIsConnected;
57 
58     @Nullable
59     private IRemoteLockscreenValidationService mService;
60 
61     @Nullable
62     private ServiceInfo mServiceInfo;
63 
RemoteLockscreenValidationClientImpl( @onNull Context context, @Nullable Executor bgExecutor, @NonNull ComponentName serviceComponent)64     RemoteLockscreenValidationClientImpl(
65             @NonNull Context context,
66             @Nullable Executor bgExecutor,
67             @NonNull ComponentName serviceComponent) {
68         mContext = context.getApplicationContext();
69         mIsServiceAvailable = isServiceAvailable(mContext, serviceComponent);
70         mHandler = new Handler(Looper.getMainLooper());
71         mLifecycleExecutor = (bgExecutor == null) ? Runnable::run : bgExecutor;
72         mRequestQueue = new ArrayDeque<>();
73     }
74 
75     @Override
isServiceAvailable()76     public boolean isServiceAvailable() {
77         return mIsServiceAvailable;
78     }
79 
80     @Override
validateLockscreenGuess( byte[] guess, IRemoteLockscreenValidationCallback callback)81     public void validateLockscreenGuess(
82             byte[] guess, IRemoteLockscreenValidationCallback callback) {
83         try {
84             if (!isServiceAvailable()) {
85                 callback.onFailure("Service is not available");
86                 return;
87             }
88         } catch (RemoteException e) {
89             Log.e(TAG, "Error while failing for service unavailable", e);
90         }
91 
92         executeApiCall(new Call() {
93             @Override
94             public void exec(IRemoteLockscreenValidationService service) throws RemoteException {
95                 service.validateLockscreenGuess(guess, callback);
96             }
97 
98             @Override
99             void onError(String msg) {
100                 try {
101                     callback.onFailure(msg);
102                 } catch (RemoteException e) {
103                     Log.e(TAG, "Error while failing validateLockscreenGuess", e);
104                 }
105             }
106         });
107     }
108 
109     @Override
disconnect()110     public void disconnect() {
111         mHandler.post(this::disconnectInternal);
112     }
113 
disconnectInternal()114     private void disconnectInternal() {
115         if (!mIsConnected) {
116             Log.w(TAG, "already disconnected");
117             return;
118         }
119         mIsConnected = false;
120         mLifecycleExecutor.execute(() -> mContext.unbindService(/* conn= */ this));
121         mService = null;
122         mRequestQueue.clear();
123     }
124 
connect()125     private void connect() {
126         mHandler.post(this::connectInternal);
127     }
128 
connectInternal()129     private void connectInternal() {
130         if (mServiceInfo == null) {
131             Log.w(TAG, "RemoteLockscreenValidation service unavailable");
132             return;
133         }
134         if (mIsConnected) {
135             return;
136         }
137         mIsConnected = true;
138         Intent intent = new Intent(SERVICE_INTERFACE);
139         intent.setComponent(mServiceInfo.getComponentName());
140         int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY;
141         mLifecycleExecutor.execute(() -> mContext.bindService(intent, this, flags));
142     }
143 
onConnectedInternal(IRemoteLockscreenValidationService service)144     private void onConnectedInternal(IRemoteLockscreenValidationService service) {
145         if (!mIsConnected) {
146             Log.w(TAG, "onConnectInternal but connection closed");
147             mService = null;
148             return;
149         }
150         mService = service;
151         for (Call call : new ArrayList<>(mRequestQueue)) {
152             performApiCallInternal(call, mService);
153             mRequestQueue.remove(call);
154         }
155     }
156 
isServiceAvailable( @onNull Context context, @NonNull ComponentName serviceComponent)157     private boolean isServiceAvailable(
158             @NonNull Context context,
159             @NonNull ComponentName serviceComponent) {
160         mServiceInfo = getServiceInfo(context, serviceComponent);
161         if (mServiceInfo == null) {
162             return false;
163         }
164 
165         if (!Manifest.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE.equals(
166                 mServiceInfo.permission)) {
167             Log.w(TAG, TextUtils.formatSimple("%s/%s does not require permission %s",
168                     mServiceInfo.packageName, mServiceInfo.name,
169                     Manifest.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE));
170             return false;
171         }
172         return true;
173     }
174 
getServiceInfo( @onNull Context context, @NonNull ComponentName serviceComponent)175     private ServiceInfo getServiceInfo(
176             @NonNull Context context, @NonNull ComponentName serviceComponent) {
177         try {
178             return context.getPackageManager().getServiceInfo(serviceComponent,
179                     PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
180         } catch (PackageManager.NameNotFoundException e) {
181             Log.w(TAG, TextUtils.formatSimple("Cannot resolve service %s",
182                     serviceComponent.getClassName()));
183             return null;
184         }
185     }
186 
executeApiCall(Call call)187     private void executeApiCall(Call call) {
188         mHandler.post(() -> executeInternal(call));
189     }
190 
executeInternal(RemoteLockscreenValidationClientImpl.Call call)191     private void executeInternal(RemoteLockscreenValidationClientImpl.Call call) {
192         if (mIsConnected && mService != null) {
193             performApiCallInternal(call, mService);
194         } else {
195             mRequestQueue.add(call);
196             connect();
197         }
198     }
199 
performApiCallInternal( RemoteLockscreenValidationClientImpl.Call apiCaller, IRemoteLockscreenValidationService service)200     private void performApiCallInternal(
201             RemoteLockscreenValidationClientImpl.Call apiCaller,
202             IRemoteLockscreenValidationService service) {
203         if (service == null) {
204             apiCaller.onError("Service is null");
205             return;
206         }
207         try {
208             apiCaller.exec(service);
209         } catch (RemoteException e) {
210             Log.w(TAG, "executeInternal error", e);
211             apiCaller.onError(e.getMessage());
212             disconnect();
213         }
214     }
215 
216     @Override // ServiceConnection
onServiceConnected(ComponentName name, IBinder binder)217     public void onServiceConnected(ComponentName name, IBinder binder) {
218         IRemoteLockscreenValidationService service =
219                 IRemoteLockscreenValidationService.Stub.asInterface(binder);
220         mHandler.post(() -> onConnectedInternal(service));
221     }
222 
223     @Override // ServiceConnection
onServiceDisconnected(ComponentName name)224     public void onServiceDisconnected(ComponentName name) {
225         // Do not disconnect, as we may later be re-connected
226     }
227 
228     @Override // ServiceConnection
onBindingDied(ComponentName name)229     public void onBindingDied(ComponentName name) {
230         // This is a recoverable error but the client will need to reconnect.
231         disconnect();
232     }
233 
234     @Override // ServiceConnection
onNullBinding(ComponentName name)235     public void onNullBinding(ComponentName name) {
236         disconnect();
237     }
238 
239     private abstract static class Call {
exec(IRemoteLockscreenValidationService service)240         abstract void exec(IRemoteLockscreenValidationService service)
241                 throws RemoteException;
onError(String msg)242         abstract void onError(String msg);
243     }
244 }
245