/* * Copyright (C) 2015 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.car.content.pm; import static android.car.Car.PERMISSION_CONTROL_APP_BLOCKING; import static android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY; import static android.car.CarLibLog.TAG_CAR; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.car.Car; import android.car.CarManagerBase; import android.car.CarVersion; import android.car.feature.Flags; import android.content.ComponentName; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.ArrayMap; import android.util.Slog; import com.android.car.internal.ICarBase; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; /** * Provides car specific API related with package management. */ public final class CarPackageManager extends CarManagerBase { private static final String TAG = CarPackageManager.class.getSimpleName(); /** * Flag for {@link #setAppBlockingPolicy(String, CarAppBlockingPolicy, int)}. When this * flag is set, the call will be blocked until policy is set to system. This can take time * and the flag cannot be used in main thread. * * @hide * @deprecated see the {@link #setAppBlockingPolicy(String, CarAppBlockingPolicy, int)} * documentation for alternative mechanism. */ @SystemApi @Deprecated public static final int FLAG_SET_POLICY_WAIT_FOR_CHANGE = 0x1; /** * Flag for {@link #setAppBlockingPolicy(String, CarAppBlockingPolicy, int)}. When this * flag is set, passed policy is added to existing policy set from the current package. * If none of {@link #FLAG_SET_POLICY_ADD} or {@link #FLAG_SET_POLICY_REMOVE} is set, existing * policy is replaced. Note that policy per each package is always replaced and will not be * added. * * @hide * @deprecated see the {@link #setAppBlockingPolicy(String, CarAppBlockingPolicy, int)} * documentation for alternative mechanism. */ @SystemApi @Deprecated public static final int FLAG_SET_POLICY_ADD = 0x2; /** * Flag for {@link #setAppBlockingPolicy(String, CarAppBlockingPolicy, int)}. When this * flag is set, passed policy is removed from existing policy set from the current package. * If none of {@link #FLAG_SET_POLICY_ADD} or {@link #FLAG_SET_POLICY_REMOVE} is set, existing * policy is replaced. * * @hide * @deprecated see the {@link #setAppBlockingPolicy(String, CarAppBlockingPolicy, int)} * documentation for alternative mechanism. */ @SystemApi @Deprecated public static final int FLAG_SET_POLICY_REMOVE = 0x4; /** * Name of blocked activity. * * @hide */ public static final String BLOCKING_INTENT_EXTRA_BLOCKED_ACTIVITY_NAME = "blocked_activity"; /** * int task id of the blocked task. * * @hide */ public static final String BLOCKING_INTENT_EXTRA_BLOCKED_TASK_ID = "blocked_task_id"; /** * Name of root activity of blocked task. * * @hide */ public static final String BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME = "root_activity_name"; /** * Boolean indicating whether the root activity is distraction-optimized (DO). * Blocking screen should show a button to restart the task if {@code true}. * * @hide */ public static final String BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO = "is_root_activity_do"; /** * int display id of the blocked task. * * @hide */ public static final String BLOCKING_INTENT_EXTRA_DISPLAY_ID = "display_id"; /** * Represents support of all regions for driving safety. * * @hide */ public static final String DRIVING_SAFETY_REGION_ALL = "android.car.drivingsafetyregion.all"; /** * Metadata which Activity can use to specify the driving safety regions it is supporting. * *
Definition of driving safety region is car OEM specific for now and only OEM apps * should use this. If there are multiple regions, it should be comma separated. Not specifying * this means supporting all regions. * *
Some examples are:
*
Format is in the form {@code major:minor} or {@code major}. * *
For example, for {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13}, it would be:
*
* <meta-data android:name="android.car.targetCarVersion" android:value="33"/>
*
*
*
Or:
*
*
* <meta-data android:name="android.car.targetCarVersion" android:value="33:0"/>
*
*
*
And for {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13} first update:
*
* This requires {@code android.permission.REAL_GET_TASKS} permission.
*
* @hide
*/
public void restartTask(int taskId) {
try {
mService.restartTask(taskId);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
/**
* Check if finishing Activity will lead into safe Activity (=allowed Activity) to be shown.
* This can be used by unsafe activity blocking Activity to check if finishing itself can
* lead into being launched again due to unsafe activity shown. Note that checking this does not
* guarantee that blocking will not be done as driving state can change after this call is made.
*
* @param activityName
* @return true if there is a safe Activity (or car is stopped) in the back of task stack
* so that finishing the Activity will not trigger another Activity blocking. If
* the given Activity is not in foreground, then it will return true as well as
* finishing the Activity will not make any difference.
*
* @hide
*/
@SystemApi
public boolean isActivityBackedBySafeActivity(ComponentName activityName) {
try {
return mService.isActivityBackedBySafeActivity(activityName);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, false);
}
}
/**
* Enable/Disable Activity Blocking. This is to provide an option for toggling app blocking
* behavior for development purposes.
* @hide
*/
@TestApi
public void setEnableActivityBlocking(boolean enable) {
try {
mService.setEnableActivityBlocking(enable);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
/**
* Returns whether an activity is distraction optimized, i.e, allowed in a restricted
* driving state.
*
* @param packageName the activity's {@link android.content.pm.ActivityInfo#packageName}.
* @param className the activity's {@link android.content.pm.ActivityInfo#name}.
* @return true if the activity is distraction optimized, false if it isn't or if the value
* could not be determined.
*/
public boolean isActivityDistractionOptimized(String packageName, String className) {
try {
return mService.isActivityDistractionOptimized(packageName, className);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, false);
}
}
/**
* Returns whether the given {@link PendingIntent} represents an activity that is distraction
* optimized, i.e, allowed in a restricted driving state.
*
* @param pendingIntent the {@link PendingIntent} to check.
* @return true if the pending intent represents an activity that is distraction optimized,
* false if it isn't or if the value could not be determined.
*/
public boolean isPendingIntentDistractionOptimized(@NonNull PendingIntent pendingIntent) {
try {
return mService.isPendingIntentDistractionOptimized(pendingIntent);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, false);
}
}
/**
* Check if given service is distraction optimized, i.e, allowed in a restricted
* driving state.
*
* @param packageName
* @param className
* @return
*/
public boolean isServiceDistractionOptimized(String packageName, String className) {
try {
return mService.isServiceDistractionOptimized(packageName, className);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, false);
}
}
/**
* Returns the current driving safety region of the system. It will return OEM specific regions
* or {@link #DRIVING_SAFETY_REGION_ALL} when all regions are supported.
*
* System's driving safety region is static and does not change until system restarts.
*
* @hide
*/
@RequiresPermission(anyOf = {PERMISSION_CONTROL_APP_BLOCKING,
Car.PERMISSION_CAR_DRIVING_STATE})
@NonNull
public String getCurrentDrivingSafetyRegion() {
try {
return mService.getCurrentDrivingSafetyRegion();
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, DRIVING_SAFETY_REGION_ALL);
}
}
/**
* Enables or disables bypassing of unsafe {@code Activity} blocking for a specific
* {@code Activity} temporarily.
*
* Enabling bypassing only lasts until the user stops using the car or until a user
* switching happens. Apps like launcher may ask user's consent to bypass. Note that bypassing
* is done for the package for all android users including the current user and user 0.
* If bypassing is disabled and if the unsafe app is in foreground with driving state, the
* app will be immediately blocked.
*
* @param packageName Target package name.
* @param activityClassName Target Activity name (in full class name).
* @param bypass Bypass {@code Activity} blocking when true. Do not bypass anymore when false.
* @param userId User Id where the package is installed. Even if the bypassing is enabled for
* all android users, the package should be available for the specified user id.
*
* @throws NameNotFoundException If the given package / Activity class does not exist for the
* user.
*
* @hide
*/
@RequiresPermission(allOf = {PERMISSION_CONTROL_APP_BLOCKING,
android.Manifest.permission.QUERY_ALL_PACKAGES})
public void controlTemporaryActivityBlockingBypassingAsUser(String packageName,
String activityClassName, boolean bypass, @UserIdInt int userId)
throws NameNotFoundException {
try {
mService.controlOneTimeActivityBlockingBypassingAsUser(packageName, activityClassName,
bypass, userId);
} catch (ServiceSpecificException e) {
handleServiceSpecificFromCarService(e, packageName, activityClassName, userId);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
/**
* Returns all supported driving safety regions for the given Activity. If the Activity supports
* all regions, it will only include {@link #DRIVING_SAFETY_REGION_ALL}.
*
* The permission specification requires {@code PERMISSION_CONTROL_APP_BLOCKING} and
* {@code QUERY_ALL_PACKAGES} but this API will also work if the client has
* {@link Car#PERMISSION_CAR_DRIVING_STATE} and {@code QUERY_ALL_PACKAGES} permissions.
*
* @param packageName Target package name.
* @param activityClassName Target Activity name (in full class name).
* @param userId Android user Id to check the package.
*
* @return Empty list if the Activity does not support driving safety (=no
* {@code distractionOptimized} metadata). Otherwise returns full list of all supported
* regions.
*
* @throws NameNotFoundException If the given package / Activity class does not exist for the
* user.
*
* @hide
*/
@RequiresPermission(allOf = {PERMISSION_CONTROL_APP_BLOCKING,
android.Manifest.permission.QUERY_ALL_PACKAGES})
@NonNull
public List If the app manifest doesn't contain the {@link #MANIFEST_METADATA_TARGET_CAR_VERSION}
* metadata attribute or if the attribute format is invalid, the returned {@code CarVersion}
* will be using the
* {@link android.content.pm.ApplicationInfo#targetSdkVersion target platform version} as major
* and {@code 0} as minor instead.
*
* Note: to get the target {@link CarVersion} for your own app, use
* {@link #getTargetCarVersion()} instead.
* @return Car API version targeted by the given package (as described above).
*
* @throws NameNotFoundException If the given package does not exist for the user.
*
* @hide
* @deprecated CarVersion is no longer supported by the CarService.
*/
@SystemApi
@RequiresPermission(Manifest.permission.QUERY_ALL_PACKAGES)
@NonNull
@Deprecated
public CarVersion getTargetCarVersion(@NonNull String packageName)
throws NameNotFoundException {
try {
return mService.getTargetCarVersion(packageName);
} catch (ServiceSpecificException e) {
Slog.w(TAG, "Failed to get CarVersion for " + packageName, e);
handleServiceSpecificFromCarService(e, packageName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return null; // cannot reach here but the compiler complains.
}
/**
* Gets the Car API version targeted by app (as defined by
* {@link #MANIFEST_METADATA_TARGET_CAR_VERSION}.
*
* If the app manifest doesn't contain the {@link #MANIFEST_METADATA_TARGET_CAR_VERSION}
* metadata attribute or if the attribute format is invalid, the returned {@code CarVersion}
* will be using the {@link android.content.pm.ApplicationInfo#targetSdkVersion target platform
* version} as major and {@code 0} as minor instead.
*
* @return targeted Car API version (as defined above)
* @deprecated CarVersion is no longer supported by the CarService.
*/
@NonNull
@Deprecated
public CarVersion getTargetCarVersion() {
String pkgName = mCar.getContext().getPackageName();
try {
return mService.getSelfTargetCarVersion(pkgName);
} catch (RemoteException e) {
Slog.w(TAG_CAR, "Car service threw exception calling getTargetCarVersion(" + pkgName
+ ")", e);
e.rethrowFromSystemServer();
return null;
}
}
/**
* @return true if a package requires launching in automotive display compatibility mode.
* false otherwise.
*
* @hide
*/
@FlaggedApi(Flags.FLAG_DISPLAY_COMPATIBILITY)
@SystemApi
@RequiresPermission(allOf = {PERMISSION_MANAGE_DISPLAY_COMPATIBILITY,
android.Manifest.permission.QUERY_ALL_PACKAGES})
public boolean requiresDisplayCompat(@NonNull String packageName) throws NameNotFoundException {
if (!Flags.displayCompatibility()) {
return false;
}
try {
return mService.requiresDisplayCompat(packageName);
} catch (ServiceSpecificException e) {
Slog.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ packageName + ")", e);
if (e.errorCode == ERROR_CODE_NO_PACKAGE) {
throw new NameNotFoundException("cannot find " + packageName);
}
throw new RuntimeException(e);
} catch (SecurityException e) {
Slog.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ packageName + ")", e);
throw e;
} catch (RemoteException e) {
Slog.w(TAG_CAR, "Car service threw exception calling requiresDisplayCompat("
+ packageName + ")", e);
e.rethrowFromSystemServer();
}
return false;
}
private void handleServiceSpecificFromCarService(ServiceSpecificException e,
String packageName) throws NameNotFoundException {
if (e.errorCode == ERROR_CODE_NO_PACKAGE) {
throw new NameNotFoundException(
"cannot find " + packageName + " for user " + Process.myUserHandle());
}
// don't know what this is
throw new IllegalStateException(e);
}
private static void handleServiceSpecificFromCarService(ServiceSpecificException e,
String packageName, String activityClassName, @UserIdInt int userId)
throws NameNotFoundException {
if (e.errorCode == ERROR_CODE_NO_PACKAGE) {
throw new NameNotFoundException(
"cannot find " + packageName + "/" + activityClassName + " for user id:"
+ userId);
}
// don't know what this is
throw new IllegalStateException(e);
}
/**
* Callback interface to finish the blocking ui.
*
* @hide
*/
public interface BlockingUiCommandListener {
/**
* Called when the blocking ui needs to be finished
*/
void finishBlockingUi();
}
/**
* Registers the {@link BlockingUiCommandListener} for the BlockingUi.
*
* Note: A listener can only listen for one displayId.
* Note: A listener cannot be registered twice. Registering the second time would result in a
* no-op.
*
* @param displayId {@link android.view.Display} for which the blocking ui is registered.
* @param callbackExecutor the executor to execute the callback with.
* @param listener {@link BlockingUiCommandListener} listener.
* @throws IllegalStateException if the listener is already registered.
* @hide
*/
public void registerBlockingUiCommandListener(int displayId,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull BlockingUiCommandListener listener) {
synchronized (mLock) {
if (mICarBlockingUiCommandListener.get(listener) != null) {
throw new IllegalStateException("BlockingUiListener already registered");
}
CarBlockingUiCommandListenerImpl carBlockingUiListener =
new CarBlockingUiCommandListenerImpl(callbackExecutor, listener);
try {
mService.registerBlockingUiCommandListener(carBlockingUiListener, displayId);
mICarBlockingUiCommandListener.put(listener, carBlockingUiListener);
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
}
}
/**
* Unregisters the {@link BlockingUiCommandListener}.
*
* @hide
*/
public void unregisterBlockingUiCommandListener(@NonNull BlockingUiCommandListener listener) {
synchronized (mLock) {
try {
if (mICarBlockingUiCommandListener.get(listener) == null) {
Slog.e(TAG, "BlockingUiListener already unregistered");
return;
}
mService.unregisterBlockingUiCommandListener(
mICarBlockingUiCommandListener.get(listener));
} catch (RemoteException e) {
handleRemoteExceptionFromCarService(e);
}
mICarBlockingUiCommandListener.remove(listener);
}
}
private class CarBlockingUiCommandListenerImpl extends ICarBlockingUiCommandListener.Stub {
private final Executor mExecutor;
private final BlockingUiCommandListener mListener;
CarBlockingUiCommandListenerImpl(Executor callbackExecutor,
BlockingUiCommandListener listener) {
mExecutor = callbackExecutor;
mListener = listener;
}
public void finishBlockingUi() throws RemoteException {
long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
if (mICarBlockingUiCommandListener.get(mListener) == null) {
Slog.e(TAG, "BlockingUiListener already unregistered");
return;
}
mExecutor.execute(mListener::finishBlockingUi);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}
* <meta-data android:name="android.car.targetCarVersion" android:value="33:1"/>
*
*
* @deprecated Car version is no longer supported by the CarService.
*/
@Deprecated
public static final String MANIFEST_METADATA_TARGET_CAR_VERSION =
"android.car.targetCarVersion";
/** @hide */
@IntDef(flag = true,
value = {FLAG_SET_POLICY_WAIT_FOR_CHANGE, FLAG_SET_POLICY_ADD, FLAG_SET_POLICY_REMOVE})
@Retention(RetentionPolicy.SOURCE)
public @interface SetPolicyFlags {}
private final ICarPackageManager mService;
private final Object mLock = new Object();
/**
* Map that stores externally created {@link ICarBlockingUiCommandListener} objects keyed by
* their corresponding internally provided {@link BlockingUiCommandListener} objects. Since
* ArrayMap's initial size is 10, and the blocking ui will have at most 1~2 listeners,
* initialize size of the map to be 2.
*/
@GuardedBy("mLock")
private final Map