Loading core/api/system-current.txt +13 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ package android { 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_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_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; field public static final String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE"; Loading Loading @@ -9063,6 +9064,18 @@ package android.service.resolver { } package android.service.resumeonreboot { public abstract class ResumeOnRebootService extends android.app.Service { ctor public ResumeOnRebootService(); method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); method @NonNull public abstract byte[] onUnwrap(@NonNull byte[]) throws java.io.IOException; method @NonNull public abstract byte[] onWrap(@NonNull byte[], long) throws java.io.IOException; field public static final String SERVICE_INTERFACE = "android.service.resumeonreboot.ResumeOnRebootService"; } } package android.service.settings.suggestions { public final class Suggestion implements android.os.Parcelable { core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl 0 → 100644 +25 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.resumeonreboot; import android.os.RemoteCallback; /** @hide */ interface IResumeOnRebootService { oneway void wrapSecret(in byte[] unwrappedBlob, in long lifeTimeInMillis, in RemoteCallback resultCallback); oneway void unwrap(in byte[] wrappedBlob, in RemoteCallback resultCallback); } No newline at end of file core/java/android/service/resumeonreboot/ResumeOnRebootService.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.resumeonreboot; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; import com.android.internal.os.BackgroundThread; import java.io.IOException; /** * Base class for service that provides wrapping/unwrapping of the opaque blob needed for * ResumeOnReboot operation. The package needs to provide a wrap/unwrap implementation for handling * the opaque blob, that's secure even when on device keystore and clock is compromised. This can * be achieved by using tamper-resistant hardware such as a secure element with a secure clock, or * using a remote server to store and retrieve data and manage timing. * * <p>To extend this class, you must declare the service in your manifest file with the * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE} permission, * include an intent filter with the {@link #SERVICE_INTERFACE} action and mark the service as * direct-boot aware. In addition, the package that contains the service must be granted * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE}. * For example:</p> * <pre> * <service android:name=".FooResumeOnRebootService" * android:exported="true" * android:priority="100" * android:directBootAware="true" * android:permission="android.permission.BIND_RESUME_ON_REBOOT_SERVICE"> * <intent-filter> * <action android:name="android.service.resumeonreboot.ResumeOnRebootService" /> * </intent-filter> * </service> * </pre> * * //TODO: Replace this with public link when available. * * @hide * @see * <a href="https://goto.google.com/server-based-ror">https://goto.google.com/server-based-ror</a> */ @SystemApi public abstract class ResumeOnRebootService extends Service { /** * The intent that the service must respond to. Add it to the intent filter of the service. */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.resumeonreboot.ResumeOnRebootService"; /** @hide */ public static final String UNWRAPPED_BLOB_KEY = "unrwapped_blob_key"; /** @hide */ public static final String WRAPPED_BLOB_KEY = "wrapped_blob_key"; /** @hide */ public static final String EXCEPTION_KEY = "exception_key"; private final Handler mHandler = BackgroundThread.getHandler(); /** * Implementation for wrapping the opaque blob used for resume-on-reboot prior to * reboot. The service should not assume any structure of the blob to be wrapped. The * implementation should wrap the opaque blob in a reasonable time or throw {@link IOException} * if it's unable to complete the action. * * @param blob The opaque blob with size on the order of 100 bytes. * @param lifeTimeInMillis The life time of the blob. This must be strictly enforced by the * implementation and any attempt to unWrap the wrapped blob returned by * this function after expiration should * fail. * @return Wrapped blob to be persisted across reboot with size on the order of 100 bytes. * @throws IOException if the implementation is unable to wrap the blob successfully. */ @NonNull public abstract byte[] onWrap(@NonNull byte[] blob, @DurationMillisLong long lifeTimeInMillis) throws IOException; /** * Implementation for unwrapping the wrapped blob used for resume-on-reboot after reboot. This * operation would happen after reboot during direct boot mode (i.e before device is unlocked * for the first time). The implementation should unwrap the wrapped blob in a reasonable time * and returns the result or throw {@link IOException} if it's unable to complete the action * and {@link IllegalArgumentException} if {@code unwrapBlob} fails because the wrappedBlob is * stale. * * @param wrappedBlob The wrapped blob with size on the order of 100 bytes. * @return Unwrapped blob used for resume-on-reboot with the size on the order of 100 bytes. * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully. */ @NonNull public abstract byte[] onUnwrap(@NonNull byte[] wrappedBlob) throws IOException; private final android.service.resumeonreboot.IResumeOnRebootService mInterface = new android.service.resumeonreboot.IResumeOnRebootService.Stub() { @Override public void wrapSecret(byte[] unwrappedBlob, @DurationMillisLong long lifeTimeInMillis, RemoteCallback resultCallback) throws RemoteException { mHandler.post(() -> { try { byte[] wrappedBlob = onWrap(unwrappedBlob, lifeTimeInMillis); Bundle bundle = new Bundle(); bundle.putByteArray(WRAPPED_BLOB_KEY, wrappedBlob); resultCallback.sendResult(bundle); } catch (Throwable e) { Bundle bundle = new Bundle(); bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); resultCallback.sendResult(bundle); } }); } @Override public void unwrap(byte[] wrappedBlob, RemoteCallback resultCallback) throws RemoteException { mHandler.post(() -> { try { byte[] unwrappedBlob = onUnwrap(wrappedBlob); Bundle bundle = new Bundle(); bundle.putByteArray(UNWRAPPED_BLOB_KEY, unwrappedBlob); resultCallback.sendResult(bundle); } catch (Throwable e) { Bundle bundle = new Bundle(); bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); resultCallback.sendResult(bundle); } }); } }; @Nullable @Override public IBinder onBind(@Nullable Intent intent) { return mInterface.asBinder(); } } core/res/AndroidManifest.xml +6 −0 Original line number Diff line number Diff line Loading @@ -2984,6 +2984,12 @@ <permission android:name="android.permission.RECOVERY" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows an application to do certain operations needed for resume on reboot feature. @hide --> <permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" android:protectionLevel="signature" /> <!-- @SystemApi Allows an application to read system update info. @hide --> <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO" Loading services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java 0 → 100644 +249 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.server.locksettings; 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.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.resumeonreboot.IResumeOnRebootService; import android.service.resumeonreboot.ResumeOnRebootService; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import java.io.IOException; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** @hide */ public class ResumeOnRebootServiceProvider { private static final String PROVIDER_PACKAGE = DeviceConfig.getString( DeviceConfig.NAMESPACE_OTA, "resume_on_reboot_service_package", ""); private static final String PROVIDER_REQUIRED_PERMISSION = Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; private static final String TAG = "ResumeOnRebootServiceProvider"; private final Context mContext; private final PackageManager mPackageManager; public ResumeOnRebootServiceProvider(Context context) { this(context, context.getPackageManager()); } @VisibleForTesting public ResumeOnRebootServiceProvider(Context context, PackageManager packageManager) { this.mContext = context; this.mPackageManager = packageManager; } @Nullable private ServiceInfo resolveService() { Intent intent = new Intent(); intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { intent.setPackage(PROVIDER_PACKAGE); } List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY); for (ResolveInfo resolvedInfo : resolvedIntents) { if (resolvedInfo.serviceInfo != null && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { return resolvedInfo.serviceInfo; } } return null; } /** Creates a new {@link ResumeOnRebootServiceConnection} */ @Nullable public ResumeOnRebootServiceConnection getServiceConnection() { ServiceInfo serviceInfo = resolveService(); if (serviceInfo == null) { return null; } return new ResumeOnRebootServiceConnection(mContext, serviceInfo.getComponentName()); } /** * Connection class used for contacting the registered {@link IResumeOnRebootService} */ public static class ResumeOnRebootServiceConnection { private static final String TAG = "ResumeOnRebootServiceConnection"; private final Context mContext; private final ComponentName mComponentName; private IResumeOnRebootService mBinder; private ResumeOnRebootServiceConnection(Context context, @NonNull ComponentName componentName) { mContext = context; mComponentName = componentName; } /** Unbind from the service */ public void unbindService() { mContext.unbindService(new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { mBinder = null; } }); } /** Bind to the service */ public void bindToService(long timeOut) throws TimeoutException { if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { CountDownLatch connectionLatch = new CountDownLatch(1); Intent intent = new Intent(); intent.setComponent(mComponentName); final boolean success = mContext.bindServiceAsUser(intent, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinder = IResumeOnRebootService.Stub.asInterface(service); connectionLatch.countDown(); } @Override public void onServiceDisconnected(ComponentName name) { } }, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, BackgroundThread.getHandler(), UserHandle.SYSTEM); if (!success) { Slog.e(TAG, "Binding: " + mComponentName + " u" + UserHandle.SYSTEM + " failed."); return; } waitForLatch(connectionLatch, "serviceConnection", timeOut); } } /** Wrap opaque blob */ public byte[] wrapBlob(byte[] unwrappedBlob, long lifeTimeInMillis, long timeOutInMillis) throws RemoteException, TimeoutException, IOException { if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { throw new RemoteException("Service not bound"); } CountDownLatch binderLatch = new CountDownLatch(1); ResumeOnRebootServiceCallback resultCallback = new ResumeOnRebootServiceCallback( binderLatch); mBinder.wrapSecret(unwrappedBlob, lifeTimeInMillis, new RemoteCallback(resultCallback)); waitForLatch(binderLatch, "wrapSecret", timeOutInMillis); if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { throwTypedException(resultCallback.getResult().getParcelable( ResumeOnRebootService.EXCEPTION_KEY)); } return resultCallback.mResult.getByteArray(ResumeOnRebootService.WRAPPED_BLOB_KEY); } /** Unwrap wrapped blob */ public byte[] unwrap(byte[] wrappedBlob, long timeOut) throws RemoteException, TimeoutException, IOException { if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { throw new RemoteException("Service not bound"); } CountDownLatch binderLatch = new CountDownLatch(1); ResumeOnRebootServiceCallback resultCallback = new ResumeOnRebootServiceCallback( binderLatch); mBinder.unwrap(wrappedBlob, new RemoteCallback(resultCallback)); waitForLatch(binderLatch, "unWrapSecret", timeOut); if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { throwTypedException(resultCallback.getResult().getParcelable( ResumeOnRebootService.EXCEPTION_KEY)); } return resultCallback.getResult().getByteArray( ResumeOnRebootService.UNWRAPPED_BLOB_KEY); } private void throwTypedException( ParcelableException exception) throws IOException { if (exception.getCause() instanceof IOException) { exception.maybeRethrow(IOException.class); } else if (exception.getCause() instanceof IllegalStateException) { exception.maybeRethrow(IllegalStateException.class); } else { // This should not happen. Wrap the cause in IllegalStateException so that it // doesn't disrupt the exception handling throw new IllegalStateException(exception.getCause()); } } private void waitForLatch(CountDownLatch latch, String reason, long timeOut) throws TimeoutException { try { if (!latch.await(timeOut, TimeUnit.SECONDS)) { throw new TimeoutException("Latch wait for " + reason + " elapsed"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Latch wait for " + reason + " interrupted"); } } } private static class ResumeOnRebootServiceCallback implements RemoteCallback.OnResultListener { private final CountDownLatch mResultLatch; private Bundle mResult; private ResumeOnRebootServiceCallback(CountDownLatch resultLatch) { this.mResultLatch = resultLatch; } @Override public void onResult(@Nullable Bundle result) { this.mResult = result; mResultLatch.countDown(); } private Bundle getResult() { return mResult; } } } Loading
core/api/system-current.txt +13 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ package android { 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_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_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; field public static final String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE"; Loading Loading @@ -9063,6 +9064,18 @@ package android.service.resolver { } package android.service.resumeonreboot { public abstract class ResumeOnRebootService extends android.app.Service { ctor public ResumeOnRebootService(); method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); method @NonNull public abstract byte[] onUnwrap(@NonNull byte[]) throws java.io.IOException; method @NonNull public abstract byte[] onWrap(@NonNull byte[], long) throws java.io.IOException; field public static final String SERVICE_INTERFACE = "android.service.resumeonreboot.ResumeOnRebootService"; } } package android.service.settings.suggestions { public final class Suggestion implements android.os.Parcelable {
core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl 0 → 100644 +25 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.resumeonreboot; import android.os.RemoteCallback; /** @hide */ interface IResumeOnRebootService { oneway void wrapSecret(in byte[] unwrappedBlob, in long lifeTimeInMillis, in RemoteCallback resultCallback); oneway void unwrap(in byte[] wrappedBlob, in RemoteCallback resultCallback); } No newline at end of file
core/java/android/service/resumeonreboot/ResumeOnRebootService.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.resumeonreboot; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; import com.android.internal.os.BackgroundThread; import java.io.IOException; /** * Base class for service that provides wrapping/unwrapping of the opaque blob needed for * ResumeOnReboot operation. The package needs to provide a wrap/unwrap implementation for handling * the opaque blob, that's secure even when on device keystore and clock is compromised. This can * be achieved by using tamper-resistant hardware such as a secure element with a secure clock, or * using a remote server to store and retrieve data and manage timing. * * <p>To extend this class, you must declare the service in your manifest file with the * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE} permission, * include an intent filter with the {@link #SERVICE_INTERFACE} action and mark the service as * direct-boot aware. In addition, the package that contains the service must be granted * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE}. * For example:</p> * <pre> * <service android:name=".FooResumeOnRebootService" * android:exported="true" * android:priority="100" * android:directBootAware="true" * android:permission="android.permission.BIND_RESUME_ON_REBOOT_SERVICE"> * <intent-filter> * <action android:name="android.service.resumeonreboot.ResumeOnRebootService" /> * </intent-filter> * </service> * </pre> * * //TODO: Replace this with public link when available. * * @hide * @see * <a href="https://goto.google.com/server-based-ror">https://goto.google.com/server-based-ror</a> */ @SystemApi public abstract class ResumeOnRebootService extends Service { /** * The intent that the service must respond to. Add it to the intent filter of the service. */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.resumeonreboot.ResumeOnRebootService"; /** @hide */ public static final String UNWRAPPED_BLOB_KEY = "unrwapped_blob_key"; /** @hide */ public static final String WRAPPED_BLOB_KEY = "wrapped_blob_key"; /** @hide */ public static final String EXCEPTION_KEY = "exception_key"; private final Handler mHandler = BackgroundThread.getHandler(); /** * Implementation for wrapping the opaque blob used for resume-on-reboot prior to * reboot. The service should not assume any structure of the blob to be wrapped. The * implementation should wrap the opaque blob in a reasonable time or throw {@link IOException} * if it's unable to complete the action. * * @param blob The opaque blob with size on the order of 100 bytes. * @param lifeTimeInMillis The life time of the blob. This must be strictly enforced by the * implementation and any attempt to unWrap the wrapped blob returned by * this function after expiration should * fail. * @return Wrapped blob to be persisted across reboot with size on the order of 100 bytes. * @throws IOException if the implementation is unable to wrap the blob successfully. */ @NonNull public abstract byte[] onWrap(@NonNull byte[] blob, @DurationMillisLong long lifeTimeInMillis) throws IOException; /** * Implementation for unwrapping the wrapped blob used for resume-on-reboot after reboot. This * operation would happen after reboot during direct boot mode (i.e before device is unlocked * for the first time). The implementation should unwrap the wrapped blob in a reasonable time * and returns the result or throw {@link IOException} if it's unable to complete the action * and {@link IllegalArgumentException} if {@code unwrapBlob} fails because the wrappedBlob is * stale. * * @param wrappedBlob The wrapped blob with size on the order of 100 bytes. * @return Unwrapped blob used for resume-on-reboot with the size on the order of 100 bytes. * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully. */ @NonNull public abstract byte[] onUnwrap(@NonNull byte[] wrappedBlob) throws IOException; private final android.service.resumeonreboot.IResumeOnRebootService mInterface = new android.service.resumeonreboot.IResumeOnRebootService.Stub() { @Override public void wrapSecret(byte[] unwrappedBlob, @DurationMillisLong long lifeTimeInMillis, RemoteCallback resultCallback) throws RemoteException { mHandler.post(() -> { try { byte[] wrappedBlob = onWrap(unwrappedBlob, lifeTimeInMillis); Bundle bundle = new Bundle(); bundle.putByteArray(WRAPPED_BLOB_KEY, wrappedBlob); resultCallback.sendResult(bundle); } catch (Throwable e) { Bundle bundle = new Bundle(); bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); resultCallback.sendResult(bundle); } }); } @Override public void unwrap(byte[] wrappedBlob, RemoteCallback resultCallback) throws RemoteException { mHandler.post(() -> { try { byte[] unwrappedBlob = onUnwrap(wrappedBlob); Bundle bundle = new Bundle(); bundle.putByteArray(UNWRAPPED_BLOB_KEY, unwrappedBlob); resultCallback.sendResult(bundle); } catch (Throwable e) { Bundle bundle = new Bundle(); bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); resultCallback.sendResult(bundle); } }); } }; @Nullable @Override public IBinder onBind(@Nullable Intent intent) { return mInterface.asBinder(); } }
core/res/AndroidManifest.xml +6 −0 Original line number Diff line number Diff line Loading @@ -2984,6 +2984,12 @@ <permission android:name="android.permission.RECOVERY" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows an application to do certain operations needed for resume on reboot feature. @hide --> <permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" android:protectionLevel="signature" /> <!-- @SystemApi Allows an application to read system update info. @hide --> <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO" Loading
services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java 0 → 100644 +249 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.server.locksettings; 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.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.resumeonreboot.IResumeOnRebootService; import android.service.resumeonreboot.ResumeOnRebootService; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import java.io.IOException; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** @hide */ public class ResumeOnRebootServiceProvider { private static final String PROVIDER_PACKAGE = DeviceConfig.getString( DeviceConfig.NAMESPACE_OTA, "resume_on_reboot_service_package", ""); private static final String PROVIDER_REQUIRED_PERMISSION = Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; private static final String TAG = "ResumeOnRebootServiceProvider"; private final Context mContext; private final PackageManager mPackageManager; public ResumeOnRebootServiceProvider(Context context) { this(context, context.getPackageManager()); } @VisibleForTesting public ResumeOnRebootServiceProvider(Context context, PackageManager packageManager) { this.mContext = context; this.mPackageManager = packageManager; } @Nullable private ServiceInfo resolveService() { Intent intent = new Intent(); intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { intent.setPackage(PROVIDER_PACKAGE); } List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY); for (ResolveInfo resolvedInfo : resolvedIntents) { if (resolvedInfo.serviceInfo != null && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { return resolvedInfo.serviceInfo; } } return null; } /** Creates a new {@link ResumeOnRebootServiceConnection} */ @Nullable public ResumeOnRebootServiceConnection getServiceConnection() { ServiceInfo serviceInfo = resolveService(); if (serviceInfo == null) { return null; } return new ResumeOnRebootServiceConnection(mContext, serviceInfo.getComponentName()); } /** * Connection class used for contacting the registered {@link IResumeOnRebootService} */ public static class ResumeOnRebootServiceConnection { private static final String TAG = "ResumeOnRebootServiceConnection"; private final Context mContext; private final ComponentName mComponentName; private IResumeOnRebootService mBinder; private ResumeOnRebootServiceConnection(Context context, @NonNull ComponentName componentName) { mContext = context; mComponentName = componentName; } /** Unbind from the service */ public void unbindService() { mContext.unbindService(new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { } @Override public void onServiceDisconnected(ComponentName name) { mBinder = null; } }); } /** Bind to the service */ public void bindToService(long timeOut) throws TimeoutException { if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { CountDownLatch connectionLatch = new CountDownLatch(1); Intent intent = new Intent(); intent.setComponent(mComponentName); final boolean success = mContext.bindServiceAsUser(intent, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinder = IResumeOnRebootService.Stub.asInterface(service); connectionLatch.countDown(); } @Override public void onServiceDisconnected(ComponentName name) { } }, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, BackgroundThread.getHandler(), UserHandle.SYSTEM); if (!success) { Slog.e(TAG, "Binding: " + mComponentName + " u" + UserHandle.SYSTEM + " failed."); return; } waitForLatch(connectionLatch, "serviceConnection", timeOut); } } /** Wrap opaque blob */ public byte[] wrapBlob(byte[] unwrappedBlob, long lifeTimeInMillis, long timeOutInMillis) throws RemoteException, TimeoutException, IOException { if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { throw new RemoteException("Service not bound"); } CountDownLatch binderLatch = new CountDownLatch(1); ResumeOnRebootServiceCallback resultCallback = new ResumeOnRebootServiceCallback( binderLatch); mBinder.wrapSecret(unwrappedBlob, lifeTimeInMillis, new RemoteCallback(resultCallback)); waitForLatch(binderLatch, "wrapSecret", timeOutInMillis); if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { throwTypedException(resultCallback.getResult().getParcelable( ResumeOnRebootService.EXCEPTION_KEY)); } return resultCallback.mResult.getByteArray(ResumeOnRebootService.WRAPPED_BLOB_KEY); } /** Unwrap wrapped blob */ public byte[] unwrap(byte[] wrappedBlob, long timeOut) throws RemoteException, TimeoutException, IOException { if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { throw new RemoteException("Service not bound"); } CountDownLatch binderLatch = new CountDownLatch(1); ResumeOnRebootServiceCallback resultCallback = new ResumeOnRebootServiceCallback( binderLatch); mBinder.unwrap(wrappedBlob, new RemoteCallback(resultCallback)); waitForLatch(binderLatch, "unWrapSecret", timeOut); if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { throwTypedException(resultCallback.getResult().getParcelable( ResumeOnRebootService.EXCEPTION_KEY)); } return resultCallback.getResult().getByteArray( ResumeOnRebootService.UNWRAPPED_BLOB_KEY); } private void throwTypedException( ParcelableException exception) throws IOException { if (exception.getCause() instanceof IOException) { exception.maybeRethrow(IOException.class); } else if (exception.getCause() instanceof IllegalStateException) { exception.maybeRethrow(IllegalStateException.class); } else { // This should not happen. Wrap the cause in IllegalStateException so that it // doesn't disrupt the exception handling throw new IllegalStateException(exception.getCause()); } } private void waitForLatch(CountDownLatch latch, String reason, long timeOut) throws TimeoutException { try { if (!latch.await(timeOut, TimeUnit.SECONDS)) { throw new TimeoutException("Latch wait for " + reason + " elapsed"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Latch wait for " + reason + " interrupted"); } } } private static class ResumeOnRebootServiceCallback implements RemoteCallback.OnResultListener { private final CountDownLatch mResultLatch; private Bundle mResult; private ResumeOnRebootServiceCallback(CountDownLatch resultLatch) { this.mResultLatch = resultLatch; } @Override public void onResult(@Nullable Bundle result) { this.mResult = result; mResultLatch.countDown(); } private Bundle getResult() { return mResult; } } }