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 android.adservices.ondevicepersonalization;
18 
19 import static android.adservices.ondevicepersonalization.OnDevicePersonalizationPermissions.MODIFY_ONDEVICEPERSONALIZATION_STATE;
20 
21 import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigService;
22 import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback;
23 import android.annotation.CallbackExecutor;
24 import android.annotation.FlaggedApi;
25 import android.annotation.NonNull;
26 import android.annotation.RequiresPermission;
27 import android.annotation.SystemApi;
28 import android.content.Context;
29 import android.os.Binder;
30 import android.os.OutcomeReceiver;
31 
32 import com.android.adservices.ondevicepersonalization.flags.Flags;
33 import com.android.federatedcompute.internal.util.AbstractServiceBinder;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
36 
37 import java.util.List;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.Executor;
40 
41 /**
42  * OnDevicePersonalizationConfigManager provides system APIs
43  * for privileged APKs to control OnDevicePersonalization's enablement status.
44  *
45  * @hide
46  */
47 @SystemApi
48 @FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED)
49 public class OnDevicePersonalizationConfigManager {
50     /** @hide */
51     public static final String ON_DEVICE_PERSONALIZATION_CONFIG_SERVICE =
52             "on_device_personalization_config_service";
53     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
54     private static final String TAG = OnDevicePersonalizationConfigManager.class.getSimpleName();
55 
56     private static final String ODP_CONFIG_SERVICE_PACKAGE_SUFFIX =
57             "com.android.ondevicepersonalization.services";
58 
59     private static final String ALT_ODP_CONFIG_SERVICE_PACKAGE_SUFFIX =
60             "com.google.android.ondevicepersonalization.services";
61     private static final String ODP_CONFIG_SERVICE_INTENT =
62             "android.OnDevicePersonalizationConfigService";
63 
64     private final AbstractServiceBinder<IOnDevicePersonalizationConfigService> mServiceBinder;
65 
66     /** @hide */
OnDevicePersonalizationConfigManager(@onNull Context context)67     public OnDevicePersonalizationConfigManager(@NonNull Context context) {
68         this(
69                 AbstractServiceBinder.getServiceBinderByIntent(
70                         context,
71                         ODP_CONFIG_SERVICE_INTENT,
72                         List.of(
73                                 ODP_CONFIG_SERVICE_PACKAGE_SUFFIX,
74                                 ALT_ODP_CONFIG_SERVICE_PACKAGE_SUFFIX),
75                         IOnDevicePersonalizationConfigService.Stub::asInterface));
76     }
77 
78     /** @hide */
79     @VisibleForTesting
OnDevicePersonalizationConfigManager( AbstractServiceBinder<IOnDevicePersonalizationConfigService> serviceBinder)80     public OnDevicePersonalizationConfigManager(
81             AbstractServiceBinder<IOnDevicePersonalizationConfigService> serviceBinder) {
82         this.mServiceBinder = serviceBinder;
83     }
84 
85     /**
86      * API users are expected to call this to modify personalization status for
87      * On Device Personalization. The status is persisted both in memory and to the disk.
88      * When reboot, the in-memory status will be restored from the disk.
89      * Personalization is disabled by default.
90      *
91      * @param enabled boolean whether On Device Personalization should be enabled.
92      * @param executor The {@link Executor} on which to invoke the callback.
93      * @param receiver This either returns null on success or {@link Exception} on failure.
94      *
95      *     In case of an error, the receiver returns one of the following exceptions:
96      *     Returns an {@link IllegalStateException} if the callback is unable to send back results.
97      *     Returns a {@link SecurityException} if the caller is unauthorized to modify
98      *     personalization status.
99      */
100     @RequiresPermission(MODIFY_ONDEVICEPERSONALIZATION_STATE)
setPersonalizationEnabled(boolean enabled, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, Exception> receiver)101     public void setPersonalizationEnabled(boolean enabled,
102                                           @NonNull @CallbackExecutor Executor executor,
103                                           @NonNull OutcomeReceiver<Void, Exception> receiver) {
104         CountDownLatch latch = new CountDownLatch(1);
105         try {
106             IOnDevicePersonalizationConfigService service = mServiceBinder.getService(executor);
107             service.setPersonalizationStatus(enabled,
108                     new IOnDevicePersonalizationConfigServiceCallback.Stub() {
109                         @Override
110                         public void onSuccess() {
111                             final long token = Binder.clearCallingIdentity();
112                             try {
113                                 executor.execute(() -> {
114                                     receiver.onResult(null);
115                                     latch.countDown();
116                                 });
117                             } finally {
118                                 Binder.restoreCallingIdentity(token);
119                             }
120                         }
121 
122                         @Override
123                         public void onFailure(int errorCode) {
124                             final long token = Binder.clearCallingIdentity();
125                             try {
126                                 executor.execute(() -> {
127                                     sLogger.w(TAG + ": Unexpected failure from ODP"
128                                             + "config service with error code: " + errorCode);
129                                     receiver.onError(
130                                             new IllegalStateException("Unexpected failure."));
131                                     latch.countDown();
132                                 });
133                             } finally {
134                                 Binder.restoreCallingIdentity(token);
135                             }
136                         }
137                     });
138         } catch (IllegalArgumentException | NullPointerException e) {
139             latch.countDown();
140             throw e;
141         } catch (SecurityException e) {
142             sLogger.w(TAG + ": Unauthorized call to ODP config service.");
143             receiver.onError(e);
144             latch.countDown();
145         } catch (Exception e) {
146             sLogger.w(TAG + ": Unexpected exception during call to ODP config service.");
147             receiver.onError(e);
148             latch.countDown();
149         } finally {
150             try {
151                 latch.await();
152             } catch (InterruptedException e) {
153                 sLogger.e(TAG + ": Failed to set personalization.", e);
154                 receiver.onError(e);
155             }
156             unbindFromService();
157         }
158     }
159 
160     /**
161      * Unbind from config service.
162      */
unbindFromService()163     private void unbindFromService() {
164         mServiceBinder.unbindFromService();
165     }
166 }
167