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