1 /* 2 * Copyright (C) 2021 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 com.android.server.locksettings; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ResolveInfo; 28 import android.content.pm.ServiceInfo; 29 import android.os.Bundle; 30 import android.os.IBinder; 31 import android.os.ParcelableException; 32 import android.os.RemoteCallback; 33 import android.os.RemoteException; 34 import android.os.SystemProperties; 35 import android.os.UserHandle; 36 import android.provider.DeviceConfig; 37 import android.service.resumeonreboot.IResumeOnRebootService; 38 import android.service.resumeonreboot.ResumeOnRebootService; 39 import android.util.Slog; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.os.BackgroundThread; 43 44 import java.io.IOException; 45 import java.util.List; 46 import java.util.concurrent.CountDownLatch; 47 import java.util.concurrent.TimeUnit; 48 import java.util.concurrent.TimeoutException; 49 50 /** @hide */ 51 public class ResumeOnRebootServiceProvider { 52 53 private static final String PROVIDER_PACKAGE = DeviceConfig.getString( 54 DeviceConfig.NAMESPACE_OTA, "resume_on_reboot_service_package", ""); 55 private static final String PROVIDER_REQUIRED_PERMISSION = 56 Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; 57 private static final String TAG = "ResumeOnRebootServiceProvider"; 58 59 // The system property name that overrides the default service provider package name. 60 static final String PROP_ROR_PROVIDER_PACKAGE = 61 "persist.sys.resume_on_reboot_provider_package"; 62 63 private final Context mContext; 64 private final PackageManager mPackageManager; 65 ResumeOnRebootServiceProvider(Context context)66 public ResumeOnRebootServiceProvider(Context context) { 67 this(context, context.getPackageManager()); 68 } 69 70 @VisibleForTesting ResumeOnRebootServiceProvider(Context context, PackageManager packageManager)71 public ResumeOnRebootServiceProvider(Context context, PackageManager packageManager) { 72 this.mContext = context; 73 this.mPackageManager = packageManager; 74 } 75 76 @Nullable resolveService()77 private ServiceInfo resolveService() { 78 Intent intent = new Intent(); 79 intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); 80 int queryFlag = PackageManager.GET_SERVICES; 81 String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, ""); 82 if (!testAppName.isEmpty()) { 83 Slog.i(TAG, "Using test app: " + testAppName); 84 intent.setPackage(testAppName); 85 } else { 86 queryFlag |= PackageManager.MATCH_SYSTEM_ONLY; 87 if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { 88 intent.setPackage(PROVIDER_PACKAGE); 89 } 90 } 91 92 List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag); 93 for (ResolveInfo resolvedInfo : resolvedIntents) { 94 if (resolvedInfo.serviceInfo != null 95 && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { 96 return resolvedInfo.serviceInfo; 97 } 98 } 99 return null; 100 } 101 102 /** Creates a new {@link ResumeOnRebootServiceConnection} */ 103 @Nullable getServiceConnection()104 public ResumeOnRebootServiceConnection getServiceConnection() { 105 ServiceInfo serviceInfo = resolveService(); 106 if (serviceInfo == null) { 107 return null; 108 } 109 return new ResumeOnRebootServiceConnection(mContext, serviceInfo.getComponentName()); 110 } 111 112 /** 113 * Connection class used for contacting the registered {@link IResumeOnRebootService} 114 */ 115 public static class ResumeOnRebootServiceConnection { 116 117 private static final String TAG = "ResumeOnRebootServiceConnection"; 118 private final Context mContext; 119 private final ComponentName mComponentName; 120 private IResumeOnRebootService mBinder; 121 @Nullable 122 ServiceConnection mServiceConnection; 123 ResumeOnRebootServiceConnection(Context context, @NonNull ComponentName componentName)124 private ResumeOnRebootServiceConnection(Context context, 125 @NonNull ComponentName componentName) { 126 mContext = context; 127 mComponentName = componentName; 128 } 129 130 /** Unbind from the service */ unbindService()131 public void unbindService() { 132 if (mServiceConnection != null) { 133 mContext.unbindService(mServiceConnection); 134 } 135 mBinder = null; 136 } 137 138 /** Bind to the service */ bindToService(long timeOut)139 public void bindToService(long timeOut) throws RemoteException, TimeoutException { 140 if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { 141 CountDownLatch connectionLatch = new CountDownLatch(1); 142 Intent intent = new Intent(); 143 intent.setComponent(mComponentName); 144 mServiceConnection = new ServiceConnection() { 145 @Override 146 public void onServiceConnected(ComponentName name, IBinder service) { 147 mBinder = IResumeOnRebootService.Stub.asInterface(service); 148 connectionLatch.countDown(); 149 } 150 151 @Override 152 public void onServiceDisconnected(ComponentName name) { 153 mBinder = null; 154 } 155 }; 156 final boolean success = mContext.bindServiceAsUser(intent, mServiceConnection, 157 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, 158 BackgroundThread.getHandler(), UserHandle.SYSTEM); 159 160 if (!success) { 161 Slog.e(TAG, "Binding: " + mComponentName + " u" + UserHandle.SYSTEM 162 + " failed."); 163 return; 164 } 165 waitForLatch(connectionLatch, "serviceConnection", timeOut); 166 } 167 } 168 169 /** Wrap opaque blob */ wrapBlob(byte[] unwrappedBlob, long lifeTimeInMillis, long timeOutInMillis)170 public byte[] wrapBlob(byte[] unwrappedBlob, long lifeTimeInMillis, 171 long timeOutInMillis) 172 throws RemoteException, TimeoutException, IOException { 173 if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { 174 throw new RemoteException("Service not bound"); 175 } 176 CountDownLatch binderLatch = new CountDownLatch(1); 177 ResumeOnRebootServiceCallback 178 resultCallback = 179 new ResumeOnRebootServiceCallback( 180 binderLatch); 181 mBinder.wrapSecret(unwrappedBlob, lifeTimeInMillis, new RemoteCallback(resultCallback)); 182 waitForLatch(binderLatch, "wrapSecret", timeOutInMillis); 183 if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { 184 throwTypedException(resultCallback.getResult().getParcelable( 185 ResumeOnRebootService.EXCEPTION_KEY, android.os.ParcelableException.class)); 186 } 187 return resultCallback.mResult.getByteArray(ResumeOnRebootService.WRAPPED_BLOB_KEY); 188 } 189 190 /** Unwrap wrapped blob */ unwrap(byte[] wrappedBlob, long timeOut)191 public byte[] unwrap(byte[] wrappedBlob, long timeOut) 192 throws RemoteException, TimeoutException, IOException { 193 if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { 194 throw new RemoteException("Service not bound"); 195 } 196 CountDownLatch binderLatch = new CountDownLatch(1); 197 ResumeOnRebootServiceCallback 198 resultCallback = 199 new ResumeOnRebootServiceCallback( 200 binderLatch); 201 mBinder.unwrap(wrappedBlob, new RemoteCallback(resultCallback)); 202 waitForLatch(binderLatch, "unWrapSecret", timeOut); 203 if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { 204 throwTypedException(resultCallback.getResult().getParcelable( 205 ResumeOnRebootService.EXCEPTION_KEY, android.os.ParcelableException.class)); 206 } 207 return resultCallback.getResult().getByteArray( 208 ResumeOnRebootService.UNWRAPPED_BLOB_KEY); 209 } 210 throwTypedException( ParcelableException exception)211 private void throwTypedException( 212 ParcelableException exception) 213 throws IOException, RemoteException { 214 if (exception != null && exception.getCause() instanceof IOException) { 215 exception.maybeRethrow(IOException.class); 216 } else { 217 // Wrap the exception and throw it as a RemoteException. 218 throw new RemoteException(TAG + " wrap/unwrap failed", exception, 219 true /* enableSuppression */, true /* writableStackTrace */); 220 } 221 } 222 waitForLatch(CountDownLatch latch, String reason, long timeOut)223 private void waitForLatch(CountDownLatch latch, String reason, long timeOut) 224 throws RemoteException, TimeoutException { 225 try { 226 if (!latch.await(timeOut, TimeUnit.SECONDS)) { 227 throw new TimeoutException("Latch wait for " + reason + " elapsed"); 228 } 229 } catch (InterruptedException e) { 230 Thread.currentThread().interrupt(); 231 throw new RemoteException("Latch wait for " + reason + " interrupted"); 232 } 233 } 234 } 235 236 private static class ResumeOnRebootServiceCallback implements 237 RemoteCallback.OnResultListener { 238 239 private final CountDownLatch mResultLatch; 240 private Bundle mResult; 241 ResumeOnRebootServiceCallback(CountDownLatch resultLatch)242 private ResumeOnRebootServiceCallback(CountDownLatch resultLatch) { 243 this.mResultLatch = resultLatch; 244 } 245 246 @Override onResult(@ullable Bundle result)247 public void onResult(@Nullable Bundle result) { 248 this.mResult = result; 249 mResultLatch.countDown(); 250 } 251 getResult()252 private Bundle getResult() { 253 return mResult; 254 } 255 } 256 } 257