Merge "Add RemoteLockscreenValidation service and client"

This commit is contained in:
Brian Lee
2023-02-11 02:16:28 +00:00
committed by Android (Google) Code Review
8 changed files with 476 additions and 0 deletions

View File

@@ -65,6 +65,7 @@ package android {
field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
field public static final String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
field public static final String BIND_RESUME_ON_REBOOT_SERVICE = "android.permission.BIND_RESUME_ON_REBOOT_SERVICE";
field public static final String BIND_ROTATION_RESOLVER_SERVICE = "android.permission.BIND_ROTATION_RESOLVER_SERVICE";
@@ -12565,6 +12566,17 @@ package android.service.quicksettings {
}
package android.service.remotelockscreenvalidation {
public abstract class RemoteLockscreenValidationService extends android.app.Service {
ctor public RemoteLockscreenValidationService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onValidateLockscreenGuess(@NonNull byte[], @NonNull android.os.OutcomeReceiver<android.app.RemoteLockscreenValidationResult,java.lang.Exception>);
field public static final String SERVICE_INTERFACE = "android.service.remotelockscreenvalidation.RemoteLockscreenValidationService";
}
}
package android.service.resolver {
public abstract class ResolverRankerService extends android.app.Service {

View File

@@ -0,0 +1,12 @@
package android.service.remotelockscreenvalidation;
import android.app.RemoteLockscreenValidationResult;
/**
* Callback interface for remote device lockscreen validation
* @hide
*/
interface IRemoteLockscreenValidationCallback {
oneway void onSuccess(in RemoteLockscreenValidationResult result);
oneway void onFailure(in String message);
}

View File

@@ -0,0 +1,12 @@
package android.service.remotelockscreenvalidation;
import android.app.RemoteLockscreenValidationResult;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
/**
* Interface used by the System to validate remote device lockscreen.
* {@hide}
*/
interface IRemoteLockscreenValidationService {
void validateLockscreenGuess(in byte[] guess, in IRemoteLockscreenValidationCallback callback);
}

View File

@@ -0,0 +1,2 @@
include /services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
brnlee@google.com

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2023 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 android.service.remotelockscreenvalidation;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import java.util.concurrent.Executor;
/**
* Client for {@link RemoteLockscreenValidationService}
* @hide
*/
public interface RemoteLockscreenValidationClient {
/**
* Create a client for the {@link RemoteLockscreenValidationService} specified by the
* {@link ComponentName}
* @hide
*/
@NonNull
static RemoteLockscreenValidationClient create(@NonNull Context context,
@NonNull ComponentName serviceComponent) {
return new RemoteLockscreenValidationClientImpl(
context,
/* bgExecutor= */ null,
serviceComponent);
}
/**
* Create a client for the {@link RemoteLockscreenValidationService} specified by the
* {@link ComponentName}
* @param context Context.
* @param bgExecutor A background {@link Executor} for service registration.
* @hide
*/
@NonNull
static RemoteLockscreenValidationClient create(@NonNull Context context,
@Nullable Executor bgExecutor, @NonNull ComponentName serviceComponent) {
return new RemoteLockscreenValidationClientImpl(context, bgExecutor, serviceComponent);
}
/**
* Returns whether the {@link RemoteLockscreenValidationService} defined by the
* {@code ComponentName} provided in the constructor is available.
*
* <p>Calling API methods like {@link #validateLockscreenGuess} will fail if unavailable.
*/
boolean isServiceAvailable();
/**
* Unbinds from the {@link RemoteLockscreenValidationService}
*/
void disconnect();
/**
* Validates the lockscreen guess.
*
* @param guess lockscreen guess
* @param callback object used to relay the response of the guess validation
*/
void validateLockscreenGuess(byte[] guess, IRemoteLockscreenValidationCallback callback);
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright (C) 2023 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 android.service.remotelockscreenvalidation;
import static android.service.remotelockscreenvalidation.RemoteLockscreenValidationService.SERVICE_INTERFACE;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;
import java.util.concurrent.Executor;
/**
* Implements {@link RemoteLockscreenValidationClient}.
*
* @hide
*/
public class RemoteLockscreenValidationClientImpl implements RemoteLockscreenValidationClient,
ServiceConnection {
private static final String TAG = RemoteLockscreenValidationClientImpl.class.getSimpleName();
private final Handler mHandler;
private final Context mContext;
private final Queue<Call> mRequestQueue;
private final Executor mLifecycleExecutor;
private final boolean mIsServiceAvailable;
private boolean mIsConnected;
@Nullable
private IRemoteLockscreenValidationService mService;
@Nullable
private ServiceInfo mServiceInfo;
RemoteLockscreenValidationClientImpl(
@NonNull Context context,
@Nullable Executor bgExecutor,
@NonNull ComponentName serviceComponent) {
mContext = context.getApplicationContext();
mIsServiceAvailable = isServiceAvailable(mContext, serviceComponent);
mHandler = new Handler(Looper.getMainLooper());
mLifecycleExecutor = (bgExecutor == null) ? Runnable::run : bgExecutor;
mRequestQueue = new ArrayDeque<>();
}
@Override
public boolean isServiceAvailable() {
return mIsServiceAvailable;
}
@Override
public void validateLockscreenGuess(
byte[] guess, IRemoteLockscreenValidationCallback callback) {
try {
if (!isServiceAvailable()) {
callback.onFailure("Service is not available");
return;
}
} catch (RemoteException e) {
Log.e(TAG, "Error while failing for service unavailable", e);
}
executeApiCall(new Call() {
@Override
public void exec(IRemoteLockscreenValidationService service) throws RemoteException {
service.validateLockscreenGuess(guess, callback);
}
@Override
void onError(String msg) {
try {
callback.onFailure(msg);
} catch (RemoteException e) {
Log.e(TAG, "Error while failing validateLockscreenGuess", e);
}
}
});
}
@Override
public void disconnect() {
mHandler.post(this::disconnectInternal);
}
private void disconnectInternal() {
if (!mIsConnected) {
Log.w(TAG, "already disconnected");
return;
}
mIsConnected = false;
mLifecycleExecutor.execute(() -> mContext.unbindService(/* conn= */ this));
mService = null;
mRequestQueue.clear();
}
private void connect() {
mHandler.post(this::connectInternal);
}
private void connectInternal() {
if (mServiceInfo == null) {
Log.w(TAG, "RemoteLockscreenValidation service unavailable");
return;
}
if (mIsConnected) {
return;
}
mIsConnected = true;
Intent intent = new Intent(SERVICE_INTERFACE);
intent.setComponent(mServiceInfo.getComponentName());
int flags = Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY;
mLifecycleExecutor.execute(() -> mContext.bindService(intent, this, flags));
}
private void onConnectedInternal(IRemoteLockscreenValidationService service) {
if (!mIsConnected) {
Log.w(TAG, "onConnectInternal but connection closed");
mService = null;
return;
}
mService = service;
for (Call call : new ArrayList<>(mRequestQueue)) {
performApiCallInternal(call, mService);
mRequestQueue.remove(call);
}
}
private boolean isServiceAvailable(
@NonNull Context context,
@NonNull ComponentName serviceComponent) {
mServiceInfo = getServiceInfo(context, serviceComponent);
if (mServiceInfo == null) {
return false;
}
if (!Manifest.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE.equals(
mServiceInfo.permission)) {
Log.w(TAG, TextUtils.formatSimple("%s/%s does not require permission %s",
mServiceInfo.packageName, mServiceInfo.name,
Manifest.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE));
return false;
}
return true;
}
private ServiceInfo getServiceInfo(
@NonNull Context context, @NonNull ComponentName serviceComponent) {
try {
return context.getPackageManager().getServiceInfo(serviceComponent,
PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, TextUtils.formatSimple("Cannot resolve service %s",
serviceComponent.getClass().getName()));
return null;
}
}
private void executeApiCall(Call call) {
mHandler.post(() -> executeInternal(call));
}
private void executeInternal(RemoteLockscreenValidationClientImpl.Call call) {
if (mIsConnected && mService != null) {
performApiCallInternal(call, mService);
} else {
mRequestQueue.add(call);
connect();
}
}
private void performApiCallInternal(
RemoteLockscreenValidationClientImpl.Call apiCaller,
IRemoteLockscreenValidationService service) {
if (service == null) {
apiCaller.onError("Service is null");
return;
}
try {
apiCaller.exec(service);
} catch (RemoteException e) {
Log.w(TAG, "executeInternal error", e);
apiCaller.onError(e.getMessage());
disconnect();
}
}
@Override // ServiceConnection
public void onServiceConnected(ComponentName name, IBinder binder) {
IRemoteLockscreenValidationService service =
IRemoteLockscreenValidationService.Stub.asInterface(binder);
mHandler.post(() -> onConnectedInternal(service));
}
@Override // ServiceConnection
public void onServiceDisconnected(ComponentName name) {
// Do not disconnect, as we may later be re-connected
}
@Override // ServiceConnection
public void onBindingDied(ComponentName name) {
// This is a recoverable error but the client will need to reconnect.
disconnect();
}
@Override // ServiceConnection
public void onNullBinding(ComponentName name) {
disconnect();
}
private abstract static class Call {
abstract void exec(IRemoteLockscreenValidationService service)
throws RemoteException;
abstract void onError(String msg);
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2023 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 android.service.remotelockscreenvalidation;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.RemoteLockscreenValidationResult;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.util.Log;
/**
* Provides an interface to validate a remote device's lockscreen
* @hide
*/
@SystemApi
public abstract class RemoteLockscreenValidationService extends Service {
/**
* The {@link Intent} that must be declared as handled by the service. To be supported, the
* service must also require the
* {@link android.Manifest.permission#BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE}
* permission so that other applications can not abuse it.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
"android.service.remotelockscreenvalidation.RemoteLockscreenValidationService";
private static final String TAG = RemoteLockscreenValidationService.class.getSimpleName();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IRemoteLockscreenValidationService mInterface =
new IRemoteLockscreenValidationService.Stub() {
@Override
public void validateLockscreenGuess(
byte[] guess, IRemoteLockscreenValidationCallback callback) {
mHandler.sendMessage(obtainMessage(
RemoteLockscreenValidationService::onValidateLockscreenGuess,
RemoteLockscreenValidationService.this, guess,
new OutcomeReceiver<RemoteLockscreenValidationResult,
Exception>() {
@Override
public void onResult(RemoteLockscreenValidationResult result) {
try {
callback.onSuccess(result);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
@Override
public void onError(Exception e) {
try {
callback.onFailure(e.getMessage());
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
}
}
));
}
};
@Override
@Nullable
public final IBinder onBind(@NonNull Intent intent) {
if (!SERVICE_INTERFACE.equals(intent.getAction())) {
Log.w(TAG, "Wrong action");
return null;
}
return mInterface.asBinder();
}
/**
* Validates the lockscreen guess.
*
* <p>Implementation should send guess to remote device and perform lockscreen validation
* using {@link android.app.KeyguardManager#validateRemoteLockScreen}.
*
* @param guess lockscreen guess
* @param callback object used to relay the response of the guess validation
*/
public abstract void onValidateLockscreenGuess(@NonNull byte[] guess,
@NonNull OutcomeReceiver<RemoteLockscreenValidationResult, Exception> callback);
}

View File

@@ -5000,6 +5000,15 @@
<permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
android:protectionLevel="signature" />
<!-- Must be required by a
{@link android.service.remotelockscreenvalidation.RemoteLockscreenValidationService}
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API
<p>Protection level: signature
-->
<permission android:name="android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE"
android:protectionLevel="signature" />
<!-- Must be required by a android.service.selectiontoolbar.SelectionToolbarRenderService,
to ensure that only the system can bind to it.
@hide This is not a third-party API (intended for OEMs and system apps).