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