/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.ondevicepersonalization; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.ondevicepersonalization.aidl.IPrivacyStatusService; import android.ondevicepersonalization.aidl.IPrivacyStatusServiceCallback; import android.os.IBinder; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.util.Slog; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * OnDevicePersonalizationPrivacyStatusManager provides system APIs * for GMSCore to control privacy statuses of ODP users. * @hide */ public class OnDevicePersonalizationPrivacyStatusManager { public static final String ON_DEVICE_PERSONALIZATION_PRIVACY_STATUS_SERVICE = "on_device_personalization_privacy_status_service"; private static final String TAG = "OdpPrivacyStatusManager"; private static final String ODP_PRIVACY_STATUS_SERVICE_INTENT = "android.OnDevicePersonalizationPrivacyStatusService"; private boolean mBound = false; private IPrivacyStatusService mService = null; private final Context mContext; private final CountDownLatch mConnectionLatch = new CountDownLatch(1); public OnDevicePersonalizationPrivacyStatusManager(@NonNull Context context) { mContext = context; } private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder binder) { mService = IPrivacyStatusService.Stub.asInterface(binder); mBound = true; mConnectionLatch.countDown(); } @Override public void onNullBinding(ComponentName name) { mBound = false; mConnectionLatch.countDown(); } @Override public void onServiceDisconnected(ComponentName name) { mService = null; mBound = false; } }; private static final int BIND_SERVICE_TIMEOUT_SEC = 5; /** * Modify the user's kid status in ODP from GMSCore. * * @param isKidStatusEnabled user's kid status available at GMSCore. * @param executor the {@link Executor} on which to invoke the callback * @param receiver This either returns true on success or {@link Exception} on failure. * @hide */ public void setKidStatus(boolean isKidStatusEnabled, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver receiver) { try { bindService(executor); mService.setKidStatus(isKidStatusEnabled, new IPrivacyStatusServiceCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> receiver.onResult(true)); } @Override public void onFailure(int errorCode) { executor.execute(() -> receiver.onError( new OnDevicePersonalizationException(errorCode))); } }); } catch (InterruptedException | RemoteException e) { receiver.onError(e); } } private void bindService(@NonNull Executor executor) throws InterruptedException { if (!mBound) { Intent intent = new Intent(ODP_PRIVACY_STATUS_SERVICE_INTENT); ComponentName serviceComponent = resolveService(intent); if (serviceComponent == null) { Slog.e(TAG, "Invalid component for ODP privacy status service"); return; } intent.setComponent(serviceComponent); boolean r = mContext.bindService( intent, Context.BIND_AUTO_CREATE, executor, mConnection); if (!r) { return; } mConnectionLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS); } } /** * Find the ComponentName of the service, given its intent. * * @return ComponentName of the service. Null if the service is not found. */ @Nullable private ComponentName resolveService(@NonNull Intent intent) { List services = mContext.getPackageManager().queryIntentServices(intent, 0); if (services == null || services.isEmpty()) { Slog.e(TAG, "Failed to find OdpPrivacyStatus service"); return null; } for (int i = 0; i < services.size(); i++) { ServiceInfo serviceInfo = services.get(i).serviceInfo; if (serviceInfo == null) { Slog.e(TAG, "Failed to find serviceInfo for OdpPrivacyStatus service."); return null; } // There should only be one matching service inside the given package. // If there's more than one, return the first one found. return new ComponentName(serviceInfo.packageName, serviceInfo.name); } Slog.e(TAG, "Didn't find any matching OdpPrivacyStatus service."); return null; } }