Add RemoteLockscreenValidation service and client
Add "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE" permission Test: manual Bug: 258505917 Change-Id: Icd61394b25a02a1f646781b24bf04685741bf4fa
This commit is contained in:
@@ -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";
|
||||
@@ -12498,6 +12499,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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
include /services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
|
||||
brnlee@google.com
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -4594,6 +4594,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).
|
||||
|
||||
Reference in New Issue
Block a user