blob: d6c15f8818424e037b425c8f8cf3a0ac354ec521 [file] [log] [blame]
/*
* 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 com.android.car;
import static android.car.user.CarUserManager.lifecycleEventTypeToString;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Process.INVALID_UID;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.car.Car;
import android.car.builtin.content.ContextHelper;
import android.car.builtin.content.pm.PackageManagerHelper;
import android.car.builtin.os.UserManagerHelper;
import android.car.builtin.util.Slogf;
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.hardware.automotive.vehicle.SubscribeOptions;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.KeyStore;
import java.security.KeyStore.SecretKeyEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
/** Utility class */
public final class CarServiceUtils {
// https://developer.android.com/reference/java/util/UUID
private static final int UUID_LENGTH = 16;
private static final String TAG = CarLog.tagFor(CarServiceUtils.class);
private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
private static final String COMMON_HANDLER_THREAD_NAME =
"CarServiceUtils_COMMON_HANDLER_THREAD";
private static final byte[] CHAR_POOL_FOR_RANDOM_STRING =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes();
private static final String PACKAGE_NOT_FOUND = "Package not found:";
private static final String ANDROID_KEYSTORE_NAME = "AndroidKeyStore";
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 128;
/** K: class name, V: HandlerThread */
private static final ArrayMap<String, HandlerThread> sHandlerThreads = new ArrayMap<>();
@ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE,
details = "private constructor")
private CarServiceUtils() {
throw new UnsupportedOperationException("contains only static methods");
}
/**
* Returns a byte buffer corresponding to the passed long argument.
*
* @param primitive data to convert format.
*/
public static byte[] longToBytes(long primitive) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(primitive);
return buffer.array();
}
/**
* Returns a byte buffer corresponding to the passed long argument.
*
* @param array data to convert format.
*/
public static long bytesToLong(byte[] array) {
ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
buffer.put(array);
buffer.flip();
long value = buffer.getLong();
return value;
}
/**
* Returns a String in Hex format that is formed from the bytes in the byte array
* Useful for debugging
*
* @param array the byte array
* @return the Hex string version of the input byte array
*/
public static String byteArrayToHexString(byte[] array) {
StringBuilder sb = new StringBuilder(array.length * 2);
for (byte b : array) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* Convert UUID to Big Endian byte array
*
* @param uuid UUID to convert
* @return the byte array representing the UUID
*/
@NonNull
public static byte[] uuidToBytes(@NonNull UUID uuid) {
return ByteBuffer.allocate(UUID_LENGTH)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits())
.array();
}
/**
* Convert Big Endian byte array to UUID
*
* @param bytes byte array to convert
* @return the UUID representing the byte array, or null if not a valid UUID
*/
@Nullable
public static UUID bytesToUUID(@NonNull byte[] bytes) {
if (bytes.length != UUID_LENGTH) {
return null;
}
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return new UUID(buffer.getLong(), buffer.getLong());
}
/**
* Generate a random zero-filled string of given length
*
* @param length of string
* @return generated string
*/
@SuppressLint("DefaultLocale") // Should always have the same format regardless of locale
public static String generateRandomNumberString(int length) {
return String.format("%0" + length + "d",
ThreadLocalRandom.current().nextInt((int) Math.pow(10, length)));
}
/**
* Concatentate the given 2 byte arrays
*
* @param a input array 1
* @param b input array 2
* @return concatenated array of arrays 1 and 2
*/
@Nullable
public static byte[] concatByteArrays(@Nullable byte[] a, @Nullable byte[] b) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
if (a != null) {
outputStream.write(a);
}
if (b != null) {
outputStream.write(b);
}
} catch (IOException e) {
return null;
}
return outputStream.toByteArray();
}
/**
* Returns the content resolver for the given user. This can be used to put/get the
* user's settings.
*
* @param context The context of the package.
* @param userId The id of the user which the content resolver is being requested for. It also
* accepts {@link UserHandle#USER_CURRENT}.
*/
public static ContentResolver getContentResolverForUser(Context context,
@UserIdInt int userId) {
if (userId == UserHandle.CURRENT.getIdentifier()) {
userId = ActivityManager.getCurrentUser();
}
return context
.createContextAsUser(
UserHandle.of(userId), /* flags= */ 0)
.getContentResolver();
}
/**
* Checks if the type of the {@code event} matches {@code expectedType}.
*
* @param tag The tag for logging.
* @param event The event to check the type against {@code expectedType}.
* @param expectedType The expected event type.
* @return true if {@code event}'s type matches {@code expectedType}.
* Otherwise, log a wtf and return false.
*/
public static boolean isEventOfType(String tag, UserLifecycleEvent event,
@UserLifecycleEventType int expectedType) {
if (event.getEventType() == expectedType) {
return true;
}
Slogf.wtf(tag, "Received an unexpected event: %s. Expected type: %s.", event,
lifecycleEventTypeToString(expectedType));
return false;
}
/**
* Checks if the type of the {@code event} is one of the types in {@code expectedTypes}.
*
* @param tag The tag for logging.
* @param event The event to check the type against {@code expectedTypes}.
* @param expectedTypes The expected event types. Must not be empty.
* @return true if {@code event}'s type can be found in {@code expectedTypes}.
* Otherwise, log a wtf and return false.
*/
public static boolean isEventAnyOfTypes(String tag, UserLifecycleEvent event,
@UserLifecycleEventType int... expectedTypes) {
for (int i = 0; i < expectedTypes.length; i++) {
if (event.getEventType() == expectedTypes[i]) {
return true;
}
}
Slogf.wtf(tag, "Received an unexpected event: %s. Expected types: [%s]", event,
Arrays.stream(expectedTypes).mapToObj(t -> lifecycleEventTypeToString(t)).collect(
Collectors.joining(",")));
return false;
}
/**
* Checks if the calling UID owns the give package.
*
* @throws SecurityException if the calling UID doesn't own the given package.
*/
public static void checkCalledByPackage(Context context, String packageName) {
int callingUid = Binder.getCallingUid();
PackageManager pm = context.getPackageManager();
int uidFromPm = INVALID_UID;
try {
uidFromPm = PackageManagerHelper.getPackageUidAsUser(pm, packageName,
UserManagerHelper.getUserId(callingUid));
} catch (PackageManager.NameNotFoundException e) {
String msg = PACKAGE_NOT_FOUND + packageName;
throw new SecurityException(msg, e);
}
if (uidFromPm != callingUid) {
throw new SecurityException(
"Package " + packageName + " is not associated to UID " + callingUid);
}
}
/**
* Execute a runnable on the main thread
*
* @param action The code to run on the main thread.
*/
public static void runOnMain(Runnable action) {
runOnLooper(Looper.getMainLooper(), action);
}
/**
* Execute a runnable in the given looper
* @param looper Looper to run the action.
* @param action The code to run.
*/
public static void runOnLooper(Looper looper, Runnable action) {
new Handler(looper).post(action);
}
/**
* Execute an empty runnable in the looper of the handler thread
* specified by the name.
*
* @param name Name of the handler thread in which to run the empty
* runnable.
*/
public static void runEmptyRunnableOnLooperSync(String name) {
runOnLooperSync(getHandlerThread(name).getLooper(), () -> {});
}
/**
* Execute a call on the application's main thread, blocking until it is
* complete. Useful for doing things that are not thread-safe, such as
* looking at or modifying the view hierarchy.
*
* @param action The code to run on the main thread.
*/
public static void runOnMainSync(Runnable action) {
runOnLooperSync(Looper.getMainLooper(), action);
}
/**
* Execute a delayed call on the application's main thread, blocking until it is
* complete. See {@link #runOnMainSync(Runnable)}
*
* @param action The code to run on the main thread.
* @param delayMillis The delay (in milliseconds) until the Runnable will be executed.
*/
public static void runOnMainSyncDelayed(Runnable action, long delayMillis) {
runOnLooperSyncDelayed(Looper.getMainLooper(), action, delayMillis);
}
/**
* Execute a call on the given Looper thread, blocking until it is
* complete.
*
* @param looper Looper to run the action.
* @param action The code to run on the looper thread.
*/
public static void runOnLooperSync(Looper looper, Runnable action) {
runOnLooperSyncDelayed(looper, action, /* delayMillis */ 0L);
}
/**
* Executes a delayed call on the given Looper thread, blocking until it is complete.
*
* @param looper Looper to run the action.
* @param action The code to run on the looper thread.
* @param delayMillis The delay (in milliseconds) until the Runnable will be executed.
*/
public static void runOnLooperSyncDelayed(Looper looper, Runnable action, long delayMillis) {
if (Looper.myLooper() == looper) {
// requested thread is the same as the current thread. call directly.
action.run();
} else {
Handler handler = new Handler(looper);
SyncRunnable sr = new SyncRunnable(action);
handler.postDelayed(sr, delayMillis);
sr.waitForComplete();
}
}
/**
* Executes a runnable on the common thread. Useful for doing any kind of asynchronous work
* across the car related code that doesn't need to be on the main thread.
*
* @param action The code to run on the common thread.
*/
public static void runOnCommon(Runnable action) {
runOnLooper(getCommonHandlerThread().getLooper(), action);
}
private static final class SyncRunnable implements Runnable {
private final Runnable mTarget;
private volatile boolean mComplete = false;
public SyncRunnable(Runnable target) {
mTarget = target;
}
@Override
public void run() {
mTarget.run();
synchronized (this) {
mComplete = true;
notifyAll();
}
}
public void waitForComplete() {
synchronized (this) {
while (!mComplete) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
}
}
public static float[] toFloatArray(List<Float> list) {
int size = list.size();
float[] array = new float[size];
for (int i = 0; i < size; ++i) {
array[i] = list.get(i);
}
return array;
}
public static long[] toLongArray(List<Long> list) {
int size = list.size();
long[] array = new long[size];
for (int i = 0; i < size; ++i) {
array[i] = list.get(i);
}
return array;
}
public static int[] toIntArray(List<Integer> list) {
int size = list.size();
int[] array = new int[size];
for (int i = 0; i < size; ++i) {
array[i] = list.get(i);
}
return array;
}
/**
* Converts array to an array list
*/
public static ArrayList<Integer> asList(int[] array) {
Preconditions.checkArgument(array != null, "Array to convert to list can not be null");
int size = array.length;
ArrayList<Integer> results = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
results.add(array[i]);
}
return results;
}
public static byte[] toByteArray(List<Byte> list) {
int size = list.size();
byte[] array = new byte[size];
for (int i = 0; i < size; ++i) {
array[i] = list.get(i);
}
return array;
}
/**
* Converts values array to array set
*/
public static ArraySet<Integer> toIntArraySet(int[] values) {
Preconditions.checkArgument(values != null,
"Values to convert to array set must not be null");
ArraySet<Integer> set = new ArraySet<>(values.length);
for (int c = 0; c < values.length; c++) {
set.add(values[c]);
}
return set;
}
/**
* Returns delta between elapsed time to uptime = {@link SystemClock#elapsedRealtime()} -
* {@link SystemClock#uptimeMillis()}. Note that this value will be always >= 0.
*/
public static long getUptimeToElapsedTimeDeltaInMillis() {
int retry = 0;
int max_retry = 2; // try only up to twice
while (true) {
long elapsed1 = SystemClock.elapsedRealtime();
long uptime = SystemClock.uptimeMillis();
long elapsed2 = SystemClock.elapsedRealtime();
if (elapsed1 == elapsed2) { // avoid possible 1 ms fluctuation.
return elapsed1 - uptime;
}
retry++;
if (retry >= max_retry) {
return elapsed1 - uptime;
}
}
}
/**
* Gets a static instance of {@code HandlerThread} for the given {@code name}. If the thread
* does not exist, create one and start it before returning.
*/
public static HandlerThread getHandlerThread(String name) {
synchronized (sHandlerThreads) {
HandlerThread thread = sHandlerThreads.get(name);
if (thread == null || !thread.isAlive()) {
Slogf.i(TAG, "Starting HandlerThread:" + name);
thread = new HandlerThread(name);
thread.start();
sHandlerThreads.put(name, thread);
}
return thread;
}
}
/**
* Gets the static instance of the common {@code HandlerThread} meant to be used across
* CarService.
*/
public static HandlerThread getCommonHandlerThread() {
return getHandlerThread(COMMON_HANDLER_THREAD_NAME);
}
/**
* Finishes all queued {@code Handler} tasks for {@code HandlerThread} created via
* {@link#getHandlerThread(String)}. This is useful only for testing.
*/
@VisibleForTesting
public static void finishAllHandlerTasks() {
ArrayList<HandlerThread> threads;
synchronized (sHandlerThreads) {
threads = new ArrayList<>(sHandlerThreads.values());
}
ArrayList<SyncRunnable> syncs = new ArrayList<>(threads.size());
for (int i = 0; i < threads.size(); i++) {
if (!threads.get(i).isAlive()) {
continue;
}
Handler handler = new Handler(threads.get(i).getLooper());
SyncRunnable sr = new SyncRunnable(() -> { });
if (handler.post(sr)) {
// Track the threads only where SyncRunnable is posted successfully.
syncs.add(sr);
}
}
for (int i = 0; i < syncs.size(); i++) {
syncs.get(i).waitForComplete();
}
}
/**
* Assert if binder call is coming from system process like system server or if it is called
* from its own process even if it is not system. The latter can happen in test environment.
* Note that car service runs as system user but test like car service test will not.
*/
public static void assertCallingFromSystemProcessOrSelf() {
if (isCallingFromSystemProcessOrSelf()) {
throw new SecurityException("Only allowed from system or self");
}
}
/**
* @return true if binder call is coming from system process like system server or if it is
* called from its own process even if it is not system.
*/
public static boolean isCallingFromSystemProcessOrSelf() {
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
return uid != Process.SYSTEM_UID && pid != Process.myPid();
}
/** Utility for checking permission */
public static void assertVehicleHalMockPermission(Context context) {
assertPermission(context, Car.PERMISSION_MOCK_VEHICLE_HAL);
}
/** Utility for checking permission */
public static void assertNavigationManagerPermission(Context context) {
assertPermission(context, Car.PERMISSION_CAR_NAVIGATION_MANAGER);
}
/** Utility for checking permission */
public static void assertClusterManagerPermission(Context context) {
assertPermission(context, Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
}
/** Utility for checking permission */
public static void assertPowerPermission(Context context) {
assertPermission(context, Car.PERMISSION_CAR_POWER);
}
/** Utility for checking permission */
public static void assertProjectionPermission(Context context) {
assertPermission(context, Car.PERMISSION_CAR_PROJECTION);
}
/** Verify the calling context has the {@link Car#PERMISSION_CAR_PROJECTION_STATUS} */
public static void assertProjectionStatusPermission(Context context) {
assertPermission(context, Car.PERMISSION_CAR_PROJECTION_STATUS);
}
/** Utility for checking permission */
public static void assertAnyDiagnosticPermission(Context context) {
assertAnyPermission(context,
Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL,
Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR);
}
/** Utility for checking permission */
public static void assertDrivingStatePermission(Context context) {
assertPermission(context, Car.PERMISSION_CAR_DRIVING_STATE);
}
/**
* Verify the calling context has either {@link Car#PERMISSION_VMS_SUBSCRIBER} or
* {@link Car#PERMISSION_VMS_PUBLISHER}
*/
public static void assertAnyVmsPermission(Context context) {
assertAnyPermission(context,
Car.PERMISSION_VMS_SUBSCRIBER,
Car.PERMISSION_VMS_PUBLISHER);
}
/** Utility for checking permission */
public static void assertVmsPublisherPermission(Context context) {
assertPermission(context, Car.PERMISSION_VMS_PUBLISHER);
}
/** Utility for checking permission */
public static void assertVmsSubscriberPermission(Context context) {
assertPermission(context, Car.PERMISSION_VMS_SUBSCRIBER);
}
/** Utility for checking permission */
public static void assertPermission(Context context, String permission) {
if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("requires " + permission);
}
}
/**
* Checks to see if the caller has a permission.
*
* @return boolean TRUE if caller has the permission.
*/
public static boolean hasPermission(Context context, String permission) {
return context.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
}
/** Utility for checking permission */
public static void assertAnyPermission(Context context, String... permissions) {
for (String permission : permissions) {
if (context.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED) {
return;
}
}
throw new SecurityException("requires any of " + Arrays.toString(permissions));
}
/**
* Turns a {@code SubscribeOptions} to {@code
* android.hardware.automotive.vehicle.V2_0.SubscribeOptions}
*/
public static android.hardware.automotive.vehicle.V2_0.SubscribeOptions subscribeOptionsToHidl(
SubscribeOptions options) {
android.hardware.automotive.vehicle.V2_0.SubscribeOptions hidlOptions =
new android.hardware.automotive.vehicle.V2_0.SubscribeOptions();
hidlOptions.propId = options.propId;
hidlOptions.sampleRate = options.sampleRate;
// HIDL backend requires flags to be set although it is not used any more.
hidlOptions.flags = android.hardware.automotive.vehicle.V2_0.SubscribeFlags.EVENTS_FROM_CAR;
// HIDL backend does not support area IDs, so we ignore options.areaId field.
return hidlOptions;
}
/**
* Returns {@code true} if the current configuration supports multiple users on multiple
* displays.
*/
public static boolean isMultipleUsersOnMultipleDisplaysSupported(UserManager userManager) {
return UserManagerHelper.isVisibleBackgroundUsersSupported(userManager);
}
/**
* Returns {@code true} if the current configuration supports visible background users on
* default display.
*/
public static boolean isVisibleBackgroundUsersOnDefaultDisplaySupported(
UserManager userManager) {
return UserManagerHelper.isVisibleBackgroundUsersOnDefaultDisplaySupported(userManager);
}
/**
* Starts Activity for the given {@code userId} and {@code displayId}.
*
* @return {@code true} when starting activity succeeds. It can fail in situation like secondary
* home package not existing.
*/
public static boolean startHomeForUserAndDisplay(Context context,
@UserIdInt int userId, int displayId) {
if (DBG) {
Slogf.d(TAG, "Starting HOME for user: %d, display:%d", userId, displayId);
}
Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME);
ActivityOptions activityOptions = ActivityOptions.makeBasic()
.setLaunchDisplayId(displayId);
try {
ContextHelper.startActivityAsUser(context, homeIntent, activityOptions.toBundle(),
UserHandle.of(userId));
if (DBG) {
Slogf.d(TAG, "Started HOME for user: %d, display:%d", userId, displayId);
}
return true;
} catch (Exception e) {
Slogf.w(TAG, e, "Cannot start HOME for user: %d, display:%d", userId, displayId);
return false;
}
}
/**
* Starts SystemUI component for a particular user - should be called for non-current user only.
*
* @return {@code true} when starting service succeeds. It can fail in situation like the
* SystemUI service component not being defined.
*/
public static boolean startSystemUiForUser(Context context, @UserIdInt int userId) {
if (DBG) Slogf.d(TAG, "Start SystemUI for user: %d", userId);
Preconditions.checkArgument(userId != UserHandle.SYSTEM.getIdentifier(),
"Cannot start SystemUI for the system user");
Preconditions.checkArgument(userId != ActivityManager.getCurrentUser(),
"Cannot start SystemUI for the current foreground user");
// TODO (b/261192740): add EventLog for SystemUI starting
ComponentName sysuiComponent = PackageManagerHelper.getSystemUiServiceComponent(context);
Intent sysUIIntent = new Intent().setComponent(sysuiComponent);
try {
context.bindServiceAsUser(sysUIIntent, sEmptyServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.of(userId));
return true;
} catch (Exception e) {
Slogf.w(TAG, e, "Cannot start SysUI component %s for user %d", sysuiComponent,
userId);
return false;
}
}
// The callbacks are not called actually, because SystemUI returns null for IBinder.
private static final ServiceConnection sEmptyServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
/**
* Stops the SystemUI component for a particular user - this function should not be called
* for the system user.
*/
public static void stopSystemUiForUser(Context context, @UserIdInt int userId) {
Preconditions.checkArgument(userId != UserHandle.SYSTEM.getIdentifier(),
"Cannot stop SystemUI for the system user");
// TODO (b/261192740): add EventLog for SystemUI stopping
String sysUiPackage = PackageManagerHelper.getSystemUiPackageName(context);
PackageManagerHelper.forceStopPackageAsUserEvenWhenStopping(context, sysUiPackage, userId);
}
/**
* Starts UserPickerActivity for the given {@code userId} and {@code displayId}.
*
* @return {@code true} when starting activity succeeds. It can fail in situation like
* package not existing.
*/
public static boolean startUserPickerOnDisplay(Context context,
int displayId, String userPickerActivityPackage) {
if (DBG) {
Slogf.d(TAG, "Starting user picker on display:%d", displayId);
}
// FLAG_ACTIVITY_MULTIPLE_TASK ensures the user picker can show up on multiple displays.
Intent intent = new Intent()
.setComponent(ComponentName.unflattenFromString(
userPickerActivityPackage))
.addFlags(FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse("data://com.android.car/userpicker/display" + displayId));
ActivityOptions activityOptions = ActivityOptions.makeBasic()
.setLaunchDisplayId(displayId);
try {
// Start the user picker as user 0.
ContextHelper.startActivityAsUser(context, intent, activityOptions.toBundle(),
UserHandle.SYSTEM);
return true;
} catch (Exception e) {
Slogf.w(TAG, e, "Cannot start user picker as user 0 on display:%d", displayId);
return false;
}
}
/**
* Generates a random string which consists of captial letters and numbers.
*/
@SuppressLint("DefaultLocale") // Should always have the same format regardless of locale
public static String generateRandomAlphaNumericString(int length) {
StringBuilder sb = new StringBuilder();
int poolSize = CHAR_POOL_FOR_RANDOM_STRING.length;
for (int i = 0; i < length; i++) {
sb.append(CHAR_POOL_FOR_RANDOM_STRING[ThreadLocalRandom.current().nextInt(poolSize)]);
}
return sb.toString();
}
/**
* Encrypts byte array with the keys stored in {@code keyAlias} using AES.
*
* @return Encrypted data and initialization vector in {@link EncryptedData}. {@code null} in
* case of errors.
*/
@Nullable
public static EncryptedData encryptData(byte[] data, String keyAlias) {
SecretKey secretKey = getOrCreateSecretKey(keyAlias);
if (secretKey == null) {
Slogf.e(TAG, "Failed to encrypt data: cannot get a secret key (keyAlias: %s)",
keyAlias);
return null;
}
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return new EncryptedData(cipher.doFinal(data), cipher.getIV());
} catch (Exception e) {
Slogf.e(TAG, e, "Failed to encrypt data: keyAlias=%s", keyAlias);
return null;
}
}
/**
* Decrypts byte array with the keys stored in {@code keyAlias} using AES.
*
* @return Decrypted data in byte array. {@code null} in case of errors.
*/
@Nullable
public static byte[] decryptData(EncryptedData data, String keyAlias) {
SecretKey secretKey = getOrCreateSecretKey(keyAlias);
if (secretKey == null) {
Slogf.e(TAG, "Failed to decrypt data: cannot get a secret key (keyAlias: %s)",
keyAlias);
return null;
}
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, data.getIv());
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
return cipher.doFinal(data.getEncryptedData());
} catch (Exception e) {
Slogf.e(TAG, e, "Failed to decrypt data: keyAlias=%s", keyAlias);
return null;
}
}
/**
* Class to hold encrypted data and its initialization vector.
*/
public static final class EncryptedData {
private final byte[] mEncryptedData;
private final byte[] mIv;
public EncryptedData(byte[] encryptedData, byte[] iv) {
mEncryptedData = encryptedData;
mIv = iv;
}
public byte[] getEncryptedData() {
return mEncryptedData;
}
public byte[] getIv() {
return mIv;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof EncryptedData)) return false;
EncryptedData data = (EncryptedData) other;
return Arrays.equals(mEncryptedData, data.mEncryptedData)
&& Arrays.equals(mIv, data.mIv);
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(mEncryptedData), Arrays.hashCode(mIv));
}
}
@Nullable
private static SecretKey getOrCreateSecretKey(String keyAlias) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE_NAME);
keyStore.load(/* KeyStore.LoadStoreParameter= */ null);
if (keyStore.containsAlias(keyAlias)) {
SecretKeyEntry secretKeyEntry = (SecretKeyEntry) keyStore.getEntry(keyAlias,
/* protParam= */ null);
if (secretKeyEntry != null) {
return secretKeyEntry.getSecretKey();
}
Slogf.e(TAG, "Android key store contains the alias (%s) but the secret key "
+ "entry is null", keyAlias);
return null;
}
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE_NAME);
KeyGenParameterSpec keyGenParameterSpec =
new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT
| KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build();
keyGenerator.init(keyGenParameterSpec);
return keyGenerator.generateKey();
} catch (Exception e) {
Slogf.e(TAG, "Failed to get or create a secret key for the alias (%s)", keyAlias);
return null;
}
}
}