1 /*
2  * Copyright (C) 2022 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.companion.datatransfer;
18 
19 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
20 import static android.app.PendingIntent.FLAG_IMMUTABLE;
21 import static android.app.PendingIntent.FLAG_ONE_SHOT;
22 import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME;
23 import static android.content.ComponentName.createRelative;
24 
25 import static com.android.server.companion.Utils.prepareForIpc;
26 import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.UserIdInt;
31 import android.app.PendingIntent;
32 import android.companion.AssociationInfo;
33 import android.companion.DeviceNotAssociatedException;
34 import android.companion.IOnMessageReceivedListener;
35 import android.companion.ISystemDataTransferCallback;
36 import android.companion.datatransfer.PermissionSyncRequest;
37 import android.companion.datatransfer.SystemDataTransferRequest;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.pm.PackageManagerInternal;
42 import android.os.Binder;
43 import android.os.Bundle;
44 import android.os.Handler;
45 import android.os.IBinder;
46 import android.os.RemoteException;
47 import android.os.ResultReceiver;
48 import android.os.UserHandle;
49 import android.permission.PermissionControllerManager;
50 import android.util.Slog;
51 
52 import com.android.server.companion.AssociationStore;
53 import com.android.server.companion.CompanionDeviceManagerService;
54 import com.android.server.companion.PackageUtils;
55 import com.android.server.companion.PermissionsUtils;
56 import com.android.server.companion.transport.CompanionTransportManager;
57 
58 import java.util.List;
59 import java.util.concurrent.ExecutorService;
60 import java.util.concurrent.Executors;
61 import java.util.concurrent.Future;
62 import java.util.concurrent.TimeUnit;
63 
64 /**
65  * This processor builds user consent intent for a
66  * {@link SystemDataTransferRequest} and processes the request once a channel is
67  * established between the local and remote companion device.
68  */
69 public class SystemDataTransferProcessor {
70 
71     private static final String LOG_TAG = "CDM_SystemDataTransferProcessor";
72 
73     // Values from UI to SystemDataTransferProcessor via ResultReceiver
74     private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED = 0;
75     private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED = 1;
76     private static final String EXTRA_PERMISSION_SYNC_REQUEST = "permission_sync_request";
77     private static final String EXTRA_COMPANION_DEVICE_NAME = "companion_device_name";
78     private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER =
79             "system_data_transfer_result_receiver";
80     private static final ComponentName SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY =
81             createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
82                     ".CompanionDeviceDataTransferActivity");
83 
84     private final Context mContext;
85     private final PackageManagerInternal mPackageManager;
86     private final AssociationStore mAssociationStore;
87     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
88     private final CompanionTransportManager mTransportManager;
89     private final PermissionControllerManager mPermissionControllerManager;
90     private final ExecutorService mExecutor;
91 
SystemDataTransferProcessor(CompanionDeviceManagerService service, PackageManagerInternal packageManager, AssociationStore associationStore, SystemDataTransferRequestStore systemDataTransferRequestStore, CompanionTransportManager transportManager)92     public SystemDataTransferProcessor(CompanionDeviceManagerService service,
93             PackageManagerInternal packageManager,
94             AssociationStore associationStore,
95             SystemDataTransferRequestStore systemDataTransferRequestStore,
96             CompanionTransportManager transportManager) {
97         mContext = service.getContext();
98         mPackageManager = packageManager;
99         mAssociationStore = associationStore;
100         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
101         mTransportManager = transportManager;
102         IOnMessageReceivedListener messageListener = new IOnMessageReceivedListener() {
103             @Override
104             public void onMessageReceived(int associationId, byte[] data) throws RemoteException {
105                 onReceivePermissionRestore(data);
106             }
107 
108             @Override
109             public IBinder asBinder() {
110                 return null;
111             }
112         };
113         mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE, messageListener);
114         mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
115         mExecutor = Executors.newSingleThreadExecutor();
116     }
117 
118     /**
119      * Resolve the requested association, throwing if the caller doesn't have
120      * adequate permissions.
121      */
resolveAssociation(String packageName, int userId, int associationId)122     private @NonNull AssociationInfo resolveAssociation(String packageName, int userId,
123             int associationId) {
124         AssociationInfo association = mAssociationStore.getAssociationById(associationId);
125         association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
126         if (association == null) {
127             throw new DeviceNotAssociatedException("Association "
128                     + associationId + " is not associated with the app " + packageName
129                     + " for user " + userId);
130         }
131         return association;
132     }
133 
134     /**
135      * Build a PendingIntent of permission sync user consent dialog
136      */
buildPermissionTransferUserConsentIntent(String packageName, @UserIdInt int userId, int associationId)137     public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
138             @UserIdInt int userId, int associationId) {
139         if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
140             Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
141             // Auto enable perm sync for the allowlisted packages, but don't override user decision
142             PermissionSyncRequest request = getPermissionSyncRequest(associationId);
143             if (request == null) {
144                 PermissionSyncRequest newRequest = new PermissionSyncRequest(associationId);
145                 newRequest.setUserConsented(true);
146                 mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
147             }
148             return null;
149         }
150 
151         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
152 
153         Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
154                 + "] associationId [" + associationId + "]");
155 
156         // Create an internal intent to launch the user consent dialog
157         final Bundle extras = new Bundle();
158         PermissionSyncRequest request = new PermissionSyncRequest(associationId);
159         request.setUserId(userId);
160         extras.putParcelable(EXTRA_PERMISSION_SYNC_REQUEST, request);
161         extras.putCharSequence(EXTRA_COMPANION_DEVICE_NAME, association.getDisplayName());
162         extras.putParcelable(EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER,
163                 prepareForIpc(mOnSystemDataTransferRequestConfirmationReceiver));
164 
165         final Intent intent = new Intent();
166         intent.setComponent(SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY);
167         intent.putExtras(extras);
168 
169         // Create a PendingIntent
170         final long token = Binder.clearCallingIdentity();
171         try {
172             return PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId, intent,
173                     FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, /* options= */ null,
174                     UserHandle.CURRENT);
175         } finally {
176             Binder.restoreCallingIdentity(token);
177         }
178     }
179 
180     /**
181      * Start system data transfer. It should first try to establish a secure channel and then sync
182      * system data.
183      *
184      * TODO: execute callback when the transfer finishes successfully or with errors.
185      */
startSystemDataTransfer(String packageName, int userId, int associationId, ISystemDataTransferCallback callback)186     public void startSystemDataTransfer(String packageName, int userId, int associationId,
187             ISystemDataTransferCallback callback) {
188         Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName
189                 + "] userId [" + userId + "] associationId [" + associationId + "]");
190 
191         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
192 
193         // Check if the request has been consented by the user.
194         PermissionSyncRequest request = getPermissionSyncRequest(associationId);
195         if (request == null || !request.isUserConsented()) {
196             String message =
197                     "User " + userId + " hasn't consented permission sync for associationId ["
198                             + associationId + ".";
199             Slog.e(LOG_TAG, message);
200             try {
201                 callback.onError(message);
202             } catch (RemoteException ignored) {
203             }
204             return;
205         }
206 
207         // Start permission sync
208         final long callingIdentityToken = Binder.clearCallingIdentity();
209         try {
210             // TODO: refactor to work with streams of data
211             mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
212                     mExecutor, backup -> {
213                         Future<?> future = mTransportManager
214                                 .requestPermissionRestore(associationId, backup);
215                         translateFutureToCallback(future, callback);
216                     });
217         } finally {
218             Binder.restoreCallingIdentity(callingIdentityToken);
219         }
220     }
221 
222     /**
223      * Enable perm sync for the association
224      */
enablePermissionsSync(int associationId)225     public void enablePermissionsSync(int associationId) {
226         final long callingIdentityToken = Binder.clearCallingIdentity();
227         try {
228             int userId = mAssociationStore.getAssociationById(associationId).getUserId();
229             PermissionSyncRequest request = new PermissionSyncRequest(associationId);
230             request.setUserConsented(true);
231             mSystemDataTransferRequestStore.writeRequest(userId, request);
232         } finally {
233             Binder.restoreCallingIdentity(callingIdentityToken);
234         }
235     }
236 
237     /**
238      * Disable perm sync for the association
239      */
disablePermissionsSync(int associationId)240     public void disablePermissionsSync(int associationId) {
241         final long callingIdentityToken = Binder.clearCallingIdentity();
242         try {
243             int userId = mAssociationStore.getAssociationById(associationId).getUserId();
244             PermissionSyncRequest request = new PermissionSyncRequest(associationId);
245             request.setUserConsented(false);
246             mSystemDataTransferRequestStore.writeRequest(userId, request);
247         } finally {
248             Binder.restoreCallingIdentity(callingIdentityToken);
249         }
250     }
251 
252     /**
253      * Get perm sync request for the association.
254      */
255     @Nullable
getPermissionSyncRequest(int associationId)256     public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
257         final long callingIdentityToken = Binder.clearCallingIdentity();
258         try {
259             int userId = mAssociationStore.getAssociationById(associationId).getUserId();
260             List<SystemDataTransferRequest> requests =
261                     mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
262                             associationId);
263             for (SystemDataTransferRequest request : requests) {
264                 if (request instanceof PermissionSyncRequest) {
265                     return (PermissionSyncRequest) request;
266                 }
267             }
268             return null;
269         } finally {
270             Binder.restoreCallingIdentity(callingIdentityToken);
271         }
272     }
273 
274     /**
275      * Remove perm sync request for the association.
276      */
removePermissionSyncRequest(int associationId)277     public void removePermissionSyncRequest(int associationId) {
278         int userId = mAssociationStore.getAssociationById(associationId).getUserId();
279         mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
280     }
281 
onReceivePermissionRestore(byte[] message)282     private void onReceivePermissionRestore(byte[] message) {
283         Slog.i(LOG_TAG, "Applying permissions.");
284         // Start applying permissions
285         UserHandle user = mContext.getUser();
286         final long callingIdentityToken = Binder.clearCallingIdentity();
287         try {
288             // TODO: refactor to work with streams of data
289             mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
290                     message, user);
291         } finally {
292             Binder.restoreCallingIdentity(callingIdentityToken);
293         }
294     }
295 
translateFutureToCallback(@onNull Future<?> future, @Nullable ISystemDataTransferCallback callback)296     private static void translateFutureToCallback(@NonNull Future<?> future,
297             @Nullable ISystemDataTransferCallback callback) {
298         try {
299             future.get(15, TimeUnit.SECONDS);
300             try {
301                 if (callback != null) {
302                     callback.onResult();
303                 }
304             } catch (RemoteException ignored) {
305             }
306         } catch (Exception e) {
307             try {
308                 if (callback != null) {
309                     callback.onError(e.getMessage());
310                 }
311             } catch (RemoteException ignored) {
312             }
313         }
314     }
315 
316     private final ResultReceiver mOnSystemDataTransferRequestConfirmationReceiver =
317             new ResultReceiver(Handler.getMain()) {
318                 @Override
319                 protected void onReceiveResult(int resultCode, Bundle data) {
320                     Slog.d(LOG_TAG, "onReceiveResult() code=" + resultCode + ", "
321                             + "data=" + data);
322 
323                     if (resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED
324                             || resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED) {
325                         final PermissionSyncRequest request =
326                                 data.getParcelable(EXTRA_PERMISSION_SYNC_REQUEST,
327                                         PermissionSyncRequest.class);
328                         if (request != null) {
329                             request.setUserConsented(
330                                     resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED);
331                             Slog.i(LOG_TAG, "Recording request: " + request);
332                             mSystemDataTransferRequestStore.writeRequest(request.getUserId(),
333                                     request);
334                         }
335 
336                         return;
337                     }
338 
339                     Slog.e(LOG_TAG, "Unknown result code:" + resultCode);
340                 }
341             };
342 }
343