Merge "[RESTRICT AUTOMERGE] Add starting silent bugreporting" into qt-qpr1-dev
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index 5132890..72feacf 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -73,3 +73,6 @@
 
 $(call inherit-product, $(SRC_TARGET_DIR)/product/core_minimal.mk)
 
+# Default dex optimization configurations
+PRODUCT_PROPERTY_OVERRIDES += \
+     pm.dexopt.disable_bg_dexopt=true
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 3c6129e..498ae82 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -18,12 +18,15 @@
 
 import android.annotation.MainThread;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.UiModeManager;
 import android.car.Car;
 import android.car.ICar;
 import android.car.cluster.renderer.IInstrumentClusterNavigation;
 import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
@@ -545,6 +548,8 @@
         private static final String COMMAND_SUSPEND = "suspend";
         private static final String COMMAND_ENABLE_TRUSTED_DEVICE = "enable-trusted-device";
         private static final String COMMAND_REMOVE_TRUSTED_DEVICES = "remove-trusted-devices";
+        private static final String COMMAND_START_FIXED_ACTIVITY_MODE = "start-fixed-activity-mode";
+        private static final String COMMAND_STOP_FIXED_ACTIVITY_MODE = "stop-fixed-activity-mode";
 
         private static final String PARAM_DAY_MODE = "day";
         private static final String PARAM_NIGHT_MODE = "night";
@@ -589,6 +594,11 @@
                     + " wireless projection");
             pw.println("\t--metrics");
             pw.println("\t  When used with dumpsys, only metrics will be in the dumpsys output.");
+            pw.println("\tstart-fixed-activity displayId packageName activityName");
+            pw.println("\t  Start an Activity the specified display as fixed mode");
+            pw.println("\tstop-fixed-mode displayId");
+            pw.println("\t  Stop fixed Activity mode for the given display. "
+                    + "The Activity will not be restarted upon crash.");
         }
 
         public void exec(String[] args, PrintWriter writer) {
@@ -715,12 +725,62 @@
                             .removeAllTrustedDevices(
                                     mUserManagerHelper.getCurrentForegroundUserId());
                     break;
+                case COMMAND_START_FIXED_ACTIVITY_MODE:
+                    handleStartFixedActivity(args, writer);
+                    break;
+                case COMMAND_STOP_FIXED_ACTIVITY_MODE:
+                    handleStopFixedMode(args, writer);
+                    break;
                 default:
                     writer.println("Unknown command: \"" + arg + "\"");
                     dumpHelp(writer);
             }
         }
 
+        private void handleStartFixedActivity(String[] args, PrintWriter writer) {
+            if (args.length != 4) {
+                writer.println("Incorrect number of arguments");
+                dumpHelp(writer);
+                return;
+            }
+            int displayId;
+            try {
+                displayId = Integer.parseInt(args[1]);
+            } catch (NumberFormatException e) {
+                writer.println("Wrong display id:" + args[1]);
+                return;
+            }
+            String packageName = args[2];
+            String activityName = args[3];
+            Intent intent = new Intent();
+            intent.setComponent(new ComponentName(packageName, activityName));
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            ActivityOptions options = ActivityOptions.makeBasic();
+            options.setLaunchDisplayId(displayId);
+            if (!mFixedActivityService.startFixedActivityModeForDisplayAndUser(intent, options,
+                    displayId, ActivityManager.getCurrentUser())) {
+                writer.println("Failed to start");
+                return;
+            }
+            writer.println("Succeeded");
+        }
+
+        private void handleStopFixedMode(String[] args, PrintWriter writer) {
+            if (args.length != 2) {
+                writer.println("Incorrect number of arguments");
+                dumpHelp(writer);
+                return;
+            }
+            int displayId;
+            try {
+                displayId = Integer.parseInt(args[1]);
+            } catch (NumberFormatException e) {
+                writer.println("Wrong display id:" + args[1]);
+                return;
+            }
+            mFixedActivityService.stopFixedActivityMode(displayId);
+        }
+
         private void forceDayNightMode(String arg, PrintWriter writer) {
             int mode;
             switch (arg) {
diff --git a/service/src/com/android/car/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
index b8f8608..20885b8 100644
--- a/service/src/com/android/car/am/FixedActivityService.java
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -15,6 +15,9 @@
  */
 package com.android.car.am;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.Process.INVALID_UID;
+
 import static com.android.car.CarLog.TAG_AM;
 
 import android.annotation.NonNull;
@@ -26,6 +29,7 @@
 import android.app.IActivityManager;
 import android.app.IProcessObserver;
 import android.app.TaskStackListener;
+import android.car.hardware.power.CarPowerManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -34,7 +38,10 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.HandlerThread;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -60,6 +67,11 @@
 
     private static final boolean DBG = false;
 
+    private static final long RECHECK_INTERVAL_MS = 500;
+    private static final int MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY = 5;
+    // If process keep running without crashing, will reset consecutive crash counts.
+    private static final long CRASH_FORGET_INTERVAL_MS = 2 * 60 * 1000; // 2 mins
+
     private static class RunningActivityInfo {
         @NonNull
         public final Intent intent;
@@ -70,9 +82,20 @@
         @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.
+        @GuardedBy("mLock")
         public boolean isVisible;
+        @GuardedBy("mLock")
+        public long lastLaunchTimeMs = 0;
+        @GuardedBy("mLock")
+        public int consecutiveRetries = 0;
+        @GuardedBy("mLock")
+        public int taskId = INVALID_TASK_ID;
+        @GuardedBy("mLock")
+        public int previousTaskId = INVALID_TASK_ID;
+        @GuardedBy("mLock")
+        public boolean inBackground;
+        @GuardedBy("mLock")
+        public boolean failureLogged;
 
         RunningActivityInfo(@NonNull Intent intent, @NonNull ActivityOptions activityOptions,
                 @UserIdInt int userId) {
@@ -81,10 +104,17 @@
             this.userId = userId;
         }
 
+        private void resetCrashCounterLocked() {
+            consecutiveRetries = 0;
+            failureLogged = false;
+        }
+
         @Override
         public String toString() {
             return "RunningActivityInfo{intent:" + intent + ",activityOptions:" + activityOptions
-                    + ",userId:" + userId + "}";
+                    + ",userId:" + userId + ",isVisible:" + isVisible
+                    + ",lastLaunchTimeMs:" + lastLaunchTimeMs
+                    + ",consecutiveRetries:" + consecutiveRetries + ",taskId:" + taskId + "}";
         }
     }
 
@@ -111,11 +141,41 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
+            String action = intent.getAction();
             if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
                     || Intent.ACTION_PACKAGE_REPLACED.equals(
                     action)) {
-                launchIfNecessary();
+                Uri packageData = intent.getData();
+                if (packageData == null) {
+                    Log.w(TAG_AM, "null packageData");
+                    return;
+                }
+                String packageName = packageData.getSchemeSpecificPart();
+                if (packageName == null) {
+                    Log.w(TAG_AM, "null packageName");
+                    return;
+                }
+                int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
+                int userId = UserHandle.getUserId(uid);
+                boolean tryLaunch = false;
+                synchronized (mLock) {
+                    for (int i = 0; i < mRunningActivities.size(); i++) {
+                        RunningActivityInfo info = mRunningActivities.valueAt(i);
+                        ComponentName component = info.intent.getComponent();
+                        // should do this for all activities as the same package can cover multiple
+                        // displays.
+                        if (packageName.equals(component.getPackageName())
+                                && info.userId == userId) {
+                            Log.i(TAG_AM, "Package updated:" + packageName
+                                    + ",user:" + userId);
+                            info.resetCrashCounterLocked();
+                            tryLaunch = true;
+                        }
+                    }
+                }
+                if (tryLaunch) {
+                    launchIfNecessary();
+                }
             }
         }
     };
@@ -126,6 +186,26 @@
         public void onTaskStackChanged() {
             launchIfNecessary();
         }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) {
+            launchIfNecessary();
+        }
+
+        @Override
+        public void onTaskRemoved(int taskId) {
+            launchIfNecessary();
+        }
+
+        @Override
+        public void onTaskMovedToFront(int taskId) {
+            launchIfNecessary();
+        }
+
+        @Override
+        public void onTaskRemovalStarted(int taskId) {
+            launchIfNecessary();
+        }
     };
 
     private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -145,6 +225,13 @@
         }
     };
 
+    private final HandlerThread mHandlerThread = new HandlerThread(
+            FixedActivityService.class.getSimpleName());
+
+    private final Runnable mActivityCheckRunnable = () -> {
+        launchIfNecessary();
+    };
+
     private final Object mLock = new Object();
 
     // key: displayId
@@ -155,13 +242,29 @@
     @GuardedBy("mLock")
     private boolean mEventMonitoringActive;
 
+    @GuardedBy("mLock")
+    private CarPowerManager mCarPowerManager;
+
+    private final CarPowerManager.CarPowerStateListener mCarPowerStateListener = (state) -> {
+        if (state != CarPowerManager.CarPowerStateListener.ON) {
+            return;
+        }
+        synchronized (mLock) {
+            for (int i = 0; i < mRunningActivities.size(); i++) {
+                RunningActivityInfo info = mRunningActivities.valueAt(i);
+                info.resetCrashCounterLocked();
+            }
+        }
+        launchIfNecessary();
+    };
+
     public FixedActivityService(Context context) {
         mContext = context;
         mAm = ActivityManager.getService();
         mUm = context.getSystemService(UserManager.class);
+        mHandlerThread.start();
     }
 
-
     @Override
     public void init() {
         // nothing to do
@@ -181,18 +284,26 @@
         }
     }
 
+    private void postRecheck(long delayMs) {
+        mHandlerThread.getThreadHandler().postDelayed(mActivityCheckRunnable, delayMs);
+    }
+
     private void startMonitoringEvents() {
+        CarPowerManager carPowerManager;
         synchronized (mLock) {
             if (mEventMonitoringActive) {
                 return;
             }
             mEventMonitoringActive = true;
+            carPowerManager = CarLocalServices.createCarPowerManager(mContext);
+            mCarPowerManager = carPowerManager;
         }
         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);
+        filter.addDataScheme("package");
         mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
                 /* broadcastPermission= */ null, /* scheduler= */ null);
         try {
@@ -201,15 +312,28 @@
         } catch (RemoteException e) {
             Log.e(TAG_AM, "remote exception from AM", e);
         }
+        try {
+            carPowerManager.setListener(mCarPowerStateListener);
+        } catch (Exception e) {
+            // should not happen
+            Log.e(TAG_AM, "Got exception from CarPowerManager", e);
+        }
     }
 
     private void stopMonitoringEvents() {
+        CarPowerManager carPowerManager;
         synchronized (mLock) {
             if (!mEventMonitoringActive) {
                 return;
             }
             mEventMonitoringActive = false;
+            carPowerManager = mCarPowerManager;
+            mCarPowerManager = null;
         }
+        if (carPowerManager != null) {
+            carPowerManager.clearListener();
+        }
+        mHandlerThread.getThreadHandler().removeCallbacks(mActivityCheckRunnable);
         CarUserService userService = CarLocalServices.getService(CarUserService.class);
         userService.removeUserCallback(mUserCallback);
         try {
@@ -243,6 +367,7 @@
             Log.e(TAG_AM, "cannot get StackInfo from AM");
             return false;
         }
+        long now = SystemClock.elapsedRealtime();
         synchronized (mLock) {
             if (mRunningActivities.size() == 0) {
                 // it must have been stopped.
@@ -264,16 +389,30 @@
                         && activityInfo.userId == topUserId && stackInfo.visible) {
                     // top one is matching.
                     activityInfo.isVisible = true;
+                    activityInfo.taskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
                     continue;
                 }
-                if (DBG) {
-                    Log.i(TAG_AM, "Unmatched top activity:" + stackInfo.topActivity
-                            + " user:" + topUserId + " display:" + stackInfo.displayId);
+                activityInfo.previousTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
+                Log.i(TAG_AM, "Unmatched top activity will be removed:"
+                        + stackInfo.topActivity + " top task id:" + activityInfo.previousTaskId
+                        + " user:" + topUserId + " display:" + stackInfo.displayId);
+                activityInfo.inBackground = false;
+                for (int i = 0; i < stackInfo.taskIds.length - 1; i++) {
+                    if (activityInfo.taskId == stackInfo.taskIds[i]) {
+                        activityInfo.inBackground = true;
+                    }
+                }
+                if (!activityInfo.inBackground) {
+                    activityInfo.taskId = INVALID_TASK_ID;
                 }
             }
             for (int i = 0; i < mRunningActivities.size(); i++) {
                 RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
+                long timeSinceLastLaunchMs = now - activityInfo.lastLaunchTimeMs;
                 if (activityInfo.isVisible) {
+                    if (timeSinceLastLaunchMs >= CRASH_FORGET_INTERVAL_MS) {
+                        activityInfo.consecutiveRetries = 0;
+                    }
                     continue;
                 }
                 if (!isComponentAvailable(activityInfo.intent.getComponent(),
@@ -281,14 +420,40 @@
                         activityInfo.userId)) {
                     continue;
                 }
+                // For 1st call (consecutiveRetries == 0), do not wait as there can be no posting
+                // for recheck.
+                if (activityInfo.consecutiveRetries > 0 && (timeSinceLastLaunchMs
+                        < RECHECK_INTERVAL_MS)) {
+                    // wait until next check interval comes.
+                    continue;
+                }
+                if (activityInfo.consecutiveRetries >= MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY) {
+                    // re-tried too many times, give up for now.
+                    if (!activityInfo.failureLogged) {
+                        activityInfo.failureLogged = true;
+                        Log.w(TAG_AM, "Too many relaunch failure of fixed activity:"
+                                + activityInfo);
+                    }
+                    continue;
+                }
+
                 Log.i(TAG_AM, "Launching Activity for fixed mode. Intent:" + activityInfo.intent
                         + ",userId:" + UserHandle.of(activityInfo.userId) + ",displayId:"
                         + mRunningActivities.keyAt(i));
+                // Increase retry count if task is not in background. In case like other app is
+                // launched and the target activity is still in background, do not consider it
+                // as retry.
+                if (!activityInfo.inBackground) {
+                    activityInfo.consecutiveRetries++;
+                }
                 try {
+                    postRecheck(RECHECK_INTERVAL_MS);
+                    postRecheck(CRASH_FORGET_INTERVAL_MS);
                     mContext.startActivityAsUser(activityInfo.intent,
                             activityInfo.activityOptions.toBundle(),
                             UserHandle.of(activityInfo.userId));
                     activityInfo.isVisible = true;
+                    activityInfo.lastLaunchTimeMs = SystemClock.elapsedRealtime();
                 } catch (Exception e) { // Catch all for any app related issues.
                     Log.w(TAG_AM, "Cannot start activity:" + activityInfo.intent, e);
                 }
@@ -368,6 +533,10 @@
         if (!isDisplayAllowedForFixedMode(displayId)) {
             return false;
         }
+        if (options == null) {
+            Log.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, null options");
+            return false;
+        }
         if (!isUserAllowedToLaunchActivity(userId)) {
             Log.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, requested user:" + userId
                     + " cannot launch activity, Intent:" + intent);
@@ -390,7 +559,16 @@
                 startMonitoringEvents = true;
             }
             RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
-            if (activityInfo == null) {
+            boolean replaceEntry = true;
+            if (activityInfo != null && activityInfo.intent.equals(intent)
+                    && options.equals(activityInfo.activityOptions)
+                    && userId == activityInfo.userId) {
+                replaceEntry = false;
+                if (activityInfo.isVisible) { // already shown.
+                    return true;
+                }
+            }
+            if (replaceEntry) {
                 activityInfo = new RunningActivityInfo(intent, options, userId);
                 mRunningActivities.put(displayId, activityInfo);
             }
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index df6c476..cc0a6b7 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -133,6 +134,7 @@
                 @Override
                 public boolean startFixedActivityModeForDisplayAndUser(Intent intent,
                         Bundle activityOptionsBundle, int userId) {
+                    Binder.clearCallingIdentity();
                     ActivityOptions options = new ActivityOptions(activityOptionsBundle);
                     FixedActivityService service = CarLocalServices.getService(
                             FixedActivityService.class);
@@ -142,6 +144,7 @@
 
                 @Override
                 public void stopFixedActivityMode(int displayId) {
+                    Binder.clearCallingIdentity();
                     FixedActivityService service = CarLocalServices.getService(
                             FixedActivityService.class);
                     service.stopFixedActivityMode(displayId);
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 8997e32..5612807 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -116,5 +116,26 @@
                   android:grantUriPermissions="true"
                   android:exported="true" />
 
+        <activity android:name=".AlwaysCrashingActivity"
+                  android:label="@string/always_crashing_activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".NoCrashActivity"
+                  android:label="@string/no_crash_activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".EmptyActivity"
+                  android:label="@string/empty_activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/empty_activity.xml b/tests/EmbeddedKitchenSinkApp/res/layout/empty_activity.xml
new file mode 100644
index 0000000..5312fee
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/empty_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/empty_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/empty_activity"
+        android:layout_weight="1" />
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 6575ce1..0d56369 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -332,4 +332,9 @@
     <string name="usernotice" translatable="false">This screen is for showing initial user notice and is not for product. Plz change config_userNoticeUiService in CarService before shipping.</string>
     <string name="dismiss_now" translatable="false">Dismiss for now</string>
     <string name="dismiss_forever" translatable="false">Do not show again</string>
+
+    <!-- [AlwaysCrashing|NoCrash|Empty]Activity -->
+    <string name="always_crashing_activity" translatable="false">Always Crash Activity</string>
+    <string name="no_crash_activity" translatable="false">No Crash Activity</string>
+    <string name="empty_activity" translatable="false">Empty Activity</string>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/AlwaysCrashingActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/AlwaysCrashingActivity.java
new file mode 100644
index 0000000..05529b2
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/AlwaysCrashingActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.google.android.car.kitchensink;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Activity for testing purpose. This one always crashes inside onCreate.
+ */
+public class AlwaysCrashingActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        throw new RuntimeException("Intended crash for testing");
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/EmptyActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/EmptyActivity.java
new file mode 100644
index 0000000..aad25cb
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/EmptyActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.google.android.car.kitchensink;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class EmptyActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.empty_activity);
+    }
+
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/NoCrashActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/NoCrashActivity.java
new file mode 100644
index 0000000..f10e1db
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/NoCrashActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.google.android.car.kitchensink;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class NoCrashActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.empty_activity);
+        TextView text = findViewById(R.id.empty_text);
+        text.setText(R.string.no_crash_activity);
+    }
+}
diff --git a/tests/fixed_activity_mode_test/fixed_activity_mode_test.sh b/tests/fixed_activity_mode_test/fixed_activity_mode_test.sh
new file mode 100755
index 0000000..8beb1f4
--- /dev/null
+++ b/tests/fixed_activity_mode_test/fixed_activity_mode_test.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+if [ -z "$ANDROID_PRODUCT_OUT" ]; then
+    echo "ANDROID_PRODUCT_OUT not set"
+    exit
+fi
+DISP_ID=1
+if [[ $# -eq 1 ]]; then
+  echo "$1"
+  DISP_ID=$1
+fi
+echo "Use display:$DISP_ID"
+
+adb root
+# Check always crashing one
+echo "Start AlwaysCrashingActivity in fixed mode"
+adb shell dumpsys car_service start-fixed-activity-mode $DISP_ID com.google.android.car.kitchensink com.google.android.car.kitchensink.AlwaysCrashingActivity
+sleep 1
+read -p "AlwaysCrashingAvtivity should not be tried any more. Press Enter"
+# package update
+echo "Will try package update:"
+adb install -r -g $ANDROID_PRODUCT_OUT/system/priv-app/EmbeddedKitchenSinkApp/EmbeddedKitchenSinkApp.apk
+read -p "AlwaysCrashingAvtivity should have been retried. Press Enter"
+# suspend-resume
+echo "Check retry for suspend - resume"
+adb shell setprop android.car.garagemodeduration 1
+adb shell dumpsys car_service suspend
+adb shell dumpsys car_service resume
+read -p "AlwaysCrashingAvtivity should have been retried. Press Enter"
+# starting other Activity
+echo "Switch to no crash Activity"
+adb shell dumpsys car_service start-fixed-activity-mode $DISP_ID com.google.android.car.kitchensink com.google.android.car.kitchensink.NoCrashActivity
+read -p "NoCrashAvtivity should have been shown. Press Enter"
+# stating other non-fixed Activity
+adb shell am start-activity --display $DISP_ID -n com.google.android.car.kitchensink/.EmptyActivity
+read -p "NoCrashAvtivity should be shown after showing EmptyActivity. Press Enter"
+# package update
+echo "Will try package update:"
+adb install -r -g $ANDROID_PRODUCT_OUT/system/priv-app/EmbeddedKitchenSinkApp/EmbeddedKitchenSinkApp.apk
+read -p "NoCrashActivity should be shown. Press Enter"
+# stop the mode
+echo "Stop fixed activity mode"
+adb shell dumpsys car_service stop-fixed-activity-mode $DISP_ID
+adb shell am start-activity --display $DISP_ID -n com.google.android.car.kitchensink/.EmptyActivity
+read -p "EmptyActivity should be shown. Press Enter"