Deprecating CarInstrumentClusterManager
Bug: 121277787
Test: Unit tests
Change-Id: I6140b125bb831edc160ef19b84d2392526e2251a
diff --git a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
index bb9991e..c42ea5e 100644
--- a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
+++ b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java
@@ -37,7 +37,7 @@
private static final int NAV_STATE_EVENT_ID = 1;
@Override
- protected NavigationRenderer getNavigationRenderer() {
+ public NavigationRenderer getNavigationRenderer() {
NavigationRenderer navigationRenderer = new NavigationRenderer() {
@Override
public CarNavigationInstrumentCluster getNavigationProperties() {
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 40ae775..1c34646 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -4,6 +4,7 @@
public final class Car {
field public static final String CABIN_SERVICE = "cabin";
field public static final String CAR_DRIVING_STATE_SERVICE = "drivingstate";
+ field public static final String CAR_EXTRA_CLUSTER_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState";
field public static final String CAR_TRUST_AGENT_ENROLLMENT_SERVICE = "trust_enroll";
field public static final String DIAGNOSTIC_SERVICE = "diagnostic";
field public static final String HVAC_SERVICE = "hvac";
@@ -138,37 +139,40 @@
package android.car.cluster {
- public class CarInstrumentClusterManager {
- method public void registerCallback(String, android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
- method public void startActivity(android.content.Intent) throws android.car.CarNotConnectedException;
- method public void unregisterCallback(android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
- field public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
- field public static final String KEY_EXTRA_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState";
+ @Deprecated public class CarInstrumentClusterManager {
+ method @Deprecated public void registerCallback(String, android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
+ method @Deprecated public void startActivity(android.content.Intent) throws android.car.CarNotConnectedException;
+ method @Deprecated public void unregisterCallback(android.car.cluster.CarInstrumentClusterManager.Callback) throws android.car.CarNotConnectedException;
+ field @Deprecated public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
+ field @Deprecated public static final String KEY_EXTRA_ACTIVITY_STATE = "android.car.cluster.ClusterActivityState";
}
- public static interface CarInstrumentClusterManager.Callback {
- method public void onClusterActivityStateChanged(String, android.os.Bundle);
+ @Deprecated public static interface CarInstrumentClusterManager.Callback {
+ method @Deprecated public void onClusterActivityStateChanged(String, android.os.Bundle);
}
}
package android.car.cluster.renderer {
- public abstract class InstrumentClusterRenderer {
- ctor public InstrumentClusterRenderer();
- method protected abstract android.car.cluster.renderer.NavigationRenderer createNavigationRenderer();
- method @Nullable public android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
- method @UiThread public final void initialize();
- method public abstract void onCreate(android.content.Context);
- method public abstract void onStart();
- method public abstract void onStop();
+ @Deprecated public abstract class InstrumentClusterRenderer {
+ ctor @Deprecated public InstrumentClusterRenderer();
+ method @Deprecated protected abstract android.car.cluster.renderer.NavigationRenderer createNavigationRenderer();
+ method @Deprecated @Nullable public android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
+ method @Deprecated @UiThread public final void initialize();
+ method @Deprecated public abstract void onCreate(android.content.Context);
+ method @Deprecated public abstract void onStart();
+ method @Deprecated public abstract void onStop();
}
public abstract class InstrumentClusterRenderingService extends android.app.Service {
ctor public InstrumentClusterRenderingService();
- method @MainThread protected abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
+ method @MainThread @Nullable public abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
- method @MainThread protected void onKeyEvent(android.view.KeyEvent);
+ method @MainThread public void onKeyEvent(@NonNull android.view.KeyEvent);
+ method @MainThread public void onNavigationComponentLaunched();
+ method @MainThread public void onNavigationComponentReleased();
+ method protected boolean startNavigationActivity(@NonNull android.content.ComponentName);
}
@UiThread public abstract class NavigationRenderer {
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 85f0ca5..84525ee 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.car.cluster.CarInstrumentClusterManager;
+import android.car.cluster.ClusterActivityState;
import android.car.content.pm.CarPackageManager;
import android.car.diagnostic.CarDiagnosticManager;
import android.car.drivingstate.CarDrivingStateManager;
@@ -91,8 +92,11 @@
/**
* Service name for {@link CarInstrumentClusterManager}
+ *
+ * @deprecated CarInstrumentClusterManager is being deprecated
* @hide
*/
+ @Deprecated
public static final String CAR_INSTRUMENT_CLUSTER_SERVICE = "cluster_service";
/**
@@ -483,6 +487,26 @@
private static final String CAR_SERVICE_CLASS = "com.android.car.CarService";
+ /**
+ * Category used by navigation applications to indicate which activity should be launched on
+ * the instrument cluster when such application holds
+ * {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION} focus.
+ *
+ * @hide
+ */
+ public static final String CAR_CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
+
+ /**
+ * When an activity is launched in the cluster, it will receive {@link ClusterActivityState} in
+ * the intent's extra under this key, containing instrument cluster information such as
+ * unobscured area, visibility, etc.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAR_EXTRA_CLUSTER_ACTIVITY_STATE =
+ "android.car.cluster.ClusterActivityState";
+
private static final long CAR_SERVICE_BIND_RETRY_INTERVAL_MS = 500;
private static final long CAR_SERVICE_BIND_MAX_RETRY = 20;
diff --git a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
index ad54893..7427fcb 100644
--- a/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
+++ b/car-lib/src/android/car/cluster/CarInstrumentClusterManager.java
@@ -23,29 +23,25 @@
import android.os.Bundle;
import android.os.Handler;
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 java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
/**
* API to work with instrument cluster.
*
+ * @deprecated use {@link android.car.CarAppFocusManager} with focus type
+ * {@link android.car.CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION} instead.
+ * InstrumentClusterService will automatically launch a "android.car.cluster.NAVIGATION" activity
+ * from the package holding navigation focus.
+ *
* @hide
*/
+@Deprecated
@SystemApi
public class CarInstrumentClusterManager implements CarManagerBase {
- private static final String TAG = CarInstrumentClusterManager.class.getSimpleName();
-
- /** @hide */
+ /**
+ * @deprecated use {@link android.car.Car#CATEGORY_NAVIGATION} instead
+ *
+ * @hide
+ */
@SystemApi
public static final String CATEGORY_NAVIGATION = "android.car.cluster.NAVIGATION";
@@ -54,34 +50,24 @@
* intent's extra thus activity will know information about unobscured area, etc. upon activity
* creation.
*
+ * @deprecated use {@link android.car.Car#CATEGORY_NAVIGATION} instead
+ *
* @hide
*/
@SystemApi
public static final String KEY_EXTRA_ACTIVITY_STATE =
"android.car.cluster.ClusterActivityState";
- private final EventHandler mHandler;
- private final Map<String, Set<Callback>> mCallbacksByCategory = new HashMap<>(0);
- private final Object mLock = new Object();
- private final Map<String, Bundle> mActivityStatesByCategory = new HashMap<>(0);
-
- private final IInstrumentClusterManagerService mService;
-
- private ClusterManagerCallback mServiceToManagerCallback;
-
/**
* Starts activity in the instrument cluster.
*
+ * @deprecated see {@link CarInstrumentClusterManager} deprecation message
+ *
* @hide
*/
@SystemApi
public void startActivity(Intent intent) throws CarNotConnectedException {
- try {
- mService.startClusterActivity(intent);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to launch activity (" + intent + ")", e);
- throw new CarNotConnectedException(e);
- }
+ // No-op
}
/**
@@ -92,42 +78,14 @@
* see {@link #CATEGORY_NAVIGATION}
* @param callback instance of {@link Callback} class to receive events.
*
+ * @deprecated see {@link CarInstrumentClusterManager} deprecation message
+ *
* @hide
*/
@SystemApi
public void registerCallback(String category, Callback callback)
throws CarNotConnectedException {
- Log.i(TAG, "registerCallback, category: " + category + ", callback: " + callback);
- ClusterManagerCallback callbackToCarService = null;
- synchronized (mLock) {
- Set<Callback> callbacks = mCallbacksByCategory.get(category);
- if (callbacks == null) {
- callbacks = new HashSet<>(1);
- mCallbacksByCategory.put(category, callbacks);
- }
- if (!callbacks.add(callback)) {
- Log.w(TAG, "registerCallback: already registered");
- return; // already registered
- }
-
- if (mActivityStatesByCategory.containsKey(category)) {
- Log.i(TAG, "registerCallback: sending activity state...");
- callback.onClusterActivityStateChanged(
- category, mActivityStatesByCategory.get(category));
- }
-
- if (mServiceToManagerCallback == null) {
- Log.i(TAG, "registerCallback: registering callback with car service...");
- mServiceToManagerCallback = new ClusterManagerCallback();
- callbackToCarService = mServiceToManagerCallback;
- }
- }
- try {
- mService.registerCallback(callbackToCarService);
- Log.i(TAG, "registerCallback: done");
- } catch (RemoteException e) {
- throw new CarNotConnectedException(e);
- }
+ // No-op
}
/**
@@ -135,46 +93,29 @@
*
* @param callback previously registered callback
*
+ * @deprecated see {@link CarInstrumentClusterManager} deprecation message
+ *
* @hide
*/
@SystemApi
public void unregisterCallback(Callback callback) throws CarNotConnectedException {
- List<String> keysToRemove = new ArrayList<>(1);
- synchronized (mLock) {
- for (Map.Entry<String, Set<Callback>> entry : mCallbacksByCategory.entrySet()) {
- Set<Callback> callbacks = entry.getValue();
- if (callbacks.remove(callback) && callbacks.isEmpty()) {
- keysToRemove.add(entry.getKey());
- }
-
- }
-
- for (String key: keysToRemove) {
- mCallbacksByCategory.remove(key);
- }
-
- if (mCallbacksByCategory.isEmpty()) {
- try {
- mService.unregisterCallback(mServiceToManagerCallback);
- } catch (RemoteException e) {
- throw new CarNotConnectedException(e);
- }
- mServiceToManagerCallback = null;
- }
- }
+ // No-op
}
/** @hide */
public CarInstrumentClusterManager(IBinder service, Handler handler) {
- mService = IInstrumentClusterManagerService.Stub.asInterface(service);
-
- mHandler = new EventHandler(handler.getLooper());
+ // No-op
}
- /** @hide */
+ /**
+ * @deprecated activity state is not longer being reported. See
+ * {@link CarInstrumentClusterManager} deprecation message for more details.
+ *
+ * @hide
+ */
+ @Deprecated
@SystemApi
public interface Callback {
-
/**
* Notify client that activity state was changed.
*
@@ -188,54 +129,4 @@
@Override
public void onCarDisconnected() {
}
-
- private class EventHandler extends Handler {
-
- final static int MSG_ACTIVITY_STATE = 1;
-
- EventHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- Log.i(TAG, "handleMessage, message: " + msg);
- switch (msg.what) {
- case MSG_ACTIVITY_STATE:
- Pair<String, Bundle> info = (Pair<String, Bundle>) msg.obj;
- String category = info.first;
- Bundle state = info.second;
- List<CarInstrumentClusterManager.Callback> callbacks = null;
- synchronized (mLock) {
- if (mCallbacksByCategory.containsKey(category)) {
- callbacks = new ArrayList<>(mCallbacksByCategory.get(category));
- }
- }
- Log.i(TAG, "handleMessage, callbacks: " + callbacks);
- if (callbacks != null) {
- for (CarInstrumentClusterManager.Callback cb : callbacks) {
- cb.onClusterActivityStateChanged(category, state);
- }
- }
- break;
- default:
- Log.e(TAG, "Unexpected message: " + msg.what);
- }
- }
- }
-
- private class ClusterManagerCallback extends IInstrumentClusterManagerCallback.Stub {
-
- @Override
- public void setClusterActivityState(String category, Bundle clusterActivityState)
- throws RemoteException {
- Log.i(TAG, "setClusterActivityState, category: " + category);
- synchronized (mLock) {
- mActivityStatesByCategory.put(category, clusterActivityState);
- }
-
- mHandler.sendMessage(mHandler.obtainMessage(EventHandler.MSG_ACTIVITY_STATE,
- new Pair<>(category, clusterActivityState)));
- }
- }
}
\ No newline at end of file
diff --git a/car-lib/src/android/car/cluster/ClusterActivityState.java b/car-lib/src/android/car/cluster/ClusterActivityState.java
index 9a6223c..79f7e76 100644
--- a/car-lib/src/android/car/cluster/ClusterActivityState.java
+++ b/car-lib/src/android/car/cluster/ClusterActivityState.java
@@ -23,6 +23,7 @@
/**
* Helper class that represents activity state in the cluster and can be serialized / deserialized
* to/from bundle.
+ *
* @hide
*/
public class ClusterActivityState {
@@ -34,24 +35,65 @@
private Rect mUnobscuredBounds;
private Bundle mExtras;
+ /**
+ * Returns true if the cluster is currently able to display content, or false if the content
+ * area of the cluster is hidden.
+ */
public boolean isVisible() {
return mVisible;
}
+ /**
+ * Get a rectangle inside the cluster content area that is not covered by any decorations.
+ * Activities designed to display content in the instrument cluster can use this information to
+ * determine where to display user-relevant content, while using the rest of the window for
+ * content bleeding. For example, a navigation activity could decide to display current road
+ * inside this rectangle, while drawing additional map background outside this area.
+ * <p>
+ * All values of this {@link Rect} represent absolute coordinates inside the activity canvas.
+ */
@Nullable public Rect getUnobscuredBounds() {
return mUnobscuredBounds;
}
+ /**
+ * Get any custom extras that were set on this activity state.
+ */
+ @Nullable public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Sets whether the cluster is currently able to display content, or false if content area of
+ * the cluster is hidden.
+ *
+ * @return this instance for chaining.
+ */
public ClusterActivityState setVisible(boolean visible) {
mVisible = visible;
return this;
}
+ /**
+ * Sets a rectangle inside that cluster content area that is not covered by any decorations.
+ * Activities designed to display content in the cluster can use this to determine where to
+ * display user-relevant content, while using the rest of the window for content bleeding.
+ *
+ * @param unobscuredBounds a {@link Rect} describing the area inside the activity canvas that is
+ * not covered by any decorations. All values of this {@link Rect}
+ * represent absolute coordinates inside the activity canvas.
+ * @return this instance for chaining.
+ */
public ClusterActivityState setUnobscuredBounds(Rect unobscuredBounds) {
mUnobscuredBounds = unobscuredBounds;
return this;
}
+ /**
+ * Set any custom extras to be included with the activity state.
+ *
+ * @return this instance for chaining.
+ */
public ClusterActivityState setExtras(Bundle bundle) {
mExtras = bundle;
return this;
@@ -60,12 +102,19 @@
/** Use factory methods instead. */
private ClusterActivityState() {}
+ /**
+ * Creates a {@link ClusterActivityState} with the given visibility and unobscured bounds (see
+ * {@link #setVisible(boolean)} and {@link #setUnobscuredBounds(Rect)} for more details)
+ */
public static ClusterActivityState create(boolean visible, Rect unobscuredBounds) {
return new ClusterActivityState()
.setVisible(visible)
.setUnobscuredBounds(unobscuredBounds);
}
+ /**
+ * Reconstructs a {@link ClusterActivityState} from a {@link Bundle}
+ */
public static ClusterActivityState fromBundle(Bundle bundle) {
return new ClusterActivityState()
.setVisible(bundle.getBoolean(KEY_VISIBLE, true))
@@ -73,6 +122,10 @@
.setExtras(bundle.getBundle(KEY_EXTRAS));
}
+ /**
+ * Returns a {@link Bundle} representation of this instance. This bundle can then be
+ * deserialized using {@link #fromBundle(Bundle)}.
+ */
public Bundle toBundle() {
Bundle b = new Bundle();
b.putBoolean(KEY_VISIBLE, mVisible);
@@ -85,7 +138,8 @@
public String toString() {
return this.getClass().getSimpleName() + " {"
+ "visible: " + mVisible + ", "
- + "unobscuredBounds: " + mUnobscuredBounds
+ + "unobscuredBounds: " + mUnobscuredBounds + ", "
+ + "extras: " + mExtras
+ " }";
}
}
diff --git a/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl b/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl
index 91a497d..d2f099e 100644
--- a/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl
+++ b/car-lib/src/android/car/cluster/IInstrumentClusterManagerCallback.aidl
@@ -19,6 +19,8 @@
/**
* Interface from Car Service to {@link android.car.cluster.CarInstrumentClusterManager}
+ *
+ * @deprecated CarInstrumentClusterManager is deprecated
* @hide
*/
interface IInstrumentClusterManagerCallback {
@@ -29,7 +31,6 @@
* see {@link android.car.cluster.CarInstrumentClusterManager} for details.
* @param clusterActivityState is a {@link Bundle} object,
* see {@link android.car.cluster.ClusterActivityState} for how to construct the bundle.
- * @hide
*/
oneway void setClusterActivityState(String category, in Bundle clusterActivityState);
}
diff --git a/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl b/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl
index aaaeaee..1f4988d 100644
--- a/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl
+++ b/car-lib/src/android/car/cluster/IInstrumentClusterManagerService.aidl
@@ -22,6 +22,7 @@
/**
* API to communicate between {@link CarInstrumentClusterManager} and Car Service.
*
+ * @deprecated CarInstrumentClusterManager is deprecated
* @hide
*/
interface IInstrumentClusterManagerService {
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
index 3458975..7deecc7 100644
--- a/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
+++ b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
@@ -15,22 +15,29 @@
*/
package android.car.cluster.renderer;
-import android.car.cluster.renderer.IInstrumentClusterCallback;
import android.car.cluster.renderer.IInstrumentClusterNavigation;
import android.view.KeyEvent;
/**
- * Binder API for Instrument Cluster.
+ * Binder API for Instrument Cluster. It defines a communication channel from Car Service to the
+ * cluster vendor implementation.
*
* @hide
*/
interface IInstrumentCluster {
- /** Returns {@link IInstrumentClusterNavigation} that will be passed to the Nav app */
+ /**
+ * Returns {@link IInstrumentClusterNavigation} that will be passed to the navigation
+ * application.
+ */
IInstrumentClusterNavigation getNavigationService();
- /** Supplies Instrument Cluster Renderer with current owner of Navigation app context */
+ /**
+ * Supplies Instrument Cluster Renderer with current owner of Navigation app context
+ */
oneway void setNavigationContextOwner(int uid, int pid);
- /** Called when key event that was addressed to instrument cluster display has been received. */
+ /**
+ * Called when key event that was addressed to instrument cluster display has been received.
+ */
oneway void onKeyEvent(in KeyEvent keyEvent);
}
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterCallback.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterCallback.aidl
deleted file mode 100644
index 996dc9e..0000000
--- a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterCallback.aidl
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.car.cluster.renderer;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-
-/**
- * This interface defines the communication channel between the cluster vendor implementation and
- * Car Service.
- *
- * @hide
- */
-interface IInstrumentClusterCallback {
- /**
- * Notify Car Service how to launch an activity for particular category.
- *
- * @param category cluster activity category,
- * see {@link android.car.cluster.CarInstrumentClusterManager} for details.
- * @param activityOptions this bundle will be converted to {@link android.app.ActivityOptions}
- * and used when starting an activity. It may contain information such as virtual display
- * id or activity stack id where to start cluster activity.
- *
- * @hide
- */
- void setClusterActivityLaunchOptions(String category, in Bundle activityOptions);
-
- /**
- * Activities launched on virtual display will be in onPause state most of the time, so they
- * can't really know whether they visible on the screen or not. We need to propagate this
- * information along with unobscured bounds (and possible other info) from instrument cluster
- * vendor implementation to activity.
- *
- * @param category cluster activity category to which this state applies,
- * see {@link android.car.cluster.CarInstrumentClusterManager} for details.
- * @param clusterActivityState is a {@link Bundle} object,
- * see {@link android.car.cluster.ClusterActivityState} for how to construct the bundle.
- * @hide
- */
- void setClusterActivityState(String category, in Bundle clusterActivityState);
-}
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl
index 6f33a9d..c4e9d72 100644
--- a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl
+++ b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl
@@ -16,15 +16,29 @@
package android.car.cluster.renderer;
import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
/**
- * Binder API for Instrument Cluster Navigation.
+ * Binder API for Instrument Cluster Navigation. This represents a direct communication channel
+ * from navigation applications to the cluster vendor implementation.
*
* @hide
*/
interface IInstrumentClusterNavigation {
+ /**
+ * Called when an event is fired to change the navigation state. Content of this events can be
+ * interpreted using androidx.car.car-cluster API.
+ *
+ * @param eventType type of navigation state change
+ * @param bundle {@link android.os.Bundle} containing the description of the navigation state
+ * change.
+ */
void onEvent(int eventType, in Bundle bundle);
+
+ /**
+ * Returns attributes of instrument cluster for navigation.
+ */
CarNavigationInstrumentCluster getInstrumentClusterInfo();
}
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
index 5086186..be4cfd4 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
@@ -18,16 +18,15 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UiThread;
-import android.car.navigation.CarNavigationInstrumentCluster;
import android.content.Context;
/**
- * Interface for instrument cluster rendering.
- *
- * TODO: implement instrument cluster feature list and extend API. bug: 32060603
+ * @deprecated This class is unused. Refer to {@link InstrumentClusterRenderingService} for
+ * documentation on how to build a instrument cluster renderer.
*
* @hide
*/
+@Deprecated
@SystemApi
public abstract class InstrumentClusterRenderer {
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index d572d9a..6874106 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -15,30 +15,44 @@
*/
package android.car.cluster.renderer;
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+
import android.annotation.CallSuper;
import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.ActivityOptions;
import android.app.Service;
+import android.car.Car;
import android.car.CarLibLog;
import android.car.CarNotConnectedException;
+import android.car.cluster.ClusterActivityState;
import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
-import android.util.Pair;
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
/**
* A service that used for interaction between Car Service and Instrument Cluster. Car Service may
@@ -58,18 +72,31 @@
*/
@SystemApi
public abstract class InstrumentClusterRenderingService extends Service {
-
private static final String TAG = CarLibLog.TAG_CLUSTER;
- private RendererBinder mRendererBinder;
-
- /** @hide */
- public static final String EXTRA_KEY_CALLBACK_SERVICE =
- "android.car.cluster.IInstrumentClusterCallback";
-
private final Object mLock = new Object();
+ private RendererBinder mRendererBinder;
+ private Handler mUiHandler = new Handler(Looper.getMainLooper());
+ private ActivityOptions mActivityOptions;
+ private ClusterActivityState mActivityState;
+ private ComponentName mNavigationComponent;
@GuardedBy("mLock")
- private IInstrumentClusterCallback mCallback;
+ private ContextOwner mNavContextOwner;
+
+ private static class ContextOwner {
+ final int mUid;
+ final int mPid;
+
+ ContextOwner(int uid, int pid) {
+ mUid = uid;
+ mPid = pid;
+ }
+
+ @Override
+ public String toString() {
+ return "{uid: " + mUid + ", pid: " + mPid + "}";
+ }
+ }
@Override
@CallSuper
@@ -78,15 +105,6 @@
Log.d(TAG, "onBind, intent: " + intent);
}
- if (intent.getExtras().containsKey(EXTRA_KEY_CALLBACK_SERVICE)) {
- IBinder callbackBinder = intent.getExtras().getBinder(EXTRA_KEY_CALLBACK_SERVICE);
- synchronized (mLock) {
- mCallback = IInstrumentClusterCallback.Stub.asInterface(callbackBinder);
- }
- } else {
- Log.w(TAG, "onBind, no callback in extra!");
- }
-
if (mRendererBinder == null) {
mRendererBinder = new RendererBinder(getNavigationRenderer());
}
@@ -94,196 +112,314 @@
return mRendererBinder;
}
- /** Returns {@link NavigationRenderer} or null if it's not supported. */
+ /**
+ * Returns {@link NavigationRenderer} or null if it's not supported. This renderer will be
+ * shared with the navigation context owner (application holding navigation focus).
+ */
@MainThread
- protected abstract NavigationRenderer getNavigationRenderer();
+ @Nullable
+ public abstract NavigationRenderer getNavigationRenderer();
- /** Called when key event that was addressed to instrument cluster display has been received. */
+ /**
+ * Called when key event that was addressed to instrument cluster display has been received.
+ */
@MainThread
- protected void onKeyEvent(KeyEvent keyEvent) {
+ public void onKeyEvent(@NonNull KeyEvent keyEvent) {
}
/**
+ * Called when a navigation application becomes a context owner (receives navigation focus) and
+ * its {@link Car#CATEGORY_NAVIGATION} activity is launched.
+ */
+ @MainThread
+ public void onNavigationComponentLaunched() {
+ }
+
+ /**
+ * Called when the current context owner (application holding navigation focus) releases the
+ * focus and its {@link Car#CAR_CATEGORY_NAVIGATION} activity is ready to be replaced by a
+ * system default.
+ */
+ @MainThread
+ public void onNavigationComponentReleased() {
+ }
+
+ /**
+ * Updates the cluster navigation activity by checking which activity to show (an activity of
+ * the {@link #mNavContextOwner}). If not yet launched, it will do so.
+ */
+ private void updateNavigationActivity() {
+ ContextOwner contextOwner = getNavigationContextOwner();
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("updateNavigationActivity (mActivityOptions: %s, "
+ + "mActivityState: %s, mNavContextOwnerUid: %s)", mActivityOptions,
+ mActivityState, contextOwner));
+ }
+
+ if (contextOwner == null || contextOwner.mUid == 0 || mActivityOptions == null
+ || mActivityState == null || !mActivityState.isVisible()) {
+ // We are not yet ready to display an activity on the cluster
+ if (mNavigationComponent != null) {
+ mNavigationComponent = null;
+ onNavigationComponentReleased();
+ }
+ return;
+ }
+
+ ComponentName component = getNavigationComponentByOwner(contextOwner);
+ if (Objects.equals(mNavigationComponent, component)) {
+ // We have already launched this component.
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Already launched component: " + component);
+ }
+ return;
+ }
+
+ if (component == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "No component found for owner: " + contextOwner);
+ }
+ return;
+ }
+
+ if (!startNavigationActivity(component)) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unable to launch component: " + component);
+ }
+ return;
+ }
+
+ mNavigationComponent = component;
+ onNavigationComponentLaunched();
+ }
+
+ /**
+ * Returns a component with category {@link Car#CAR_CATEGORY_NAVIGATION} from the same package
+ * as the given navigation context owner.
+ */
+ @Nullable
+ private ComponentName getNavigationComponentByOwner(ContextOwner contextOwner) {
+ for (String packageName : getPackageNamesForUid(contextOwner)) {
+ ComponentName component = getComponentFromPackage(packageName);
+ if (component != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Found component: " + component);
+ }
+ return component;
+ }
+ }
+ 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;
+ }
+ }
+
+ @Nullable
+ private ComponentName getComponentFromPackage(@NonNull String packageName) {
+ PackageManager packageManager = getPackageManager();
+
+ // Check package permission.
+ if (packageManager.checkPermission(Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER, packageName)
+ != PERMISSION_GRANTED) {
+ Log.i(TAG, String.format("Package '%s' doesn't have permission %s", packageName,
+ Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER));
+ return null;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Car.CAR_CATEGORY_NAVIGATION)
+ .setPackage(packageName);
+ List<ResolveInfo> resolveList = packageManager.queryIntentActivities(intent,
+ PackageManager.GET_RESOLVED_FILTER);
+ if (resolveList == null || resolveList.isEmpty()
+ || resolveList.get(0).getComponentInfo() == null) {
+ Log.i(TAG, "Failed to resolve an intent: " + intent);
+ return null;
+ }
+
+ // In case of multiple matching activities in the same package, we pick the first one.
+ return resolveList.get(0).getComponentInfo().getComponentName();
+ }
+
+ /**
+ * Starts an activity on the cluster using the given component.
*
+ * @return false if the activity couldn't be started.
+ */
+ protected boolean startNavigationActivity(@NonNull ComponentName component) {
+ // Create an explicit intent.
+ Intent intent = new Intent();
+ intent.setComponent(component);
+ intent.putExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE, mActivityState.toBundle());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivityAsUser(intent, mActivityOptions.toBundle(), UserHandle.CURRENT);
+ Log.i(TAG, String.format("Activity launched: %s (options: %s, displayId: %d)",
+ mActivityOptions, intent, mActivityOptions.getLaunchDisplayId()));
+ } catch (ActivityNotFoundException ex) {
+ Log.w(TAG, "Unable to find activity for intent: " + intent);
+ return false;
+ } catch (Exception ex) {
+ // Catch all other possible exception to prevent service disruption by misbehaving
+ // applications.
+ Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", ex);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @deprecated Use {@link #setClusterActivityLaunchOptions(ActivityOptions)} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ public void setClusterActivityLaunchOptions(String category, ActivityOptions activityOptions)
+ throws CarNotConnectedException {
+ setClusterActivityLaunchOptions(activityOptions);
+ }
+
+ /**
* Sets configuration for activities that should be launched directly in the instrument
* cluster.
*
- * @param category category of cluster activity
* @param activityOptions contains information of how to start cluster activity (on what display
- * or activity stack.
+ * or activity stack).
*
* @hide
*/
- public void setClusterActivityLaunchOptions(String category,
- ActivityOptions activityOptions) throws CarNotConnectedException {
- IInstrumentClusterCallback cb;
- synchronized (mLock) {
- cb = mCallback;
- }
- if (cb == null) throw new CarNotConnectedException();
- try {
- cb.setClusterActivityLaunchOptions(category, activityOptions.toBundle());
- } catch (RemoteException e) {
- throw new CarNotConnectedException(e);
- }
+ public void setClusterActivityLaunchOptions(ActivityOptions activityOptions) {
+ mActivityOptions = activityOptions;
+ updateNavigationActivity();
}
/**
- *
- * @param category cluster activity category,
- * see {@link android.car.cluster.CarInstrumentClusterManager}
- * @param state pass information about activity state,
- * see {@link android.car.cluster.ClusterActivityState}
- * @return true if information was sent to Car Service
- * @throws CarNotConnectedException
+ * @deprecated Use {@link #setClusterActivityState(ClusterActivityState)} instead.
*
* @hide
*/
- public void setClusterActivityState(String category, Bundle state)
- throws CarNotConnectedException {
- IInstrumentClusterCallback cb;
- synchronized (mLock) {
- cb = mCallback;
- }
- if (cb == null) throw new CarNotConnectedException();
- try {
- cb.setClusterActivityState(category, state);
- } catch (RemoteException e) {
- throw new CarNotConnectedException(e);
- }
+ @Deprecated
+ public void setClusterActivityState(String category, Bundle state) throws
+ CarNotConnectedException {
+ setClusterActivityState(ClusterActivityState.fromBundle(state));
}
+ /**
+ * Set activity state (such as unobscured bounds).
+ *
+ * @param state pass information about activity state, see
+ * {@link android.car.cluster.ClusterActivityState}
+ *
+ * @hide
+ */
+ public void setClusterActivityState(ClusterActivityState state) {
+ mActivityState = state;
+ updateNavigationActivity();
+ }
+ @CallSuper
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
writer.println("**" + getClass().getSimpleName() + "**");
writer.println("renderer binder: " + mRendererBinder);
if (mRendererBinder != null) {
writer.println("navigation renderer: " + mRendererBinder.mNavigationRenderer);
- String owner = "none";
- synchronized (mLock) {
- if (mRendererBinder.mNavContextOwner != null) {
- owner = "[uid: " + mRendererBinder.mNavContextOwner.first
- + ", pid: " + mRendererBinder.mNavContextOwner.second + "]";
- }
- }
- writer.println("navigation focus owner: " + owner);
}
- IInstrumentClusterCallback cb;
- synchronized (mLock) {
- cb = mCallback;
- }
- writer.println("callback: " + cb);
+ writer.println("navigation focus owner: " + getNavigationContextOwner());
+ 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())));
}
private class RendererBinder extends IInstrumentCluster.Stub {
-
private final NavigationRenderer mNavigationRenderer;
- private final UiHandler mUiHandler;
-
- @GuardedBy("mLock")
- private NavigationBinder mNavigationBinder;
- @GuardedBy("mLock")
- private Pair<Integer, Integer> mNavContextOwner;
RendererBinder(NavigationRenderer navigationRenderer) {
mNavigationRenderer = navigationRenderer;
- mUiHandler = new UiHandler(InstrumentClusterRenderingService.this);
}
@Override
public IInstrumentClusterNavigation getNavigationService() throws RemoteException {
- synchronized (mLock) {
- if (mNavigationBinder == null) {
- mNavigationBinder = new NavigationBinder(mNavigationRenderer);
- if (mNavContextOwner != null) {
- mNavigationBinder.setNavigationContextOwner(
- mNavContextOwner.first, mNavContextOwner.second);
- }
- }
- return mNavigationBinder;
- }
+ return new NavigationBinder(mNavigationRenderer);
}
@Override
public void setNavigationContextOwner(int uid, int pid) throws RemoteException {
synchronized (mLock) {
- mNavContextOwner = new Pair<>(uid, pid);
- if (mNavigationBinder != null) {
- mNavigationBinder.setNavigationContextOwner(uid, pid);
- }
+ mNavContextOwner = new ContextOwner(uid, pid);
}
+ mUiHandler.post(InstrumentClusterRenderingService.this::updateNavigationActivity);
}
@Override
public void onKeyEvent(KeyEvent keyEvent) throws RemoteException {
- mUiHandler.doKeyEvent(keyEvent);
+ mUiHandler.post(() -> InstrumentClusterRenderingService.this.onKeyEvent(keyEvent));
}
}
private class NavigationBinder extends IInstrumentClusterNavigation.Stub {
-
- private final NavigationRenderer mNavigationRenderer; // Thread-safe navigation renderer.
-
- private volatile Pair<Integer, Integer> mNavContextOwner;
+ private final NavigationRenderer mNavigationRenderer;
NavigationBinder(NavigationRenderer navigationRenderer) {
- mNavigationRenderer = ThreadSafeNavigationRenderer.createFor(
- Looper.getMainLooper(),
- navigationRenderer);
- }
-
- void setNavigationContextOwner(int uid, int pid) {
- mNavContextOwner = new Pair<>(uid, pid);
+ mNavigationRenderer = navigationRenderer;
}
@Override
public void onEvent(int eventType, Bundle bundle) throws RemoteException {
assertContextOwnership();
- mNavigationRenderer.onEvent(eventType, bundle);
+ mUiHandler.post(() -> {
+ if (mNavigationRenderer != null) {
+ mNavigationRenderer.onEvent(eventType, bundle);
+ }
+ });
}
@Override
public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws RemoteException {
- return mNavigationRenderer.getNavigationProperties();
+ return runAndWaitResult(() -> mNavigationRenderer.getNavigationProperties());
}
private void assertContextOwnership() {
int uid = getCallingUid();
int pid = getCallingPid();
- Pair<Integer, Integer> owner = mNavContextOwner;
- if (owner == null || owner.first != uid || owner.second != pid) {
- throw new IllegalStateException("Client (uid:" + uid + ", pid: " + pid + ") is"
- + " not an owner of APP_FOCUS_TYPE_NAVIGATION");
+ synchronized (mLock) {
+ if (mNavContextOwner.mUid != uid || mNavContextOwner.mPid != pid) {
+ throw new IllegalStateException("Client {uid:" + uid + ", pid: " + pid + "} is"
+ + " not an owner of APP_FOCUS_TYPE_NAVIGATION " + mNavContextOwner);
+ }
}
}
}
- private static class UiHandler extends Handler {
- private static int KEY_EVENT = 0;
- private final WeakReference<InstrumentClusterRenderingService> mRefService;
+ private <E> E runAndWaitResult(final Supplier<E> supplier) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<E> result = new AtomicReference<>();
- UiHandler(InstrumentClusterRenderingService service) {
- mRefService = new WeakReference<>(service);
+ mUiHandler.post(() -> {
+ result.set(supplier.get());
+ latch.countDown();
+ });
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
}
-
- @Override
- public void handleMessage(Message msg) {
- InstrumentClusterRenderingService service = mRefService.get();
- if (service == null) {
- return;
- }
-
- if (msg.what == KEY_EVENT) {
- service.onKeyEvent((KeyEvent) msg.obj);
- } else {
- throw new IllegalArgumentException("Unexpected message: " + msg);
- }
- }
-
- void doKeyEvent(KeyEvent event) {
- sendMessage(obtainMessage(KEY_EVENT, event));
- }
+ return result.get();
}
}
diff --git a/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java b/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java
deleted file mode 100644
index 251b670..0000000
--- a/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java
+++ /dev/null
@@ -1,146 +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 android.car.cluster.renderer;
-
-import android.annotation.Nullable;
-import android.car.navigation.CarNavigationInstrumentCluster;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-import java.lang.ref.WeakReference;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * A wrapper over {@link NavigationRenderer} that runs all its methods in the context of provided
- * looper. It is guaranteed that all calls will be invoked in order they were called.
- */
-// TODO(deanh): Does this class even need to exist?
-/* package */ class ThreadSafeNavigationRenderer extends NavigationRenderer {
-
- private final Handler mHandler;
- private final NavigationRenderer mRenderer;
-
- private final static int MSG_EVENT = 1;
-
- /** Creates thread-safe {@link NavigationRenderer}. Returns null if renderer == null */
- @Nullable
- static NavigationRenderer createFor(Looper looper, NavigationRenderer renderer) {
- return renderer == null ? null : new ThreadSafeNavigationRenderer(looper, renderer);
- }
-
- private ThreadSafeNavigationRenderer(Looper looper, NavigationRenderer renderer) {
- mRenderer = renderer;
- mHandler = new NavigationRendererHandler(looper, renderer);
- }
-
- @Override
- public CarNavigationInstrumentCluster getNavigationProperties() {
- if (mHandler.getLooper() == Looper.myLooper()) {
- return mRenderer.getNavigationProperties();
- } else {
- return runAndWaitResult(mHandler,
- new RunnableWithResult<CarNavigationInstrumentCluster>() {
- @Override
- protected CarNavigationInstrumentCluster createResult() {
- return mRenderer.getNavigationProperties();
- }
- });
- }
- }
-
- @Override
- public void onEvent(int eventType, Bundle bundle) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_EVENT, eventType, 0, bundle));
- }
-
- private static class NavigationRendererHandler extends RendererHandler<NavigationRenderer> {
-
- NavigationRendererHandler(Looper looper, NavigationRenderer renderer) {
- super(looper, renderer);
- }
-
- @Override
- public void handleMessage(Message msg, NavigationRenderer renderer) {
- switch (msg.what) {
- case MSG_EVENT:
- Bundle bundle = (Bundle) msg.obj;
- renderer.onEvent(msg.arg1, bundle);
- break;
- default:
- throw new IllegalArgumentException("Msg: " + msg.what);
- }
- }
- }
-
- private static <E> E runAndWaitResult(Handler handler, final RunnableWithResult<E> runnable) {
- final CountDownLatch latch = new CountDownLatch(1);
-
- Runnable wrappedRunnable = new Runnable() {
- @Override
- public void run() {
- runnable.run();
- latch.countDown();
- }
- };
-
- handler.post(wrappedRunnable);
-
- try {
- latch.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- return runnable.getResult();
- }
-
- private static abstract class RunnableWithResult<T> implements Runnable {
- private volatile T result;
-
- protected abstract T createResult();
-
- @Override
- public void run() {
- result = createResult();
- }
-
- public T getResult() {
- return result;
- }
- }
-
- private static abstract class RendererHandler<T> extends Handler {
-
- private final WeakReference<T> mRendererRef;
-
- RendererHandler(Looper looper, T renderer) {
- super(looper);
- mRendererRef = new WeakReference<>(renderer);
- }
-
- @Override
- public void handleMessage(Message msg) {
- T renderer = mRendererRef.get();
- if (renderer != null) {
- handleMessage(msg, renderer);
- }
- }
-
- public abstract void handleMessage(Message msg, T renderer);
- }
-}
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index 360f6b4..16c0e94 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -15,38 +15,24 @@
*/
package com.android.car.cluster;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
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;
import android.car.cluster.IInstrumentClusterManagerCallback;
import android.car.cluster.IInstrumentClusterManagerService;
import android.car.cluster.renderer.IInstrumentCluster;
-import android.car.cluster.renderer.IInstrumentClusterCallback;
import android.car.cluster.renderer.IInstrumentClusterNavigation;
-import android.car.cluster.renderer.InstrumentClusterRenderingService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
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;
import android.view.KeyEvent;
import com.android.car.AppFocusService;
@@ -59,11 +45,7 @@
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
/**
* Service responsible for interaction with car's instrument cluster.
@@ -71,41 +53,39 @@
* @hide
*/
@SystemApi
-public class InstrumentClusterService implements CarServiceBase,
- FocusOwnershipCallback, KeyEventListener {
-
+public class InstrumentClusterService implements CarServiceBase, FocusOwnershipCallback,
+ KeyEventListener {
private static final String TAG = CarLog.TAG_CLUSTER;
- private static final Boolean DBG = false;
+ private static final ContextOwner NO_OWNER = new ContextOwner(0, 0);
private final Context mContext;
-
private final AppFocusService mAppFocusService;
private final CarInputService mCarInputService;
- private final PackageManager mPackageManager;
- private final Object mSync = new Object();
-
- private final ClusterServiceCallback mClusterCallback = new ClusterServiceCallback();
+ /**
+ * TODO: (b/121277787) Remove this on master.
+ * @deprecated CarInstrumentClusterManager is being deprecated.
+ */
+ @Deprecated
private final ClusterManagerService mClusterManagerService = new ClusterManagerService();
-
+ private final Object mSync = new Object();
@GuardedBy("mSync")
- private ContextOwner mNavContextOwner;
+ private ContextOwner mNavContextOwner = NO_OWNER;
@GuardedBy("mSync")
private IInstrumentCluster mRendererService;
- @GuardedBy("mSync")
- private final HashMap<String, ClusterActivityInfo> mActivityInfoByCategory = new HashMap<>();
- @GuardedBy("mSync")
- private final HashMap<IBinder, ManagerCallbackInfo> mManagerCallbacks = new HashMap<>();
-
// If renderer service crashed / stopped and this class fails to rebind with it immediately,
// we should wait some time before next attempt. This may happen during APK update for example.
private DeferredRebinder mDeferredRebinder;
-
+ // Whether {@link android.car.cluster.renderer.InstrumentClusterRendererService} is bound
+ // (although not necessarily connected)
private boolean mRendererBound = false;
+ /**
+ * Connection to {@link android.car.cluster.renderer.InstrumentClusterRendererService}
+ */
private final ServiceConnection mRendererServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
- if (DBG) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
}
IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder);
@@ -114,14 +94,16 @@
mRendererService = service;
navContextOwner = mNavContextOwner;
}
- if (navContextOwner != null && service != null) {
- notifyNavContextOwnerChanged(service, navContextOwner.uid, navContextOwner.pid);
+ if (navContextOwner != null && service != null) {
+ notifyNavContextOwnerChanged(service, navContextOwner);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
- Log.d(TAG, "onServiceDisconnected, name: " + name);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onServiceDisconnected, name: " + name);
+ }
mContext.unbindService(this);
mRendererBound = false;
@@ -141,12 +123,11 @@
mContext = context;
mAppFocusService = appFocusService;
mCarInputService = carInputService;
- mPackageManager = mContext.getPackageManager();
}
@Override
public void init() {
- if (DBG) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "init");
}
@@ -157,7 +138,7 @@
@Override
public void release() {
- if (DBG) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "release");
}
@@ -173,51 +154,51 @@
writer.println("**" + getClass().getSimpleName() + "**");
writer.println("bound with renderer: " + mRendererBound);
writer.println("renderer service: " + mRendererService);
+ writer.println("context owner: " + mNavContextOwner);
}
@Override
public void onFocusAcquired(int appType, int uid, int pid) {
- if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
- return;
- }
-
- IInstrumentCluster service;
- synchronized (mSync) {
- mNavContextOwner = new ContextOwner(uid, pid);
- service = mRendererService;
- }
-
- if (service != null) {
- notifyNavContextOwnerChanged(service, uid, pid);
- }
+ changeNavContextOwner(appType, uid, pid, true);
}
@Override
public void onFocusAbandoned(int appType, int uid, int pid) {
+ changeNavContextOwner(appType, uid, pid, false);
+ }
+
+ private void changeNavContextOwner(int appType, int uid, int pid, boolean acquire) {
if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
return;
}
IInstrumentCluster service;
+ ContextOwner requester = new ContextOwner(uid, pid);
+ ContextOwner newOwner = acquire ? requester : NO_OWNER;
synchronized (mSync) {
- if (mNavContextOwner == null
- || mNavContextOwner.uid != uid
- || mNavContextOwner.pid != pid) {
- return; // Nothing to do here, no active focus or not owned by this client.
+ if ((acquire && Objects.equals(mNavContextOwner, requester))
+ || (!acquire && !Objects.equals(mNavContextOwner, requester))) {
+ // Nothing to do here. Either the same owner is acquiring twice, or someone is
+ // abandoning a focus they didn't have.
+ Log.w(TAG, "Invalid nav context owner change (acquiring: " + acquire
+ + "), current owner: [" + mNavContextOwner
+ + "], requester: [" + requester + "]");
+ return;
}
- mNavContextOwner = null;
+ mNavContextOwner = newOwner;
service = mRendererService;
}
if (service != null) {
- notifyNavContextOwnerChanged(service, 0, 0);
+ notifyNavContextOwnerChanged(service, newOwner);
}
}
- private static void notifyNavContextOwnerChanged(IInstrumentCluster service, int uid, int pid) {
+ private static void notifyNavContextOwnerChanged(IInstrumentCluster service,
+ ContextOwner owner) {
try {
- service.setNavigationContextOwner(uid, pid);
+ service.setNavigationContextOwner(owner.uid, owner.pid);
} catch (RemoteException e) {
Log.e(TAG, "Failed to call setNavigationContextOwner", e);
}
@@ -234,23 +215,14 @@
Intent intent = new Intent();
intent.setComponent(ComponentName.unflattenFromString(rendererService));
- Bundle extras = new Bundle();
- extras.putBinder(
- InstrumentClusterRenderingService.EXTRA_KEY_CALLBACK_SERVICE,
- mClusterCallback);
- intent.putExtras(extras);
return mContext.bindServiceAsUser(intent, mRendererServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM);
}
@Nullable
public IInstrumentClusterNavigation getNavigationService() {
- IInstrumentCluster service;
- synchronized (mSync) {
- service = mRendererService;
- }
-
try {
+ IInstrumentCluster service = getInstrumentClusterRendererService();
return service == null ? null : service.getNavigationService();
} catch (RemoteException e) {
Log.e(TAG, "getNavigationServiceBinder" , e);
@@ -258,21 +230,21 @@
}
}
+ /**
+ * @deprecated {@link android.car.cluster.CarInstrumentClusterManager} is now deprecated.
+ */
+ @Deprecated
public IInstrumentClusterManagerService.Stub getManagerService() {
return mClusterManagerService;
}
@Override
public boolean onKeyEvent(KeyEvent event) {
- if (DBG) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event);
}
- IInstrumentCluster service;
- synchronized (mSync) {
- service = mRendererService;
- }
-
+ IInstrumentCluster service = getInstrumentClusterRendererService();
if (service != null) {
try {
service.onKeyEvent(event);
@@ -283,6 +255,14 @@
return true;
}
+ private IInstrumentCluster getInstrumentClusterRendererService() {
+ IInstrumentCluster service;
+ synchronized (mSync) {
+ service = mRendererService;
+ }
+ return service;
+ }
+
private static class ContextOwner {
final int uid;
final int pid;
@@ -291,257 +271,47 @@
this.uid = uid;
this.pid = pid;
}
- }
- private static class ClusterActivityInfo {
- Bundle launchOptions; // ActivityOptions
- Bundle state; // ClusterActivityState
- }
-
- private void enforcePermission(String permission) {
- int callingUid = Binder.getCallingUid();
- int callingPid = Binder.getCallingPid();
- if (Binder.getCallingUid() == Process.myUid()) {
- if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
- throw new SecurityException("Permission " + permission + " is not granted to "
- + "client {uid: " + callingUid + ", pid: " + callingPid + "}");
- }
- }
- }
-
- private void enforceClusterControlPermission() {
- enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
- }
-
- private void doStartClusterActivity(Intent intent) {
- enforceClusterControlPermission();
-
- // Category from given intent should match category from cluster vendor implementation.
- List<ResolveInfo> resolveList = mPackageManager.queryIntentActivities(intent,
- PackageManager.GET_RESOLVED_FILTER);
- if (resolveList == null || resolveList.isEmpty()) {
- Log.w(TAG, "Failed to resolve an intent: " + intent);
- return;
- }
-
- 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;
- }
-
- // TODO(b/63861009): we may have multiple navigation apps that eligible to be launched in
- // the cluster. We need to resolve intent that may have multiple activity candidates, right
- // now we pickup the first one that matches registered category (resolveList is sorted
- // priority).
- Pair<ResolveInfo, ClusterActivityInfo> attributedResolveInfo =
- findClusterActivityOptions(resolveList);
- if (attributedResolveInfo == null) {
- Log.w(TAG, "Unable to start an activity with intent: " + intent + " in the cluster: "
- + "category intent didn't match with any categories from vendor "
- + "implementation");
- return;
- }
- ClusterActivityInfo opts = attributedResolveInfo.second;
-
- // Intent was already checked for permission and resolved, make it explicit.
- intent.setComponent(attributedResolveInfo.first.getComponentInfo().getComponentName());
-
- intent.putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE, opts.state);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // Virtual display could be private and not available to calling process.
- final long token = Binder.clearCallingIdentity();
- try {
- 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);
- }
- }
-
- private List<ResolveInfo> checkPermission(List<ResolveInfo> resolveList,
- String permission) {
- List<ResolveInfo> permittedResolveList = new ArrayList<>(resolveList.size());
- for (ResolveInfo info : resolveList) {
- String pkgName = info.getComponentInfo().packageName;
- if (mPackageManager.checkPermission(permission, pkgName) == PERMISSION_GRANTED) {
- permittedResolveList.add(info);
- } else {
- Log.w(TAG, "Permission " + permission + " not granted for "
- + info.getComponentInfo());
- }
-
- }
- return permittedResolveList;
- }
-
- private void doRegisterManagerCallback(IInstrumentClusterManagerCallback callback)
- throws RemoteException {
- enforceClusterControlPermission();
- IBinder binder = callback.asBinder();
-
- List<Pair<String, Bundle>> knownActivityStates = null;
- ManagerCallbackDeathRecipient deathRecipient = new ManagerCallbackDeathRecipient(binder);
- synchronized (mSync) {
- if (mManagerCallbacks.containsKey(binder)) {
- Log.w(TAG, "Manager callback already registered for binder: " + binder);
- return;
- }
- mManagerCallbacks.put(binder, new ManagerCallbackInfo(callback, deathRecipient));
- if (!mActivityInfoByCategory.isEmpty()) {
- knownActivityStates = new ArrayList<>(mActivityInfoByCategory.size());
- for (Map.Entry<String, ClusterActivityInfo> it : mActivityInfoByCategory.entrySet()) {
- knownActivityStates.add(new Pair<>(it.getKey(), it.getValue().state));
- }
- }
- }
- binder.linkToDeath(deathRecipient, 0);
-
- // Notify manager immediately with known states.
- if (knownActivityStates != null) {
- for (Pair<String, Bundle> it : knownActivityStates) {
- callback.setClusterActivityState(it.first, it.second);
- }
- }
- }
-
- private void doUnregisterManagerCallback(IBinder binder) throws RemoteException {
- enforceClusterControlPermission();
- ManagerCallbackInfo info;
- synchronized (mSync) {
- info = mManagerCallbacks.get(binder);
- if (info == null) {
- Log.w(TAG, "Unable to unregister manager callback binder: " + binder + " because "
- + "it wasn't previously registered.");
- return;
- }
- mManagerCallbacks.remove(binder);
- }
- binder.unlinkToDeath(info.deathRecipient, 0);
- }
-
- @Nullable
- private Pair<ResolveInfo, ClusterActivityInfo> findClusterActivityOptions(
- List<ResolveInfo> resolveList) {
- synchronized (mSync) {
- Set<String> registeredCategories = mActivityInfoByCategory.keySet();
-
- for (ResolveInfo resolveInfo : resolveList) {
- if (resolveInfo.filter == null) {
- continue;
- }
- for (String category : registeredCategories) {
- if (resolveInfo.filter.hasCategory(category)) {
- ClusterActivityInfo categoryInfo = mActivityInfoByCategory.get(category);
- return new Pair<>(resolveInfo, categoryInfo);
- }
- }
- }
- }
- return null;
- }
-
- private class ManagerCallbackDeathRecipient implements DeathRecipient {
- private final IBinder mBinder;
-
- ManagerCallbackDeathRecipient(IBinder binder) {
- mBinder = binder;
+ @Override
+ public String toString() {
+ return "uid: " + uid + ", pid: " + pid;
}
@Override
- public void binderDied() {
- try {
- doUnregisterManagerCallback(mBinder);
- } catch (RemoteException e) {
- // Ignore, shutdown route.
- }
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ContextOwner that = (ContextOwner) o;
+ return uid == that.uid && pid == that.pid;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uid, pid);
}
}
+ /**
+ * TODO: (b/121277787) Remove on master
+ * @deprecated CarClusterManager is being deprecated.
+ */
+ @Deprecated
private class ClusterManagerService extends IInstrumentClusterManagerService.Stub {
-
@Override
public void startClusterActivity(Intent intent) throws RemoteException {
- doStartClusterActivity(intent);
+ // No op.
}
@Override
public void registerCallback(IInstrumentClusterManagerCallback callback)
throws RemoteException {
- doRegisterManagerCallback(callback);
+ // No op.
}
@Override
public void unregisterCallback(IInstrumentClusterManagerCallback callback)
throws RemoteException {
- doUnregisterManagerCallback(callback.asBinder());
- }
- }
-
- @GuardedBy("mSync")
- private ClusterActivityInfo getOrCreateActivityInfoLocked(String category) {
- return mActivityInfoByCategory.computeIfAbsent(category, k -> new ClusterActivityInfo());
- }
-
- /** This is communication channel from vendor cluster implementation to Car Service. */
- private class ClusterServiceCallback extends IInstrumentClusterCallback.Stub {
-
- @Override
- public void setClusterActivityLaunchOptions(String category, Bundle activityOptions)
- throws RemoteException {
- doSetActivityLaunchOptions(category, activityOptions);
- }
-
- @Override
- public void setClusterActivityState(String category, Bundle clusterActivityState)
- throws RemoteException {
- doSetClusterActivityState(category, clusterActivityState);
- }
- }
-
- /** Called from cluster vendor implementation */
- private void doSetActivityLaunchOptions(String category, Bundle activityOptions) {
- if (DBG) {
- Log.d(TAG, "doSetActivityLaunchOptions, category: " + category
- + ", options: " + activityOptions);
- }
- synchronized (mSync) {
- ClusterActivityInfo info = getOrCreateActivityInfoLocked(category);
- info.launchOptions = activityOptions;
- }
- }
-
- /** Called from cluster vendor implementation */
- private void doSetClusterActivityState(String category, Bundle clusterActivityState)
- throws RemoteException {
- if (DBG) {
- Log.d(TAG, "doSetClusterActivityState, category: " + category
- + ", state: " + clusterActivityState);
- }
-
- List<ManagerCallbackInfo> managerCallbacks;
- synchronized (mSync) {
- ClusterActivityInfo info = getOrCreateActivityInfoLocked(category);
- info.state = clusterActivityState;
- managerCallbacks = new ArrayList<>(mManagerCallbacks.values());
- }
-
- for (ManagerCallbackInfo cbInfo : managerCallbacks) {
- cbInfo.callback.setClusterActivityState(category, clusterActivityState);
- }
- }
-
- private static class ManagerCallbackInfo {
- final IInstrumentClusterManagerCallback callback;
- final ManagerCallbackDeathRecipient deathRecipient;
-
- ManagerCallbackInfo(IInstrumentClusterManagerCallback callback,
- ManagerCallbackDeathRecipient deathRecipient) {
- this.callback = callback;
- this.deathRecipient = deathRecipient;
+ // No op.
}
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
index 8a383fd..31aad7d 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterRenderingServiceImpl.java
@@ -165,7 +165,7 @@
}
@Override
- protected void onKeyEvent(KeyEvent keyEvent) {
+ public void onKeyEvent(KeyEvent keyEvent) {
Log.d(TAG, "onKeyEvent, keyEvent: " + keyEvent);
Bundle data = new Bundle();
data.putParcelable(MSG_KEY_KEY_EVENT, keyEvent);
@@ -194,7 +194,7 @@
}
@Override
- protected NavigationRenderer getNavigationRenderer() {
+ public NavigationRenderer getNavigationRenderer() {
NavigationRenderer navigationRenderer = new NavigationRenderer() {
@Override
public CarNavigationInstrumentCluster getNavigationProperties() {
@@ -243,7 +243,7 @@
if (args != null && args.length > 0) {
execShellCommand(args);
} else {
- writer.println("* dump " + getClass().getCanonicalName() + " *");
+ super.dump(fd, writer, args);
writer.println("DisplayProvider: " + mDisplayProvider);
}
}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index cf38e1f..9e8cb59 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -15,7 +15,6 @@
*/
package android.car.cluster.sample;
-import static android.car.cluster.CarInstrumentClusterManager.CATEGORY_NAVIGATION;
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;
@@ -33,7 +32,7 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.car.cluster.CarInstrumentClusterManager;
+import android.car.Car;
import android.car.cluster.ClusterActivityState;
import android.car.cluster.sample.sensors.Sensors;
import android.content.ActivityNotFoundException;
@@ -340,7 +339,7 @@
private void reportNavDisplay(VirtualDisplay virtualDisplay) {
Bundle data = new Bundle();
- data.putString(MSG_KEY_CATEGORY, CATEGORY_NAVIGATION);
+ 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
.create(virtualDisplay.mDisplayId != Display.INVALID_DISPLAY,
@@ -445,7 +444,7 @@
if (navigationActivity == null) {
throw new ActivityNotFoundException();
}
- Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION)
+ Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Car.CAR_CATEGORY_NAVIGATION)
.setPackage(navigationActivity.getPackageName())
.setComponent(navigationActivity)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -467,7 +466,7 @@
/**
* Returns a default navigation activity to show in the cluster.
* In the current implementation we search for an activity with the
- * {@link CarInstrumentClusterManager#CATEGORY_NAVIGATION} category from the same navigation app
+ * {@link Car#CAR_CATEGORY_NAVIGATION} category from the same navigation app
* selected from CarLauncher (see CarLauncher#getMapsIntent()).
* Alternatively, other implementations could:
* <ul>
@@ -486,7 +485,7 @@
PackageManager.MATCH_DEFAULT_ONLY, userId);
// Get all possible cluster activities
- intent = new Intent(Intent.ACTION_MAIN).addCategory(CATEGORY_NAVIGATION);
+ intent = new Intent(Intent.ACTION_MAIN).addCategory(Car.CAR_CATEGORY_NAVIGATION);
List<ResolveInfo> candidates = pm.queryIntentActivitiesAsUser(intent, 0, userId);
// If there is a select navigation app, try finding a matching auxiliary navigation activity
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 798e6b4..d7c6856 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -93,13 +93,13 @@
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"
android:resizeableActivity="true"
- android:allowEmbedded="true"
- android:permission="android.car.permission.CAR_DISPLAY_IN_CLUSTER">
- <intent-filter android:priority="-1">
+ android:allowEmbedded="true">
+ <intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.car.cluster.NAVIGATION"/>
</intent-filter>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
index d244904..a29296c 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
@@ -48,12 +48,5 @@
android:padding="20dp"
android:text="@string/cluster_stop"
android:id="@+id/cluster_stop_button"/>
- <Button
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="10dp"
- android:padding="20dp"
- android:text="@string/cluster_start_activity"
- android:id="@+id/cluster_start_activity"/>
</LinearLayout>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java
index d50f053..1ef5449 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/FakeClusterNavigationActivity.java
@@ -18,14 +18,10 @@
import android.app.Activity;
import android.car.Car;
-import android.car.CarNotConnectedException;
-import android.car.cluster.CarInstrumentClusterManager;
import android.car.cluster.ClusterActivityState;
-import android.content.ComponentName;
-import android.content.ServiceConnection;
+import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.IBinder;
import android.util.Log;
import android.widget.ImageView;
import android.widget.RelativeLayout;
@@ -35,14 +31,9 @@
/**
* Fake navigation activity for instrument cluster.
*/
-public class FakeClusterNavigationActivity
- extends Activity
- implements CarInstrumentClusterManager.Callback {
-
+public class FakeClusterNavigationActivity extends Activity {
private final static String TAG = FakeClusterNavigationActivity.class.getSimpleName();
- private Car mCarApi;
- private CarInstrumentClusterManager mClusterManager;
private ImageView mUnobscuredArea;
@Override
@@ -52,26 +43,27 @@
setContentView(R.layout.fake_cluster_navigation_activity);
mUnobscuredArea = findViewById(R.id.unobscuredArea);
- mCarApi = Car.createCar(this /* context */, new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- onCarConnected(mCarApi);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- onCarDisconnected(mCarApi);
- }
- });
- Log.i(TAG, "Connecting to car api...");
- mCarApi.connect();
+ handleIntent(getIntent());
}
-
@Override
- public void onClusterActivityStateChanged(String category, Bundle clusterActivityState) {
- ClusterActivityState state = ClusterActivityState.fromBundle(clusterActivityState);
- Log.i(TAG, "onClusterActivityStateChanged, category: " + category + ", state: " + state);
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ if (intent == null) {
+ Log.w(TAG, "Received a null intent");
+ return;
+ }
+ Bundle bundle = intent.getBundleExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
+ if (bundle == null) {
+ Log.w(TAG, "Received an intent without " + Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
+ return;
+ }
+ ClusterActivityState state = ClusterActivityState.fromBundle(bundle);
+ Log.i(TAG, "handling intent with state: " + state);
Rect unobscured = state.getUnobscuredBounds();
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
@@ -79,26 +71,4 @@
lp.setMargins(unobscured.left, unobscured.top, 0, 0);
mUnobscuredArea.setLayoutParams(lp);
}
-
- private void onCarConnected(Car car) {
- Log.i(TAG, "onCarConnected, car: " + car);
- try {
- mClusterManager = (CarInstrumentClusterManager) car.getCarManager(
- android.car.Car.CAR_INSTRUMENT_CLUSTER_SERVICE);
- } catch (CarNotConnectedException e) {
- throw new IllegalStateException(e);
- }
-
- try {
- Log.i(TAG, "registering callback...");
- mClusterManager.registerCallback(CarInstrumentClusterManager.CATEGORY_NAVIGATION, this);
- Log.i(TAG, "callback registered");
- } catch (android.car.CarNotConnectedException e) {
- throw new IllegalStateException(e);
- }
- }
-
- private void onCarDisconnected(Car car) {
-
- }
}
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
index 7e40b5b..a711cbb 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -19,10 +19,8 @@
import android.car.Car;
import android.car.CarAppFocusManager;
import android.car.CarNotConnectedException;
-import android.car.cluster.CarInstrumentClusterManager;
import android.car.navigation.CarNavigationStatusManager;
import android.content.ComponentName;
-import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
@@ -39,7 +37,6 @@
import androidx.car.cluster.navigation.NavigationState;
import androidx.fragment.app.Fragment;
-import com.google.android.car.kitchensink.KitchenSinkActivity;
import com.google.android.car.kitchensink.R;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -161,7 +158,6 @@
view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
view.findViewById(R.id.cluster_stop_button).setOnClickListener(v -> stopCluster());
- view.findViewById(R.id.cluster_start_activity).setOnClickListener(v -> startNavActivity());
mTurnByTurnButton = view.findViewById(R.id.cluster_turn_left_button);
mTurnByTurnButton.setOnClickListener(v -> toggleSendTurn());
@@ -175,32 +171,6 @@
super.onCreate(savedInstanceState);
}
- private void startNavActivity() {
- CarInstrumentClusterManager clusterManager;
- try {
- clusterManager = (CarInstrumentClusterManager) mCarApi.getCarManager(
- android.car.Car.CAR_INSTRUMENT_CLUSTER_SERVICE);
- } catch (CarNotConnectedException e) {
- Log.e(TAG, "Failed to get CarInstrumentClusterManager", e);
- Toast.makeText(getContext(), "Failed to get CarInstrumentClusterManager",
- Toast.LENGTH_LONG).show();
- return;
- }
-
- // Implicit intent ("startActivity" method doesn't work with explicit intents)
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addCategory(CarInstrumentClusterManager.CATEGORY_NAVIGATION);
- intent.setPackage(KitchenSinkActivity.class.getPackage().getName());
- try {
- clusterManager.startActivity(intent);
- } catch (android.car.CarNotConnectedException e) {
- Log.e(TAG, "Failed to startActivity in cluster", e);
- Toast.makeText(getContext(), getText(R.string.cluster_start_activity_failed),
- Toast.LENGTH_LONG).show();
- return;
- }
- }
-
/**
* Enables/disables sending turn-by-turn data through the {@link CarNavigationStatusManager}
*/
@@ -264,6 +234,7 @@
private void initCluster() {
if (hasFocus()) {
+ Log.i(TAG, "Already has focus");
return;
}
try {
@@ -271,11 +242,9 @@
CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
mCarAppFocusManager.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
mFocusCallback);
- if (!hasFocus()) {
- throw new RuntimeException("Focus was not acquired.");
- }
+ Log.i(TAG, "Focus requested");
} catch (CarNotConnectedException e) {
- Log.e(TAG, "Failed to set active focus", e);
+ Log.e(TAG, "Failed to request focus", e);
}
}
diff --git a/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java b/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java
index 15296d9..066072e 100644
--- a/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java
+++ b/tests/InstrumentClusterRendererSample/src/com/android/car/cluster/sample/InstrumentClusterRenderingServiceImpl.java
@@ -46,12 +46,12 @@
}
@Override
- protected NavigationRenderer getNavigationRenderer() {
+ public NavigationRenderer getNavigationRenderer() {
return mController.getNavigationRenderer();
}
@Override
- protected void onKeyEvent(KeyEvent keyEvent) {
+ public void onKeyEvent(KeyEvent keyEvent) {
// No need to handle key events in this implementation.
}
}