/* * Copyright (C) 2021 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 com.android.car.admin; import static com.android.car.PermissionHelper.checkHasDumpPermissionGranted; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.car.admin.CarDevicePolicyManager; import android.car.admin.ICarDevicePolicyService; import android.car.builtin.os.UserManagerHelper; import android.car.builtin.util.Slogf; import android.car.user.UserCreationRequest; import android.car.user.UserCreationResult; import android.car.user.UserRemovalResult; import android.car.user.UserStartResult; import android.car.user.UserStopResult; import android.car.util.concurrent.AndroidFuture; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.UserManager; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; import com.android.car.BuiltinPackageDependency; import com.android.car.CarLog; import com.android.car.CarServiceBase; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.car.internal.ResultCallbackImpl; import com.android.car.internal.common.UserHelperLite; import com.android.car.internal.os.CarSystemProperties; import com.android.car.internal.util.DebugUtils; import com.android.car.internal.util.IndentingPrintWriter; import com.android.car.user.CarUserService; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Service for device policy related features. */ public final class CarDevicePolicyService extends ICarDevicePolicyService.Stub implements CarServiceBase { @VisibleForTesting static final String TAG = CarLog.tagFor(CarDevicePolicyService.class); private static final int HAL_TIMEOUT_MS = CarSystemProperties.getUserHalTimeout().orElse(5_000); private static final String PREFIX_NEW_USER_DISCLAIMER_STATUS = "NEW_USER_DISCLAIMER_STATUS_"; // TODO(b/175057848) must be public because of DebugUtils.constantToString() public static final int NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED = 0; public static final int NEW_USER_DISCLAIMER_STATUS_RECEIVED = 1; public static final int NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT = 2; public static final int NEW_USER_DISCLAIMER_STATUS_SHOWN = 3; public static final int NEW_USER_DISCLAIMER_STATUS_ACKED = 4; private final Object mLock = new Object(); private final CarUserService mCarUserService; private final Context mContext; private final Context mCarServiceBuiltinPackageContext; @Retention(RetentionPolicy.SOURCE) @IntDef(flag = false, prefix = { PREFIX_NEW_USER_DISCLAIMER_STATUS }, value = { NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED, NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT, NEW_USER_DISCLAIMER_STATUS_RECEIVED, NEW_USER_DISCLAIMER_STATUS_SHOWN, NEW_USER_DISCLAIMER_STATUS_ACKED }) public @interface NewUserDisclaimerStatus {} @GuardedBy("mLock") private final SparseIntArray mUserDisclaimerStatusPerUser = new SparseIntArray(); private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int userId = ActivityManager.getCurrentUser(); Slogf.d(TAG, "Received intent for user " + userId + ": " + intent); if (!mContext.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) { Slogf.d(TAG, "Not handling ACTION_SHOW_NEW_USER_DISCLAIMER because device " + "doesn't have %s", PackageManager.FEATURE_DEVICE_ADMIN); return; } switch(intent.getAction()) { case DevicePolicyManager.ACTION_SHOW_NEW_USER_DISCLAIMER: Slogf.d(TAG, "Action show new user disclaimer"); setUserDisclaimerStatus(userId, NEW_USER_DISCLAIMER_STATUS_RECEIVED); showNewUserDisclaimer(userId); break; default: Slogf.w(TAG, "received unexpected intent: %s" , intent); } } }; public CarDevicePolicyService(@NonNull Context context, @NonNull Context carServiceBuiltinPackageContext, @NonNull CarUserService carUserService) { mCarUserService = carUserService; mContext = context; mCarServiceBuiltinPackageContext = carServiceBuiltinPackageContext; } @Override public void init() { Slogf.d(TAG, "init()"); mContext.registerReceiverForAllUsers(mBroadcastReceiver, new IntentFilter(DevicePolicyManager.ACTION_SHOW_NEW_USER_DISCLAIMER), /* broadcastPermissions= */ null, /* scheduler= */ null, Context.RECEIVER_NOT_EXPORTED); } @Override public void release() { Slogf.d(TAG, "release()"); mContext.unregisterReceiver(mBroadcastReceiver); } @Override public void removeUser(@UserIdInt int userId, ResultCallbackImpl callback) { mCarUserService.removeUser(userId, /* hasCallerRestrictions= */ true, callback); } @Override public void createUser(@Nullable String name, @CarDevicePolicyManager.UserType int type, ResultCallbackImpl callback) { UserCreationRequest.Builder userCreationRequestBuilder = new UserCreationRequest.Builder().setName(name); int userInfoFlags = 0; String userType = UserManager.USER_TYPE_FULL_SECONDARY; switch(type) { case CarDevicePolicyManager.USER_TYPE_REGULAR: break; case CarDevicePolicyManager.USER_TYPE_ADMIN: userInfoFlags = UserManagerHelper.FLAG_ADMIN; userCreationRequestBuilder.setAdmin(); break; case CarDevicePolicyManager.USER_TYPE_GUEST: userType = UserManager.USER_TYPE_FULL_GUEST; userCreationRequestBuilder.setGuest(); break; default: Slogf.d(TAG, "createUser(): invalid userType (%s) / flags (%08x) " + "combination", userType, userInfoFlags); callback.complete( new UserCreationResult(UserCreationResult.STATUS_INVALID_REQUEST)); return; } Slogf.d(TAG, "calling createUser(%s, %s, %d, %d)", UserHelperLite.safeName(name), userType, userInfoFlags, HAL_TIMEOUT_MS); mCarUserService.createUser(userCreationRequestBuilder.build(), HAL_TIMEOUT_MS, callback); } @Override public void startUserInBackground(@UserIdInt int userId, AndroidFuture receiver) { mCarUserService.startUserInBackground(userId, receiver); } @Override public void stopUser(@UserIdInt int userId, AndroidFuture receiver) { mCarUserService.stopUser(userId, receiver); } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dump(@NonNull IndentingPrintWriter writer) { checkHasDumpPermissionGranted(mContext, "dump()"); writer.println("*CarDevicePolicyService*"); synchronized (mLock) { int numUsers = mUserDisclaimerStatusPerUser.size(); writer.println("**mDisclaimerStatusPerUser**"); for (int i = 0; i < numUsers; i++) { int userId = mUserDisclaimerStatusPerUser.keyAt(i); int status = mUserDisclaimerStatusPerUser.get(userId); writer.printf("userId=%d disclaimerStatus=%s\n", userId, newUserDisclaimerStatusToString(status)); } } writer.printf("HAL_TIMEOUT_MS: %d\n", HAL_TIMEOUT_MS); } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dumpProto(ProtoOutputStream proto) {} /** * Updates the internal state with the disclaimer status as shown. */ @Override public void setUserDisclaimerShown(int userId) { setUserDisclaimerStatus(userId, NEW_USER_DISCLAIMER_STATUS_SHOWN); } /** * Updates the internal state with the disclaimer status as acknowledged. */ @Override public void setUserDisclaimerAcknowledged(int userId) { setUserDisclaimerStatus(userId, NEW_USER_DISCLAIMER_STATUS_ACKED); UserHandle user = UserHandle.of(userId); BuiltinPackageDependency.createNotificationHelper(mCarServiceBuiltinPackageContext) .cancelUserDisclaimerNotification(user); DevicePolicyManager dpm = mContext.createContextAsUser(user, 0) .getSystemService(DevicePolicyManager.class); dpm.acknowledgeNewUserDisclaimer(); } @VisibleForTesting @NewUserDisclaimerStatus int getNewUserDisclaimerStatus(int userId) { synchronized (mLock) { return mUserDisclaimerStatusPerUser.get(userId, NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED); } } private void showNewUserDisclaimer(@UserIdInt int userId) { // TODO(b/175057848) persist status so it's shown again if car service crashes? BuiltinPackageDependency.createNotificationHelper(mCarServiceBuiltinPackageContext) .showUserDisclaimerNotification(UserHandle.of(userId)); setUserDisclaimerStatus(userId, NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT); } private void setUserDisclaimerStatus(@UserIdInt int userId, @NewUserDisclaimerStatus int status) { synchronized (mLock) { Slogf.d(TAG, "Changing status from %s to %s", newUserDisclaimerStatusToString( mUserDisclaimerStatusPerUser.get( userId, NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED)), newUserDisclaimerStatusToString(status)); mUserDisclaimerStatusPerUser.put(userId, status); } } @VisibleForTesting static String newUserDisclaimerStatusToString(@NewUserDisclaimerStatus int status) { return DebugUtils.constantToString(CarDevicePolicyService.class, PREFIX_NEW_USER_DISCLAIMER_STATUS, status); } }