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.ondevicepersonalization; 18 19 import android.annotation.CallbackExecutor; 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.ResolveInfo; 27 import android.content.pm.ServiceInfo; 28 import android.ondevicepersonalization.aidl.IPrivacyStatusService; 29 import android.ondevicepersonalization.aidl.IPrivacyStatusServiceCallback; 30 import android.os.IBinder; 31 import android.os.OutcomeReceiver; 32 import android.os.RemoteException; 33 import android.util.Slog; 34 35 import java.util.List; 36 import java.util.concurrent.CountDownLatch; 37 import java.util.concurrent.Executor; 38 import java.util.concurrent.TimeUnit; 39 40 /** 41 * OnDevicePersonalizationPrivacyStatusManager provides system APIs 42 * for GMSCore to control privacy statuses of ODP users. 43 * @hide 44 */ 45 public class OnDevicePersonalizationPrivacyStatusManager { 46 public static final String ON_DEVICE_PERSONALIZATION_PRIVACY_STATUS_SERVICE = 47 "on_device_personalization_privacy_status_service"; 48 private static final String TAG = "OdpPrivacyStatusManager"; 49 private static final String ODP_PRIVACY_STATUS_SERVICE_INTENT = 50 "android.OnDevicePersonalizationPrivacyStatusService"; 51 private boolean mBound = false; 52 private IPrivacyStatusService mService = null; 53 private final Context mContext; 54 private final CountDownLatch mConnectionLatch = new CountDownLatch(1); 55 OnDevicePersonalizationPrivacyStatusManager(@onNull Context context)56 public OnDevicePersonalizationPrivacyStatusManager(@NonNull Context context) { 57 mContext = context; 58 } 59 60 private final ServiceConnection mConnection = new ServiceConnection() { 61 @Override 62 public void onServiceConnected(ComponentName name, IBinder binder) { 63 mService = IPrivacyStatusService.Stub.asInterface(binder); 64 mBound = true; 65 mConnectionLatch.countDown(); 66 } 67 68 @Override 69 public void onNullBinding(ComponentName name) { 70 mBound = false; 71 mConnectionLatch.countDown(); 72 } 73 74 @Override 75 public void onServiceDisconnected(ComponentName name) { 76 mService = null; 77 mBound = false; 78 } 79 }; 80 81 private static final int BIND_SERVICE_TIMEOUT_SEC = 5; 82 83 /** 84 * Modify the user's kid status in ODP from GMSCore. 85 * 86 * @param isKidStatusEnabled user's kid status available at GMSCore. 87 * @param executor the {@link Executor} on which to invoke the callback 88 * @param receiver This either returns true on success or {@link Exception} on failure. 89 * @hide 90 */ setKidStatus(boolean isKidStatusEnabled, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> receiver)91 public void setKidStatus(boolean isKidStatusEnabled, 92 @NonNull @CallbackExecutor Executor executor, 93 @NonNull OutcomeReceiver<Boolean, Exception> receiver) { 94 try { 95 bindService(executor); 96 97 mService.setKidStatus(isKidStatusEnabled, new IPrivacyStatusServiceCallback.Stub() { 98 @Override 99 public void onSuccess() { 100 executor.execute(() -> receiver.onResult(true)); 101 } 102 103 @Override 104 public void onFailure(int errorCode) { 105 executor.execute(() -> receiver.onError( 106 new OnDevicePersonalizationException(errorCode))); 107 } 108 }); 109 } catch (InterruptedException | RemoteException e) { 110 receiver.onError(e); 111 } 112 } 113 bindService(@onNull Executor executor)114 private void bindService(@NonNull Executor executor) throws InterruptedException { 115 if (!mBound) { 116 Intent intent = new Intent(ODP_PRIVACY_STATUS_SERVICE_INTENT); 117 ComponentName serviceComponent = resolveService(intent); 118 if (serviceComponent == null) { 119 Slog.e(TAG, "Invalid component for ODP privacy status service"); 120 return; 121 } 122 123 intent.setComponent(serviceComponent); 124 boolean r = mContext.bindService( 125 intent, Context.BIND_AUTO_CREATE, executor, mConnection); 126 if (!r) { 127 return; 128 } 129 mConnectionLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS); 130 } 131 } 132 133 /** 134 * Find the ComponentName of the service, given its intent. 135 * 136 * @return ComponentName of the service. Null if the service is not found. 137 */ 138 @Nullable resolveService(@onNull Intent intent)139 private ComponentName resolveService(@NonNull Intent intent) { 140 List<ResolveInfo> services = mContext.getPackageManager().queryIntentServices(intent, 0); 141 if (services == null || services.isEmpty()) { 142 Slog.e(TAG, "Failed to find OdpPrivacyStatus service"); 143 return null; 144 } 145 146 for (int i = 0; i < services.size(); i++) { 147 ServiceInfo serviceInfo = services.get(i).serviceInfo; 148 if (serviceInfo == null) { 149 Slog.e(TAG, "Failed to find serviceInfo for OdpPrivacyStatus service."); 150 return null; 151 } 152 // There should only be one matching service inside the given package. 153 // If there's more than one, return the first one found. 154 return new ComponentName(serviceInfo.packageName, serviceInfo.name); 155 } 156 Slog.e(TAG, "Didn't find any matching OdpPrivacyStatus service."); 157 return null; 158 } 159 } 160