More multi-user fixes on SampleClusterService
Bug: 79884417
Test: Ran on device
Change-Id: Idedb6d708c99a7e170b3310e39954329dd8b5085
diff --git a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
index 0ec6d72..ad54893 100644
--- a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
+++ b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
@@ -79,6 +79,7 @@
try {
mService.startClusterActivity(intent);
} catch (RemoteException e) {
+ Log.e(TAG, "Unable to launch activity (" + intent + ")", e);
throw new CarNotConnectedException(e);
}
}
diff --git a/car-lib/src/android/car/navigation/CarNavigationStatusManager.java b/car-lib/src/android/car/navigation/CarNavigationStatusManager.java
index df0dee9..6d4a561 100644
--- a/car-lib/src/android/car/navigation/CarNavigationStatusManager.java
+++ b/car-lib/src/android/car/navigation/CarNavigationStatusManager.java
@@ -55,6 +55,7 @@
try {
mService.onEvent(eventType, bundle);
} catch (IllegalStateException e) {
+ Log.e(TAG, "Illegal state sending event " + eventType, e);
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
handleCarServiceRemoteExceptionAndThrow(e);
@@ -63,7 +64,9 @@
/** @hide */
@Override
- public void onCarDisconnected() {}
+ public void onCarDisconnected() {
+ Log.e(TAG, "Car service disconnected");
+ }
/** Returns navigation features of instrument cluster */
public CarNavigationInstrumentCluster getInstrumentClusterInfo()
@@ -78,12 +81,7 @@
private void handleCarServiceRemoteExceptionAndThrow(RemoteException e)
throws CarNotConnectedException {
- handleCarServiceRemoteException(e);
- throw new CarNotConnectedException();
- }
-
- private void handleCarServiceRemoteException(RemoteException e) {
- Log.w(TAG, "RemoteException from car service:" + e.getMessage());
- // nothing to do for now
+ Log.e(TAG, "RemoteException from car service:" + e);
+ throw new CarNotConnectedException(e);
}
}
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index ab3ea62..91b8c1a 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.ActivityOptions;
import android.car.Car;
import android.car.CarAppFocusManager;
import android.car.cluster.CarInstrumentClusterManager;
@@ -42,6 +43,7 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -323,6 +325,8 @@
resolveList = checkPermission(resolveList, Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER);
if (resolveList.isEmpty()) {
+ Log.w(TAG, String.format("intent didn't have permission %s: %s",
+ Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER, intent));
return;
}
@@ -348,7 +352,10 @@
// Virtual display could be private and not available to calling process.
final long token = Binder.clearCallingIdentity();
try {
- mContext.startActivity(intent, opts.launchOptions);
+ mContext.startActivityAsUser(intent, opts.launchOptions, UserHandle.CURRENT);
+ Log.i(TAG, String.format("activity launched: %s (options: %s, displayId: %d)",
+ opts.launchOptions, intent, new ActivityOptions(opts.launchOptions)
+ .getLaunchDisplayId()));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -422,8 +429,11 @@
Set<String> registeredCategories = mActivityInfoByCategory.keySet();
for (ResolveInfo resolveInfo : resolveList) {
+ if (resolveInfo.filter == null) {
+ continue;
+ }
for (String category : registeredCategories) {
- if (resolveInfo.filter != null && resolveInfo.filter.hasCategory(category)) {
+ if (resolveInfo.filter.hasCategory(category)) {
ClusterActivityInfo categoryInfo = mActivityInfoByCategory.get(category);
return new Pair<>(resolveInfo, categoryInfo);
}
diff --git a/tests/DirectRenderingClusterSample/AndroidManifest.xml b/tests/DirectRenderingClusterSample/AndroidManifest.xml
index ad59fdf..9ec4eb5 100644
--- a/tests/DirectRenderingClusterSample/AndroidManifest.xml
+++ b/tests/DirectRenderingClusterSample/AndroidManifest.xml
@@ -35,6 +35,8 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<!-- Required by 'startActivityAsUser' -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <!-- Required to detect the current user in the device -->
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index b8a8016..3e31615 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -105,11 +105,15 @@
mActivity.get().onKeyEvent(data.getParcelable(MSG_KEY_KEY_EVENT));
break;
case MSG_ON_NAVIGATION_STATE_CHANGED:
- data.setClassLoader(ParcelUtils.class.getClassLoader());
- NavigationState navState = NavigationState
- .fromParcelable(data.getParcelable(
- SampleClusterServiceImpl.NAV_STATE_BUNDLE_KEY));
- mActivity.get().onNavigationStateChange(navState);
+ if (data == null) {
+ mActivity.get().onNavigationStateChange(null);
+ } else {
+ data.setClassLoader(ParcelUtils.class.getClassLoader());
+ NavigationState navState = NavigationState
+ .fromParcelable(data.getParcelable(
+ SampleClusterServiceImpl.NAV_STATE_BUNDLE_KEY));
+ mActivity.get().onNavigationStateChange(navState);
+ }
break;
default:
super.handleMessage(msg);
@@ -148,9 +152,9 @@
Log.d(TAG, "onDestroy");
if (mService != null) {
sendServiceMessage(MSG_UNREGISTER_CLIENT, null, mServiceCallbacks);
- unbindService(mServiceConnection);
mService = null;
}
+ unbindService(mServiceConnection);
}
private void onKeyEvent(KeyEvent event) {
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
index 30b3b49..b45a806 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NavStateController.java
@@ -15,6 +15,7 @@
*/
package android.car.cluster.sample;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
@@ -55,14 +56,14 @@
/**
* Updates views to reflect the provided navigation state
*/
- public void update(NavigationState state) {
+ public void update(@Nullable NavigationState state) {
Log.i(TAG, "Updating nav state: " + state);
Step step = getImmediateStep(state);
mManeuver.setImageDrawable(getManeuverIcon(step != null ? step.getManeuver() : null));
mDistance.setText(formatDistance(step != null ? step.getDistance() : null));
}
- private Drawable getManeuverIcon(Maneuver maneuver) {
+ private Drawable getManeuverIcon(@Nullable Maneuver maneuver) {
if (maneuver == null) {
return null;
}
@@ -179,11 +180,11 @@
return null;
}
- private Step getImmediateStep(NavigationState state) {
- return state.getSteps().size() > 0 ? state.getSteps().get(0) : null;
+ private Step getImmediateStep(@Nullable NavigationState state) {
+ return state != null && state.getSteps().size() > 0 ? state.getSteps().get(0) : null;
}
- private String formatDistance(Distance distance) {
+ private String formatDistance(@Nullable Distance distance) {
if (distance == null || distance.getDisplayUnit() == Distance.Unit.UNKNOWN) {
return null;
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java
index 5066162..a9c76a3 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/NetworkedVirtualDisplay.java
@@ -180,13 +180,12 @@
encoder.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
- Log.i(TAG, "onInputBufferAvailable, index: " + index);
+ // Nothing to do
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index,
@NonNull BufferInfo info) {
- Log.i(TAG, "onOutputBufferAvailable, index: " + index);
mCounter.outputBuffers++;
doOutputBufferAvailable(index, info);
}
@@ -247,8 +246,9 @@
private void sendFrame(byte[] buf, int len) {
try {
- mOutputStream.write(buf, 0, len);
- Log.i(TAG, "Bytes written: " + len);
+ if (mOutputStream != null) {
+ mOutputStream.write(buf, 0, len);
+ }
} catch (IOException e) {
mCounter.clientsDisconnected++;
mOutputStream = null;
@@ -326,7 +326,6 @@
case MSG_RESUBMIT_FRAME:
if (mServerSocket != null && mOutputStream != null) {
- Log.i(TAG, "Resending the last frame again. Buffer: " + mLastFrameLength);
sendFrame(mBuffer, mLastFrameLength);
}
// We will keep sending last frame every second as a heartbeat.
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SampleClusterServiceImpl.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SampleClusterServiceImpl.java
index cd337e5..e8a2a12 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SampleClusterServiceImpl.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SampleClusterServiceImpl.java
@@ -15,17 +15,23 @@
*/
package android.car.cluster.sample;
+import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static java.lang.Integer.parseInt;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.car.CarNotConnectedException;
import android.car.cluster.ClusterActivityState;
import android.car.cluster.renderer.InstrumentClusterRenderingService;
import android.car.cluster.renderer.NavigationRenderer;
import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.Rect;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Bundle;
@@ -59,6 +65,8 @@
public class SampleClusterServiceImpl extends InstrumentClusterRenderingService {
private static final String TAG = "Cluster.SampleService";
+ private static final int NO_DISPLAY = -1;
+
static final String LOCAL_BINDING_ACTION = "local";
static final String NAV_STATE_BUNDLE_KEY = "navstate";
static final int NAV_STATE_EVENT_ID = 1;
@@ -75,6 +83,52 @@
private List<Messenger> mClients = new ArrayList<>();
private ClusterDisplayProvider mDisplayProvider;
+ private int mDisplayId = NO_DISPLAY;
+ private UserReceiver mUserReceiver;
+
+ private final DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ Log.i(TAG, "Cluster display found, displayId: " + displayId);
+ mDisplayId = displayId;
+ tryLaunchActivity();
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ Log.w(TAG, "Cluster display has been removed");
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+
+ }
+ };
+
+ private static class UserReceiver extends BroadcastReceiver {
+ private WeakReference<SampleClusterServiceImpl> mService;
+
+ UserReceiver(SampleClusterServiceImpl service) {
+ mService = new WeakReference<>(service);
+ }
+
+ public void register(Context context) {
+ IntentFilter intentFilter = new IntentFilter(ACTION_USER_UNLOCKED);
+ intentFilter.addAction(ACTION_USER_SWITCHED);
+ context.registerReceiver(this, intentFilter);
+ }
+
+ public void unregister(Context context) {
+ context.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ SampleClusterServiceImpl service = mService.get();
+ Log.d(TAG, "Broadcast received: " + intent);
+ service.tryLaunchActivity();
+ }
+ }
private static class MessageHandler extends Handler {
private final WeakReference<SampleClusterServiceImpl> mService;
@@ -88,19 +142,22 @@
Log.d(TAG, "handleMessage: " + msg.what);
try {
switch (msg.what) {
- case MSG_SET_ACTIVITY_LAUNCH_OPTIONS:
- mService.get().setClusterActivityLaunchOptions(
- msg.getData().getString(MSG_KEY_CATEGORY),
- ActivityOptions.fromBundle(
- msg.getData().getBundle(MSG_KEY_ACTIVITY_OPTIONS)
- ));
+ case MSG_SET_ACTIVITY_LAUNCH_OPTIONS: {
+ ActivityOptions options = ActivityOptions.fromBundle(
+ msg.getData().getBundle(MSG_KEY_ACTIVITY_OPTIONS));
+ String category = msg.getData().getString(MSG_KEY_CATEGORY);
+ mService.get().setClusterActivityLaunchOptions(category, options);
+ Log.d(TAG, String.format("activity options set: %s = %s (displayeId: %d)",
+ category, options, options.getLaunchDisplayId()));
break;
- case MSG_SET_ACTIVITY_STATE:
- mService.get().setClusterActivityState(
- msg.getData().getString(MSG_KEY_CATEGORY),
- msg.getData().getBundle(MSG_KEY_ACTIVITY_STATE)
- );
+ }
+ case MSG_SET_ACTIVITY_STATE: {
+ Bundle state = msg.getData().getBundle(MSG_KEY_ACTIVITY_STATE);
+ String category = msg.getData().getString(MSG_KEY_CATEGORY);
+ 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;
@@ -128,34 +185,26 @@
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
-
- mDisplayProvider = new ClusterDisplayProvider(this,
- new DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- Log.i(TAG, "Cluster display found, displayId: " + displayId);
- doClusterDisplayConnected(displayId);
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- Log.w(TAG, "Cluster display has been removed");
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
-
- }
- });
+ mDisplayProvider = new ClusterDisplayProvider(this, mDisplayListener);
+ mUserReceiver = new UserReceiver(this);
+ mUserReceiver.register(this);
}
- private void doClusterDisplayConnected(int displayId) {
+ private void tryLaunchActivity() {
+ int userHandle = ActivityManager.getCurrentUser();
+ if (userHandle == UserHandle.USER_SYSTEM || mDisplayId == NO_DISPLAY) {
+ Log.d(TAG, String.format("Launch activity ignored (user: %d, display: %d)", userHandle,
+ mDisplayId));
+ // Not ready to launch yet.
+ return;
+ }
ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(displayId);
+ options.setLaunchDisplayId(mDisplayId);
Intent intent = new Intent(this, MainClusterActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
- startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
- Log.d(TAG, "launching main activity: " + intent);
+ startActivityAsUser(intent, options.toBundle(), UserHandle.of(userHandle));
+ Log.i(TAG, String.format("launching main activity: %s (user: %d, display: %d)", intent,
+ userHandle, mDisplayId));
}
@Override
@@ -191,6 +240,7 @@
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "onDestroy");
+ mUserReceiver.unregister(this);
}
@Override
@@ -206,24 +256,31 @@
@Override
public void onEvent(int eventType, Bundle bundle) {
- StringBuilder bundleSummary = new StringBuilder();
- if (eventType == NAV_STATE_EVENT_ID) {
- bundle.setClassLoader(ParcelUtils.class.getClassLoader());
- NavigationState navState = NavigationState
- .fromParcelable(bundle.getParcelable(NAV_STATE_BUNDLE_KEY));
- bundleSummary.append(navState.toString());
+ try {
+ StringBuilder bundleSummary = new StringBuilder();
+ if (eventType == NAV_STATE_EVENT_ID) {
+ bundle.setClassLoader(ParcelUtils.class.getClassLoader());
+ NavigationState navState = NavigationState
+ .fromParcelable(bundle.getParcelable(NAV_STATE_BUNDLE_KEY));
+ bundleSummary.append(navState.toString());
- // Update clients
- broadcastClientMessage(MSG_ON_NAVIGATION_STATE_CHANGED, bundle);
- } else {
- for (String key : bundle.keySet()) {
- bundleSummary.append(key);
- bundleSummary.append("=");
- bundleSummary.append(bundle.get(key));
- bundleSummary.append(" ");
+ // Update clients
+ broadcastClientMessage(MSG_ON_NAVIGATION_STATE_CHANGED, bundle);
+ } else {
+ for (String key : bundle.keySet()) {
+ bundleSummary.append(key);
+ bundleSummary.append("=");
+ bundleSummary.append(bundle.get(key));
+ bundleSummary.append(" ");
+ }
}
+ 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);
}
- Log.d(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
}
};