Merge "Update Trusted Device Enrollment APIs."
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index f33934f..7cf03a7 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -169,6 +169,7 @@
public abstract class InstrumentClusterRenderingService extends android.app.Service {
ctor public InstrumentClusterRenderingService();
+ method @Nullable public android.graphics.Bitmap getBitmap(android.net.Uri);
method @MainThread @Nullable public abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
method @MainThread public void onKeyEvent(@NonNull android.view.KeyEvent);
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index 4a9e5f5..f00d6ff 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -32,11 +32,16 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -47,14 +52,19 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
- * A service that used for interaction between Car Service and Instrument Cluster. Car Service may
+ * A service used for interaction between Car Service and Instrument Cluster. Car Service may
* provide internal navigation binder interface to Navigation App and all notifications will be
* eventually land in the {@link NavigationRenderer} returned by {@link #getNavigationRenderer()}.
*
@@ -85,15 +95,45 @@
private static class ContextOwner {
final int mUid;
final int mPid;
+ final Set<String> mPackageNames;
+ final Set<String> mAuthorities;
- ContextOwner(int uid, int pid) {
+ ContextOwner(int uid, int pid, PackageManager packageManager) {
mUid = uid;
mPid = pid;
+ String[] packageNames = uid != 0 ? packageManager.getPackagesForUid(uid)
+ : null;
+ mPackageNames = packageNames != null
+ ? Collections.unmodifiableSet(new HashSet<>(Arrays.asList(packageNames)))
+ : Collections.emptySet();
+ mAuthorities = Collections.unmodifiableSet(mPackageNames.stream()
+ .map(packageName -> getAuthoritiesForPackage(packageManager, packageName))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet()));
}
@Override
public String toString() {
- return "{uid: " + mUid + ", pid: " + mPid + "}";
+ return "{uid: " + mUid + ", pid: " + mPid + ", packagenames: " + mPackageNames
+ + ", authorities: " + mAuthorities + "}";
+ }
+
+ private List<String> getAuthoritiesForPackage(PackageManager packageManager,
+ String packageName) {
+ try {
+ ProviderInfo[] providers = packageManager.getPackageInfo(packageName,
+ PackageManager.GET_PROVIDERS).providers;
+ if (providers == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(providers)
+ .map(provider -> provider.authority)
+ .collect(Collectors.toList());
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package name not found while retrieving content provider authorities: "
+ + packageName);
+ return Collections.emptyList();
+ }
}
}
@@ -199,7 +239,7 @@
*/
@Nullable
private ComponentName getNavigationComponentByOwner(ContextOwner contextOwner) {
- for (String packageName : getPackageNamesForUid(contextOwner)) {
+ for (String packageName : contextOwner.mPackageNames) {
ComponentName component = getComponentFromPackage(packageName);
if (component != null) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -211,14 +251,6 @@
return null;
}
- private String[] getPackageNamesForUid(ContextOwner contextOwner) {
- if (contextOwner == null || contextOwner.mUid == 0 || contextOwner.mPid == 0) {
- return new String[0];
- }
- String[] packageNames = getPackageManager().getPackagesForUid(contextOwner.mUid);
- return packageNames != null ? packageNames : new String[0];
- }
-
private ContextOwner getNavigationContextOwner() {
synchronized (mLock) {
return mNavContextOwner;
@@ -338,8 +370,7 @@
writer.println("activity options: " + mActivityOptions);
writer.println("activity state: " + mActivityState);
writer.println("current nav component: " + mNavigationComponent);
- writer.println("current nav packages: " + Arrays.toString(getPackageNamesForUid(
- getNavigationContextOwner())));
+ writer.println("current nav packages: " + getNavigationContextOwner().mPackageNames);
}
private class RendererBinder extends IInstrumentCluster.Stub {
@@ -356,8 +387,11 @@
@Override
public void setNavigationContextOwner(int uid, int pid) throws RemoteException {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Updating navigation ownership to uid: " + uid + ", pid: " + pid);
+ }
synchronized (mLock) {
- mNavContextOwner = new ContextOwner(uid, pid);
+ mNavContextOwner = new ContextOwner(uid, pid, getPackageManager());
}
mUiHandler.post(InstrumentClusterRenderingService.this::updateNavigationActivity);
}
@@ -419,4 +453,57 @@
}
return result.get();
}
+
+ /**
+ * Fetches a bitmap from the navigation context owner (application holding navigation focus).
+ * It returns null if:
+ * <ul>
+ * <li>there is no navigation context owner
+ * <li>or if the {@link Uri} is invalid
+ * <li>or if it references a process other than the current navigation context owner
+ * </ul>
+ * This is a costly operation. Returned bitmaps should be cached and fetching should be done on
+ * a secondary thread.
+ */
+ @Nullable
+ public Bitmap getBitmap(Uri uri) {
+ try {
+ ContextOwner contextOwner = getNavigationContextOwner();
+ if (contextOwner == null) {
+ Log.e(TAG, "No context owner available while fetching: " + uri);
+ return null;
+ }
+
+ String host = uri.getHost();
+
+ if (!contextOwner.mAuthorities.contains(host)) {
+ Log.e(TAG, "Uri points to an authority not handled by the current context owner: "
+ + uri + " (valid authorities: " + contextOwner.mAuthorities + ")");
+ return null;
+ }
+
+ // Add user to URI to make the request to the right instance of content provider
+ // (see ContentProvider#getUserIdFromAuthority()).
+ int userId = UserHandle.getUserId(contextOwner.mUid);
+ Uri filteredUid = uri.buildUpon().encodedAuthority(userId + "@" + host).build();
+
+ // Fetch the bitmap
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Requesting bitmap: " + uri);
+ }
+ ParcelFileDescriptor fileDesc = getContentResolver()
+ .openFileDescriptor(filteredUid, "r");
+ if (fileDesc != null) {
+ Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
+ fileDesc.close();
+ return bitmap;
+ } else {
+ Log.e(TAG, "Failed to create pipe for uri string: " + uri);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "Unable to fetch uri: " + uri, e);
+ }
+
+ return null;
+ }
}
diff --git a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
index 4681a8b..644e17e 100644
--- a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
@@ -18,7 +18,6 @@
import android.annotation.SystemApi;
import android.annotation.UiThread;
import android.car.navigation.CarNavigationInstrumentCluster;
-import android.graphics.Bitmap;
import android.os.Bundle;
/**
@@ -32,10 +31,10 @@
/**
* Returns properties of instrument cluster for navigation.
*/
- abstract public CarNavigationInstrumentCluster getNavigationProperties();
+ public abstract CarNavigationInstrumentCluster getNavigationProperties();
/**
* Called when an event is fired to change the navigation state.
*/
- abstract public void onEvent(int eventType, Bundle bundle);
+ public abstract void onEvent(int eventType, Bundle bundle);
}
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index a6c680b..998c47a 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -22,6 +22,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Enable multi-user. -->
<bool name="config_enableMultiUserUI">true</bool>
+ <!-- Maximum number of users we allow to be running at a time -->
+ <integer name="config_multiuserMaxRunningUsers">2</integer>
<!-- If true, all guest users created on the device will be ephemeral. -->
<bool name="config_guestUserEphemeral">true</bool>
<!-- Car Mode -->
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
index e2f59d8..ea83ad3 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/styles_device_default.xml
@@ -76,6 +76,12 @@
<item name="android:textColor">@*android:color/car_button_text_color</item>
</style>
+ <style name="Widget.DeviceDefault.TextView" parent="android:Widget.Material.TextView">
+ <item name="android:ellipsize">none</item>
+ <item name="android:requiresFadingEdge">horizontal</item>
+ <item name="android:fadingEdgeLength">@*android:dimen/car_textview_fading_edge_length</item>
+ </style>
+
<style name="Widget.DeviceDefault.Button" parent="android:Widget.Material.Button">
<item name="android:background">@*android:drawable/car_button_background</item>
<item name="android:layout_height">@*android:dimen/car_button_height</item>
diff --git a/evs/sampleDriver/EvsEnumerator.cpp b/evs/sampleDriver/EvsEnumerator.cpp
index 9838258..96fd067 100644
--- a/evs/sampleDriver/EvsEnumerator.cpp
+++ b/evs/sampleDriver/EvsEnumerator.cpp
@@ -35,9 +35,10 @@
std::list<EvsEnumerator::CameraRecord> EvsEnumerator::sCameraList;
wp<EvsGlDisplay> EvsEnumerator::sActiveDisplay;
+// Number of trials to open the camera.
+static const unsigned int kMaxRetry = 3;
-EvsEnumerator::EvsEnumerator()
-: kMaxRetry(3) {
+EvsEnumerator::EvsEnumerator() {
ALOGD("EvsEnumerator created");
enumerateDevices();
@@ -80,6 +81,21 @@
Return<void> EvsEnumerator::getCameraList(getCameraList_cb _hidl_cb) {
ALOGD("getCameraList");
+ if (sCameraList.size() < 1) {
+ // WAR: this assumes that the device has at least one compatible camera and
+ // therefore keeps trying until it succeeds to open.
+ // TODO: this is required for external USB camera so would be better to
+ // subscribe hot-plug event.
+ unsigned tries = 0;
+ ALOGI("No camera is available; enumerate devices again.");
+ while (sCameraList.size() < 1 && tries++ < kMaxRetry) {
+ enumerateDevices();
+
+ // TODO: remove this.
+ usleep(5000);
+ }
+ }
+
const unsigned numCameras = sCameraList.size();
// Build up a packed array of CameraDesc for return
@@ -104,19 +120,6 @@
// Is this a recognized camera id?
CameraRecord *pRecord = findCameraById(cameraId);
- // WAR: this assumes that the device has at least one compatible camera and
- // therefore keeps trying until it succeeds to open.
- // TODO: this is required for external USB camera so would be better to
- // subscribe hot-plug event.
- unsigned tries = 0;
- while (!pRecord && tries++ < kMaxRetry) {
- ALOGI("Requested camera %s not found, enumerate again, %d", cameraId.c_str(), tries);
- enumerateDevices();
- pRecord = findCameraById(cameraId);
-
- // TODO: remove this.
- usleep(5000);
- }
// Has this camera already been instantiated by another caller?
sp<EvsV4lCamera> pActiveCamera = pRecord->activeInstance.promote();
diff --git a/evs/sampleDriver/EvsEnumerator.h b/evs/sampleDriver/EvsEnumerator.h
index 96c2e91..e7e94d4 100644
--- a/evs/sampleDriver/EvsEnumerator.h
+++ b/evs/sampleDriver/EvsEnumerator.h
@@ -48,8 +48,6 @@
// Implementation details
EvsEnumerator();
- const unsigned kMaxRetry;
-
private:
struct CameraRecord {
CameraDesc desc;
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 9086a4a..34c4c31 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -179,6 +179,11 @@
android:label="@string/car_permission_label_diag_clear"
android:description="@string/car_permission_desc_diag_clear" />
<permission
+ android:name="android.car.permission.BIND_VMS_CLIENT"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_vms_client"
+ android:description="@string/car_permission_desc_bind_vms_client" />
+ <permission
android:name="android.car.permission.VMS_PUBLISHER"
android:protectionLevel="system|signature"
android:label="@string/car_permission_label_vms_publisher"
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 41b6815..6f5975a 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -47,10 +47,17 @@
blocked in a UX restricted state.
Format of each entry is either to specify package name to whitelist the whole package or
use format of "packagename/activity_classname" for tagging each activities.
+ For general guidelines to design distraction optimized apps, please refer
+ to Android Auto Driver Distraction Guidelines. -->
+ <string name="activityWhitelist"></string>
+ <!-- Comma separated list of activities that need to be exempted from getting
+ blocked in a UX restricted state.
+ Format of each entry is either to specify package name to whitelist the whole package or
+ use format of "packagename/activity_classname" for tagging each activities.
The current implementations expects the following system packages/activities to be
whitelisted. For general guidelines to design distraction optimized apps, please refer
to Android Auto Driver Distraction Guidelines. -->
- <string name="activityWhitelist">com.android.systemui,com.google.android.packageinstaller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity,android/com.android.internal.app.ResolverActivity</string>
+ <string name="systemActivityWhitelist">com.android.systemui,com.google.android.packageinstaller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity,android/com.android.internal.app.ResolverActivity</string>
<!-- Comma separated list of activities that will be blocked during restricted state.
Format of each entry is either to specify package name to whitelist the whole package
or use format of "packagename/activity_classname" for tagging each activities.-->
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 0855727..0d9e698 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -124,14 +124,19 @@
<string name="car_permission_desc_diag_clear">Clear diagnostic data from the car</string>
<!-- Permission text: apps can publish VMS data [CHAR LIMIT=NONE] -->
- <string name="car_permission_label_vms_publisher">VMS publisher</string>
+ <string name="car_permission_label_vms_publisher">VMS Publisher</string>
<!-- Permission text: apps can send VMS messages to the car [CHAR LIMIT=NONE] -->
- <string name="car_permission_desc_vms_publisher">Publish vms messages</string>
+ <string name="car_permission_desc_vms_publisher">Publish VMS messages</string>
<!-- Permission text: apps can subscribe to VMS data [CHAR LIMIT=NONE] -->
- <string name="car_permission_label_vms_subscriber">VMS subscriber</string>
+ <string name="car_permission_label_vms_subscriber">VMS Subscriber</string>
<!-- Permission text: apps can receive VMS messages from the car [CHAR LIMIT=NONE] -->
- <string name="car_permission_desc_vms_subscriber">Subscribe to vms messages</string>
+ <string name="car_permission_desc_vms_subscriber">Subscribe to VMS messages</string>
+
+ <!-- Permission text: apps can act as VMS router core [CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_bind_vms_client">VMS Client Service</string>
+ <!-- Permission text: apps can act as VMS router core [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_bind_vms_client">Bind to VMS clients</string>
<!-- Permission text: apps can monitor flash storage usage [CHAR LIMIT=NONE] -->
<string name="car_permission_label_storage_monitoring">Flash storage monitoring</string>
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index d4d34be..0fc83dc 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -30,26 +30,22 @@
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.ICarPropertyEventListener;
-import android.car.userlib.CarUserManagerHelper;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
-import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.JsonReader;
import android.util.JsonWriter;
import android.util.Log;
+import android.util.Slog;
+import com.android.car.systeminterface.SystemInterface;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -88,14 +84,15 @@
private static final float SPEED_NOT_AVAILABLE = -1.0F;
@VisibleForTesting
- /* package */ static final String CONFIG_FILENAME_PRODUCTION = "prod_config.json";
+ /* package */ static final String CONFIG_FILENAME_PRODUCTION =
+ "ux_restrictions_prod_config.json";
@VisibleForTesting
- /* package */ static final String CONFIG_FILENAME_STAGED = "staged_config.json";
+ /* package */ static final String CONFIG_FILENAME_STAGED =
+ "ux_restrictions_staged_config.json";
private final Context mContext;
private final CarDrivingStateService mDrivingStateService;
private final CarPropertyService mCarPropertyService;
- private final CarUserManagerHelper mCarUserManagerHelper;
// List of clients listening to UX restriction events.
private final List<UxRestrictionsClient> mUxRClients = new ArrayList<>();
private CarUxRestrictionsConfiguration mCarUxRestrictionsConfiguration;
@@ -107,66 +104,14 @@
// For dumpsys logging
private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>();
- // When UXR service boots up the context does not have access to storage yet, so it
- // will likely read configuration from XML resource. Register to receive broadcast to
- // attempt to read saved configuration when it becomes available.
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
- // If the system user is not headless, then we can read config as soon as the
- // system has completed booting.
- if (!mCarUserManagerHelper.isHeadlessSystemUser()) {
- logd("not headless on boot complete");
- PendingResult pendingResult = goAsync();
- LoadRestrictionsTask task = new LoadRestrictionsTask(pendingResult);
- task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- logd("USER_SWITCHED: " + userHandle);
- if (mCarUserManagerHelper.isHeadlessSystemUser()
- && userHandle > UserHandle.USER_SYSTEM) {
- PendingResult pendingResult = goAsync();
- LoadRestrictionsTask task = new LoadRestrictionsTask(pendingResult);
- task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- }
- }
- };
-
- private class LoadRestrictionsTask extends AsyncTask<Void, Void, Void> {
- private final BroadcastReceiver.PendingResult mPendingResult;
-
- private LoadRestrictionsTask(BroadcastReceiver.PendingResult pendingResult) {
- mPendingResult = pendingResult;
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- mCarUxRestrictionsConfiguration = loadConfig();
- handleDispatchUxRestrictions(mDrivingStateService.getCurrentDrivingState().eventValue,
- getCurrentSpeed());
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- mPendingResult.finish();
- }
- }
-
public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService,
- CarPropertyService propertyService, CarUserManagerHelper carUserManagerHelper) {
+ CarPropertyService propertyService) {
mContext = context;
mDrivingStateService = drvService;
mCarPropertyService = propertyService;
- mCarUserManagerHelper = carUserManagerHelper;
- // NOTE: during boot phase context cannot access file system so we most likely will
- // use XML config. If prod config is set, it will be loaded in broadcast receiver.
- mCarUxRestrictionsConfiguration = loadConfig();
+ // Dir for config files are not available at this point. Read from XML.
+ // If prod config is set, it will be loaded during init().
+ mCarUxRestrictionsConfiguration = readXmlConfig();
// Unrestricted until driving state information is received. During boot up, we don't want
// everything to be blocked until data is available from CarPropertyManager. If we start
// driving and we don't get speed or gear information, we have bigger problems.
@@ -183,17 +128,12 @@
// subscribe to property service for speed
mCarPropertyService.registerListener(VehicleProperty.PERF_VEHICLE_SPEED,
PROPERTY_UPDATE_RATE, mICarPropertyEventListener);
- registerReceiverToLoadConfig();
+ // Load config again after car driving state service inits. At this stage the driving
+ // state is known, which determines whether it's safe to load new config.
+ mCarUxRestrictionsConfiguration = loadConfig();
initializeUxRestrictions();
}
- private void registerReceiverToLoadConfig() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
- mContext.registerReceiver(mBroadcastReceiver, filter);
- }
-
@Override
public CarUxRestrictionsConfiguration getConfig() {
return mCarUxRestrictionsConfiguration;
@@ -201,6 +141,7 @@
/**
* Loads a UX restrictions configuration and returns it.
+ *
* <p>Reads config from the following sources in order:
* <ol>
* <li>saved config set by
@@ -208,14 +149,16 @@
* <li>XML resource config from {@code R.xml.car_ux_restrictions_map};
* <li>hardcoded default config.
* </ol>
+ *
+ * This method attempts to promote staged config file. Doing which depends on driving state.
*/
@VisibleForTesting
/* package */ synchronized CarUxRestrictionsConfiguration loadConfig() {
promoteStagedConfig();
- CarUxRestrictionsConfiguration config = null;
+ CarUxRestrictionsConfiguration config;
// Production config, if available, is the first choice.
- File prodConfig = mContext.getFileStreamPath(CONFIG_FILENAME_PRODUCTION);
+ File prodConfig = getFile(CONFIG_FILENAME_PRODUCTION);
if (prodConfig.exists()) {
logd("Attempting to read production config");
config = readPersistedConfig(prodConfig);
@@ -236,6 +179,11 @@
return createDefaultConfig();
}
+ private File getFile(String filename) {
+ SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
+ return new File(systemInterface.getSystemCarDir(), filename);
+ }
+
@Nullable
private CarUxRestrictionsConfiguration readXmlConfig() {
try {
@@ -248,7 +196,7 @@
}
private void promoteStagedConfig() {
- Path stagedConfig = mContext.getFileStreamPath(CONFIG_FILENAME_STAGED).toPath();
+ Path stagedConfig = getFile(CONFIG_FILENAME_STAGED).toPath();
CarDrivingStateEvent currentDrivingStateEvent =
mDrivingStateService.getCurrentDrivingState();
@@ -256,7 +204,8 @@
if (currentDrivingStateEvent != null
&& currentDrivingStateEvent.eventValue == CarDrivingStateEvent.DRIVING_STATE_PARKED
&& Files.exists(stagedConfig)) {
- Path prod = mContext.getFileStreamPath(CONFIG_FILENAME_PRODUCTION).toPath();
+
+ Path prod = getFile(CONFIG_FILENAME_PRODUCTION).toPath();
try {
logd("Attempting to promote stage config");
Files.move(stagedConfig, prod, REPLACE_EXISTING);
@@ -400,7 +349,7 @@
@Override
@Nullable
public CarUxRestrictionsConfiguration getStagedConfig() {
- File stagedConfig = mContext.getFileStreamPath(CONFIG_FILENAME_STAGED);
+ File stagedConfig = getFile(CONFIG_FILENAME_STAGED);
if (stagedConfig.exists()) {
logd("Attempting to read staged config");
return readPersistedConfig(stagedConfig);
@@ -415,7 +364,8 @@
* IO access on file is not thread safe. Caller should ensure threading protection.
*/
private boolean persistConfig(CarUxRestrictionsConfiguration config, String filename) {
- AtomicFile stagedFile = new AtomicFile(mContext.getFileStreamPath(filename));
+ File file = getFile(filename);
+ AtomicFile stagedFile = new AtomicFile(file);
FileOutputStream fos;
try {
fos = stagedFile.startWrite();
@@ -700,7 +650,7 @@
private static void logd(String msg) {
if (DBG) {
- Log.d(TAG, msg);
+ Slog.d(TAG, msg);
}
}
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index d81285a..2789186 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -123,7 +123,7 @@
mCarPropertyService = new CarPropertyService(serviceContext, mHal.getPropertyHal());
mCarDrivingStateService = new CarDrivingStateService(serviceContext, mCarPropertyService);
mCarUXRestrictionsService = new CarUxRestrictionsManagerService(serviceContext,
- mCarDrivingStateService, mCarPropertyService, mUserManagerHelper);
+ mCarDrivingStateService, mCarPropertyService);
mCarPackageManagerService = new CarPackageManagerService(serviceContext,
mCarUXRestrictionsService,
mSystemActivityMonitoringService);
@@ -466,6 +466,7 @@
private static final String COMMAND_ENABLE_UXR = "enable-uxr";
private static final String COMMAND_GARAGE_MODE = "garage-mode";
private static final String COMMAND_GET_DO_ACTIVITIES = "get-do-activities";
+ private static final String COMMAND_GET_CARPROPERTYCONFIG = "get-carpropertyconfig";
private static final String PARAM_DAY_MODE = "day";
private static final String PARAM_NIGHT_MODE = "night";
@@ -490,6 +491,8 @@
pw.println("\t Force into garage mode or check status.");
pw.println("\tget-do-activities pkgname");
pw.println("\t Get Distraction Optimized activities in given package.");
+ pw.println("\tget-carpropertyconfig [propertyId]");
+ pw.println("\t Get a CarPropertyConfig by Id in Hex or list all CarPropertyConfigs");
}
public void exec(String[] args, PrintWriter writer) {
@@ -557,6 +560,10 @@
}
}
break;
+ case COMMAND_GET_CARPROPERTYCONFIG:
+ String propertyId = args.length < 2 ? "" : args[1];
+ mHal.dumpPropertyConfigs(writer, propertyId);
+ break;
default:
writer.println("Unknown command: \"" + arg + "\"");
dumpHelp(writer);
diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java
index 468dde4..c2abdc6 100644
--- a/service/src/com/android/car/SystemActivityMonitoringService.java
+++ b/service/src/com/android/car/SystemActivityMonitoringService.java
@@ -17,6 +17,7 @@
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
+import android.app.ActivityOptions;
import android.app.IActivityManager;
import android.app.IProcessObserver;
import android.app.TaskStackListener;
@@ -34,6 +35,7 @@
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.view.Display;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -54,11 +56,16 @@
public static class TopTaskInfoContainer {
public final ComponentName topActivity;
public final int taskId;
+ public final int displayId;
+ public final int position;
public final StackInfo stackInfo;
- private TopTaskInfoContainer(ComponentName topActivity, int taskId, StackInfo stackInfo) {
+ private TopTaskInfoContainer(ComponentName topActivity, int taskId,
+ int displayId, int position, StackInfo stackInfo) {
this.topActivity = topActivity;
this.taskId = taskId;
+ this.displayId = displayId;
+ this.position = position;
this.stackInfo = stackInfo;
}
@@ -66,14 +73,17 @@
return taskInfo != null
&& Objects.equals(this.topActivity, taskInfo.topActivity)
&& this.taskId == taskInfo.taskId
+ && this.displayId == taskInfo.displayId
+ && this.position == taskInfo.position
&& this.stackInfo.userId == taskInfo.stackInfo.userId;
}
@Override
public String toString() {
return String.format(
- "TaskInfoContainer [topActivity=%s, taskId=%d, stackId=%d, userId=%d",
- topActivity, taskId, stackInfo.stackId, stackInfo.userId);
+ "TaskInfoContainer [topActivity=%s, taskId=%d, stackId=%d, userId=%d, "
+ + "displayId=%d, position=%d",
+ topActivity, taskId, stackInfo.stackId, stackInfo.userId, displayId, position);
}
}
@@ -137,15 +147,16 @@
@Override
public void dump(PrintWriter writer) {
writer.println("*SystemActivityMonitoringService*");
- writer.println(" Top Tasks:");
+ writer.println(" Top Tasks per display:");
synchronized (this) {
for (int i = 0; i < mTopTasks.size(); i++) {
+ int displayId = mTopTasks.keyAt(i);
TopTaskInfoContainer info = mTopTasks.valueAt(i);
if (info != null) {
- writer.println(info);
+ writer.println("display id " + displayId + ": " + info);
}
}
- writer.println(" Foregroud uid-pids:");
+ writer.println(" Foreground uid-pids:");
for (Integer key : mForegroundUidPids.keySet()) {
Set<Integer> pids = mForegroundUidPids.get(key);
if (pids == null) {
@@ -267,47 +278,39 @@
return;
}
mTasksToDispatch.clear();
+
+ SparseArray<TopTaskInfoContainer> topTasks = new SparseArray<>();
ActivityLaunchListener listener;
synchronized (this) {
listener = mActivityLaunchListener;
- Set<Integer> allStackIds = new ArraySet<>(infos.size());
- Set<Integer> stackIdsToRemove = new ArraySet<>(infos.size());
for (StackInfo info : infos) {
- int stackId = info.stackId;
- allStackIds.add(info.stackId);
+ int displayId = info.displayId;
if (info.taskNames.length == 0 || !info.visible) { // empty stack or not shown
- stackIdsToRemove.add(stackId);
continue;
}
TopTaskInfoContainer newTopTaskInfo = new TopTaskInfoContainer(
- info.topActivity, info.taskIds[info.taskIds.length - 1], info);
- TopTaskInfoContainer currentTopTaskInfo = mTopTasks.get(stackId);
+ info.topActivity, info.taskIds[info.taskIds.length - 1],
+ info.displayId, info.position, info);
+ TopTaskInfoContainer currentTopTaskInfo = topTasks.get(displayId);
- // if a new task is added to stack or focused stack changes, should notify
if (currentTopTaskInfo == null ||
- !currentTopTaskInfo.isMatching(newTopTaskInfo) ||
- (focusedStackId == stackId && focusedStackId != mFocusedStackId)) {
- mTopTasks.put(stackId, newTopTaskInfo);
- mTasksToDispatch.add(newTopTaskInfo);
+ newTopTaskInfo.position > currentTopTaskInfo.position) {
+ topTasks.put(displayId, newTopTaskInfo);
if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
- Log.i(CarLog.TAG_AM, "New top task: " + newTopTaskInfo);
+ Log.i(CarLog.TAG_AM, "Current top task " + newTopTaskInfo);
}
}
}
- for (int i = 0; i < mTopTasks.size(); i++) {
- TopTaskInfoContainer topTask = mTopTasks.valueAt(i);
- if (topTask == null) {
- Log.wtf(CarLog.TAG_AM, "unexpected null value in sparse array");
- continue;
- }
- if (!allStackIds.contains(mTopTasks.keyAt(i))) {
- stackIdsToRemove.add(mTopTasks.keyAt(i));
+ // Assuming displays remains the same.
+ for (int i = 0; i < topTasks.size(); i++) {
+ int displayId = topTasks.keyAt(i);
+ TopTaskInfoContainer current = topTasks.get(displayId);
+ TopTaskInfoContainer previous = mTopTasks.get(displayId);
+ if (!current.isMatching(previous)) {
+ mTasksToDispatch.add(current);
+ mTopTasks.put(displayId, current);
}
}
- for (int stackIdToRemove : stackIdsToRemove) {
- mTopTasks.remove(stackIdToRemove);
- }
- mFocusedStackId = focusedStackId;
}
if (listener != null) {
for (TopTaskInfoContainer topTask : mTasksToDispatch) {
@@ -374,7 +377,11 @@
* block the current task with the provided new activity.
*/
private void handleBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) {
- mContext.startActivityAsUser(newActivityIntent,
+ // Only block default display.
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
+
+ mContext.startActivityAsUser(newActivityIntent, options.toBundle(),
new UserHandle(currentTask.stackInfo.userId));
// Now make stack with new activity focused.
findTaskAndGrantFocus(newActivityIntent.getComponent());
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index f73d07c..428fc3a 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -467,33 +467,8 @@
for (HalServiceBase service: mAllServices) {
service.dump(writer);
}
-
- List<VehiclePropConfig> configList;
- synchronized (this) {
- configList = new ArrayList<>(mAllProperties.values());
- }
-
- writer.println("**All properties**");
- for (VehiclePropConfig config : configList) {
- StringBuilder builder = new StringBuilder()
- .append("Property:0x").append(toHexString(config.prop))
- .append(",Property name:").append(VehicleProperty.toString(config.prop))
- .append(",access:0x").append(toHexString(config.access))
- .append(",changeMode:0x").append(toHexString(config.changeMode))
- .append(",config:0x").append(Arrays.toString(config.configArray.toArray()))
- .append(",fs min:").append(config.minSampleRate)
- .append(",fs max:").append(config.maxSampleRate);
- for (VehicleAreaConfig area : config.areaConfigs) {
- builder.append(",areaId :").append(toHexString(area.areaId))
- .append(",f min:").append(area.minFloatValue)
- .append(",f max:").append(area.maxFloatValue)
- .append(",i min:").append(area.minInt32Value)
- .append(",i max:").append(area.maxInt32Value)
- .append(",i64 min:").append(area.minInt64Value)
- .append(",i64 max:").append(area.maxInt64Value);
- }
- writer.println(builder.toString());
- }
+ // Dump all VHAL property configure.
+ dumpPropertyConfigs(writer, "");
writer.println(String.format("**All Events, now ns:%d**",
SystemClock.elapsedRealtimeNanos()));
for (VehiclePropertyEventInfo info : mEventLog.values()) {
@@ -510,6 +485,55 @@
}
/**
+ * Dump VHAL property configs.
+ *
+ * @param writer
+ * @param propId Property ID in Hex. If propid is empty string, dump all properties.
+ */
+ public void dumpPropertyConfigs(PrintWriter writer, String propId) {
+ List<VehiclePropConfig> configList;
+ synchronized (this) {
+ configList = new ArrayList<>(mAllProperties.values());
+ }
+
+ if (propId.equals("")) {
+ writer.println("**All properties**");
+ for (VehiclePropConfig config : configList) {
+ writer.println(dumpPropertyConfigsHelp(config));
+ }
+ return;
+ }
+ for (VehiclePropConfig config : configList) {
+ if (toHexString(config.prop).equals(propId)) {
+ writer.println(dumpPropertyConfigsHelp(config));
+ return;
+ }
+ }
+
+ }
+
+ /** Use VehiclePropertyConfig to construct string for dumping */
+ private String dumpPropertyConfigsHelp(VehiclePropConfig config) {
+ StringBuilder builder = new StringBuilder()
+ .append("Property:0x").append(toHexString(config.prop))
+ .append(",Property name:").append(VehicleProperty.toString(config.prop))
+ .append(",access:0x").append(toHexString(config.access))
+ .append(",changeMode:0x").append(toHexString(config.changeMode))
+ .append(",config:0x").append(Arrays.toString(config.configArray.toArray()))
+ .append(",fs min:").append(config.minSampleRate)
+ .append(",fs max:").append(config.maxSampleRate);
+ for (VehicleAreaConfig area : config.areaConfigs) {
+ builder.append(",areaId :").append(toHexString(area.areaId))
+ .append(",f min:").append(area.minFloatValue)
+ .append(",f max:").append(area.maxFloatValue)
+ .append(",i min:").append(area.minInt32Value)
+ .append(",i max:").append(area.maxInt32Value)
+ .append(",i64 min:").append(area.minInt64Value)
+ .append(",i64 max:").append(area.maxInt64Value);
+ }
+ return builder.toString();
+ }
+ /**
* Inject a VHAL event
*
* @param property the Vehicle property Id as defined in the HAL
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 6ac9473..136b3e3 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -16,6 +16,7 @@
package com.android.car.pm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.StackInfo;
import android.car.Car;
@@ -51,6 +52,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
+import android.view.Display;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
@@ -93,6 +95,7 @@
// Store the white list and black list strings from the resource file.
private String mConfiguredWhitelist;
+ private String mConfiguredSystemWhitelist;
private String mConfiguredBlacklist;
private final List<String> mAllowedAppInstallSources;
@@ -486,7 +489,7 @@
}
boolean isInstalledPackageMatching(AppBlockingPackageInfo info) {
- PackageInfo packageInfo = null;
+ PackageInfo packageInfo;
try {
packageInfo = mPackageManager.getPackageInfo(info.packageName,
PackageManager.GET_SIGNATURES);
@@ -500,8 +503,8 @@
if ((info.flags & AppBlockingPackageInfo.FLAG_SYSTEM_APP) == 0 ||
(!packageInfo.applicationInfo.isSystemApp() &&
!packageInfo.applicationInfo.isUpdatedSystemApp())) {
- Signature[] signatires = packageInfo.signatures;
- if (!isAnySignatureMatching(signatires, info.signatures)) {
+ Signature[] signatures = packageInfo.signatures;
+ if (!isAnySignatureMatching(signatures, info.signatures)) {
return false;
}
}
@@ -550,15 +553,24 @@
*/
private void generateActivityWhitelistMap() {
HashMap<String, AppBlockingPackageInfoWrapper> activityWhitelist = new HashMap<>();
+
+ // Get the apps/activities that are whitelisted in the configuration XML resources.
+ HashMap<String, Set<String>> configWhitelist = new HashMap<>();
mConfiguredWhitelist = mContext.getString(R.string.activityWhitelist);
- // Get the apps/activities that are whitelisted in the configuration XML resource
- HashMap<String, Set<String>> configWhitelist = parseConfigList(mConfiguredWhitelist);
- if (configWhitelist == null) {
+ if (mConfiguredWhitelist == null) {
if (DBG_POLICY_CHECK) {
- Log.w(CarLog.TAG_PACKAGE, "White list null. No apps whitelisted");
+ Log.w(CarLog.TAG_PACKAGE, "White list is null.");
}
- return;
}
+ parseConfigList(mConfiguredWhitelist, configWhitelist);
+ mConfiguredSystemWhitelist = mContext.getString(R.string.systemActivityWhitelist);
+ if (mConfiguredSystemWhitelist == null) {
+ if (DBG_POLICY_CHECK) {
+ Log.w(CarLog.TAG_PACKAGE, "System white list is null.");
+ }
+ }
+ parseConfigList(mConfiguredSystemWhitelist, configWhitelist);
+
// Add the blocking overlay activity to the whitelist, since that needs to run in a
// restricted state to communicate the reason an app was blocked.
Set<String> defaultActivity = new ArraySet<>();
@@ -689,20 +701,16 @@
*/
private void generateActivityBlacklistMap() {
HashMap<String, AppBlockingPackageInfoWrapper> activityBlacklist = new HashMap<>();
+
+ // Parse blacklisted activities.
+ Map<String, Set<String>> configBlacklist = new HashMap<>();
mConfiguredBlacklist = mContext.getString(R.string.activityBlacklist);
if (mConfiguredBlacklist == null) {
if (DBG_POLICY_CHECK) {
Log.d(CarLog.TAG_PACKAGE, "Null blacklist in config");
}
- return;
}
- Map<String, Set<String>> configBlacklist = parseConfigList(mConfiguredBlacklist);
- if (configBlacklist == null) {
- if (DBG_POLICY_CHECK) {
- Log.w(CarLog.TAG_PACKAGE, "Black list null. No apps blacklisted");
- }
- return;
- }
+ parseConfigList(mConfiguredBlacklist, configBlacklist);
for (String pkg : configBlacklist.keySet()) {
if (TextUtils.isEmpty(pkg)) {
@@ -756,16 +764,22 @@
}
/**
- * Parses the given resource and returns a map of packages and activities.
- * Key is package name and value is list of activities. Empty list implies whole package is
+ * Parses the given resource and updates the input map of packages and activities.
+ *
+ * Key is package name and value is list of activities. Empty set implies whole package is
* included.
+ *
+ * When there are multiple entries regarding one package, the entry with
+ * greater scope wins. Namely if there were 2 entires such that one whitelists
+ * an activity, and the other whitelists the entire package of the activity,
+ * the package is whitelisted, regardless of input order.
*/
- @Nullable
- private HashMap<String, Set<String>> parseConfigList(String configList) {
+ @VisibleForTesting
+ /* package */ void parseConfigList(String configList,
+ @NonNull Map<String, Set<String>> packageToActivityMap) {
if (configList == null) {
- return null;
+ return;
}
- HashMap<String, Set<String>> packageToActivityMap = new HashMap<>();
String[] entries = configList.split(PACKAGE_DELIMITER);
for (String entry : entries) {
String[] packageActivityPair = entry.split(PACKAGE_ACTIVITY_DELIMITER);
@@ -785,7 +799,6 @@
}
}
}
- return packageToActivityMap;
}
@Nullable
@@ -929,8 +942,11 @@
}
private void blockTopActivityIfNecessary(TopTaskInfoContainer topTask) {
- boolean restricted = mUxRestrictionsListener.isRestricted();
- if (!restricted) {
+ // Only block activities launched on default display.
+ if (topTask.displayId != Display.DEFAULT_DISPLAY) {
+ return;
+ }
+ if (!mUxRestrictionsListener.isRestricted()) {
return;
}
doBlockTopActivityIfNotAllowed(topTask);
@@ -957,7 +973,7 @@
return;
}
}
- if (DBG_POLICY_CHECK) {
+ if (DBG_POLICY_ENFORCEMENT) {
Log.i(CarLog.TAG_PACKAGE, "Current activity " + topTask.topActivity +
" not allowed, will block, number of tasks in stack:" +
topTask.stackInfo.taskIds.length);
@@ -1017,6 +1033,7 @@
BLOCKING_INTENT_EXTRA_ROOT_ACTIVITY_NAME, taskRootActivity);
newActivityIntent.putExtra(
BLOCKING_INTENT_EXTRA_IS_ROOT_ACTIVITY_DO, isRootDo);
+
return newActivityIntent;
}
@@ -1268,7 +1285,7 @@
@Override
public void onUxRestrictionsChanged(CarUxRestrictions restrictions) {
- if (DBG_POLICY_CHECK) {
+ if (DBG_POLICY_ENFORCEMENT) {
Log.d(CarLog.TAG_PACKAGE, "Received uxr restrictions: "
+ restrictions.isRequiresDistractionOptimization()
+ " : " + restrictions.getActiveRestrictions());
@@ -1294,7 +1311,7 @@
shouldCheck = true;
}
}
- if (DBG_POLICY_CHECK) {
+ if (DBG_POLICY_ENFORCEMENT) {
Log.d(CarLog.TAG_PACKAGE, "block?: " + shouldCheck);
}
if (shouldCheck) {
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
index 84d67b9..e3ee05a 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
@@ -27,12 +27,9 @@
import android.content.Intent;
import android.graphics.Rect;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -47,37 +44,41 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
/**
* Implementation of {@link InstrumentClusterRenderingService} which renders an activity on a
* virtual display that is transmitted to an external screen.
*/
-public class ClusterRenderingServiceImpl extends InstrumentClusterRenderingService {
+public class ClusterRenderingServiceImpl extends InstrumentClusterRenderingService implements
+ ImageResolver.BitmapFetcher {
private static final String TAG = "Cluster.SampleService";
private static final int NO_DISPLAY = -1;
+ static final int NAV_STATE_EVENT_ID = 1;
static final String LOCAL_BINDING_ACTION = "local";
static final String NAV_STATE_BUNDLE_KEY = "navstate";
- static final int NAV_STATE_EVENT_ID = 1;
- static final int MSG_SET_ACTIVITY_LAUNCH_OPTIONS = 1;
- static final int MSG_ON_NAVIGATION_STATE_CHANGED = 2;
- static final int MSG_ON_KEY_EVENT = 3;
- static final int MSG_REGISTER_CLIENT = 4;
- static final int MSG_UNREGISTER_CLIENT = 5;
- static final String MSG_KEY_CATEGORY = "category";
- static final String MSG_KEY_ACTIVITY_DISPLAY_ID = "activity_display_id";
- static final String MSG_KEY_ACTIVITY_STATE = "activity_state";
- static final String MSG_KEY_KEY_EVENT = "key_event";
- private List<Messenger> mClients = new ArrayList<>();
+ private List<ServiceClient> mClients = new ArrayList<>();
private ClusterDisplayProvider mDisplayProvider;
private int mDisplayId = NO_DISPLAY;
- private final IBinder mLocalBinder = new Messenger(new MessageHandler(this)).getBinder();
+ private final IBinder mLocalBinder = new LocalBinder();
+ private final ImageResolver mImageResolver = new ImageResolver(this);
+
+ public interface ServiceClient {
+ void onKeyEvent(KeyEvent keyEvent);
+ void onNavigationStateChange(NavigationState navState);
+ }
+
+ public class LocalBinder extends Binder {
+ ClusterRenderingServiceImpl getService() {
+ return ClusterRenderingServiceImpl.this;
+ }
+ }
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
@@ -98,43 +99,33 @@
}
};
- private static class MessageHandler extends Handler {
- private final WeakReference<ClusterRenderingServiceImpl> mService;
-
- MessageHandler(ClusterRenderingServiceImpl service) {
- mService = new WeakReference<>(service);
+ public void setActivityLaunchOptions(int displayId, ClusterActivityState state) {
+ ActivityOptions options = displayId != Display.INVALID_DISPLAY
+ ? ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
+ : null;
+ setClusterActivityLaunchOptions(options);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("activity options set: %s (displayeId: %d)",
+ options, options.getLaunchDisplayId()));
}
-
- @Override
- public void handleMessage(Message msg) {
- Log.d(TAG, "handleMessage: " + msg.what);
- switch (msg.what) {
- case MSG_SET_ACTIVITY_LAUNCH_OPTIONS: {
- int displayId = msg.getData().getInt(MSG_KEY_ACTIVITY_DISPLAY_ID);
- Bundle state = msg.getData().getBundle(MSG_KEY_ACTIVITY_STATE);
- String category = msg.getData().getString(MSG_KEY_CATEGORY);
- ActivityOptions options = displayId != Display.INVALID_DISPLAY
- ? ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
- : null;
- mService.get().setClusterActivityLaunchOptions(category, options);
- Log.d(TAG, String.format("activity options set: %s = %s (displayeId: %d)",
- category, options, options.getLaunchDisplayId()));
- mService.get().setClusterActivityState(category, state);
- Log.d(TAG, String.format("activity state set: %s = %s", category, state));
- break;
- }
- case MSG_REGISTER_CLIENT:
- mService.get().mClients.add(msg.replyTo);
- break;
- case MSG_UNREGISTER_CLIENT:
- mService.get().mClients.remove(msg.replyTo);
- break;
- default:
- super.handleMessage(msg);
- }
+ setClusterActivityState(state);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("activity state set: %s", state));
}
}
+ public void registerClient(ServiceClient client) {
+ mClients.add(client);
+ }
+
+ public void unregisterClient(ServiceClient client) {
+ mClients.remove(client);
+ }
+
+ public ImageResolver getImageResolver() {
+ return mImageResolver;
+ }
+
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind, intent: " + intent);
@@ -161,30 +152,20 @@
@Override
public void onKeyEvent(KeyEvent keyEvent) {
- Log.d(TAG, "onKeyEvent, keyEvent: " + keyEvent);
- Bundle data = new Bundle();
- data.putParcelable(MSG_KEY_KEY_EVENT, keyEvent);
- broadcastClientMessage(MSG_ON_KEY_EVENT, data);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onKeyEvent, keyEvent: " + keyEvent);
+ }
+ broadcastClientEvent(client -> client.onKeyEvent(keyEvent));
}
/**
- * Broadcasts a message to all the registered service clients
+ * Broadcasts an event to all the registered service clients
*
- * @param what event identifier
- * @param data event data
+ * @param event event to broadcast
*/
- private void broadcastClientMessage(int what, Bundle data) {
- Log.d(TAG, "broadcast message " + what + " to " + mClients.size() + " clients");
- for (int i = mClients.size() - 1; i >= 0; i--) {
- Messenger client = mClients.get(i);
- try {
- Message msg = Message.obtain(null, what);
- msg.setData(data);
- client.send(msg);
- } catch (RemoteException ex) {
- Log.e(TAG, "Client " + i + " is dead", ex);
- mClients.remove(i);
- }
+ private void broadcastClientEvent(Consumer<ServiceClient> event) {
+ for (ServiceClient client : mClients) {
+ event.accept(client);
}
}
@@ -210,7 +191,7 @@
bundleSummary.append(navState.toString());
// Update clients
- broadcastClientMessage(MSG_ON_NAVIGATION_STATE_CHANGED, bundle);
+ broadcastClientEvent(client -> client.onNavigationStateChange(navState));
} else {
for (String key : bundle.keySet()) {
bundleSummary.append(key);
@@ -222,9 +203,8 @@
Log.d(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
} catch (Exception e) {
Log.e(TAG, "Error parsing event data (" + eventType + ", " + bundle + ")", e);
- bundle.putParcelable(NAV_STATE_BUNDLE_KEY, new NavigationState.Builder().build()
- .toParcelable());
- broadcastClientMessage(MSG_ON_NAVIGATION_STATE_CHANGED, bundle);
+ NavigationState navState = new NavigationState.Builder().build();
+ broadcastClientEvent(client -> client.onNavigationStateChange(navState));
}
}
};
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java
index e0d0d12..ae86850 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/CueView.java
@@ -18,19 +18,34 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
+import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
+import android.util.Log;
import android.widget.TextView;
+import androidx.car.cluster.navigation.ImageReference;
import androidx.car.cluster.navigation.RichText;
import androidx.car.cluster.navigation.RichTextElement;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
/**
* View component that displays the Cue information on the instrument cluster display
*/
public class CueView extends TextView {
+ private static final String TAG = "Cluster.CueView";
+
private String mImageSpanText;
+ private CompletableFuture<?> mFuture;
+ private Handler mHandler = new Handler();
+ private RichText mContent;
public CueView(Context context) {
super(context);
@@ -45,20 +60,45 @@
mImageSpanText = context.getString(R.string.span_image);
}
- public void setRichText(RichText richText) {
+ public void setRichText(RichText richText, ImageResolver imageResolver) {
if (richText == null) {
setText(null);
return;
}
+ if (mFuture != null && !Objects.equals(richText, mContent)) {
+ mFuture.cancel(true);
+ }
+
+ List<ImageReference> imageReferences = richText.getElements().stream()
+ .filter(element -> element.getImage() != null)
+ .map(element -> element.getImage())
+ .collect(Collectors.toList());
+ mFuture = imageResolver
+ .getBitmaps(imageReferences, 0, getLineHeight())
+ .thenAccept(bitmaps -> {
+ mHandler.post(() -> update(richText, bitmaps));
+ mFuture = null;
+ })
+ .exceptionally(ex -> {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unable to fetch images for cue: " + richText);
+ }
+ mHandler.post(() -> update(richText, Collections.emptyMap()));
+ return null;
+ });
+ mContent = richText;
+ }
+
+ private void update(RichText richText, Map<ImageReference, Bitmap> bitmaps) {
SpannableStringBuilder builder = new SpannableStringBuilder();
+
for (RichTextElement element : richText.getElements()) {
if (builder.length() > 0) {
builder.append(" ");
}
if (element.getImage() != null) {
- Bitmap bitmap = ImageResolver.getInstance().getBitmapConstrained(getContext(),
- element.getImage(), 0, getLineHeight());
+ Bitmap bitmap = bitmaps.get(element.getImage());
if (bitmap != null) {
String imageText = element.getText().isEmpty() ? mImageSpanText :
element.getText();
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java
index f306143..5e03b9b 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ImageResolver.java
@@ -15,95 +15,126 @@
*/
package android.car.cluster.sample;
-import android.content.ContentResolver;
-import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
-import android.os.ParcelFileDescriptor;
import android.util.Log;
+import android.util.LruCache;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.car.cluster.navigation.ImageReference;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
/**
* Class for retrieving bitmap images from a ContentProvider
*/
public class ImageResolver {
private static final String TAG = "Cluster.ImageResolver";
+ private static final int IMAGE_CACHE_SIZE_BYTES = 4 * 1024 * 1024; /* 4 mb */
- private static ImageResolver sImageResolver = new ImageResolver();
+ private final BitmapFetcher mFetcher;
+ private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(
+ IMAGE_CACHE_SIZE_BYTES) {
+ @Override
+ protected int sizeOf(String key, Bitmap value) {
+ return value.getByteCount();
+ }
+ };
- private ImageResolver() {}
-
- public static ImageResolver getInstance() {
- return sImageResolver;
+ public interface BitmapFetcher {
+ Bitmap getBitmap(Uri uri);
}
/**
- * Returns a bitmap from an URI string from a content provider
- *
- * @param context View context
+ * Creates a resolver that delegate the image retrieval to the given fetcher.
*/
- @Nullable
- public Bitmap getBitmap(Context context, Uri uri) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Requesting: " + uri);
- }
- try {
- ContentResolver contentResolver = context.getContentResolver();
- ParcelFileDescriptor fileDesc = contentResolver.openFileDescriptor(uri, "r");
- if (fileDesc != null) {
- Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDesc.getFileDescriptor());
- fileDesc.close();
- return bitmap;
- } else {
- Log.e(TAG, "Null pointer: Failed to create pipe for uri string: " + uri);
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "File not found for uri string: " + uri, e);
- } catch (IOException e) {
- Log.e(TAG, "File descriptor could not close: ", e);
- }
-
- return null;
+ public ImageResolver(BitmapFetcher fetcher) {
+ mFetcher = fetcher;
}
/**
- * Returns a bitmap from a Car Instrument Cluster {@link ImageReference} that would fit inside
- * the provided size. Either width, height or both should be greater than 0.
+ * Returns a {@link CompletableFuture} that provides a bitmap from a {@link ImageReference}.
+ * This image would fit inside the provided size. Either width, height or both should be greater
+ * than 0.
*
- * @param context View context
* @param width required width, or 0 if width is flexible based on height.
* @param height required height, or 0 if height is flexible based on width.
*/
- @Nullable
- public Bitmap getBitmapConstrained(Context context, ImageReference img, int width,
- int height) {
+ @NonNull
+ public CompletableFuture<Bitmap> getBitmap(@NonNull ImageReference img, int width, int height) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, String.format("Requesting image %s (width: %d, height: %d)",
img.getRawContentUri(), width, height));
}
- // Adjust the size to fit in the requested box.
- Point adjusted = getAdjustedSize(img.getOriginalWidth(), img.getOriginalHeight(), width,
- height);
- if (adjusted == null) {
- Log.e(TAG, "The provided image has no original size: " + img.getRawContentUri());
- return null;
- }
- Bitmap bitmap = getBitmap(context, img.getContentUri(adjusted.x, adjusted.y));
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("Returning image %s (width: %d, height: %d)",
- img.getRawContentUri(), width, height));
- }
- return bitmap != null ? Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true)
- : null;
+ return CompletableFuture.supplyAsync(() -> {
+ // Adjust the size to fit in the requested box.
+ Point adjusted = getAdjustedSize(img.getOriginalWidth(), img.getOriginalHeight(), width,
+ height);
+ if (adjusted == null) {
+ Log.e(TAG, "The provided image has no original size: " + img.getRawContentUri());
+ return null;
+ }
+ Uri uri = img.getContentUri(adjusted.x, adjusted.y);
+ Bitmap bitmap = mCache.get(uri.toString());
+ if (bitmap == null) {
+ bitmap = mFetcher.getBitmap(uri);
+ if (bitmap == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unable to fetch image: " + uri);
+ }
+ return null;
+ }
+ if (bitmap.getWidth() != adjusted.x || bitmap.getHeight() != adjusted.y) {
+ bitmap = Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true);
+ }
+ mCache.put(uri.toString(), bitmap);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("Returning image %s (width: %d, height: %d)",
+ img.getRawContentUri(), width, height));
+ }
+ return bitmap != null ? Bitmap.createScaledBitmap(bitmap, adjusted.x, adjusted.y, true)
+ : null;
+ });
+ }
+
+ /**
+ * Same as {@link #getBitmap(ImageReference, int, int)} but it works on a list of images. The
+ * returning {@link CompletableFuture} will contain a map from each {@link ImageReference} to
+ * its bitmap. If any image fails to be fetched, the whole future completes exceptionally.
+ *
+ * @param width required width, or 0 if width is flexible based on height.
+ * @param height required height, or 0 if height is flexible based on width.
+ */
+ @NonNull
+ public CompletableFuture<Map<ImageReference, Bitmap>> getBitmaps(
+ @NonNull List<ImageReference> imgs, int width, int height) {
+ CompletableFuture<Map<ImageReference, Bitmap>> future = new CompletableFuture<>();
+
+ Map<ImageReference, CompletableFuture<Bitmap>> bitmapFutures = imgs.stream().collect(
+ Collectors.toMap(
+ img -> img,
+ img -> getBitmap(img, width, height)));
+
+ CompletableFuture.allOf(bitmapFutures.values().toArray(new CompletableFuture[0]))
+ .thenAccept(v -> {
+ Map<ImageReference, Bitmap> bitmaps = bitmapFutures.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry
+ .getValue().join()));
+ future.complete(bitmaps);
+ })
+ .exceptionally(ex -> {
+ future.completeExceptionally(ex);
+ return null;
+ });
+
+ return future;
}
/**
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index 3181b1e..13a1399 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -16,17 +16,6 @@
package android.car.cluster.sample;
import static android.car.cluster.sample.ClusterRenderingServiceImpl.LOCAL_BINDING_ACTION;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_DISPLAY_ID;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_ACTIVITY_STATE;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_CATEGORY;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_KEY_KEY_EVENT;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_ON_KEY_EVENT;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl
- .MSG_ON_NAVIGATION_STATE_CHANGED;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_REGISTER_CLIENT;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl
- .MSG_SET_ACTIVITY_LAUNCH_OPTIONS;
-import static android.car.cluster.sample.ClusterRenderingServiceImpl.MSG_UNREGISTER_CLIENT;
import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
@@ -48,9 +37,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -69,7 +55,6 @@
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProviders;
-import androidx.versionedparcelable.ParcelUtils;
import androidx.viewpager.widget.ViewPager;
import java.lang.ref.WeakReference;
@@ -95,7 +80,8 @@
* This is necessary because the navigation app runs under a normal user, and different users will
* see different instances of the same application, with their own personalized data.
*/
-public class MainClusterActivity extends FragmentActivity {
+public class MainClusterActivity extends FragmentActivity implements
+ ClusterRenderingServiceImpl.ServiceClient {
private static final String TAG = "Cluster.MainActivity";
private static final NavigationState NULL_NAV_STATE = new NavigationState.Builder().build();
@@ -110,8 +96,7 @@
private Map<Sensors.Gear, View> mGearsToIcon = new HashMap<>();
private InputMethodManager mInputMethodManager;
- private Messenger mService;
- private Messenger mServiceCallbacks = new Messenger(new MessageHandler(this));
+ private ClusterRenderingServiceImpl mService;
private VirtualDisplay mPendingVirtualDisplay = null;
private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
@@ -142,7 +127,7 @@
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
- mPager.setCurrentItem(mButtonToFacet.get(v).order);
+ mPager.setCurrentItem(mButtonToFacet.get(v).mOrder);
}
}
};
@@ -151,8 +136,9 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected, name: " + name + ", service: " + service);
- mService = new Messenger(service);
- sendServiceMessage(MSG_REGISTER_CLIENT, null, mServiceCallbacks);
+ mService = ((ClusterRenderingServiceImpl.LocalBinder) service).getService();
+ mService.registerClient(MainClusterActivity.this);
+ mNavStateController.setImageResolver(mService.getImageResolver());
if (mPendingVirtualDisplay != null) {
// If haven't reported the virtual display yet, do so on service connect.
reportNavDisplay(mPendingVirtualDisplay);
@@ -164,44 +150,11 @@
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected, name: " + name);
mService = null;
+ mNavStateController.setImageResolver(null);
onNavigationStateChange(NULL_NAV_STATE);
}
};
- private static class MessageHandler extends Handler {
- private final WeakReference<MainClusterActivity> mActivity;
-
- MessageHandler(MainClusterActivity activity) {
- mActivity = new WeakReference<>(activity);
- }
-
- @Override
- public void handleMessage(Message msg) {
- Bundle data = msg.getData();
- switch (msg.what) {
- case MSG_ON_KEY_EVENT:
- KeyEvent event = data.getParcelable(MSG_KEY_KEY_EVENT);
- if (event != null) {
- mActivity.get().onKeyEvent(event);
- }
- break;
- case MSG_ON_NAVIGATION_STATE_CHANGED:
- if (data == null) {
- mActivity.get().onNavigationStateChange(null);
- } else {
- data.setClassLoader(ParcelUtils.class.getClassLoader());
- NavigationState navState = NavigationState
- .fromParcelable(data.getParcelable(
- ClusterRenderingServiceImpl.NAV_STATE_BUNDLE_KEY));
- mActivity.get().onNavigationStateChange(navState);
- }
- break;
- default:
- super.handleMessage(msg);
- }
- }
- }
-
private ActivityMonitor.ActivityListener mNavigationActivityMonitor = (displayId, activity) -> {
if (displayId != mNavigationDisplayId) {
return;
@@ -259,7 +212,7 @@
mPager = findViewById(R.id.pager);
mPager.setAdapter(new ClusterPageAdapter(getSupportFragmentManager()));
- mOrderToFacet.get(0).button.requestFocus();
+ mOrderToFacet.get(0).mButton.requestFocus();
mNavStateController = new NavStateController(findViewById(R.id.navigation_state));
mClusterViewModel = ViewModelProviders.of(this).get(ClusterViewModel.class);
@@ -300,13 +253,14 @@
mUserReceiver.unregister(this);
mActivityMonitor.stop();
if (mService != null) {
- sendServiceMessage(MSG_UNREGISTER_CLIENT, null, mServiceCallbacks);
+ mService.unregisterClient(this);
mService = null;
}
unbindService(mClusterRenderingServiceConnection);
}
- private void onKeyEvent(KeyEvent event) {
+ @Override
+ public void onKeyEvent(KeyEvent event) {
Log.i(TAG, "onKeyEvent, event: " + event);
// This is a hack. We use SOURCE_CLASS_POINTER here because this type of input is associated
@@ -316,7 +270,8 @@
mInputMethodManager.dispatchKeyEventFromInputMethod(getCurrentFocus(), event);
}
- private void onNavigationStateChange(NavigationState state) {
+ @Override
+ public void onNavigationStateChange(NavigationState state) {
Log.d(TAG, "onNavigationStateChange: " + state);
if (mNavStateController != null) {
mNavStateController.update(state);
@@ -338,32 +293,9 @@
}
private void reportNavDisplay(VirtualDisplay virtualDisplay) {
- Bundle data = new Bundle();
- data.putString(MSG_KEY_CATEGORY, Car.CAR_CATEGORY_NAVIGATION);
- data.putInt(MSG_KEY_ACTIVITY_DISPLAY_ID, virtualDisplay.mDisplayId);
- data.putBundle(MSG_KEY_ACTIVITY_STATE, ClusterActivityState
+ mService.setActivityLaunchOptions(virtualDisplay.mDisplayId, ClusterActivityState
.create(virtualDisplay.mDisplayId != Display.INVALID_DISPLAY,
- virtualDisplay.mUnobscuredBounds)
- .toBundle());
- sendServiceMessage(MSG_SET_ACTIVITY_LAUNCH_OPTIONS, data, null);
- }
-
- /**
- * Sends a message to the {@link ClusterRenderingServiceImpl}, which runs on a different
- * process.
- * @param what action to perform
- * @param data action data
- * @param replyTo {@link Messenger} where to reply back
- */
- private void sendServiceMessage(int what, Bundle data, Messenger replyTo) {
- try {
- Message message = Message.obtain(null, what);
- message.setData(data);
- message.replyTo = replyTo;
- mService.send(message);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to deliver message " + what + ". Service died");
- }
+ virtualDisplay.mUnobscuredBounds));
}
public class ClusterPageAdapter extends FragmentPagerAdapter {
@@ -383,21 +315,21 @@
}
private <T> void registerFacet(Facet<T> facet) {
- mOrderToFacet.append(facet.order, facet);
- mButtonToFacet.put(facet.button, facet);
+ mOrderToFacet.append(facet.mOrder, facet);
+ mButtonToFacet.put(facet.mButton, facet);
- facet.button.setOnFocusChangeListener(mFacetButtonFocusListener);
+ facet.mButton.setOnFocusChangeListener(mFacetButtonFocusListener);
}
private static class Facet<T> {
- Button button;
- Class<T> clazz;
- int order;
+ Button mButton;
+ Class<T> mClazz;
+ int mOrder;
Facet(Button button, int order, Class<T> clazz) {
- this.button = button;
- this.order = order;
- this.clazz = clazz;
+ this.mButton = button;
+ this.mOrder = order;
+ this.mClazz = clazz;
}
private Fragment mFragment;
@@ -405,8 +337,9 @@
Fragment getOrCreateFragment() {
if (mFragment == null) {
try {
- mFragment = (Fragment) clazz.getConstructors()[0].newInstance();
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ mFragment = (Fragment) mClazz.getConstructors()[0].newInstance();
+ } catch (InstantiationException | IllegalAccessException
+ | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
index 0d07962..3ba31d7 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
@@ -46,6 +46,7 @@
private TextView mEta;
private CueView mCue;
private Context mContext;
+ private ImageResolver mImageResolver;
/**
* Creates a controller to coordinate updates to the views displaying navigation state
@@ -63,6 +64,10 @@
mContext = container.getContext();
}
+ public void setImageResolver(@Nullable ImageResolver imageResolver) {
+ mImageResolver = imageResolver;
+ }
+
/**
* Updates views to reflect the provided navigation state
*/
@@ -80,7 +85,7 @@
mEta.setTextColor(getTrafficColor(traffic));
mManeuver.setImageDrawable(getManeuverIcon(step != null ? step.getManeuver() : null));
mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
- mCue.setRichText(step != null ? step.getCue() : null);
+ mCue.setRichText(step != null ? step.getCue() : null, mImageResolver);
if (step != null && step.getLanes().size() > 0) {
mLane.setLanes(step.getLanes());
diff --git a/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java b/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java
index 746c0df..0d1cece 100644
--- a/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java
+++ b/tests/DirectRenderingClusterSample/tests/robotests/src/android/car/cluster/sample/ImageResolverTest.java
@@ -32,7 +32,7 @@
@Before
public void setup() {
- mImageResolver = ImageResolver.getInstance();
+ mImageResolver = new ImageResolver((uri) -> null);
}
@Test
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 398ae94..8176e0b 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -68,16 +68,9 @@
android:name="android.car.application"
android:resource="@xml/automotive_app_desc" />
- <activity android:name=".setting.CarServiceSettingsActivity"
- android:label="@string/car_settings">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity>
-
<activity android:name=".orientation.LandscapeActivity"
- android:label="@string/landscpae_activity"
- android:screenOrientation="landscape">
+ android:label="@string/landscpae_activity"
+ android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@@ -91,11 +84,6 @@
</intent-filter>
</activity>
- <activity android:name=".setting.UsbHostManagementActivity"
- android:theme="@android:style/Theme.Material.Light.Dialog"
- android:launchMode="singleTop">
- </activity>
-
<activity android:name=".cluster.FakeClusterNavigationActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleInstance"
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index 38c5bc2..f4e7799 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -57,7 +57,6 @@
import com.google.android.car.kitchensink.power.PowerTestFragment;
import com.google.android.car.kitchensink.property.PropertyTestFragment;
import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
-import com.google.android.car.kitchensink.setting.CarServiceSettingsActivity;
import com.google.android.car.kitchensink.storagelifetime.StorageLifetimeFragment;
import com.google.android.car.kitchensink.touch.TouchTestFragment;
import com.google.android.car.kitchensink.users.UsersFragment;
@@ -152,11 +151,6 @@
new FragmentMenuEntry("audio", AudioTestFragment.class),
new FragmentMenuEntry("bluetooth headset", BluetoothHeadsetFragment.class),
new FragmentMenuEntry("bluetooth messaging test", MapMceTestFragment.class),
- new OnClickMenuEntry("car service settings", () -> {
- Intent intent = new Intent(KitchenSinkActivity.this,
- CarServiceSettingsActivity.class);
- startActivity(intent);
- }),
new FragmentMenuEntry("carboard", KeyboardTestFragment.class),
new FragmentMenuEntry("connectivity", ConnectivityFragment.class),
new FragmentMenuEntry("cubes test", CubesTestFragment.class),
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/CarServiceSettingsActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/CarServiceSettingsActivity.java
deleted file mode 100644
index d5f2cf2..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/CarServiceSettingsActivity.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting;
-
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.widget.Button;
-
-import com.google.android.car.kitchensink.R;
-
-import java.util.List;
-
-public class CarServiceSettingsActivity extends PreferenceActivity {
- private static final String TAG = CarServiceSettingsActivity.class.getSimpleName();
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Button button = new Button(this);
- button.setText("Finish");
- button.setOnClickListener(view -> finish());
- setListFooter(button);
- }
-
- @Override
- public void onBuildHeaders(List<Header> target) {
- loadHeadersFromResource(R.xml.preference_header, target);
- }
-
- @Override
- protected boolean isValidFragment(String fragmentName) {
- if (UsbManagerFragment.class.getName().equals(fragmentName)) {
- return true;
- }
- return false;
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbHostManagementActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbHostManagementActivity.java
deleted file mode 100644
index e025f0d..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbHostManagementActivity.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting;
-
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbManager;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.google.android.car.kitchensink.R;
-import com.google.android.car.kitchensink.setting.usb.UsbDeviceSettings;
-import com.google.android.car.kitchensink.setting.usb.UsbHostController;
-
-import java.util.List;
-
-/**
- * Activity to handle USB device attached.
- * <p>
- * When user plugs in USB device:
- * a) Device was used before and user selected handler for it.
- * In this case handler will be launched.
- * b) Device has not handler assigned. In this case supported handlers will be captured,
- * and user will be presented with choice to assign default handler.
- * After that handler will be lauched.
- */
-public class UsbHostManagementActivity extends Activity
- implements UsbHostController.UsbHostControllerCallbacks {
- private static final String TAG = UsbHostManagementActivity.class.getSimpleName();
- private static final boolean LOCAL_LOGD = true;
- private static final boolean LOCAL_LOGV = true;
-
- private HandlersAdapter mListAdapter;
- private ListView mHandlersList;
- private LinearLayout mProgressInfo;
- private UsbHostController mController;
- private PackageManager mPackageManager;
-
- private final AdapterView.OnItemClickListener mHandlerClickListener =
- new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
- UsbDeviceSettings settings = (UsbDeviceSettings) parent.getItemAtPosition(position);
- mController.applyDeviceSettings(settings);
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.usb_host);
- mHandlersList = (ListView) findViewById(R.id.usb_handlers_list);
- mProgressInfo = (LinearLayout) findViewById(R.id.usb_handlers_progress);
- mListAdapter = new HandlersAdapter(this);
- mHandlersList.setAdapter(mListAdapter);
- mHandlersList.setOnItemClickListener(mHandlerClickListener);
- mController = new UsbHostController(this, this);
- mPackageManager = getPackageManager();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mController.release();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- UsbDevice connectedDevice = getDevice();
- if (connectedDevice != null) {
- mController.processDevice(connectedDevice);
- } else {
- finish();
- }
- }
-
- @Override
- public void shutdown() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- finish();
- }
- });
- }
-
- @Override
- public void processingStateChanged(final boolean processing) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mProgressInfo.setVisibility(processing ? View.VISIBLE : View.GONE);
- }
- });
- }
-
- @Override
- public void titleChanged(String title) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- setTitle(title);
- }
- });
- }
-
- @Override
- public void optionsUpdated(List<UsbDeviceSettings> options) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mListAdapter.clear();
- mListAdapter.addAll(options);
- }
- });
- }
-
-
- @Override
- public void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
- }
-
- @Nullable
- private UsbDevice getDevice() {
- if (!UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) {
- return null;
- }
- return (UsbDevice) getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
- }
-
- private static Intent createDeviceAttachedIntent(UsbDevice device) {
- Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- private class HandlersAdapter extends ArrayAdapter<UsbDeviceSettings> {
- class HandlerHolder {
- public TextView mAppName;
- public ImageView mAppIcon;
- }
-
- HandlersAdapter(Context context) {
- super(context, R.layout.usb_handler_row);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View rowView = convertView;
- if (rowView == null) {
- rowView = getLayoutInflater().inflate(R.layout.usb_handler_row, null);
- HandlerHolder holder = new HandlerHolder();
- holder.mAppName = (TextView) rowView.findViewById(R.id.usb_handler_title);
- holder.mAppIcon = (ImageView) rowView.findViewById(R.id.usb_handler_icon);
- rowView.setTag(holder);
- }
-
- HandlerHolder holder = (HandlerHolder) rowView.getTag();
- ComponentName handler = getItem(position).getHandler();
-
- try {
- ApplicationInfo appInfo =
- mPackageManager.getApplicationInfo(handler.getPackageName(), 0);
- holder.mAppName.setText(appInfo.loadLabel(mPackageManager));
- holder.mAppIcon.setImageDrawable(appInfo.loadIcon(mPackageManager));
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Handling package not found: " + handler.getPackageName());
- holder.mAppName.setText(handler.flattenToShortString());
- holder.mAppIcon.setImageResource(android.R.color.transparent);
- }
- return rowView;
- }
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbManagerFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbManagerFragment.java
deleted file mode 100644
index c5656e8..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbManagerFragment.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbManager;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceGroup;
-import android.util.Log;
-
-import com.google.android.car.kitchensink.R;
-import com.google.android.car.kitchensink.setting.usb.UsbDevicePreference;
-import com.google.android.car.kitchensink.setting.usb.UsbDeviceSettings;
-import com.google.android.car.kitchensink.setting.usb.UsbSettingsStorage;
-
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Usb manager handles management for USB device settings.
- */
-public class UsbManagerFragment extends PreferenceFragment
- implements UsbDevicePreference.UsbDevicePreferenceCallback {
-
- // TODO: for produciton settings version we need to handle detach through broadcast.
-
- private static final String TAG = UsbManagerFragment.class.getSimpleName();
- private static final boolean LOCAL_LOGD = true;
- private static final boolean LOCAL_LOGV = true;
-
- private UsbSettingsStorage mUsbSettingsStorage;
- private UsbManager mUsbManager;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.usb_manager_prefs);
- mUsbSettingsStorage = new UsbSettingsStorage(getContext());
- mUsbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- populateSettings();
- }
-
- private void populateSettings() {
- getPreferenceScreen().removeAll();
- HashMap<String, UsbDevice> attachedDevices = mUsbManager.getDeviceList();
- List<UsbDeviceSettings> allSavedSettings = mUsbSettingsStorage.getAllSettings();
- PreferenceGroup availableDevicesCategory =
- createPreferenceCategory(1, R.string.usb_available_devices);
- getPreferenceScreen().addPreference(availableDevicesCategory);
-
- PreferenceGroup savedDevicesCategory =
- createPreferenceCategory(2, R.string.usb_saved_devices);
- getPreferenceScreen().addPreference(savedDevicesCategory);
-
- for (UsbDevice attachedDevice : attachedDevices.values()) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "Attached device: " + attachedDevice);
- }
- if (attachedDevice.getSerialNumber() == null) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Attached device: " + attachedDevice + " misseshave serial number");
- }
- continue;
- }
- UsbDeviceSettings deviceSettings = getSavedSetting(allSavedSettings, attachedDevice);
- if (deviceSettings != null) {
- // This might be slow if there are a lot of settings.
- allSavedSettings.remove(deviceSettings);
- } else {
- deviceSettings = UsbDeviceSettings.constructSettings(attachedDevice);
- }
- UsbDevicePreference preference = new UsbDevicePreference(
- getContext(), deviceSettings, this);
- availableDevicesCategory.addPreference(preference);
- }
- availableDevicesCategory.setEnabled(availableDevicesCategory.getPreferenceCount() > 0);
-
- for (UsbDeviceSettings savedSettings : allSavedSettings) {
- UsbDevicePreference preference = new UsbDevicePreference(
- getContext(), savedSettings, this);
- savedDevicesCategory.addPreference(preference);
- }
- savedDevicesCategory.setEnabled(savedDevicesCategory.getPreferenceCount() > 0);
- }
-
- private PreferenceGroup createPreferenceCategory(int order, int titleId) {
- PreferenceGroup preferenceGroup = new PreferenceCategory(getContext());
- preferenceGroup.setTitle(titleId);
- preferenceGroup.setOrder(order);
- preferenceGroup.setSelectable(false);
- return preferenceGroup;
- }
-
- @Nullable
- private UsbDeviceSettings getSavedSetting(List<UsbDeviceSettings> settings, UsbDevice device) {
- for (UsbDeviceSettings savedSetting : settings) {
- if (savedSetting.matchesDevice(device)) {
- return savedSetting;
- }
- }
- return null;
- }
-
- @Override
- public void onUsbDevicePreferenceDelete(Preference preference, UsbDeviceSettings settings) {
- mUsbSettingsStorage.deleteSettings(
- settings.getSerialNumber(), settings.getVid(), settings.getPid());
- populateSettings();
- }
-
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/AoapInterface.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/AoapInterface.java
deleted file mode 100644
index 8907dc6..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/AoapInterface.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/**
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.hardware.usb.UsbConstants;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
-import android.util.Log;
-
-import java.io.IOException;
-
-final class AoapInterface {
- /**
- * Use Google Vendor ID when in accessory mode
- */
- public static final int USB_ACCESSORY_VENDOR_ID = 0x18D1;
-
- /**
- * Product ID to use when in accessory mode
- */
- public static final int USB_ACCESSORY_PRODUCT_ID = 0x2D00;
-
- /**
- * Product ID to use when in accessory mode and adb is enabled
- */
- public static final int USB_ACCESSORY_ADB_PRODUCT_ID = 0x2D01;
-
- /**
- * Indexes for strings sent by the host via ACCESSORY_SEND_STRING
- */
- public static final int ACCESSORY_STRING_MANUFACTURER = 0;
- public static final int ACCESSORY_STRING_MODEL = 1;
- public static final int ACCESSORY_STRING_DESCRIPTION = 2;
- public static final int ACCESSORY_STRING_VERSION = 3;
- public static final int ACCESSORY_STRING_URI = 4;
- public static final int ACCESSORY_STRING_SERIAL = 5;
-
- /**
- * Control request for retrieving device's protocol version
- *
- * requestType: USB_DIR_IN | USB_TYPE_VENDOR
- * request: ACCESSORY_GET_PROTOCOL
- * value: 0
- * index: 0
- * data version number (16 bits little endian)
- * 1 for original accessory support
- * 2 adds HID and device to host audio support
- */
- public static final int ACCESSORY_GET_PROTOCOL = 51;
-
- /**
- * Control request for host to send a string to the device
- *
- * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
- * request: ACCESSORY_SEND_STRING
- * value: 0
- * index: string ID
- * data zero terminated UTF8 string
- *
- * The device can later retrieve these strings via the
- * ACCESSORY_GET_STRING_* ioctls
- */
- public static final int ACCESSORY_SEND_STRING = 52;
-
- /**
- * Control request for starting device in accessory mode.
- * The host sends this after setting all its strings to the device.
- *
- * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
- * request: ACCESSORY_START
- * value: 0
- * index: 0
- * data none
- */
- public static final int ACCESSORY_START = 53;
-
- /**
- * Max payload size for AOAP. Limited by driver.
- */
- public static final int MAX_PAYLOAD_SIZE = 16384;
-
- /**
- * Accessory write timeout.
- */
- public static final int AOAP_TIMEOUT_MS = 2000;
-
- private static final String TAG = AoapInterface.class.getSimpleName();
-
- public static int getProtocol(UsbDeviceConnection conn) {
- byte[] buffer = new byte[2];
- int len = conn.controlTransfer(
- UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR,
- ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, AOAP_TIMEOUT_MS);
- if (len != 2) {
- return -1;
- }
- return (buffer[1] << 8) | buffer[0];
- }
-
- public static void sendString(UsbDeviceConnection conn, int index, String string)
- throws IOException {
- byte[] buffer = (string + "\0").getBytes();
- int len = conn.controlTransfer(
- UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
- ACCESSORY_SEND_STRING, 0, index,
- buffer, buffer.length, AOAP_TIMEOUT_MS);
- if (len != buffer.length) {
- throw new IOException("Failed to send string " + index + ": \"" + string + "\"");
- } else {
- Log.i(TAG, "Sent string " + index + ": \"" + string + "\"");
- }
- }
-
- public static void sendAoapStart(UsbDeviceConnection conn) throws IOException {
- int len = conn.controlTransfer(
- UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
- ACCESSORY_START, 0, 0, null, 0, AOAP_TIMEOUT_MS);
- if (len < 0) {
- throw new IOException("Control transfer for accessory start failed:" + len);
- }
- }
-
- public static boolean isDeviceInAoapMode(UsbDevice device) {
- if (device == null) {
- return false;
- }
- final int vid = device.getVendorId();
- final int pid = device.getProductId();
- return vid == USB_ACCESSORY_VENDOR_ID
- && (pid == USB_ACCESSORY_PRODUCT_ID || pid == USB_ACCESSORY_ADB_PRODUCT_ID);
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceHandlerResolver.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceHandlerResolver.java
deleted file mode 100644
index c66fd2f..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceHandlerResolver.java
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.car.IUsbAoapSupportCheckService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.res.XmlResourceParser;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbInterface;
-import android.hardware.usb.UsbManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.util.XmlUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-/**
- * Resolves supported handlers for USB device.
- */
-public final class UsbDeviceHandlerResolver
- implements UsbDeviceStateController.UsbDeviceStateListener {
- private static final String TAG = UsbDeviceHandlerResolver.class.getSimpleName();
- private static final boolean LOCAL_LOGD = true;
-
- private static final int MODE_OFF = 0;
- private static final int MODE_PROBE = 1;
- private static final int MODE_PROBE_AOAP = 2;
- private static final int MODE_DISPATCH = 3;
-
- /**
- * Callbacks for device reolver.
- */
- public interface UsbDeviceHandlerResolverCallback {
- /** Handlers are reolved */
- void onHandlersResolveCompleted(
- UsbDevice device, List<UsbDeviceSettings> availableSettings);
- /** Device was dispatched */
- void onDeviceDispatched();
- }
-
- private final UsbManager mUsbManager;
- private final PackageManager mPackageManager;
- private final UsbDeviceHandlerResolverCallback mDeviceCallback;
- private final Context mContext;
- private final HandlerThread mHandlerThread;
- private final UsbDeviceResolverHandler mHandler;
- private final UsbDeviceStateController mStateController;
- private final Queue<Pair<ResolveInfo, DeviceFilter>> mActiveDeviceOptions = new LinkedList<>();
- private final List<UsbDeviceSettings> mActiveDeviceSettings = new ArrayList<>();
-
- private String mActiveDeviceSerial;
- private UsbDevice mActiveUsbDevice;
- private UsbDeviceSettings mBaseSettings;
- private int mDeviceMode = MODE_OFF;
- private Intent mDispatchIntent;
- private int mDispatchUid;
- private IUsbAoapSupportCheckService mIUsbAoapSupportCheckService;
- private boolean mBound;
-
- // This class is used to describe a USB device.
- // When used in HashMaps all values must be specified,
- // but wildcards can be used for any of the fields in
- // the package meta-data.
- private static class DeviceFilter {
- // USB Vendor ID (or -1 for unspecified)
- public final int mVendorId;
- // USB Product ID (or -1 for unspecified)
- public final int mProductId;
- // USB device or interface class (or -1 for unspecified)
- public final int mClass;
- // USB device subclass (or -1 for unspecified)
- public final int mSubclass;
- // USB device protocol (or -1 for unspecified)
- public final int mProtocol;
- // USB device manufacturer name string (or null for unspecified)
- public final String mManufacturerName;
- // USB device product name string (or null for unspecified)
- public final String mProductName;
- // USB device serial number string (or null for unspecified)
- public final String mSerialNumber;
-
- // USB device in AOAP mode manufacturer
- public final String mAoapManufacturer;
- // USB device in AOAP mode modeal
- public final String mAoapModel;
- // USB device in AOAP mode description string
- public final String mAoapDescription;
- // USB device in AOAP mode version
- public final String mAoapVersion;
- // USB device in AOAP mode URI
- public final String mAoapUri;
- // USB device in AOAP mode serial
- public final String mAoapSerial;
- // USB device in AOAP mode verification service
- public final String mAoapService;
-
- DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
- String manufacturer, String product, String serialnum,
- String aoapManufacturer, String aoapModel, String aoapDescription,
- String aoapVersion, String aoapUri, String aoapSerial,
- String aoapService) {
- mVendorId = vid;
- mProductId = pid;
- mClass = clasz;
- mSubclass = subclass;
- mProtocol = protocol;
- mManufacturerName = manufacturer;
- mProductName = product;
- mSerialNumber = serialnum;
-
- mAoapManufacturer = aoapManufacturer;
- mAoapModel = aoapModel;
- mAoapDescription = aoapDescription;
- mAoapVersion = aoapVersion;
- mAoapUri = aoapUri;
- mAoapSerial = aoapSerial;
- mAoapService = aoapService;
- }
-
- DeviceFilter(UsbDevice device) {
- mVendorId = device.getVendorId();
- mProductId = device.getProductId();
- mClass = device.getDeviceClass();
- mSubclass = device.getDeviceSubclass();
- mProtocol = device.getDeviceProtocol();
- mManufacturerName = device.getManufacturerName();
- mProductName = device.getProductName();
- mSerialNumber = device.getSerialNumber();
- mAoapManufacturer = null;
- mAoapModel = null;
- mAoapDescription = null;
- mAoapVersion = null;
- mAoapUri = null;
- mAoapSerial = null;
- mAoapService = null;
- }
-
- public static DeviceFilter read(XmlPullParser parser, boolean aoapData)
- throws XmlPullParserException, IOException {
- int vendorId = -1;
- int productId = -1;
- int deviceClass = -1;
- int deviceSubclass = -1;
- int deviceProtocol = -1;
- String manufacturerName = null;
- String productName = null;
- String serialNumber = null;
-
- String aoapManufacturer = null;
- String aoapModel = null;
- String aoapDescription = null;
- String aoapVersion = null;
- String aoapUri = null;
- String aoapSerial = null;
- String aoapService = null;
-
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- String name = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
- // Attribute values are ints or strings
- if (!aoapData && "manufacturer-name".equals(name)) {
- manufacturerName = value;
- } else if (!aoapData && "product-name".equals(name)) {
- productName = value;
- } else if (!aoapData && "serial-number".equals(name)) {
- serialNumber = value;
- } else if (aoapData && "manufacturer".equals(name)) {
- aoapManufacturer = value;
- } else if (aoapData && "model".equals(name)) {
- aoapModel = value;
- } else if (aoapData && "description".equals(name)) {
- aoapDescription = value;
- } else if (aoapData && "version".equals(name)) {
- aoapVersion = value;
- } else if (aoapData && "uri".equals(name)) {
- aoapUri = value;
- } else if (aoapData && "serial".equals(name)) {
- aoapSerial = value;
- } else if (aoapData && "service".equals(name)) {
- aoapService = value;
- } else if (!aoapData) {
- int intValue = -1;
- int radix = 10;
- if (value != null && value.length() > 2 && value.charAt(0) == '0'
- && (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
- // allow hex values starting with 0x or 0X
- radix = 16;
- value = value.substring(2);
- }
- try {
- intValue = Integer.parseInt(value, radix);
- } catch (NumberFormatException e) {
- Log.e(TAG, "invalid number for field " + name, e);
- continue;
- }
- if ("vendor-id".equals(name)) {
- vendorId = intValue;
- } else if ("product-id".equals(name)) {
- productId = intValue;
- } else if ("class".equals(name)) {
- deviceClass = intValue;
- } else if ("subclass".equals(name)) {
- deviceSubclass = intValue;
- } else if ("protocol".equals(name)) {
- deviceProtocol = intValue;
- }
- }
- }
- return new DeviceFilter(vendorId, productId,
- deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, serialNumber, aoapManufacturer,
- aoapModel, aoapDescription, aoapVersion, aoapUri, aoapSerial,
- aoapService);
- }
-
- private boolean matches(int clasz, int subclass, int protocol) {
- return ((mClass == -1 || clasz == mClass)
- && (mSubclass == -1 || subclass == mSubclass)
- && (mProtocol == -1 || protocol == mProtocol));
- }
-
- public boolean isAoap() {
- return (mVendorId == AoapInterface.USB_ACCESSORY_VENDOR_ID
- && mProductId == AoapInterface.USB_ACCESSORY_PRODUCT_ID);
- }
-
- public boolean matches(UsbDevice device) {
- if (mVendorId != -1 && device.getVendorId() != mVendorId) {
- return false;
- }
- if (mProductId != -1 && device.getProductId() != mProductId) {
- return false;
- }
- if (mManufacturerName != null && device.getManufacturerName() == null) {
- return false;
- }
- if (mProductName != null && device.getProductName() == null) {
- return false;
- }
- if (mSerialNumber != null && device.getSerialNumber() == null) {
- return false;
- }
- if (mManufacturerName != null && device.getManufacturerName() != null
- && !mManufacturerName.equals(device.getManufacturerName())) {
- return false;
- }
- if (mProductName != null && device.getProductName() != null
- && !mProductName.equals(device.getProductName())) {
- return false;
- }
- if (mSerialNumber != null && device.getSerialNumber() != null
- && !mSerialNumber.equals(device.getSerialNumber())) {
- return false;
- }
-
- // check device class/subclass/protocol
- if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
- device.getDeviceProtocol())) {
- return true;
- }
-
- // if device doesn't match, check the interfaces
- int count = device.getInterfaceCount();
- for (int i = 0; i < count; i++) {
- UsbInterface intf = device.getInterface(i);
- if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
- intf.getInterfaceProtocol())) {
- return true;
- }
- }
-
- return false;
- }
-
- @Override
- public boolean equals(Object obj) {
- // can't compare if we have wildcard strings
- if (mVendorId == -1 || mProductId == -1
- || mClass == -1 || mSubclass == -1 || mProtocol == -1) {
- return false;
- }
- if (obj instanceof DeviceFilter) {
- DeviceFilter filter = (DeviceFilter) obj;
-
- if (filter.mVendorId != mVendorId
- || filter.mProductId != mProductId
- || filter.mClass != mClass
- || filter.mSubclass != mSubclass
- || filter.mProtocol != mProtocol) {
- return false;
- }
- if ((filter.mManufacturerName != null && mManufacturerName == null)
- || (filter.mManufacturerName == null && mManufacturerName != null)
- || (filter.mProductName != null && mProductName == null)
- || (filter.mProductName == null && mProductName != null)
- || (filter.mSerialNumber != null && mSerialNumber == null)
- || (filter.mSerialNumber == null && mSerialNumber != null)) {
- return false;
- }
- if ((filter.mManufacturerName != null && mManufacturerName != null
- && !mManufacturerName.equals(filter.mManufacturerName))
- || (filter.mProductName != null && mProductName != null
- && !mProductName.equals(filter.mProductName))
- || (filter.mSerialNumber != null && mSerialNumber != null
- && !mSerialNumber.equals(filter.mSerialNumber))) {
- return false;
- }
- return true;
- }
- if (obj instanceof UsbDevice) {
- UsbDevice device = (UsbDevice) obj;
- if (device.getVendorId() != mVendorId
- || device.getProductId() != mProductId
- || device.getDeviceClass() != mClass
- || device.getDeviceSubclass() != mSubclass
- || device.getDeviceProtocol() != mProtocol) {
- return false;
- }
- if ((mManufacturerName != null && device.getManufacturerName() == null)
- || (mManufacturerName == null && device.getManufacturerName() != null)
- || (mProductName != null && device.getProductName() == null)
- || (mProductName == null && device.getProductName() != null)
- || (mSerialNumber != null && device.getSerialNumber() == null)
- || (mSerialNumber == null && device.getSerialNumber() != null)) {
- return false;
- }
- if ((device.getManufacturerName() != null
- && !mManufacturerName.equals(device.getManufacturerName()))
- || (device.getProductName() != null
- && !mProductName.equals(device.getProductName()))
- || (device.getSerialNumber() != null
- && !mSerialNumber.equals(device.getSerialNumber()))) {
- return false;
- }
- return true;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return (((mVendorId << 16) | mProductId)
- ^ ((mClass << 16) | (mSubclass << 8) | mProtocol));
- }
-
- @Override
- public String toString() {
- return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
- + ",mClass=" + mClass + ",mSubclass=" + mSubclass
- + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
- + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + "]";
- }
- }
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- Log.i(TAG, "onServiceConnected: " + className);
- mHandler.requestOnServiceConnectionStateChanged(
- IUsbAoapSupportCheckService.Stub.asInterface(service));
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- Log.i(TAG, "onServiceDisconnected: " + className);
- mHandler.requestOnServiceConnectionStateChanged(null);
- }
- };
-
- public UsbDeviceHandlerResolver(UsbManager manager, Context context,
- UsbDeviceHandlerResolverCallback deviceListener) {
- mUsbManager = manager;
- mContext = context;
- mDeviceCallback = deviceListener;
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
- mPackageManager = context.getPackageManager();
- mStateController = new UsbDeviceStateController(context, this, manager);
- mStateController.init();
- }
-
- /**
- * Releases current object.
- */
- public void release() {
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- }
- if (mStateController != null) {
- mStateController.release();
- }
- }
-
- /**
- * Resolves handlers for USB device.
- */
- public void resolve(UsbDevice device) {
- mHandler.requestResolveHandlers(device);
- }
-
- /**
- * Dispatches device to component.
- */
- public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap:" + inAoap);
- }
-
- mActiveUsbDevice = device;
- mDeviceMode = MODE_DISPATCH;
- ActivityInfo activityInfo;
- try {
- activityInfo = mPackageManager.getActivityInfo(component, PackageManager.GET_META_DATA);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Activity not found: " + component);
- return false;
- }
-
- Intent intent = createDeviceAttachedIntent(device);
- if (inAoap) {
- DeviceFilter filter = packageMatches(activityInfo, intent.getAction(), device, true);
- intent.setComponent(component);
- mDispatchIntent = intent;
- mDispatchUid = activityInfo.applicationInfo.uid;
- if (filter != null) {
- requestAoapSwitch(filter);
- return true;
- }
- }
-
- intent.setComponent(component);
- mUsbManager.grantPermission(device, activityInfo.applicationInfo.uid);
-
- mContext.startActivity(intent);
- mHandler.requestCompleteDeviceDispatch();
- return true;
- }
-
- private static Intent createDeviceAttachedIntent(UsbDevice device) {
- Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- private void doHandleResolveHandlers(
- UsbDevice device) {
- mActiveDeviceSettings.clear();
- mActiveDeviceOptions.clear();
- mActiveUsbDevice = device;
- mDeviceMode = MODE_PROBE;
-
- if (LOCAL_LOGD) {
- Log.d(TAG, "doHandleResolveHandlers: " + device);
- }
- boolean maySupportAoap = UsbUtil.possiblyAndroid(device);
- boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
-
- Intent intent = createDeviceAttachedIntent(device);
- List<Pair<ResolveInfo, DeviceFilter>> matches = getDeviceMatches(device, intent, false);
- if (LOCAL_LOGD) {
- Log.d(TAG, "matches size: " + matches.size());
- }
- for (Pair<ResolveInfo, DeviceFilter> info : matches) {
- UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(mActiveUsbDevice);
- setting.setHandler(
- new ComponentName(
- info.first.activityInfo.packageName, info.first.activityInfo.name));
- mActiveDeviceSettings.add(setting);
- }
- mBaseSettings = UsbDeviceSettings.constructSettings(mActiveUsbDevice);
- if (!AoapInterface.isDeviceInAoapMode(device) && maySupportAoap) {
- mActiveDeviceOptions.addAll(getDeviceMatches(device, intent, true));
- handleTryNextAoapMode();
- } else {
- doHandleCompleteDeviceProbing();
- }
- }
-
- private void handleTryNextAoapMode() {
- if (LOCAL_LOGD) {
- Log.d(TAG, "handleTryNextAoapMode");
- }
- Pair<ResolveInfo, DeviceFilter> option = mActiveDeviceOptions.peek();
- if (option == null) {
- mHandler.requestCompleteDeviceProbing();
- return;
- }
- requestAoapSwitch(option.second);
- }
-
- private void requestAoapSwitch(DeviceFilter filter) {
- UsbDeviceStateController.AoapSwitchRequest request =
- new UsbDeviceStateController.AoapSwitchRequest(
- mActiveUsbDevice,
- filter.mAoapManufacturer,
- filter.mAoapModel,
- filter.mAoapDescription,
- filter.mAoapVersion,
- filter.mAoapUri,
- filter.mAoapSerial);
- mStateController.startAoap(request);
- }
-
- private void doHandleCompleteDeviceProbing() {
- if (LOCAL_LOGD) {
- Log.d(TAG, "doHandleCompleteDeviceProbing");
- }
- mDeviceCallback.onHandlersResolveCompleted(mActiveUsbDevice, mActiveDeviceSettings);
- stopDeviceProcessing(mActiveUsbDevice);
- mActiveUsbDevice = null;
- mBaseSettings = null;
- mDeviceMode = MODE_OFF;
- }
-
- private void doHandleAoapStartComplete(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "doHandleAoapStartComplete:" + device + " mode: " + MODE_DISPATCH);
- }
- if (mDeviceMode == MODE_DISPATCH && mDispatchIntent != null) {
- mDispatchIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
- mUsbManager.grantPermission(device, mDispatchUid);
- mContext.startActivity(mDispatchIntent);
- mDispatchIntent = null;
- mDispatchUid = 0;
- mDeviceMode = MODE_OFF;
- mDeviceCallback.onDeviceDispatched();
- return;
- }
- mActiveUsbDevice = device;
- mDeviceMode = MODE_PROBE_AOAP;
- if (device == null) {
- mActiveDeviceOptions.poll();
- handleTryNextAoapMode();
- }
-
- Pair<ResolveInfo, DeviceFilter> option = mActiveDeviceOptions.peek();
- if (option == null) {
- Log.w(TAG, "No more options left.");
- mStateController.startDeviceReset(mActiveUsbDevice);
- return;
- }
- DeviceFilter filter = option.second;
- Intent serviceIntent = new Intent();
- serviceIntent.setComponent(ComponentName.unflattenFromString(option.second.mAoapService));
- boolean bound = mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
- if (bound) {
- mHandler.requestServiceConnectionTimeout();
- } else {
- if (LOCAL_LOGD) {
- Log.d(TAG, "Failed to bind to the service");
- }
- mStateController.startDeviceReset(mActiveUsbDevice);
- }
- }
-
- private void doHandleDeviceResetComplete(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "doHandleDeviceResetComplete:" + device);
- }
- mActiveDeviceOptions.poll();
- mActiveUsbDevice = device;
- mDeviceMode = MODE_PROBE;
- handleTryNextAoapMode();
- }
-
- private void doHandleServiceConnectionStateChanged(IUsbAoapSupportCheckService service) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "doHandleServiceConnectionStateChanged: " + service);
- }
- mBound = service != null;
- mIUsbAoapSupportCheckService = service;
- if (mBound && mActiveUsbDevice != null && mDeviceMode == MODE_PROBE_AOAP) {
- boolean deviceSupported = false;
- try {
- deviceSupported =
- mIUsbAoapSupportCheckService.isDeviceSupported(mActiveUsbDevice);
- } catch (RemoteException e) {
- Log.e(TAG, "Call to remote service failed", e);
- }
- if (deviceSupported) {
- Pair<ResolveInfo, DeviceFilter> option = mActiveDeviceOptions.peek();
-
- UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(mBaseSettings);
- setting.setHandler(
- new ComponentName(
- option.first.activityInfo.packageName, option.first.activityInfo.name));
- setting.setAoap(true);
- mActiveDeviceSettings.add(setting);
- }
- mContext.unbindService(mConnection);
- mBound = false;
- mIUsbAoapSupportCheckService = null;
- mStateController.startDeviceReset(mActiveUsbDevice);
- } else if (mActiveUsbDevice != null && mDeviceMode == MODE_PROBE_AOAP) {
- mStateController.startDeviceReset(mActiveUsbDevice);
- } else {
- handleTryNextAoapMode();
- }
- }
-
- private List<Pair<ResolveInfo, DeviceFilter>> getDeviceMatches(
- UsbDevice device, Intent intent, boolean forAoap) {
- List<Pair<ResolveInfo, DeviceFilter>> matches = new ArrayList<>();
- List<ResolveInfo> resolveInfos =
- mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA);
- for (ResolveInfo resolveInfo : resolveInfos) {
- DeviceFilter filter = packageMatches(resolveInfo.activityInfo,
- intent.getAction(), device, forAoap);
- if (filter != null) {
- matches.add(Pair.create(resolveInfo, filter));
- }
- }
- return matches;
- }
-
- private DeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device,
- boolean forAoap) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "packageMatches ai: " + ai + "metaDataName: " + metaDataName + " forAoap: "
- + forAoap);
- }
- String filterTagName = forAoap ? "usb-aoap-accessory" : "usb-device";
- XmlResourceParser parser = null;
- try {
- parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
- if (parser == null) {
- Log.w(TAG, "no meta-data for " + ai);
- return null;
- }
-
- XmlUtils.nextElement(parser);
- while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (device != null && filterTagName.equals(tagName)) {
- DeviceFilter filter = DeviceFilter.read(parser, forAoap);
- if (forAoap || filter.matches(device)) {
- return filter;
- }
- }
- XmlUtils.nextElement(parser);
- }
- } catch (Exception e) {
- Log.w(TAG, "Unable to load component info " + ai.toString(), e);
- } finally {
- if (parser != null) parser.close();
- }
- return null;
- }
-
- @Override
- public void onDeviceResetComplete(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "onDeviceResetComplete: " + device);
- }
- mHandler.requestOnDeviceResetComplete(device);
- }
-
- @Override
- public void onAoapStartComplete(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "onAoapStartComplete: " + device);
- }
- mHandler.requestOnAoapStartComplete(device);
- }
-
- @Override
- public void onAoapStartFailed(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "onAoapStartFailed: " + device);
- }
- mActiveDeviceOptions.poll();
- handleTryNextAoapMode();
- }
-
- private boolean isDeviceProcessing(UsbDevice device) {
- return mActiveDeviceSerial != null
- && mActiveDeviceSerial.equals(device.getSerialNumber());
- }
-
- private boolean stopDeviceProcessing(UsbDevice device) {
- if (device == null || device.getSerialNumber().equals(mActiveDeviceSerial)) {
- mActiveDeviceSerial = null;
- return true;
- }
- return false;
- }
-
- private boolean startDeviceProcessing(UsbDevice device) {
- if (mActiveDeviceSerial != null) {
- return false;
- } else {
- mActiveDeviceSerial = device.getSerialNumber();
- return true;
- }
- }
-
- private class UsbDeviceResolverHandler extends Handler {
- private static final int MSG_RESOLVE_HANDLERS = 0;
- private static final int MSG_DEVICE_RESET_COMPLETE = 1;
- private static final int MSG_AOAP_START_COMPLETE = 2;
- private static final int MSG_SERVICE_CONNECTION_STATE_CHANGE = 3;
- private static final int MSG_SERVICE_CONNECTION_TIMEOUT = 4;
- private static final int MSG_COMPLETE_PROBING = 5;
- private static final int MSG_COMPLETE_DISPATCH = 6;
-
- private static final long RESCHEDULE_TIMEOUT_MS = 100;
- private static final long CONNECT_TIMEOUT_MS = 5000;
- private static final long FINISH_PROBING_TIMEOUT_MS = 200;
-
- private UsbDeviceResolverHandler(Looper looper) {
- super(looper);
- }
-
- public void requestResolveHandlers(UsbDevice device) {
- Message msg = obtainMessage(MSG_RESOLVE_HANDLERS, device);
- sendMessage(msg);
- }
-
- public void requestOnAoapStartComplete(UsbDevice device) {
- sendMessage(obtainMessage(MSG_AOAP_START_COMPLETE, device));
- }
-
- public void requestOnDeviceResetComplete(UsbDevice device) {
- sendMessage(obtainMessage(MSG_DEVICE_RESET_COMPLETE, device));
- }
-
- public void requestOnServiceConnectionStateChanged(IUsbAoapSupportCheckService service) {
- sendMessage(obtainMessage(MSG_SERVICE_CONNECTION_STATE_CHANGE, service));
- }
-
- public void requestServiceConnectionTimeout() {
- sendEmptyMessageDelayed(MSG_SERVICE_CONNECTION_TIMEOUT, CONNECT_TIMEOUT_MS);
- }
-
- public void requestCompleteDeviceProbing() {
- sendEmptyMessageDelayed(MSG_COMPLETE_PROBING, FINISH_PROBING_TIMEOUT_MS);
- }
-
- public void requestCompleteDeviceDispatch() {
- sendEmptyMessage(MSG_COMPLETE_DISPATCH);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_RESOLVE_HANDLERS:
- UsbDevice device = (UsbDevice) msg.obj;
- // if this device is already being processed - drop the request.
- if (!isDeviceProcessing(device)) {
- if (startDeviceProcessing(device)) {
- doHandleResolveHandlers(device);
- } else {
- // Reschedule this device for processing at later time.
- sendMessageDelayed(msg, RESCHEDULE_TIMEOUT_MS);
- }
- } else {
- Log.i(TAG, "Device is already being processed: " + device);
- }
- break;
- case MSG_AOAP_START_COMPLETE:
- doHandleAoapStartComplete((UsbDevice) msg.obj);
- break;
- case MSG_DEVICE_RESET_COMPLETE:
- doHandleDeviceResetComplete((UsbDevice) msg.obj);
- break;
- case MSG_SERVICE_CONNECTION_STATE_CHANGE:
- removeMessages(MSG_SERVICE_CONNECTION_TIMEOUT);
- doHandleServiceConnectionStateChanged((IUsbAoapSupportCheckService) msg.obj);
- break;
- case MSG_SERVICE_CONNECTION_TIMEOUT:
- Log.i(TAG, "Service connection timeout");
- doHandleServiceConnectionStateChanged(null);
- break;
- case MSG_COMPLETE_PROBING:
- doHandleCompleteDeviceProbing();
- break;
- case MSG_COMPLETE_DISPATCH:
- mDeviceCallback.onDeviceDispatched();
- break;
- default:
- Log.w(TAG, "Unsupported message: " + msg);
- }
- }
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDevicePreference.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDevicePreference.java
deleted file mode 100644
index 62f624e..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDevicePreference.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.preference.Preference;
-
-import com.google.android.car.kitchensink.R;
-
-/**
- * Setting preferabce used for USB devices.
- */
-public final class UsbDevicePreference extends Preference
- implements Preference.OnPreferenceClickListener {
-
- /**
- * Callbacks to handle preference changes.
- */
- public interface UsbDevicePreferenceCallback {
- /** Preference deleted */
- void onUsbDevicePreferenceDelete(Preference preference, UsbDeviceSettings settings);
- }
-
- private static final String TAG = UsbDevicePreference.class.getSimpleName();
-
- private final UsbDeviceSettings mUsbDeviceSettings;
- private final UsbDevicePreferenceCallback mCallback;
-
- public UsbDevicePreference(Context context, UsbDeviceSettings usbDeviceSettings,
- UsbDevicePreferenceCallback callback) {
- super(context);
- mCallback = callback;
- mUsbDeviceSettings = usbDeviceSettings;
- setTitle(usbDeviceSettings.getDeviceName());
- if (usbDeviceSettings.getHandler() != null) {
- setSummary(usbDeviceSettings.getHandler().flattenToShortString());
- }
- setOnPreferenceClickListener(this);
- }
-
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.usb_pref_delete_title)
- .setMessage(String.format(
- getContext().getResources().getString(R.string.usb_pref_delete_message),
- mUsbDeviceSettings.getDeviceName()))
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setPositiveButton(R.string.usb_pref_delete_yes,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- mCallback.onUsbDevicePreferenceDelete(
- preference, mUsbDeviceSettings);
- }})
- .setNegativeButton(R.string.usb_pref_delete_cancel, null)
- .show();
- return true;
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceSettings.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceSettings.java
deleted file mode 100644
index 7d4dc8a..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceSettings.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.content.ComponentName;
-import android.hardware.usb.UsbDevice;
-
-import com.android.internal.util.Preconditions;
-
-/**
- * Settings for USB device.
- * @hide
- */
-public final class UsbDeviceSettings {
-
- private final String mSerialNumber;
- private final int mVid;
- private final int mPid;
- private String mDeviceName;
- private ComponentName mHandler;
- private boolean mAoap;
-
- UsbDeviceSettings(String serialNumber, int vid, int pid) {
- Preconditions.checkNotNull(serialNumber);
-
- mSerialNumber = serialNumber;
- mVid = vid;
- mPid = pid;
- }
-
- public String getSerialNumber() {
- return mSerialNumber;
- }
-
- public int getVid() {
- return mVid;
- }
-
- public int getPid() {
- return mPid;
- }
-
- public void setDeviceName(String deviceName) {
- mDeviceName = deviceName;
- }
-
- public String getDeviceName() {
- return mDeviceName;
- }
-
- public void setHandler(ComponentName handler) {
- mHandler = handler;
- }
-
- public ComponentName getHandler() {
- return mHandler;
- }
-
- public void setAoap(boolean aoap) {
- mAoap = aoap;
- }
-
- public boolean getAoap() {
- return mAoap;
- }
-
- @Override
- public String toString() {
- return "UsbDeviceSettings{serial=" + getSerialNumber() + ", vid=" + mVid + "pid=" + mPid
- + "name=" + getDeviceName() + ", handler=" + getHandler().toString()
- + "aoap=" + getAoap() + "}";
- }
-
- /**
- * Checks if setting matches {@code UsbDevice}.
- */
- public boolean matchesDevice(UsbDevice device) {
- return (getSerialNumber().equals(device.getSerialNumber())
- && getVid() == device.getVendorId()
- && getPid() == device.getProductId());
- }
-
- /**
- * Creates settings from {@code UsbDevice}.
- */
- public static UsbDeviceSettings constructSettings(UsbDevice device) {
- UsbDeviceSettings settings = new UsbDeviceSettings(
- device.getSerialNumber(), device.getVendorId(), device.getProductId());
- settings.setDeviceName(device.getProductName());
- return settings;
- }
-
- /**
- * Creates settings from other settings.
- * <p>
- * Only basic properties are inherited.
- */
- public static UsbDeviceSettings constructSettings(UsbDeviceSettings origSettings) {
- UsbDeviceSettings settings = new UsbDeviceSettings(
- origSettings.getSerialNumber(), origSettings.getVid(), origSettings.getPid());
- settings.setDeviceName(origSettings.getDeviceName());
- return settings;
- }
-
- /**
- * Creates settings.
- */
- public static UsbDeviceSettings constructSettings(String serialNumber, int vid, int pid,
- String deviceName, ComponentName handler, boolean aoap) {
- UsbDeviceSettings settings = new UsbDeviceSettings(serialNumber, vid, pid);
- settings.setDeviceName(deviceName);
- settings.setHandler(handler);
- settings.setAoap(aoap);
- return settings;
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceStateController.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceStateController.java
deleted file mode 100644
index 438fbaf..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceStateController.java
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
-import android.hardware.usb.UsbManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-import dalvik.system.CloseGuard;
-
-import java.io.IOException;
-import java.util.LinkedList;
-
-/**
- * Controller to change device into AOAP mode and back.
- */
-class UsbDeviceStateController {
- /**
- * Listener for USB device mode controller.
- */
- public interface UsbDeviceStateListener {
- void onDeviceResetComplete(UsbDevice device);
- void onAoapStartComplete(UsbDevice device);
- void onAoapStartFailed(UsbDevice device);
- }
-
- private static final String TAG = UsbDeviceStateController.class.getSimpleName();
- private static final boolean LOCAL_LOGD = true;
-
- // Because of the bug in UsbDeviceManager total time for AOAP reset should be >10s.
- // 21*500 = 10.5 s.
- private static final int MAX_USB_DETACH_CHANGE_WAIT = 21;
- private static final int MAX_USB_ATTACH_CHANGE_WAIT = 21;
- private static final long USB_STATE_DETACH_WAIT_TIMEOUT_MS = 500;
- private static final long USB_STATE_ATTACH_WAIT_TIMEOUT_MS = 500;
-
- private final Context mContext;
- private final UsbDeviceStateListener mListener;
- private final UsbManager mUsbManager;
- private final HandlerThread mHandlerThread;
- private final UsbStateHandler mHandler;
- private final UsbDeviceBroadcastReceiver mUsbStateBroadcastReceiver;
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
- private final Object mUsbConnectionChangeWait = new Object();
- private final LinkedList<UsbDevice> mDevicesRemoved = new LinkedList<>();
- private final LinkedList<UsbDevice> mDevicesAdded = new LinkedList<>();
- private boolean mShouldQuit = false;
-
- UsbDeviceStateController(Context context, UsbDeviceStateListener listener,
- UsbManager usbManager) {
- mContext = context;
- mListener = listener;
- mUsbManager = usbManager;
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
- mCloseGuard.open("release");
- mHandler = new UsbStateHandler(mHandlerThread.getLooper());
- mUsbStateBroadcastReceiver = new UsbDeviceBroadcastReceiver();
- }
-
- public void init() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
- mContext.registerReceiver(mUsbStateBroadcastReceiver, filter);
- }
-
- public void release() {
- mCloseGuard.close();
- mContext.unregisterReceiver(mUsbStateBroadcastReceiver);
- synchronized (mUsbConnectionChangeWait) {
- mShouldQuit = true;
- mUsbConnectionChangeWait.notifyAll();
- }
- mHandlerThread.quit();
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- mCloseGuard.warnIfOpen();
- boolean release = false;
- synchronized (mUsbConnectionChangeWait) {
- release = !mShouldQuit;
- }
- if (release) {
- release();
- }
- } finally {
- super.finalize();
- }
- }
-
- public void startDeviceReset(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "startDeviceReset: " + device);
- }
- mHandler.requestDeviceReset(device);
- }
-
- public void startAoap(AoapSwitchRequest request) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "startAoap: " + request.device);
- }
- mHandler.requestAoap(request);
- }
-
- private void doHandleDeviceReset(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "doHandleDeviceReset: " + device);
- }
- synchronized (mUsbConnectionChangeWait) {
- mDevicesRemoved.clear();
- mDevicesAdded.clear();
- }
- boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
- UsbDevice completedDevice = null;
- if (isInAoap) {
- completedDevice = resetUsbDeviceAndConfirmModeChange(device);
- } else {
- UsbDeviceConnection conn = openConnection(device);
- if (conn == null) {
- throw new RuntimeException("cannot open conneciton for device: " + device);
- } else {
- try {
- if (!conn.resetDevice()) {
- throw new RuntimeException("resetDevice failed for device: " + device);
- } else {
- completedDevice = device;
- }
- } finally {
- conn.close();
- }
- }
- }
- mListener.onDeviceResetComplete(completedDevice);
- }
-
- private void doHandleAoapStart(AoapSwitchRequest request) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "doHandleAoapStart: " + request.device);
- }
- UsbDevice device = request.device;
- boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
- if (isInAoap) {
- device = resetUsbDeviceAndConfirmModeChange(device);
- if (device == null) {
- mListener.onAoapStartComplete(null);
- return;
- }
- }
- synchronized (mUsbConnectionChangeWait) {
- mDevicesRemoved.clear();
- mDevicesAdded.clear();
- }
- UsbDeviceConnection connection = openConnection(device);
- try {
- AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER,
- request.manufacturer);
- AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL,
- request.model);
- AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION,
- request.description);
- AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION,
- request.version);
- AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI, request.uri);
- AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL,
- request.serial);
- AoapInterface.sendAoapStart(connection);
- device = resetUsbDeviceAndConfirmModeChange(device);
- } catch (IOException e) {
- Log.w(TAG, "Failed to switch device into AOSP mode", e);
- }
- if (device == null) {
- mListener.onAoapStartComplete(null);
- connection.close();
- return;
- }
- if (AoapInterface.isDeviceInAoapMode(device)) {
- mListener.onAoapStartComplete(device);
- } else {
- Log.w(TAG, "Device not in AOAP mode after switching: " + device);
- mListener.onAoapStartFailed(device);
- }
- connection.close();
- }
-
- private UsbDevice resetUsbDeviceAndConfirmModeChange(UsbDevice device) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "resetUsbDeviceAndConfirmModeChange: " + device);
- }
- int retry = 0;
- boolean removalDetected = false;
- while (retry < MAX_USB_DETACH_CHANGE_WAIT) {
- UsbDeviceConnection connNow = openConnection(device);
- if (connNow == null) {
- removalDetected = true;
- break;
- }
- connNow.resetDevice();
- connNow.close();
- synchronized (mUsbConnectionChangeWait) {
- try {
- mUsbConnectionChangeWait.wait(USB_STATE_DETACH_WAIT_TIMEOUT_MS);
- } catch (InterruptedException e) {
- break;
- }
- if (mShouldQuit) {
- return null;
- }
- if (isDeviceRemovedLocked(device)) {
- removalDetected = true;
- break;
- }
- }
- retry++;
- connNow = null;
- }
- if (!removalDetected) {
- Log.w(TAG, "resetDevice failed for device, device still in the same mode: " + device);
- return null;
- }
- retry = 0;
- UsbDevice newlyAttached = null;
- while (retry < MAX_USB_ATTACH_CHANGE_WAIT) {
- synchronized (mUsbConnectionChangeWait) {
- try {
- mUsbConnectionChangeWait.wait(USB_STATE_ATTACH_WAIT_TIMEOUT_MS);
- } catch (InterruptedException e) {
- break;
- }
- if (mShouldQuit) {
- return null;
- }
- newlyAttached = checkDeviceAttachedLocked(device);
- }
- if (newlyAttached != null) {
- break;
- }
- retry++;
- }
- if (newlyAttached == null) {
- Log.w(TAG, "resetDevice failed for device, device disconnected: " + device);
- return null;
- }
- return newlyAttached;
- }
-
- private boolean isDeviceRemovedLocked(UsbDevice device) {
- for (UsbDevice removed : mDevicesRemoved) {
- if (UsbUtil.isDevicesMatching(device, removed)) {
- mDevicesRemoved.clear();
- return true;
- }
- }
- mDevicesRemoved.clear();
- return false;
- }
-
- private UsbDevice checkDeviceAttachedLocked(UsbDevice device) {
- for (UsbDevice attached : mDevicesAdded) {
- if (UsbUtil.isTheSameDevice(device, attached)) {
- mDevicesAdded.clear();
- return attached;
- }
- }
- mDevicesAdded.clear();
- return null;
- }
-
- public UsbDeviceConnection openConnection(UsbDevice device) {
- mUsbManager.grantPermission(device);
- return mUsbManager.openDevice(device);
- }
-
- private void handleUsbDeviceAttached(UsbDevice device) {
- synchronized (mUsbConnectionChangeWait) {
- mDevicesAdded.add(device);
- mUsbConnectionChangeWait.notifyAll();
- }
- }
-
- private void handleUsbDeviceDetached(UsbDevice device) {
- synchronized (mUsbConnectionChangeWait) {
- mDevicesRemoved.add(device);
- mUsbConnectionChangeWait.notifyAll();
- }
- }
-
- private class UsbStateHandler extends Handler {
- private static final int MSG_RESET_DEVICE = 1;
- private static final int MSG_AOAP = 2;
-
- private UsbStateHandler(Looper looper) {
- super(looper);
- }
-
- private void requestDeviceReset(UsbDevice device) {
- Message msg = obtainMessage(MSG_RESET_DEVICE, device);
- sendMessage(msg);
- }
-
- private void requestAoap(AoapSwitchRequest request) {
- Message msg = obtainMessage(MSG_AOAP, request);
- sendMessage(msg);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_RESET_DEVICE:
- doHandleDeviceReset((UsbDevice) msg.obj);
- break;
- case MSG_AOAP:
- doHandleAoapStart((AoapSwitchRequest) msg.obj);
- break;
- }
- }
- }
-
- private class UsbDeviceBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
- UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
- handleUsbDeviceDetached(device);
- } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
- UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
- handleUsbDeviceAttached(device);
- }
- }
- }
-
- public static class AoapSwitchRequest {
- public final UsbDevice device;
- public final String manufacturer;
- public final String model;
- public final String description;
- public final String version;
- public final String uri;
- public final String serial;
-
- AoapSwitchRequest(UsbDevice device, String manufacturer, String model,
- String description, String version, String uri, String serial) {
- this.device = device;
- this.manufacturer = manufacturer;
- this.model = model;
- this.description = description;
- this.version = version;
- this.uri = uri;
- this.serial = serial;
- }
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbHostController.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbHostController.java
deleted file mode 100644
index fe6bdef..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbHostController.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Controller used to handle USB device connections.
- */
-public final class UsbHostController
- implements UsbDeviceHandlerResolver.UsbDeviceHandlerResolverCallback {
-
- /**
- * Callbacks for controller
- */
- public interface UsbHostControllerCallbacks {
- /** Host controller ready for shutdown*/
- void shutdown();
- /** Change of processing state*/
- void processingStateChanged(boolean processing);
- /** Title of processing changed */
- void titleChanged(String title);
- /** Options for USB device changed */
- void optionsUpdated(List<UsbDeviceSettings> options);
- }
-
- private static final String TAG = UsbHostController.class.getSimpleName();
- private static final boolean LOCAL_LOGD = true;
- private static final boolean LOCAL_LOGV = true;
-
-
- private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
- private final Context mContext;
- private final UsbHostControllerCallbacks mCallback;
- private final UsbSettingsStorage mUsbSettingsStorage;
- private final UsbManager mUsbManager;
- private final PackageManager mPackageManager;
- private final UsbDeviceHandlerResolver mUsbReslover;
- private final UsbHostControllerHandler mHandler;
-
- private final BroadcastReceiver mUsbBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
- UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
- unsetActiveDeviceIfSerialMatch(device);
- } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
- UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
- setActiveDeviceIfSerialMatch(device);
- }
- }
- };
-
- @GuardedBy("this")
- private UsbDevice mActiveDevice;
-
- @GuardedBy("this")
- private String mProcessingDeviceSerial;
-
- public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
- mContext = context;
- mCallback = callbacks;
- mHandler = new UsbHostControllerHandler(Looper.myLooper());
- mUsbSettingsStorage = new UsbSettingsStorage(context);
- mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
- mPackageManager = context.getPackageManager();
- mUsbReslover = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
- IntentFilter filter = new IntentFilter();
- filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
- context.registerReceiver(mUsbBroadcastReceiver, filter);
-
- }
-
- private synchronized void setActiveDeviceIfSerialMatch(UsbDevice device) {
- if (device != null && device.getSerialNumber() != null
- && device.getSerialNumber().equals(mProcessingDeviceSerial)) {
- mActiveDevice = device;
- }
- }
-
- private synchronized void unsetActiveDeviceIfSerialMatch(UsbDevice device) {
- mHandler.requestDeviceRemoved();
- if (mActiveDevice != null && mActiveDevice.getSerialNumber() != null
- && mActiveDevice.getSerialNumber().equals(device.getSerialNumber())) {
- mActiveDevice = null;
- }
- }
-
- private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
- if (mActiveDevice == null) {
- mActiveDevice = device;
- mProcessingDeviceSerial = device.getSerialNumber();
- return true;
- }
- return false;
- }
-
- private synchronized void stopDeviceProcessing() {
- mActiveDevice = null;
- mProcessingDeviceSerial = null;
- }
-
- private synchronized UsbDevice getActiveDevice() {
- return mActiveDevice;
- }
-
- private boolean deviceMatchedActiveDevice(UsbDevice device) {
- UsbDevice activeDevice = getActiveDevice();
- return activeDevice != null && activeDevice.getSerialNumber() != null
- && activeDevice.getSerialNumber().equals(device.getSerialNumber());
- }
-
- /**
- * Processes device new device.
- * <p>
- * It will load existing settings or resolve supported handlers.
- */
- public void processDevice(UsbDevice device) {
- if (!startDeviceProcessingIfNull(device)) {
- Log.w(TAG, "Currently, other device is being processed");
- }
- mCallback.optionsUpdated(mEmptyList);
- mCallback.processingStateChanged(true);
-
- UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(
- device.getSerialNumber(), device.getVendorId(), device.getProductId());
- if (settings != null && mUsbReslover.dispatch(
- mActiveDevice, settings.getHandler(), settings.getAoap())) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "Usb Device: " + device + " was sent to component: "
- + settings.getHandler());
- }
- return;
- }
- mCallback.titleChanged(device.getManufacturerName() + " " + device.getProductName());
- mUsbReslover.resolve(device);
- }
-
- /**
- * Applies device settings.
- */
- public void applyDeviceSettings(UsbDeviceSettings settings) {
- mUsbSettingsStorage.saveSettings(settings);
- mUsbReslover.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
- }
-
- /**
- * Release object.
- */
- public void release() {
- mContext.unregisterReceiver(mUsbBroadcastReceiver);
- mUsbReslover.release();
- }
-
- @Override
- public void onHandlersResolveCompleted(
- UsbDevice device, List<UsbDeviceSettings> handlers) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "onHandlersResolveComplete: " + device);
- }
- if (deviceMatchedActiveDevice(device)) {
- mCallback.processingStateChanged(false);
- if (handlers.isEmpty()) {
- onDeviceDispatched();
- } else {
- mCallback.optionsUpdated(handlers);
- }
- } else {
- Log.w(TAG, "Handlers ignored as they came for inactive device");
- }
- }
-
- @Override
- public void onDeviceDispatched() {
- stopDeviceProcessing();
- mCallback.shutdown();
- }
-
- void doHandleDeviceRemoved() {
- if (getActiveDevice() == null) {
- if (LOCAL_LOGD) {
- Log.d(TAG, "USB device detached");
- }
- stopDeviceProcessing();
- mCallback.shutdown();
- }
- }
-
- private static Intent createDeviceAttachedIntent(UsbDevice device) {
- Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- private class UsbHostControllerHandler extends Handler {
- private static final int MSG_DEVICE_REMOVED = 1;
-
- private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
-
- private UsbHostControllerHandler(Looper looper) {
- super(looper);
- }
-
- private void requestDeviceRemoved() {
- sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DEVICE_REMOVED:
- doHandleDeviceRemoved();
- break;
- default:
- Log.w(TAG, "Unhandled message: " + msg);
- super.handleMessage(msg);
- }
- }
- }
-
-
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbSettingsStorage.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbSettingsStorage.java
deleted file mode 100644
index 1f647c9..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbSettingsStorage.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Provides API to persist USB device settings.
- */
-public final class UsbSettingsStorage {
- private static final String TAG = UsbSettingsStorage.class.getSimpleName();
-
- private static final String TABLE_USB_SETTINGS = "usb_devices";
- private static final String COLUMN_SERIAL = "serial";
- private static final String COLUMN_VID = "vid";
- private static final String COLUMN_PID = "pid";
- private static final String COLUMN_NAME = "name";
- private static final String COLUMN_HANDLER = "handler";
- private static final String COLUMN_AOAP = "aoap";
-
- private final UsbSettingsDbHelper mDbHelper;
-
- public UsbSettingsStorage(Context context) {
- mDbHelper = new UsbSettingsDbHelper(context);
- }
-
- /**
- * Returns settings for {@serialNumber} or null if it doesn't exist.
- */
- @Nullable
- public UsbDeviceSettings getSettings(String serialNumber, int vid, int pid) {
- try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
- Cursor resultCursor = db.query(
- TABLE_USB_SETTINGS,
- null,
- COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?",
- new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)},
- null,
- null,
- null)) {
- if (resultCursor.getCount() > 1) {
- throw new RuntimeException("Quering for serial number: " + serialNumber
- + " vid: " + vid + " pid: " + pid + " returned "
- + resultCursor.getCount() + " results");
- }
- if (resultCursor.getCount() == 0) {
- Log.w(TAG, "Usb setting missing for device serial: " + serialNumber
- + " vid: " + vid + " pid: " + pid);
- return null;
- }
- List<UsbDeviceSettings> settings = constructSettings(resultCursor);
- return settings.get(0);
- }
- }
-
- /**
- * Saves or updates settings for USB device.
- */
- public void saveSettings(UsbDeviceSettings settings) {
- try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
- long result = db.replace(
- TABLE_USB_SETTINGS,
- null,
- settingsToContentValues(settings));
- if (result == -1) {
- Log.e(TAG, "Failed to save settings: " + settings);
- }
- }
- }
-
- /**
- * Delete settings for USB device.
- */
- public void deleteSettings(String serialNumber, int vid, int pid) {
- try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
- int result = db.delete(
- TABLE_USB_SETTINGS,
- COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID
- + " = ?",
- new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)});
- if (result == 0) {
- Log.w(TAG, "No settings with serialNumber: " + serialNumber
- + " vid: " + vid + " pid: " + pid);
- }
- if (result > 1) {
- Log.e(TAG, "Deleted multiple rows (" + result + ") for serialNumber: "
- + serialNumber + " vid: " + vid + " pid: " + pid);
- }
- }
- }
-
- /**
- * Returns all saved settings.
- */
- public List<UsbDeviceSettings> getAllSettings() {
- try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
- Cursor resultCursor = db.query(
- TABLE_USB_SETTINGS,
- null,
- null,
- null,
- null,
- null,
- null)) {
- return constructSettings(resultCursor);
- }
- }
-
- private List<UsbDeviceSettings> constructSettings(Cursor cursor) {
- if (!cursor.isBeforeFirst()) {
- throw new RuntimeException("Cursor is not reset to before first element");
- }
- int serialNumberColumnId = cursor.getColumnIndex(COLUMN_SERIAL);
- int vidColumnId = cursor.getColumnIndex(COLUMN_VID);
- int pidColumnId = cursor.getColumnIndex(COLUMN_PID);
- int deviceNameColumnId = cursor.getColumnIndex(COLUMN_NAME);
- int handlerColumnId = cursor.getColumnIndex(COLUMN_HANDLER);
- int aoapColumnId = cursor.getColumnIndex(COLUMN_AOAP);
- List<UsbDeviceSettings> results = new ArrayList<>(cursor.getCount());
- while (cursor.moveToNext()) {
- results.add(UsbDeviceSettings.constructSettings(
- cursor.getString(serialNumberColumnId),
- cursor.getInt(vidColumnId),
- cursor.getInt(pidColumnId),
- cursor.getString(deviceNameColumnId),
- ComponentName.unflattenFromString(
- cursor.getString(handlerColumnId)),
- cursor.getInt(aoapColumnId) != 0));
- }
- return results;
- }
-
- /**
- * Converts {@code UsbDeviceSettings} to {@code ContentValues}.
- */
- public ContentValues settingsToContentValues(UsbDeviceSettings settings) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(COLUMN_SERIAL, settings.getSerialNumber());
- contentValues.put(COLUMN_VID, settings.getVid());
- contentValues.put(COLUMN_PID, settings.getPid());
- contentValues.put(COLUMN_NAME, settings.getDeviceName());
- contentValues.put(COLUMN_HANDLER, settings.getHandler().flattenToShortString());
- contentValues.put(COLUMN_AOAP, settings.getAoap() ? 1 : 0);
- return contentValues;
- }
-
-
- private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
- private static final int DATABASE_VERSION = 2;
- private static final String DATABASE_NAME = "usb_devices.db";
-
- UsbSettingsDbHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_USB_SETTINGS + " ("
- + COLUMN_SERIAL + " TEXT,"
- + COLUMN_VID + " INTEGER,"
- + COLUMN_PID + " INTEGER,"
- + COLUMN_NAME + " TEXT, "
- + COLUMN_HANDLER + " TEXT,"
- + COLUMN_AOAP + " INTEGER,"
- + "PRIMARY KEY (" + COLUMN_SERIAL + ", " + COLUMN_VID + ", "
- + COLUMN_PID + "))");
- }
-
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion < 2) {
- db.execSQL("ALTER TABLE " + TABLE_USB_SETTINGS + " ADD " + COLUMN_AOAP
- + " INTEGER");
- }
- // Do nothing at this point. Not required for v1 database.
- }
- }
-}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbUtil.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbUtil.java
deleted file mode 100644
index e27a86e..0000000
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbUtil.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
-
-import android.hardware.usb.UsbConstants;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbInterface;
-import android.hardware.usb.UsbManager;
-import android.text.TextUtils;
-
-import java.util.HashMap;
-import java.util.LinkedList;
-
-/**
- * Util methods to work with USB devices.
- */
-class UsbUtil {
- public static final String ADB_INTERFACE_NAME = "ADB Interface";
- public static final String AOAP_INTERFACE_NAME = "Android Accessory Interface";
- public static final String MTP_INTERFACE_NAME = "MTP";
-
- public static LinkedList<UsbDevice> findAllPossibleAndroidDevices(UsbManager usbManager) {
- HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
- LinkedList<UsbDevice> androidDevices = null;
- for (UsbDevice device : devices.values()) {
- if (possiblyAndroid(device)) {
- if (androidDevices == null) {
- androidDevices = new LinkedList<>();
- }
- androidDevices.add(device);
- }
- }
- return androidDevices;
- }
-
- public static boolean possiblyAndroid(UsbDevice device) {
- int numInterfaces = device.getInterfaceCount();
- for (int i = 0; i < numInterfaces; i++) {
- UsbInterface usbInterface = device.getInterface(i);
- String interfaceName = usbInterface.getName();
- int interfaceClass = usbInterface.getInterfaceClass();
- // more thorough check can be added, later
- if (AOAP_INTERFACE_NAME.equals(interfaceName)
- || ADB_INTERFACE_NAME.equals(interfaceName)
- || MTP_INTERFACE_NAME.equals(interfaceName)
- || interfaceClass == UsbConstants.USB_CLASS_MASS_STORAGE) {
- return true;
- }
- }
- return false;
- }
-
- public static boolean isTheSameDevice(UsbDevice l, UsbDevice r) {
- if (TextUtils.equals(l.getManufacturerName(), r.getManufacturerName())
- && TextUtils.equals(l.getProductName(), r.getProductName())
- && TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) {
- return true;
- }
- return false;
- }
-
- public static boolean isDevicesMatching(UsbDevice l, UsbDevice r) {
- if (l.getVendorId() == r.getVendorId() && l.getProductId() == r.getProductId()
- && TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) {
- return true;
- }
- return false;
- }
-
- public static boolean isDeviceConnected(UsbManager usbManager, UsbDevice device) {
- HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
- for (UsbDevice dev : devices.values()) {
- if (isDevicesMatching(dev, device)) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/tests/UxRestrictionsSample/Android.mk b/tests/UxRestrictionsSample/Android.mk
index 50419f4..3e006b4 100644
--- a/tests/UxRestrictionsSample/Android.mk
+++ b/tests/UxRestrictionsSample/Android.mk
@@ -42,7 +42,6 @@
LOCAL_STATIC_JAVA_LIBRARIES += vehicle-hal-support-lib
LOCAL_STATIC_ANDROID_LIBRARIES += \
- androidx.car_car \
androidx.legacy_legacy-support-v4 \
androidx.appcompat_appcompat
diff --git a/tests/UxRestrictionsSample/res/layout/activity_sample_message.xml b/tests/UxRestrictionsSample/res/layout/activity_sample_message.xml
deleted file mode 100644
index a778975..0000000
--- a/tests/UxRestrictionsSample/res/layout/activity_sample_message.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <Button
- android:id="@+id/home_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/return_home"
- android:textAllCaps="false"
- android:textAppearance="?android:textAppearanceLarge"/>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <androidx.car.widget.PagedListView
- android:id="@+id/paged_list_view"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:layout_weight="4"/>
- </LinearLayout>
-</LinearLayout>
diff --git a/tests/UxRestrictionsSample/res/layout/main_activity.xml b/tests/UxRestrictionsSample/res/layout/main_activity.xml
index 0b097fb..1d21b50 100644
--- a/tests/UxRestrictionsSample/res/layout/main_activity.xml
+++ b/tests/UxRestrictionsSample/res/layout/main_activity.xml
@@ -125,27 +125,6 @@
android:layout_marginBottom="10dp"
android:background="@android:color/darker_gray"/>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/sample_header"
- android:padding="@dimen/section_padding"
- android:textSize="@dimen/header_text_size"
- android:textAppearance="?android:textAppearanceLarge"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <Button
- android:id="@+id/launch_message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="@dimen/section_padding"
- android:text="@string/sample_msg_activity"
- android:textAllCaps="false"
- android:textSize="@dimen/info_text_size"/>
- </LinearLayout>
-
<View
android:layout_width="match_parent"
android:layout_height="1dp"
diff --git a/tests/UxRestrictionsSample/res/values/strings.xml b/tests/UxRestrictionsSample/res/values/strings.xml
index b36d7f9..aae0e0d 100644
--- a/tests/UxRestrictionsSample/res/values/strings.xml
+++ b/tests/UxRestrictionsSample/res/values/strings.xml
@@ -25,9 +25,6 @@
<string name="disable_uxr" translatable="false">Disable Ux Restriction Engine</string>
<string name="show_staged_config" translatable="false">Show Staged Config</string>
<string name="show_prod_config" translatable="false">Show Production Config</string>
- <string name="sample_header" translatable="false"><u>Sample Activities</u></string>
- <string name="sample_msg_activity" translatable="false">Sample Message Activity</string>
- <string name="return_home" translatable="false"><u>Return Home</u></string>
<string name="save_uxr_config_header" translatable="false"><u>Save UX Restrictions For Next Boot</u></string>
<string name="save_uxr_config" translatable="false">Save UX Restrictions</string>
<string name="set_uxr_config_dialog_title" translatable="false">Select restrictions for IDLING/MOVING</string>
diff --git a/tests/UxRestrictionsSample/res/values/styles.xml b/tests/UxRestrictionsSample/res/values/styles.xml
index 05d0d03..b752323 100644
--- a/tests/UxRestrictionsSample/res/values/styles.xml
+++ b/tests/UxRestrictionsSample/res/values/styles.xml
@@ -15,7 +15,7 @@
-->
<resources>
- <style name="AppTheme" parent="@style/Theme.Car.NoActionBar">
+ <style name="AppTheme" parent="@style/android:Theme.DeviceDefault.NoActionBar">
<item name="android:windowBackground">@android:color/black</item>
</style>
</resources>
\ No newline at end of file
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
index 0fa9259..60268ea 100644
--- a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
+++ b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
@@ -29,13 +29,11 @@
import android.car.drivingstate.CarUxRestrictionsConfiguration;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.content.ComponentName;
-import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.JsonWriter;
import android.util.Log;
-import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@@ -188,9 +186,6 @@
mShowProdConfig.setOnClickListener(v -> showProdUxRestrictionsConfig());
mToggleButton.setOnClickListener(v -> updateToggleUxREnable());
- mSampleMsgButton = findViewById(R.id.launch_message);
- mSampleMsgButton.setOnClickListener(this::launchSampleMsgActivity);
-
// Connect to car service
mCar = Car.createCar(this, mCarConnectionListener);
mCar.connect();
@@ -273,11 +268,6 @@
}
}
- private void launchSampleMsgActivity(View view) {
- Intent msgIntent = new Intent(this, SampleMessageActivity.class);
- startActivity(msgIntent);
- }
-
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/SampleMessageActivity.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/SampleMessageActivity.java
deleted file mode 100644
index 1c9bc95..0000000
--- a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/SampleMessageActivity.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2018 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.google.android.car.uxr.sample;
-
-import android.annotation.DrawableRes;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.car.widget.ListItem;
-import androidx.car.widget.ListItemAdapter;
-import androidx.car.widget.ListItemProvider;
-import androidx.car.widget.PagedListView;
-import androidx.car.widget.TextListItem;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A Sample Messaging Activity that illustrates how to truncate the string length on receiving the
- * corresponding UX restriction.
- */
-public class SampleMessageActivity extends Activity {
- private Button mHomeButton;
- private PagedListView mPagedListView;
- private ListItemAdapter mAdapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_sample_message);
- mHomeButton = findViewById(R.id.home_button);
- mHomeButton.setOnClickListener(this::returnHome);
-
- mPagedListView = findViewById(R.id.paged_list_view);
- setUpPagedListView();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- if (mAdapter != null) {
- mAdapter.start();
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (mAdapter != null) {
- mAdapter.stop();
- }
- }
-
- private void returnHome(View view) {
- Intent homeIntent = new Intent(this, MainActivity.class);
- startActivity(homeIntent);
- }
-
- private void setUpPagedListView() {
- mAdapter = new ListItemAdapter(this, populateData());
- mPagedListView.setAdapter(mAdapter);
- }
-
- private ListItemProvider populateData() {
- List<ListItem> items = new ArrayList<>();
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "alice",
- "i have a really important message but it may hinder your ability to drive. "));
-
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "bob",
- "hey this is a really long message that i have always wanted to say. but before "
- + "saying it i feel it's only appropriate if i lay some groundwork for it. "
- + ""));
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "mom",
- "i think you are the best. i think you are the best. i think you are the best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. i think you are the "
- + "best. "
- + "i think you are the best. i think you are the best. "));
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "john", "hello world"));
- items.add(createMessage(android.R.drawable.ic_menu_myplaces, "jeremy",
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "
- + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
- + "quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
- + "consequat. Duis aute irure dolor in reprehenderit in voluptate velit "
- + "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat "
- + "cupidatat non proident, sunt in culpa qui officia deserunt mollit "
- + "anim id est laborum."));
- return new ListItemProvider.ListProvider(items);
- }
-
- private TextListItem createMessage(@DrawableRes int profile, String contact, String message) {
- TextListItem item = new TextListItem(this);
- item.setPrimaryActionIcon(profile, TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
- item.setTitle(contact);
- item.setBody(message);
- item.setSupplementalIcon(android.R.drawable.stat_notify_chat, false);
- return item;
- }
-
-}
diff --git a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
index de1c16e..3c545d6 100644
--- a/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/CarUxRestrictionsManagerServiceTest.java
@@ -15,15 +15,15 @@
*/
package com.android.car;
+import static com.android.car.CarUxRestrictionsManagerService.CONFIG_FILENAME_PRODUCTION;
+import static com.android.car.CarUxRestrictionsManagerService.CONFIG_FILENAME_STAGED;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,7 +31,6 @@
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarUxRestrictionsConfiguration;
import android.car.hardware.CarPropertyValue;
-import android.car.userlib.CarUserManagerHelper;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
@@ -42,6 +41,8 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.car.systeminterface.SystemInterface;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
+import java.nio.file.Files;
@RunWith(AndroidJUnit4.class)
@MediumTest
@@ -67,47 +69,49 @@
@Mock
private CarPropertyService mMockCarPropertyService;
@Mock
- private CarUserManagerHelper mMockCarUserManagerHelper;
+ private SystemInterface mMockSystemInterface;
private Context mSpyContext;
+ private File mTempSystemCarDir;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ // Spy context because service needs to access xml resource during init.
mSpyContext = spy(InstrumentationRegistry.getTargetContext());
+ CarLocalServices.removeServiceForTest(SystemInterface.class);
+ CarLocalServices.addService(SystemInterface.class, mMockSystemInterface);
+
+ mTempSystemCarDir = Files.createTempDirectory("uxr_test").toFile();
+ when(mMockSystemInterface.getSystemCarDir()).thenReturn(mTempSystemCarDir);
+
setUpMockParkedState();
mService = new CarUxRestrictionsManagerService(mSpyContext,
- mMockDrivingStateService, mMockCarPropertyService, mMockCarUserManagerHelper);
+ mMockDrivingStateService, mMockCarPropertyService);
}
@After
public void tearDown() throws Exception {
mService = null;
+ CarLocalServices.removeAllServices();
}
@Test
public void testSaveConfig_WriteStagedFile() throws Exception {
- // Reset spy context because service tries to access production config
- // during construction (due to calling loadConfig()).
- reset(mSpyContext);
-
- File staged = setupMockFile(CarUxRestrictionsManagerService.CONFIG_FILENAME_STAGED, null);
+ File staged = setupMockFile(CONFIG_FILENAME_STAGED, null);
CarUxRestrictionsConfiguration config = createEmptyConfig();
assertTrue(mService.saveUxRestrictionsConfigurationForNextBoot(config));
assertTrue(readFile(staged).equals(config));
- // Verify prod config file was not touched.
- verify(mSpyContext, never()).getFileStreamPath(
- CarUxRestrictionsManagerService.CONFIG_FILENAME_PRODUCTION);
+ // Verify prod config file was not created.
+ assertFalse(new File(mTempSystemCarDir, CONFIG_FILENAME_PRODUCTION).exists());
}
@Test
public void testSaveConfig_ReturnFalseOnException() throws Exception {
- File tempFile = File.createTempFile("uxr_test", null);
- doReturn(tempFile).when(mSpyContext).getFileStreamPath(anyString());
-
CarUxRestrictionsConfiguration spyConfig = spy(createEmptyConfig());
doThrow(new IOException()).when(spyConfig).writeJson(any(JsonWriter.class));
@@ -116,8 +120,6 @@
@Test
public void testSaveConfig_DoesNotAffectCurrentConfig() throws Exception {
- File tempFile = File.createTempFile("uxr_test", null);
- doReturn(tempFile).when(mSpyContext).getFileStreamPath(anyString());
CarUxRestrictionsConfiguration spyConfig = spy(createEmptyConfig());
CarUxRestrictionsConfiguration currentConfig = mService.getConfig();
@@ -151,7 +153,7 @@
@Test
public void testLoadConfig_UseProdConfig() throws IOException {
CarUxRestrictionsConfiguration expected = createEmptyConfig();
- setupMockFile(CarUxRestrictionsManagerService.CONFIG_FILENAME_PRODUCTION, expected);
+ setupMockFile(CONFIG_FILENAME_PRODUCTION, expected);
CarUxRestrictionsConfiguration actual = mService.loadConfig();
@@ -162,10 +164,9 @@
public void testLoadConfig_PromoteStagedFileWhenParked() throws Exception {
CarUxRestrictionsConfiguration expected = createEmptyConfig();
// Staged file contains actual config. Ignore prod since it should be overwritten by staged.
- File staged = setupMockFile(CarUxRestrictionsManagerService.CONFIG_FILENAME_STAGED,
- expected);
+ File staged = setupMockFile(CONFIG_FILENAME_STAGED, expected);
// Set up temp file for prod to avoid polluting other tests.
- setupMockFile(CarUxRestrictionsManagerService.CONFIG_FILENAME_PRODUCTION, null);
+ setupMockFile(CONFIG_FILENAME_PRODUCTION, null);
CarUxRestrictionsConfiguration actual = mService.loadConfig();
@@ -177,9 +178,9 @@
@Test
public void testLoadConfig_NoPromoteStagedFileWhenMoving() throws Exception {
CarUxRestrictionsConfiguration expected = createEmptyConfig();
- File staged = setupMockFile(CarUxRestrictionsManagerService.CONFIG_FILENAME_STAGED, null);
+ File staged = setupMockFile(CONFIG_FILENAME_STAGED, null);
// Prod file contains actual config. Ignore staged since it should not be promoted.
- setupMockFile(CarUxRestrictionsManagerService.CONFIG_FILENAME_PRODUCTION, expected);
+ setupMockFile(CONFIG_FILENAME_PRODUCTION, expected);
setUpMockDrivingState();
CarUxRestrictionsConfiguration actual = mService.loadConfig();
@@ -215,8 +216,8 @@
private File setupMockFile(String filename, CarUxRestrictionsConfiguration config)
throws IOException {
- File f = File.createTempFile("uxr_test", null);
- doReturn(f).when(mSpyContext).getFileStreamPath(filename);
+ File f = new File(mTempSystemCarDir, filename);
+ assertTrue(f.createNewFile());
if (config != null) {
try (JsonWriter writer = new JsonWriter(
diff --git a/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java b/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
new file mode 100644
index 0000000..4983bb0
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/pm/CarPackageManagerServiceTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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.pm;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.car.CarUxRestrictionsManagerService;
+import com.android.car.SystemActivityMonitoringService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for {@link CarPackageManagerService}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarPackageManagerServiceTest {
+ CarPackageManagerService mService;
+
+ private Context mContext;
+ @Mock
+ private CarUxRestrictionsManagerService mMockUxrService;
+ @Mock
+ private SystemActivityMonitoringService mMockSamService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getTargetContext();
+
+ mService = new CarPackageManagerService(mContext, mMockUxrService, mMockSamService);
+ }
+
+ @Test
+ public void testParseConfigList_SingleActivity() {
+ String config = "com.android.test/.TestActivity";
+ Map<String, Set<String>> map = new HashMap<>();
+
+ mService.parseConfigList(config, map);
+
+ assertTrue(map.get("com.android.test").size() == 1);
+ assertEquals(".TestActivity", map.get("com.android.test").iterator().next());
+ }
+
+ @Test
+ public void testParseConfigList_Package() {
+ String config = "com.android.test";
+ Map<String, Set<String>> map = new HashMap<>();
+
+ mService.parseConfigList(config, map);
+
+ assertTrue(map.get("com.android.test").size() == 0);
+ }
+
+ @Test
+ public void testParseConfigList_MultipleActivities() {
+ String config = "com.android.test/.TestActivity0,com.android.test/.TestActivity1";
+ Map<String, Set<String>> map = new HashMap<>();
+
+ mService.parseConfigList(config, map);
+
+ assertTrue(map.get("com.android.test").size() == 2);
+ assertTrue(map.get("com.android.test").contains(".TestActivity0"));
+ assertTrue(map.get("com.android.test").contains(".TestActivity1"));
+ }
+
+ @Test
+ public void testParseConfigList_PackageAndActivity() {
+ String config = "com.android.test/.TestActivity0,com.android.test";
+ Map<String, Set<String>> map = new HashMap<>();
+
+ mService.parseConfigList(config, map);
+
+ assertTrue(map.get("com.android.test").size() == 0);
+ }
+}
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index 78ea244..ef12ab4 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -34,6 +34,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.sysprop.CarProperties;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -636,10 +637,17 @@
}
/**
- * Checks if the foreground user can switch to other users.
+ * Returns whether the foreground user can switch to other users.
+ *
+ * <p>For instance switching users is not allowed if the current user is in a phone call,
+ * or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
*/
public boolean canForegroundUserSwitchUsers() {
- return !foregroundUserHasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+ boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
+ == TelephonyManager.CALL_STATE_IDLE;
+ boolean disallowUserSwitching =
+ foregroundUserHasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+ return (inIdleCallState && !disallowUserSwitching);
}
// Current process user information accessors
@@ -717,10 +725,17 @@
}
/**
- * Checks if the user running the current process is allowed to switch to another user.
+ * Returns whether the current process user can switch to other users.
+ *
+ * <p>For instance switching users is not allowed if the user is in a phone call,
+ * or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
*/
public boolean canCurrentProcessSwitchUsers() {
- return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_USER_SWITCH);
+ boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
+ == TelephonyManager.CALL_STATE_IDLE;
+ boolean disallowUserSwitching =
+ isCurrentProcessUserHasRestriction(UserManager.DISALLOW_USER_SWITCH);
+ return (inIdleCallState && !disallowUserSwitching);
}
/**