Merge "Add FixedActivityService" into qt-qpr1-dev
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
index 7deecc7..4f41796 100644
--- a/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
+++ b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl
@@ -28,6 +28,8 @@
/**
* Returns {@link IInstrumentClusterNavigation} that will be passed to the navigation
* application.
+ *
+ * TODO(b/141992448) : remove blocking call
*/
IInstrumentClusterNavigation getNavigationService();
diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentClusterHelper.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterHelper.aidl
new file mode 100644
index 0000000..680e241
--- /dev/null
+++ b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterHelper.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cluster.renderer;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Helper binder API for InstrumentClusterRenderingService. This contains binder calls to car
+ * service.
+ *
+ * @hide
+ */
+interface IInstrumentClusterHelper {
+ /**
+ * Start an activity to specified display / user. The activity is considered as
+ * in fixed mode for the display and will be re-launched if the activity crashes, the package
+ * is updated or goes to background for whatever reason.
+ * Only one activity can exist in fixed mode for the target display and calling this multiple
+ * times with different {@code Intent} will lead into making all previous activities into
+ * non-fixed normal state (= will not be re-launched.)
+ *
+ * Do not change binder transaction number.
+ */
+ boolean startFixedActivityModeForDisplayAndUser(in Intent intent,
+ in Bundle activityOptionsBundle, int userId) = 0;
+ /**
+ * The activity lauched on the display is no longer in fixed mode. Re-launching or finishing
+ * should not trigger re-launfhing any more. Note that Activity for non-current user will
+ * be auto-stopped and there is no need to call this for user swiching. Note that this does not
+ * stop the activity but it will not be re-launched any more.
+ *
+ * Do not change binder transaction number.
+ */
+ void stopFixedActivityMode(int displayId) = 1;
+}
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index b692c02..29ae9b8 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.Service;
import android.car.Car;
@@ -82,6 +83,15 @@
*/
@SystemApi
public abstract class InstrumentClusterRenderingService extends Service {
+ /**
+ * Key to pass IInstrumentClusterHelper binder in onBind call {@link Intent} through extra
+ * {@link Bundle). Both extra bundle and binder itself use this key.
+ *
+ * @hide
+ */
+ public static final String EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER =
+ "android.car.cluster.renderer.IInstrumentClusterHelper";
+
private static final String TAG = CarLibLog.TAG_CLUSTER;
/**
@@ -94,15 +104,20 @@
private static final String BITMAP_QUERY_WIDTH = "w";
private static final String BITMAP_QUERY_HEIGHT = "h";
+ private final Handler mUiHandler = new Handler(Looper.getMainLooper());
+
private final Object mLock = new Object();
+ // Main thread only
private RendererBinder mRendererBinder;
- private Handler mUiHandler = new Handler(Looper.getMainLooper());
private ActivityOptions mActivityOptions;
private ClusterActivityState mActivityState;
private ComponentName mNavigationComponent;
@GuardedBy("mLock")
private ContextOwner mNavContextOwner;
+ @GuardedBy("mLock")
+ private IInstrumentClusterHelper mInstrumentClusterHelper;
+
private static final int IMAGE_CACHE_SIZE_BYTES = 4 * 1024 * 1024; /* 4 mb */
private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(
IMAGE_CACHE_SIZE_BYTES) {
@@ -164,6 +179,18 @@
Log.d(TAG, "onBind, intent: " + intent);
}
+ Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER);
+ IBinder binder = null;
+ if (bundle != null) {
+ binder = bundle.getBinder(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER);
+ }
+ if (binder == null) {
+ Log.wtf(TAG, "IInstrumentClusterHelper not passed through binder");
+ } else {
+ synchronized (mLock) {
+ mInstrumentClusterHelper = IInstrumentClusterHelper.Stub.asInterface(binder);
+ }
+ }
if (mRendererBinder == null) {
mRendererBinder = new RendererBinder(getNavigationRenderer());
}
@@ -203,6 +230,76 @@
public void onNavigationComponentReleased() {
}
+ @Nullable
+ private IInstrumentClusterHelper getClusterHelper() {
+ synchronized (mLock) {
+ if (mInstrumentClusterHelper == null) {
+ Log.w("mInstrumentClusterHelper still null, should wait until onBind",
+ new RuntimeException());
+ }
+ return mInstrumentClusterHelper;
+ }
+ }
+
+ /**
+ * Start Activity in fixed mode.
+ *
+ * <p>Activity launched in this way will stay visible across crash, package updatge
+ * or other Activity launch. So this should be carefully used for case like apps running
+ * in instrument cluster.</p>
+ *
+ * <p> Only one Activity can stay in this mode for a display and launching other Activity
+ * with this call means old one get out of the mode. Alternatively
+ * {@link #stopFixedActivityMode(int)} can be called to get the top activitgy out of this
+ * mode.</p>
+ *
+ * @param intent Should include specific {@code ComponentName}.
+ * @param options Should include target display.
+ * @param userId Target user id
+ * @return {@code true} if succeeded. {@code false} may mean the target component is not ready
+ * or available. Note that failure can happen during early boot-up stage even if the
+ * target Activity is in normal state and client should retry when it fails. Once it is
+ * successfully launched, car service will guarantee that it is running across crash or
+ * other events.
+ *
+ * @hide
+ */
+ protected boolean startFixedActivityModeFoDisplayAndUser(@NonNull Intent intent,
+ @NonNull ActivityOptions options, @UserIdInt int userId) {
+ IInstrumentClusterHelper helper = getClusterHelper();
+ if (helper == null) {
+ return false;
+ }
+ try {
+ return helper.startFixedActivityModeForDisplayAndUser(intent, options.toBundle(),
+ userId);
+ } catch (RemoteException e) {
+ Log.w("Remote exception from car service", e);
+ // Probably car service will restart and rebind. So do nothing.
+ }
+ return false;
+ }
+
+
+ /**
+ * Stop fixed mode for top Activity in the display. Crashing or launching other Activity
+ * will not re-launch the top Activity any more.
+ *
+ * @hide
+ */
+ protected void stopFixedActivityMode(int displayId) {
+ IInstrumentClusterHelper helper = getClusterHelper();
+ if (helper == null) {
+ return;
+ }
+ try {
+ helper.stopFixedActivityMode(displayId);
+ } catch (RemoteException e) {
+ Log.w("Remote exception from car service, displayId:" + displayId, e);
+ // Probably car service will restart and rebind. So do nothing.
+ }
+ }
+
/**
* 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.
@@ -379,16 +476,19 @@
@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);
+ synchronized (mLock) {
+ writer.println("**" + getClass().getSimpleName() + "**");
+ writer.println("renderer binder: " + mRendererBinder);
+ if (mRendererBinder != null) {
+ writer.println("navigation renderer: " + mRendererBinder.mNavigationRenderer);
+ }
+ 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: " + getNavigationContextOwner().mPackageNames);
+ writer.println("mInstrumentClusterHelper" + mInstrumentClusterHelper);
}
- 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: " + getNavigationContextOwner().mPackageNames);
}
private class RendererBinder extends IInstrumentCluster.Stub {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index fe7c4d6..3c6129e 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -37,6 +37,7 @@
import android.util.Slog;
import android.util.TimingsTraceLog;
+import com.android.car.am.FixedActivityService;
import com.android.car.audio.CarAudioService;
import com.android.car.cluster.InstrumentClusterService;
import com.android.car.garagemode.GarageModeService;
@@ -80,6 +81,7 @@
private final CarPropertyService mCarPropertyService;
private final CarNightService mCarNightService;
private final AppFocusService mAppFocusService;
+ private final FixedActivityService mFixedActivityService;
private final GarageModeService mGarageModeService;
private final InstrumentClusterService mInstrumentClusterService;
private final CarLocationService mCarLocationService;
@@ -149,6 +151,7 @@
mAppFocusService = new AppFocusService(serviceContext, mSystemActivityMonitoringService);
mCarAudioService = new CarAudioService(serviceContext);
mCarNightService = new CarNightService(serviceContext, mCarPropertyService);
+ mFixedActivityService = new FixedActivityService(serviceContext);
mInstrumentClusterService = new InstrumentClusterService(serviceContext,
mAppFocusService, mCarInputService);
mSystemStateControllerService = new SystemStateControllerService(
@@ -177,6 +180,7 @@
CarLocalServices.addService(SystemInterface.class, mSystemInterface);
CarLocalServices.addService(CarDrivingStateService.class, mCarDrivingStateService);
CarLocalServices.addService(PerUserCarServiceHelper.class, mPerUserCarServiceHelper);
+ CarLocalServices.addService(FixedActivityService.class, mFixedActivityService);
// Be careful with order. Service depending on other service should be inited later.
List<CarServiceBase> allServices = new ArrayList<>();
@@ -193,6 +197,7 @@
allServices.add(mAppFocusService);
allServices.add(mCarAudioService);
allServices.add(mCarNightService);
+ allServices.add(mFixedActivityService);
allServices.add(mInstrumentClusterService);
allServices.add(mSystemStateControllerService);
allServices.add(mPerUserCarServiceHelper);
diff --git a/service/src/com/android/car/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
new file mode 100644
index 0000000..b1ca176
--- /dev/null
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.am;
+
+import static com.android.car.CarLog.TAG_AM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityOptions;
+import android.app.IActivityManager;
+import android.app.IProcessObserver;
+import android.app.TaskStackListener;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+
+import com.android.car.CarLocalServices;
+import com.android.car.CarServiceBase;
+import com.android.car.user.CarUserService;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Monitors top activity for a display and guarantee activity in fixed mode is re-launched if it has
+ * crashed or gone to background for whatever reason.
+ *
+ * <p>This component also monitors the upddate of the target package and re-launch it once
+ * update is complete.</p>
+ */
+public final class FixedActivityService implements CarServiceBase {
+
+ private static final boolean DBG = false;
+
+ private static class RunningActivityInfo {
+ @NonNull
+ public final Intent intent;
+
+ @NonNull
+ public final ActivityOptions activityOptions;
+
+ @UserIdInt
+ public final int userId;
+
+ // Only used in a method for local book-keeping. So do not need a lock.
+ // This does not represent the current visibility.
+ public boolean isVisible;
+
+ RunningActivityInfo(@NonNull Intent intent, @NonNull ActivityOptions activityOptions,
+ @UserIdInt int userId) {
+ this.intent = intent;
+ this.activityOptions = activityOptions;
+ this.userId = userId;
+ }
+
+ @Override
+ public String toString() {
+ return "RunningActivityInfo{intent:" + intent + ",activityOptions:" + activityOptions
+ + ",userId:" + userId + "}";
+ }
+ }
+
+ private final Context mContext;
+
+ private final IActivityManager mAm;
+
+ private final UserManager mUm;
+
+ private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
+ @Override
+ public void onUserLockChanged(@UserIdInt int userId, boolean unlocked) {
+ // Nothing to do
+ }
+
+ @Override
+ public void onSwitchUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ mRunningActivities.clear();
+ }
+ }
+ };
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
+ || Intent.ACTION_PACKAGE_REPLACED.equals(
+ action)) {
+ launchIfNecessary();
+ }
+ }
+ };
+
+ // It says listener but is actually callback.
+ private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+ @Override
+ public void onTaskStackChanged() {
+ launchIfNecessary();
+ }
+ };
+
+ private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
+ @Override
+ public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
+ launchIfNecessary();
+ }
+
+ @Override
+ public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
+ // ignore
+ }
+
+ @Override
+ public void onProcessDied(int pid, int uid) {
+ launchIfNecessary();
+ }
+ };
+
+ private final Object mLock = new Object();
+
+ // key: displayId
+ @GuardedBy("mLock")
+ private final SparseArray<RunningActivityInfo> mRunningActivities =
+ new SparseArray<>(/* capacity= */ 1); // default to one cluster only case
+
+ @GuardedBy("mLock")
+ private boolean mEventMonitoringActive;
+
+ public FixedActivityService(Context context) {
+ mContext = context;
+ mAm = ActivityManager.getService();
+ mUm = context.getSystemService(UserManager.class);
+ }
+
+
+ @Override
+ public void init() {
+ // nothing to do
+ }
+
+ @Override
+ public void release() {
+ stopMonitoringEvents();
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("*FixedActivityService*");
+ synchronized (mLock) {
+ writer.println("mRunningActivities:" + mRunningActivities
+ + " ,mEventMonitoringActive:" + mEventMonitoringActive);
+ }
+ }
+
+ private void startMonitoringEvents() {
+ synchronized (mLock) {
+ if (mEventMonitoringActive) {
+ return;
+ }
+ mEventMonitoringActive = true;
+ }
+ CarUserService userService = CarLocalServices.getService(CarUserService.class);
+ userService.addUserCallback(mUserCallback);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
+ /* broadcastPermission= */ null, /* scheduler= */ null);
+ try {
+ mAm.registerTaskStackListener(mTaskStackListener);
+ mAm.registerProcessObserver(mProcessObserver);
+ } catch (RemoteException e) {
+ Log.e(TAG_AM, "remote exception from AM", e);
+ }
+ }
+
+ private void stopMonitoringEvents() {
+ synchronized (mLock) {
+ if (!mEventMonitoringActive) {
+ return;
+ }
+ mEventMonitoringActive = false;
+ }
+ CarUserService userService = CarLocalServices.getService(CarUserService.class);
+ userService.removeUserCallback(mUserCallback);
+ try {
+ mAm.unregisterTaskStackListener(mTaskStackListener);
+ mAm.unregisterProcessObserver(mProcessObserver);
+ } catch (RemoteException e) {
+ Log.e(TAG_AM, "remote exception from AM", e);
+ }
+ }
+
+ @Nullable
+ private List<StackInfo> getStackInfos() {
+ try {
+ return mAm.getAllStackInfos();
+ } catch (RemoteException e) {
+ Log.e(TAG_AM, "remote exception from AM", e);
+ }
+ return null;
+ }
+
+ /**
+ * Launches all stored fixed mode activities if necessary.
+ * @param displayId Display id to check if it is visible. If check is not necessary, should pass
+ * {@link Display#INVALID_DISPLAY}.
+ * @return true if fixed Activity for given {@code displayId} is visible / successfully
+ * launched. It will return false for {@link Display#INVALID_DISPLAY} {@code displayId}.
+ */
+ private boolean launchIfNecessary(int displayId) {
+ List<StackInfo> infos = getStackInfos();
+ if (infos == null) {
+ Log.e(TAG_AM, "cannot get StackInfo from AM");
+ return false;
+ }
+ synchronized (mLock) {
+ if (mRunningActivities.size() == 0) {
+ // it must have been stopped.
+ if (DBG) {
+ Log.i(TAG_AM, "empty activity list", new RuntimeException());
+ }
+ return false;
+ }
+ for (int i = 0; i < mRunningActivities.size(); i++) {
+ mRunningActivities.valueAt(i).isVisible = false;
+ }
+ for (StackInfo stackInfo : infos) {
+ RunningActivityInfo activityInfo = mRunningActivities.get(stackInfo.displayId);
+ if (activityInfo == null) {
+ continue;
+ }
+ int topUserId = stackInfo.taskUserIds[stackInfo.taskUserIds.length - 1];
+ if (activityInfo.intent.getComponent().equals(stackInfo.topActivity)
+ && activityInfo.userId == topUserId && stackInfo.visible) {
+ // top one is matching.
+ activityInfo.isVisible = true;
+ continue;
+ }
+ if (DBG) {
+ Log.i(TAG_AM, "Unmatched top activity:" + stackInfo.topActivity
+ + " user:" + topUserId + " display:" + stackInfo.displayId);
+ }
+ }
+ for (int i = 0; i < mRunningActivities.size(); i++) {
+ RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
+ if (activityInfo.isVisible) {
+ continue;
+ }
+ if (!isComponentAvailable(activityInfo.intent.getComponent(),
+ activityInfo.userId) || !isUserAllowedToLaunchActivity(
+ activityInfo.userId)) {
+ continue;
+ }
+ Log.i(TAG_AM, "Launching Activity for fixed mode. Intent:" + activityInfo.intent
+ + ",userId:" + UserHandle.of(activityInfo.userId) + ",displayId:"
+ + mRunningActivities.keyAt(i));
+ try {
+ mContext.startActivityAsUser(activityInfo.intent,
+ activityInfo.activityOptions.toBundle(),
+ UserHandle.of(activityInfo.userId));
+ activityInfo.isVisible = true;
+ } catch (Exception e) { // Catch all for any app related issues.
+ Log.w(TAG_AM, "Cannot start activity:" + activityInfo.intent, e);
+ }
+ }
+ RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
+ if (activityInfo == null) {
+ return false;
+ }
+ return activityInfo.isVisible;
+ }
+ }
+
+ private void launchIfNecessary() {
+ launchIfNecessary(Display.INVALID_DISPLAY);
+ }
+
+ private void logComponentNotFound(ComponentName component, @UserIdInt int userId,
+ Exception e) {
+ Log.e(TAG_AM, "Specified Component not found:" + component
+ + " for userid:" + userId, e);
+ }
+
+ private boolean isComponentAvailable(ComponentName component, @UserIdInt int userId) {
+ PackageInfo packageInfo;
+ try {
+ packageInfo = mContext.getPackageManager().getPackageInfoAsUser(
+ component.getPackageName(), PackageManager.GET_ACTIVITIES, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ logComponentNotFound(component, userId, e);
+ return false;
+ }
+ if (packageInfo == null || packageInfo.activities == null) {
+ // may not be necessary but additional safety check
+ logComponentNotFound(component, userId, new RuntimeException());
+ return false;
+ }
+ String fullName = component.getClassName();
+ String shortName = component.getShortClassName();
+ for (ActivityInfo info : packageInfo.activities) {
+ if (info.name.equals(fullName) || info.name.equals(shortName)) {
+ return true;
+ }
+ }
+ logComponentNotFound(component, userId, new RuntimeException());
+ return false;
+ }
+
+ private boolean isUserAllowedToLaunchActivity(@UserIdInt int userId) {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (userId == currentUser) {
+ return true;
+ }
+ int[] profileIds = mUm.getEnabledProfileIds(currentUser);
+ for (int id : profileIds) {
+ if (id == userId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isDisplayAllowedForFixedMode(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY || displayId == Display.INVALID_DISPLAY) {
+ Log.w(TAG_AM, "Target display cannot be used for fixed mode, displayId:" + displayId,
+ new RuntimeException());
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks {@link InstrumentClusterRenderingService#startFixedActivityModeFoDisplayAndUser(
+ * Intent, ActivityOptions, int)}
+ */
+ public boolean startFixedActivityModeForDisplayAndUser(@NonNull Intent intent,
+ @NonNull ActivityOptions options, int displayId, @UserIdInt int userId) {
+ if (!isDisplayAllowedForFixedMode(displayId)) {
+ return false;
+ }
+ if (!isUserAllowedToLaunchActivity(userId)) {
+ Log.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, requested user:" + userId
+ + " cannot launch activity, Intent:" + intent);
+ return false;
+ }
+ ComponentName component = intent.getComponent();
+ if (component == null) {
+ Log.e(TAG_AM,
+ "startFixedActivityModeForDisplayAndUser: No component specified for "
+ + "requested Intent"
+ + intent);
+ return false;
+ }
+ if (!isComponentAvailable(component, userId)) {
+ return false;
+ }
+ boolean startMonitoringEvents = false;
+ synchronized (mLock) {
+ if (mRunningActivities.size() == 0) {
+ startMonitoringEvents = true;
+ }
+ RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
+ if (activityInfo == null) {
+ activityInfo = new RunningActivityInfo(intent, options, userId);
+ mRunningActivities.put(displayId, activityInfo);
+ }
+ }
+ boolean launched = launchIfNecessary(displayId);
+ if (!launched) {
+ synchronized (mLock) {
+ mRunningActivities.remove(displayId);
+ }
+ }
+ // If first trial fails, let client know and do not retry as it can be wrong setting.
+ if (startMonitoringEvents && launched) {
+ startMonitoringEvents();
+ }
+ return launched;
+ }
+
+ /** Check {@link InstrumentClusterRenderingService#stopFixedActivityMode(int)} */
+ public void stopFixedActivityMode(int displayId) {
+ if (!isDisplayAllowedForFixedMode(displayId)) {
+ return;
+ }
+ boolean stopMonitoringEvents = false;
+ synchronized (mLock) {
+ mRunningActivities.remove(displayId);
+ if (mRunningActivities.size() == 0) {
+ stopMonitoringEvents = true;
+ }
+ }
+ if (stopMonitoringEvents) {
+ stopMonitoringEvents();
+ }
+ }
+}
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index fd16da5..df6c476 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -15,17 +15,22 @@
*/
package com.android.car.cluster;
+import static android.car.cluster.renderer.InstrumentClusterRenderingService.EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER;
+
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.ActivityOptions;
import android.car.CarAppFocusManager;
import android.car.cluster.IInstrumentClusterManagerCallback;
import android.car.cluster.IInstrumentClusterManagerService;
import android.car.cluster.renderer.IInstrumentCluster;
+import android.car.cluster.renderer.IInstrumentClusterHelper;
import android.car.cluster.renderer.IInstrumentClusterNavigation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -43,6 +48,7 @@
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.R;
+import com.android.car.am.FixedActivityService;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
@@ -69,16 +75,18 @@
*/
@Deprecated
private final ClusterManagerService mClusterManagerService = new ClusterManagerService();
- private final Object mSync = new Object();
- @GuardedBy("mSync")
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private ContextOwner mNavContextOwner = NO_OWNER;
- @GuardedBy("mSync")
+ @GuardedBy("mLock")
private IInstrumentCluster mRendererService;
// 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.
+ @GuardedBy("mLock")
private DeferredRebinder mDeferredRebinder;
// Whether {@link android.car.cluster.renderer.InstrumentClusterRendererService} is bound
// (although not necessarily connected)
+ @GuardedBy("mLock")
private boolean mRendererBound = false;
/**
@@ -92,7 +100,7 @@
}
IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder);
ContextOwner navContextOwner;
- synchronized (mSync) {
+ synchronized (mLock) {
mRendererService = service;
navContextOwner = mNavContextOwner;
}
@@ -107,19 +115,39 @@
Log.d(TAG, "onServiceDisconnected, name: " + name);
}
mContext.unbindService(this);
- mRendererBound = false;
-
- synchronized (mSync) {
+ DeferredRebinder rebinder;
+ synchronized (mLock) {
+ mRendererBound = false;
mRendererService = null;
+ if (mDeferredRebinder == null) {
+ mDeferredRebinder = new DeferredRebinder();
+ }
+ rebinder = mDeferredRebinder;
}
-
- if (mDeferredRebinder == null) {
- mDeferredRebinder = new DeferredRebinder();
- }
- mDeferredRebinder.rebind();
+ rebinder.rebind();
}
};
+ private final IInstrumentClusterHelper mInstrumentClusterHelper =
+ new IInstrumentClusterHelper.Stub() {
+ @Override
+ public boolean startFixedActivityModeForDisplayAndUser(Intent intent,
+ Bundle activityOptionsBundle, int userId) {
+ ActivityOptions options = new ActivityOptions(activityOptionsBundle);
+ FixedActivityService service = CarLocalServices.getService(
+ FixedActivityService.class);
+ return service.startFixedActivityModeForDisplayAndUser(intent, options,
+ options.getLaunchDisplayId(), userId);
+ }
+
+ @Override
+ public void stopFixedActivityMode(int displayId) {
+ FixedActivityService service = CarLocalServices.getService(
+ FixedActivityService.class);
+ service.stopFixedActivityMode(displayId);
+ }
+ };
+
public InstrumentClusterService(Context context, AppFocusService appFocusService,
CarInputService carInputService) {
mContext = context;
@@ -181,7 +209,7 @@
IInstrumentCluster service;
ContextOwner requester = new ContextOwner(uid, pid);
ContextOwner newOwner = acquire ? requester : NO_OWNER;
- synchronized (mSync) {
+ synchronized (mLock) {
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
@@ -221,6 +249,11 @@
Intent intent = new Intent();
intent.setComponent(ComponentName.unflattenFromString(rendererService));
+ // Litle bit inefficiency here as Intent.getIBinderExtra() is a hidden API.
+ Bundle bundle = new Bundle();
+ bundle.putBinder(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER,
+ mInstrumentClusterHelper.asBinder());
+ intent.putExtra(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, bundle);
return mContext.bindServiceAsUser(intent, mRendererServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM);
}
@@ -262,7 +295,7 @@
private IInstrumentCluster getInstrumentClusterRendererService() {
IInstrumentCluster service;
- synchronized (mSync) {
+ synchronized (mLock) {
service = mRendererService;
}
return service;