Merge "CTS Verifier - Request runtime permissions" into mnc-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index f743fb2..4d30dda 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -111,6 +111,7 @@
     CtsVoiceInteractionService \
     CtsVoiceInteractionApp \
     CtsVoiceSettingsService \
+    CtsWidgetProviderApp \
     $(cts_account_support_packages) \
     $(cts_security_apps_list) \
     $(cts_security_keysets_list)
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index e03ebdc..8e27e72 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -72,7 +72,7 @@
                 <action android:name="com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY" />
             </intent-filter>
         </activity>
-        <activity android:name=".UserRestrictionActivity" >
+        <activity android:name=".SetPolicyActivity" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileWidgetPrimaryUserTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileWidgetPrimaryUserTest.java
new file mode 100644
index 0000000..bb1640b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileWidgetPrimaryUserTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2015 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.cts.managedprofile;
+
+import android.app.Instrumentation;
+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.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class contains tests for cross profile widget providers that are run on the primary users.
+ * The tests connect to a {@link android.appwidget.AppWidgetHost} and check whether the cross
+ * cross-profile widget can / cannot be found from the primary user.
+ * The tests cannot be run independently, but are part of one hostside test.
+ */
+public class CrossProfileWidgetPrimaryUserTest extends AndroidTestCase {
+    private static final String TAG = "CrossProfileWidgetPrimaryUserTest";
+
+    private static final int MSG_RESULT = 0;
+    private static final int MSG_PROVIDER_PRESENT = 1;
+    private static final int MSG_PROVIDER_UPDATES = 2;
+
+    private static final int RESULT_UNKNOWN = 0;
+    private static final int RESULT_PRESENT = 1;
+    private static final int RESULT_NOTPRESENT = 2;
+    private static final int RESULT_INTERRUPTED = 3;
+    private static final int RESULT_TIMEOUT = 4;
+
+    private static final String PACKAGE_EXTRA = "package-extra";
+
+    private Messenger mService;
+    private Connection mConnection;
+    private Result mResult;
+    private Messenger mResultMessenger;
+
+    @Override
+    protected void setUp() throws Exception {
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName(CrossProfileWidgetTest.WIDGET_PROVIDER_PKG,
+                CrossProfileWidgetTest.WIDGET_PROVIDER_PKG + ".SimpleAppWidgetHostService"));
+        mConnection = new Connection();
+        getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        mConnection.waitForService();
+        mResult = new Result(Looper.getMainLooper());
+        mResultMessenger = new Messenger(mResult);
+    }
+
+    public void testHasCrossProfileWidgetProvider_false() throws Exception {
+        int result = sendMessageToCallbacksService(MSG_PROVIDER_PRESENT,
+                CrossProfileWidgetTest.WIDGET_PROVIDER_PKG);
+        assertEquals(RESULT_NOTPRESENT, result);
+    }
+
+    public void testHostReceivesWidgetUpdates_false() throws Exception {
+        int result = sendMessageToCallbacksService(MSG_PROVIDER_UPDATES,
+                CrossProfileWidgetTest.WIDGET_PROVIDER_PKG);
+        assertEquals(RESULT_NOTPRESENT, result);
+    }
+
+    public void testHasCrossProfileWidgetProvider_true() throws Exception {
+        int result = sendMessageToCallbacksService(MSG_PROVIDER_PRESENT,
+                CrossProfileWidgetTest.WIDGET_PROVIDER_PKG);
+        assertEquals(RESULT_PRESENT, result);
+    }
+
+    public void testHostReceivesWidgetUpdates_true() throws Exception {
+        int result = sendMessageToCallbacksService(MSG_PROVIDER_UPDATES,
+                CrossProfileWidgetTest.WIDGET_PROVIDER_PKG);
+        assertEquals(RESULT_PRESENT, result);
+    }
+
+    private int sendMessageToCallbacksService(int msg, String packageName)
+            throws Exception {
+        Bundle params = new Bundle();
+        params.putString(PACKAGE_EXTRA, packageName);
+
+        Message message = Message.obtain(null, msg, params);
+        message.replyTo = mResultMessenger;
+
+        mService.send(message);
+
+        return mResult.waitForResult();
+    }
+
+    private static class Result extends Handler {
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+        public int result = 0;
+
+        public Result(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_RESULT) {
+                result = msg.arg1;
+                mSemaphore.release();
+            } else {
+                super.handleMessage(msg);
+            }
+        }
+
+        public int waitForResult() {
+            try {
+                if (mSemaphore.tryAcquire(120, TimeUnit.SECONDS)) {
+                    return result;
+                }
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Interrupted when talking to service", e);
+            }
+            return RESULT_TIMEOUT;
+        }
+    }
+
+    private class Connection implements ServiceConnection {
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mService = new Messenger(service);
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mService = null;
+        }
+
+        public void waitForService() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return;
+                }
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Interrupted when connecting to service", e);
+            }
+            fail("failed to connect to service");
+        }
+    };
+
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileWidgetTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileWidgetTest.java
new file mode 100644
index 0000000..085d56f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileWidgetTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 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.cts.managedprofile;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class contains tests for cross profile widget providers that are run on the managed
+ * profile. Policies are set using {@link SetPolicyActivity} and then verified in these tests.
+ * The tests cannot be run independently, but are part of one hostside test.
+ */
+public class CrossProfileWidgetTest extends BaseManagedProfileTest {
+    static final String WIDGET_PROVIDER_PKG = "com.android.cts.widgetprovider";
+
+    private AppWidgetManager mAppWidgetManager;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        mAppWidgetManager = (AppWidgetManager) mContext.getSystemService(Context.APPWIDGET_SERVICE);
+    }
+
+    /**
+     * This test checks that the widget provider was successfully whitelisted and verifies that
+     * if was added successfully and can be found inside the profile.
+     */
+    public void testCrossProfileWidgetProviderAdded() {
+        List<String> providers = mDevicePolicyManager.getCrossProfileWidgetProviders(
+                ADMIN_RECEIVER_COMPONENT);
+        assertEquals(1, providers.size());
+        assertTrue(providers.contains(WIDGET_PROVIDER_PKG));
+        // check that widget can be found inside the profile
+        assertTrue(containsWidgetProviderPkg(mAppWidgetManager.getInstalledProviders()));
+    }
+
+    /**
+     * This test verifies that the widget provider was successfully removed from the whitelist.
+     */
+    public void testCrossProfileWidgetProviderRemoved() {
+        List<String> providers = mDevicePolicyManager.getCrossProfileWidgetProviders(
+                ADMIN_RECEIVER_COMPONENT);
+        assertTrue(providers.isEmpty());
+        // check that widget can still be found inside the profile
+        // This does not currently work correctly: http://b/issues/21180997
+        // assertTrue(containsWidgetProviderPkg(mAppWidgetManager.getInstalledProviders()));
+    }
+
+    private boolean containsWidgetProviderPkg(List<AppWidgetProviderInfo> widgets) {
+        for (AppWidgetProviderInfo widget : widgets) {
+            if (WIDGET_PROVIDER_PKG.equals(widget.provider.getPackageName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/UserRestrictionActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SetPolicyActivity.java
similarity index 60%
rename from hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/UserRestrictionActivity.java
rename to hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SetPolicyActivity.java
index e8decf8..f59363b 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/UserRestrictionActivity.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SetPolicyActivity.java
@@ -28,15 +28,18 @@
 /**
  * Simple activity that adds or clears a user restriction depending on the value of the extras.
  */
-public class UserRestrictionActivity extends Activity {
+public class SetPolicyActivity extends Activity {
 
-    private static final String TAG = UserRestrictionActivity.class.getName();
+    private static final String TAG = SetPolicyActivity.class.getName();
 
     private static final String EXTRA_RESTRICTION_KEY = "extra-restriction-key";
+    private static final String EXTRA_PACKAGE_NAME = "extra-package-name";
     private static final String EXTRA_COMMAND = "extra-command";
 
-    private static final String ADD_COMMAND = "add-restriction";
-    private static final String CLEAR_COMMAND = "clear-restriction";
+    private static final String ADD_RESTRICTION_COMMAND = "add-restriction";
+    private static final String CLEAR_RESTRICTION_COMMAND = "clear-restriction";
+    private static final String ADD_CROSS_PROFILE_WIDGET_COMMAND = "add-cross-profile-widget";
+    private static final String REMOVE_CROSS_PROFILE_WIDGET_COMMAND = "remove-cross-profile-widget";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -62,19 +65,32 @@
     private void handleIntent(Intent intent) {
         DevicePolicyManager dpm = (DevicePolicyManager)
                 getSystemService(Context.DEVICE_POLICY_SERVICE);
-        String restrictionKey = intent.getStringExtra(EXTRA_RESTRICTION_KEY);
         String command = intent.getStringExtra(EXTRA_COMMAND);
-        Log.i(TAG, "Command: \"" + command + "\". Restriction: \"" + restrictionKey + "\"");
+        Log.i(TAG, "Command: \"" + command);
 
-        if (ADD_COMMAND.equals(command)) {
+        if (ADD_RESTRICTION_COMMAND.equals(command)) {
+            String restrictionKey = intent.getStringExtra(EXTRA_RESTRICTION_KEY);
             dpm.addUserRestriction(BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT, restrictionKey);
             Log.i(TAG, "Added user restriction " + restrictionKey
                     + " for user " + Process.myUserHandle());
-        } else if (CLEAR_COMMAND.equals(command)) {
+        } else if (CLEAR_RESTRICTION_COMMAND.equals(command)) {
+            String restrictionKey = intent.getStringExtra(EXTRA_RESTRICTION_KEY);
             dpm.clearUserRestriction(
                     BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT, restrictionKey);
             Log.i(TAG, "Cleared user restriction " + restrictionKey
                     + " for user " + Process.myUserHandle());
+        } else if (ADD_CROSS_PROFILE_WIDGET_COMMAND.equals(command)) {
+            String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+            dpm.addCrossProfileWidgetProvider(BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT,
+                    packageName);
+            Log.i(TAG, "Added cross-profile widget provider for package:" + packageName
+                    + " for user " + Process.myUserHandle());
+        } else if (REMOVE_CROSS_PROFILE_WIDGET_COMMAND.equals(command)) {
+            String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+            dpm.removeCrossProfileWidgetProvider(BaseManagedProfileTest.ADMIN_RECEIVER_COMPONENT,
+                    packageName);
+            Log.i(TAG, "Removed cross-profile widget provider for package:" + packageName
+                    + " for user " + Process.myUserHandle());
         } else {
             Log.e(TAG, "Invalid command: " + command);
         }
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/Android.mk b/hostsidetests/devicepolicy/app/WidgetProvider/Android.mk
new file mode 100644
index 0000000..c0e35fa
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2015 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsWidgetProviderApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml b/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
new file mode 100644
index 0000000..77246b5
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2015 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.widgetprovider">
+
+    <uses-permission android:name="android.permission.BIND_APPWIDGET"/>
+
+    <application>
+        <receiver android:name="SimpleWidgetProvider" >
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data android:name="android.appwidget.provider"
+                       android:resource="@xml/simple_widget_provider_info" />
+        </receiver>
+        <service android:name=".SimpleAppWidgetHostService" >
+            <intent-filter>
+                <action android:name="com.android.cts.widgetprovider.REGISTER_CALLBACK" />
+            </intent-filter>
+        </service>
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/res/layout/simple_widget.xml b/hostsidetests/devicepolicy/app/WidgetProvider/res/layout/simple_widget.xml
new file mode 100644
index 0000000..338c57a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/res/layout/simple_widget.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2015 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:padding="10dip">
+
+    <ImageView
+        android:id="@+id/pkg_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"/>
+
+    <TextView
+        android:id="@+id/pkg_name"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_centerVertical="true"
+        android:paddingLeft="10dip"
+        android:layout_toRightOf="@id/pkg_icon"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/res/xml/simple_widget_provider_info.xml b/hostsidetests/devicepolicy/app/WidgetProvider/res/xml/simple_widget_provider_info.xml
new file mode 100644
index 0000000..8a002d2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/res/xml/simple_widget_provider_info.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2015 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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="40dp"
+    android:minHeight="40dp"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen">
+</appwidget-provider>
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/src/com/android/cts/widgetprovider/SimpleAppWidgetHostService.java b/hostsidetests/devicepolicy/app/WidgetProvider/src/com/android/cts/widgetprovider/SimpleAppWidgetHostService.java
new file mode 100644
index 0000000..b5b6003
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/src/com/android/cts/widgetprovider/SimpleAppWidgetHostService.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2015 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.cts.widgetprovider;
+
+import android.app.Service;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Service that acts as AppWidgetHost that listens to onProvidersChanged callbacks and updates the
+ * internally stored list of profile widgets. The service reacts to messages sent from the device
+ * side tests and returns whether the expected widget provider is currently present or not.
+ */
+public class SimpleAppWidgetHostService extends Service {
+    private static final String TAG = "SimpleAppWidgetHostService";
+
+    private static final int MSG_RESULT = 0;
+    private static final int MSG_PROVIDER_PRESENT = 1;
+    private static final int MSG_PROVIDER_UPDATES = 2;
+
+    private static final int RESULT_UNKNOWN = 0;
+    private static final int RESULT_PRESENT = 1;
+    private static final int RESULT_NOT_PRESENT = 2;
+    private static final int RESULT_INTERRUPTED = 3;
+    private static final int RESULT_TIMEOUT = 4;
+
+    public static final String USER_EXTRA = "user-extra";
+    public static final String PACKAGE_EXTRA = "package-extra";
+    public static final String REPLY_EXTRA = "reply-extra";
+
+    private AppWidgetManager mAppWidgetManager;
+    private SimpleAppWidgetHost mAppWidgetHost;
+    private SimpleAppWidgetHostView mAppWidgetHostView;
+    private int mAppWidgetId;
+    private Messenger mMessenger;
+    private UserHandle mUserHandle;
+    private Semaphore mWidgetUpdateSemaphore = new Semaphore(0);
+    private RemoteViews mRemoteViews;
+
+    class CheckHandler extends Handler {
+        public CheckHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Bundle params = null;
+            if (msg.obj instanceof Bundle) {
+                params = (Bundle) (msg.obj);
+            }
+            try {
+                switch (msg.what) {
+                    case MSG_PROVIDER_PRESENT: {
+                        Log.d(TAG, "MSG_PROVIDER_PRESENT");
+                        int result = RESULT_UNKNOWN;
+                        try {
+                            AppWidgetProviderInfo info = mAppWidgetHost.getProvider(params);
+                            result = (info != null) ? RESULT_PRESENT : RESULT_NOT_PRESENT;
+                            if (info != null) {
+                                bindAppWidget(info);
+                            }
+                        } catch (InterruptedException e) {
+                            result = RESULT_INTERRUPTED;
+                        }
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT, result
+                                , 0 /* not used */));
+                        break;
+                    }
+                    case MSG_PROVIDER_UPDATES: {
+                        Log.d(TAG, "MSG_PROVIDER_UPDATES");
+                        int result = RESULT_UNKNOWN;
+                        try {
+                            updateWidgetViaWidgetId();
+                            boolean update = waitForUpdate();
+                            result = update ? RESULT_PRESENT : RESULT_NOT_PRESENT;
+                        } catch (InterruptedException e) {
+                            result = RESULT_INTERRUPTED;
+                        }
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT, result
+                                , 0 /* not used */));
+                        break;
+                    }
+                    default:
+                        super.handleMessage(msg);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to report test status");
+            }
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            return START_NOT_STICKY;
+        }
+        if ("com.android.cts.widgetprovider.REGISTER_CALLBACK".equals(intent.getAction())) {
+            mUserHandle = getUserHandleArgument(this, USER_EXTRA, intent);
+            Log.d(TAG, "mUserHandle=" + mUserHandle);
+            setup();
+        }
+        return START_STICKY;
+    }
+
+    private void setup() {
+        HandlerThread handlerThread = new HandlerThread("Widget test callback handler");
+        handlerThread.start();
+        mMessenger = new Messenger(new CheckHandler(handlerThread.getLooper()));
+        mAppWidgetManager = (AppWidgetManager) getSystemService(Context.APPWIDGET_SERVICE);
+        mAppWidgetHost = new SimpleAppWidgetHost(this, 0);
+        mAppWidgetHost.deleteHost();
+        mAppWidgetHost.startListening();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    @Override
+    public void onDestroy() {
+        mAppWidgetHost.stopListening();
+        mAppWidgetHost.deleteAppWidgetId(mAppWidgetId);
+        mAppWidgetHost.deleteHost();
+    }
+
+    private void bindAppWidget(AppWidgetProviderInfo info) {
+        mAppWidgetId = mAppWidgetHost.allocateAppWidgetId();
+        Log.d(TAG, "Registering app widget with id:" + mAppWidgetId);
+        mAppWidgetManager.bindAppWidgetIdIfAllowed(mAppWidgetId, mUserHandle, info.provider, null);
+        mAppWidgetHostView = (SimpleAppWidgetHostView) mAppWidgetHost.createView(this,
+                mAppWidgetId, info);
+        mRemoteViews = new RemoteViews(info.provider.getPackageName(), R.layout.simple_widget);
+    }
+
+    private UserHandle getUserHandleArgument(Context context, String key,
+            Intent intent) {
+        UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+        int serial = intent.getIntExtra(key, 0);
+        Log.d(TAG, "userId=" + serial);
+        return um.getUserForSerialNumber(serial);
+    }
+
+    private void updateWidgetViaWidgetId() {
+        Log.d(TAG, "Forcing widget update via widget id");
+        mWidgetUpdateSemaphore.drainPermits();
+        // trigger a widget update
+        mAppWidgetManager.updateAppWidget(mAppWidgetId, mRemoteViews);
+    }
+
+    private boolean waitForUpdate() throws InterruptedException {
+        // wait for updateAppWidget to arrive
+        return mWidgetUpdateSemaphore.tryAcquire(20, TimeUnit.SECONDS);
+    }
+
+    private class SimpleAppWidgetHost extends AppWidgetHost {
+        private List<AppWidgetProviderInfo> mProviders;
+        private Semaphore mSemaphore = new Semaphore(0);
+        public SimpleAppWidgetHost(Context context, int hostId) {
+            super(context, hostId);
+            synchronized (this) {
+                mProviders = mAppWidgetManager.getInstalledProvidersForProfile(mUserHandle);
+            }
+        }
+
+        @Override
+        protected void onProvidersChanged() {
+            super.onProvidersChanged();
+            Log.d(TAG, "onProvidersChanged callback received");
+            synchronized (this) {
+                mProviders = mAppWidgetManager.getInstalledProvidersForProfile(mUserHandle);
+            }
+            mSemaphore.release();
+        }
+
+        @Override
+        protected AppWidgetHostView onCreateView(Context context, int id, AppWidgetProviderInfo info) {
+            return new SimpleAppWidgetHostView(context);
+        }
+
+        public AppWidgetProviderInfo getProvider(Bundle params) throws InterruptedException {
+            String packageName = params.getString(PACKAGE_EXTRA);
+            while (mSemaphore.tryAcquire(30, TimeUnit.SECONDS)) {
+                mSemaphore.drainPermits();
+                Log.d(TAG, "checking for " + packageName + " " + mUserHandle);
+                synchronized (this) {
+                    for (AppWidgetProviderInfo providerInfo : mProviders) {
+                        if (providerInfo.provider.getPackageName().equals(packageName)) {
+                            Log.d(TAG, "Provider exists " + packageName
+                                    + " for user " + mUserHandle);
+                            return providerInfo;
+                        }
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+    private class SimpleAppWidgetHostView extends AppWidgetHostView {
+        public SimpleAppWidgetHostView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void updateAppWidget(RemoteViews views) {
+            super.updateAppWidget(views);
+            Log.d(TAG, "Host view received widget update");
+            mWidgetUpdateSemaphore.release();
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/src/com/android/cts/widgetprovider/SimpleWidgetProvider.java b/hostsidetests/devicepolicy/app/WidgetProvider/src/com/android/cts/widgetprovider/SimpleWidgetProvider.java
new file mode 100644
index 0000000..3bd4530
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/src/com/android/cts/widgetprovider/SimpleWidgetProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 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.cts.widgetprovider;
+
+import android.appwidget.AppWidgetProvider;
+
+/**
+ * A simple widget provider to test cross-profile widgets.
+ */
+public class SimpleWidgetProvider extends AppWidgetProvider {}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 671c3e5..650e963 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -185,9 +185,6 @@
     protected boolean runDeviceTestsAsUser(
             String pkgName, @Nullable String testClassName, String testMethodName, int userId)
             throws DeviceNotAvailableException {
-        if (testClassName.startsWith(".")) {
-            testClassName = pkgName + testClassName;
-        }
         return runDeviceTests(pkgName, testClassName, testMethodName, userId);
     }
 
@@ -200,6 +197,9 @@
     protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
             @Nullable String testMethodName, @Nullable Integer userId, @Nullable String params)
                    throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
         TestRunResult runResult = (userId == null && params == null)
                 ? doRunTests(pkgName, testClassName, testMethodName)
                 : doRunTestsAsUser(pkgName, testClassName, testMethodName,
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 93e1aff..acc5b26 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -48,6 +48,9 @@
     private static final String WIFI_CONFIG_CREATOR_PKG = "com.android.cts.wificonfigcreator";
     private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
 
+    private static final String WIDGET_PROVIDER_APK = "CtsWidgetProviderApp.apk";
+    private static final String WIDGET_PROVIDER_PKG = "com.android.cts.widgetprovider";
+
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
 
@@ -596,6 +599,47 @@
                 "testNfcShareEnabled", 0));
     }
 
+    public void testCrossProfileWidgets() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        try {
+            installApp(WIDGET_PROVIDER_APK);
+            getDevice().executeShellCommand("appwidget grantbind --user 0 --package "
+                    + WIDGET_PROVIDER_PKG);
+            startWidgetHostService();
+
+            String commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+                    "add-cross-profile-widget", mUserId);
+            assertTrue("Command was expected to succeed " + commandOutput,
+                    commandOutput.contains("Status: ok"));
+
+            assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
+                    "testCrossProfileWidgetProviderAdded", mUserId));
+            assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, ".CrossProfileWidgetPrimaryUserTest",
+                    "testHasCrossProfileWidgetProvider_true", 0));
+            assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, ".CrossProfileWidgetPrimaryUserTest",
+                    "testHostReceivesWidgetUpdates_true", 0));
+
+            commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+                    "remove-cross-profile-widget", mUserId);
+            assertTrue("Command was expected to succeed " + commandOutput,
+                    commandOutput.contains("Status: ok"));
+
+            assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
+                    "testCrossProfileWidgetProviderRemoved", mUserId));
+            assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, ".CrossProfileWidgetPrimaryUserTest",
+                    "testHasCrossProfileWidgetProvider_false", 0));
+            assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, ".CrossProfileWidgetPrimaryUserTest",
+                    "testHostReceivesWidgetUpdates_false", 0));
+        } finally {
+            changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
+                    mUserId);
+            getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
+        }
+    }
+
     private void disableActivityForUser(String activityName, int userId)
             throws DeviceNotAvailableException {
         String command = "am start -W --user " + userId
@@ -612,7 +656,20 @@
                 + " -c android.intent.category.DEFAULT "
                 + " --es extra-command " + command
                 + " --es extra-restriction-key " + key
-                + " " + MANAGED_PROFILE_PKG + "/.UserRestrictionActivity";
+                + " " + MANAGED_PROFILE_PKG + "/.SetPolicyActivity";
+        String commandOutput = getDevice().executeShellCommand(adbCommand);
+        CLog.logAndDisplay(LogLevel.INFO,
+                "Output for command " + adbCommand + ": " + commandOutput);
+        return commandOutput;
+    }
+
+    private String changeCrossProfileWidgetForUser(String packageName, String command, int userId)
+            throws DeviceNotAvailableException {
+        String adbCommand = "am start -W --user " + userId
+                + " -c android.intent.category.DEFAULT "
+                + " --es extra-command " + command
+                + " --es extra-package-name " + packageName
+                + " " + MANAGED_PROFILE_PKG + "/.SetPolicyActivity";
         String commandOutput = getDevice().executeShellCommand(adbCommand);
         CLog.logAndDisplay(LogLevel.INFO,
                 "Output for command " + adbCommand + ": " + commandOutput);
@@ -627,6 +684,15 @@
                 + getDevice().executeShellCommand(command));
     }
 
+    protected void startWidgetHostService() throws Exception {
+        String command = "am startservice --user 0 "
+                + "-a " + WIDGET_PROVIDER_PKG + ".REGISTER_CALLBACK "
+                + "--ei user-extra " + getUserSerialNumber(mUserId)
+                + " " + WIDGET_PROVIDER_PKG + "/.SimpleAppWidgetHostService";
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": "
+              + getDevice().executeShellCommand(command));
+    }
+
     private void assertAppLinkResult(String methodName) throws DeviceNotAvailableException {
         assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".AppLinkTest", methodName, mUserId));
     }
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
index 0589792..a787cdd 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
@@ -349,7 +349,12 @@
                     continue;
                 }
 
-                String[] parts = line.split(",");
+
+                // With a default limit of 0, empty strings at the end are discarded.
+                // We still consider the empty string as a valid value in some cases.
+                // Using any negative number for the limit will preserve a trailing empty string.
+                // @see String#split(String, int)
+                String[] parts = line.split(",", -1);
                 assertInteger(parts[0]); // old version
                 assertInteger(parts[1]); // UID
                 switch (parts[2]) { // aggregation type
diff --git a/tests/tests/assist/AndroidManifest.xml b/tests/tests/assist/AndroidManifest.xml
index b6cd684..97bd874 100644
--- a/tests/tests/assist/AndroidManifest.xml
+++ b/tests/tests/assist/AndroidManifest.xml
@@ -29,6 +29,7 @@
               <action android:name="android.intent.action.TEST_START_ACTIVITY_ASSIST_STRUCTURE" />
               <action android:name="android.intent.action.TEST_START_ACTIVITY_DISABLE_CONTEXT" />
               <action android:name="android.intent.action.TEST_START_ACTIVITY_FLAG_SECURE" />
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_LIFECYCLE" />
               <category android:name="android.intent.category.LAUNCHER" />
               <category android:name="android.intent.category.DEFAULT" />
           </intent-filter>
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index bf051c0..8d555da 100644
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -24,8 +24,11 @@
 public class Utils {
     public static final String TESTCASE_TYPE = "testcase_type";
     public static final String TESTINFO = "testinfo";
-    public static final String BROADCAST_INTENT = "android.intent.action.ASSIST_TESTAPP";
-    public static final String BROADCAST_ASSIST_DATA_INTENT = "android.intent.action.ASSIST_DATA";
+    public static final String ACTION_PREFIX = "android.intent.action.";
+    public static final String BROADCAST_INTENT = ACTION_PREFIX + "ASSIST_TESTAPP";
+    public static final String BROADCAST_ASSIST_DATA_INTENT = ACTION_PREFIX + "ASSIST_DATA";
+    public static final String BROADCAST_INTENT_START_ASSIST = ACTION_PREFIX + "START_ASSIST";
+    public static final String ASSIST_RECEIVER_REGISTERED = ACTION_PREFIX + "ASSIST_READY";
     public static final String TEST_ERROR = "Error In Test:";
 
     public static final String ASSIST_STRUCTURE_KEY = "assist_structure";
@@ -33,12 +36,30 @@
     public static final String ASSIST_BUNDLE_KEY = "assist_bundle";
     public static final String ASSIST_SCREENSHOT_KEY = "assist_screenshot";
 
+
+    /** Lifecycle Test intent constants */
+    public static final String LIFECYCLE_PREFIX = ACTION_PREFIX + "lifecycle_";
+    public static final String LIFECYCLE_HASRESUMED = LIFECYCLE_PREFIX + "hasResumed";
+    public static final String LIFECYCLE_ONPAUSE = LIFECYCLE_PREFIX + "onpause";
+    public static final String LIFECYCLE_ONSTOP = LIFECYCLE_PREFIX + "onstop";
+    public static final String LIFECYCLE_ONDESTROY = LIFECYCLE_PREFIX + "ondestroy";
+
+    /** Flag Secure Test intent constants */
+    public static final String FLAG_SECURE_HASRESUMED = ACTION_PREFIX + "flag_secure_hasResumed";
+
+    /** Two second timeout for getting back assist context */
     public static final int TIMEOUT_MS = 2 * 1000; // TODO(awlee): what is the timeout
+    public static final int ACTIVITY_ONRESUME_TIMEOUT_MS = 4000;
+    public static final String EXTRA_REGISTER_RECEIVER = "register_receiver";
 
     /** Test name suffixes */
     public static final String ASSIST_STRUCTURE = "ASSIST_STRUCTURE";
     public static final String DISABLE_CONTEXT = "DISABLE_CONTEXT";
     public static final String FLAG_SECURE = "FLAG_SECURE";
+    public static final String LIFECYCLE = "LIFECYCLE";
+
+    /** Session intent constants */
+    public static final String HIDE_SESSION = "android.intent.action.hide_session";
 
     /**
      * The shim activity that starts the service associated with each test.
@@ -46,10 +67,12 @@
     public static final String getTestActivity(String testCaseType) {
         switch (testCaseType) {
             case ASSIST_STRUCTURE:
-            case FLAG_SECURE:
                 return "service.AssistStructureActivity";
             case DISABLE_CONTEXT:
                 return "service.DisableContextActivity";
+            case FLAG_SECURE:
+            case LIFECYCLE:
+                return "service.DelayedAssistantActivity";
             default:
                 return "";
         }
@@ -67,6 +90,9 @@
             case FLAG_SECURE:
                 return new ComponentName(
                         "android.assist.testapp", "android.assist.testapp.SecureActivity");
+            case LIFECYCLE:
+                return new ComponentName(
+                        "android.assist.testapp", "android.assist.testapp.LifecycleActivity");
             default:
                 return new ComponentName("","");
         }
diff --git a/tests/tests/assist/service/AndroidManifest.xml b/tests/tests/assist/service/AndroidManifest.xml
index 2c5206a..cdbeef0 100644
--- a/tests/tests/assist/service/AndroidManifest.xml
+++ b/tests/tests/assist/service/AndroidManifest.xml
@@ -34,7 +34,6 @@
       <activity android:name=".AssistStructureActivity" >
           <intent-filter>
               <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
-              <action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
               <category android:name="android.intent.category.DEFAULT" />
           </intent-filter>
       </activity>
@@ -45,6 +44,14 @@
               <category android:name="android.intent.category.DEFAULT" />
           </intent-filter>
       </activity>
+      <activity android:name=".DelayedAssistantActivity"
+                android:label="Delay Assistant Start Activity">
+          <intent-filter>
+              <action android:name="android.intent.action.START_TEST_LIFECYCLE" />
+              <action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
+              <category android:name="android.intent.category.DEFAULT" />
+          </intent-filter>
+      </activity>
       <service android:name=".MainInteractionSessionService"
               android:permission="android.permission.BIND_VOICE_INTERACTION"
               android:process=":session">
diff --git a/tests/tests/assist/service/res/layout/assist_layer.xml b/tests/tests/assist/service/res/layout/assist_layer.xml
new file mode 100644
index 0000000..49f35c9
--- /dev/null
+++ b/tests/tests/assist/service/res/layout/assist_layer.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/assist_layer"
+        android:layout_width="match_parent"
+        android:background="@color/assist_layer_background"
+        android:layout_height="match_parent">
+    <TextView
+        android:layout_centerInParent="true"
+        android:text="@string/test_assistant_text"
+        android:textColor="@android:color/white"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</RelativeLayout>
diff --git a/tests/tests/assist/service/res/values/colors.xml b/tests/tests/assist/service/res/values/colors.xml
new file mode 100644
index 0000000..4423140
--- /dev/null
+++ b/tests/tests/assist/service/res/values/colors.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="assist_layer_background">#aa000000</color>
+</resources>
diff --git a/tests/tests/assist/service/res/values/strings.xml b/tests/tests/assist/service/res/values/strings.xml
new file mode 100644
index 0000000..ea959e6
--- /dev/null
+++ b/tests/tests/assist/service/res/values/strings.xml
@@ -0,0 +1,18 @@
+<!--
+  Copyright 2015 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. -->
+
+<resources>
+    <string name="test_assistant_text">I am an Assistant</string>
+</resources>
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java b/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
new file mode 100644
index 0000000..31d1694
--- /dev/null
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.assist.service;
+
+import android.app.Activity;
+import android.assist.common.Utils;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.util.Log;
+
+public class DelayedAssistantActivity extends Activity {
+    static final String TAG = "DelatyedAssistantActivity";
+
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(this, MainInteractionService.class));
+        intent.putExtra(Utils.EXTRA_REGISTER_RECEIVER, true);
+        finish();
+        ComponentName serviceName = startService(intent);
+        Log.i(TAG, "Started service: " + serviceName);
+    }
+}
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
index 85bd6ea..7530933 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -16,8 +16,15 @@
 
 package android.assist.service;
 
+import static android.service.voice.VoiceInteractionSession.SHOW_WITH_ASSIST;
+import static android.service.voice.VoiceInteractionSession.SHOW_WITH_SCREENSHOT;
+
+import android.assist.common.Utils;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Bundle;
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionSession;
@@ -27,6 +34,7 @@
     static final String TAG = "MainInteractionService";
     private Intent mIntent;
     private boolean mReady = false;
+    private BroadcastReceiver mBroadcastReceiver;
 
     @Override
     public void onReady() {
@@ -36,7 +44,7 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        Log.i(TAG, "onStartCommand received");
+        Log.i(TAG, "onStartCommand received - intent: " + intent);
         mIntent = intent;
         maybeStart();
         return START_NOT_STICKY;
@@ -47,14 +55,40 @@
             Log.wtf(TAG, "Can't start session because either intent is null or onReady() "
                     + "has not been called yet. mIntent = " + mIntent + ", mReady = " + mReady);
         } else {
-            Log.i(TAG, "Yay! about to start session");
             if (isActiveService(this, new ComponentName(this, getClass()))) {
-                showSession(new Bundle(), VoiceInteractionSession.SHOW_WITH_ASSIST |
-                        VoiceInteractionSession.SHOW_WITH_SCREENSHOT);
+                if (mIntent.getBooleanExtra(Utils.EXTRA_REGISTER_RECEIVER, false)) {
+                    Log.i(TAG, "Registering receiver to start session later");
+                    if (mBroadcastReceiver == null) {
+                        mBroadcastReceiver = new MainInteractionServiceBroadcastReceiver();
+                        registerReceiver(mBroadcastReceiver, 
+                                new IntentFilter(Utils.BROADCAST_INTENT_START_ASSIST));
+                    }
+                    sendBroadcast(new Intent(Utils.ASSIST_RECEIVER_REGISTERED));
+                } else {
+                    Log.i(TAG, "Yay! about to start session");
+                    showSession(new Bundle(), VoiceInteractionSession.SHOW_WITH_ASSIST |
+                                VoiceInteractionSession.SHOW_WITH_SCREENSHOT);
+                }
             } else {
                 Log.wtf(TAG, "**** Not starting MainInteractionService because" +
                     " it is not set as the current voice interaction service");
             }
         }
     }
+
+    private class MainInteractionServiceBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Utils.BROADCAST_INTENT_START_ASSIST)) {
+                showSession(new Bundle(), SHOW_WITH_ASSIST | SHOW_WITH_SCREENSHOT);
+            }
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mBroadcastReceiver != null) {
+            unregisterReceiver(mBroadcastReceiver);
+        }
+    }
 }
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
index 9a835c2..f297b3e 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -18,13 +18,17 @@
 
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
+import android.assist.service.R;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.service.voice.VoiceInteractionSession;
 import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
 
 import java.io.ByteArrayOutputStream;
 
@@ -39,6 +43,7 @@
 
     private boolean hasReceivedAssistData = false;
     private boolean hasReceivedScreenshot = false;
+    private BroadcastReceiver mReceiver;
 
     MainInteractionSession(Context context) {
         super(context);
@@ -48,12 +53,27 @@
     @Override
     public void onCreate() {
         super.onCreate();
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(Utils.HIDE_SESSION)) {
+                    hide();
+                }
+            }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Utils.HIDE_SESSION);
+        mContext.registerReceiver(mReceiver, filter);
     }
 
     @Override
     public void onDestroy() {
         Log.i(TAG, "onDestroy()");
         super.onDestroy();
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
     }
 
     @Override
@@ -115,6 +135,15 @@
         }
     }
 
+    @Override
+    public View onCreateContentView() {
+        LayoutInflater f = getLayoutInflater();
+        if (f == null) {
+            Log.wtf(TAG, "layout inflater was null");
+        }
+        return f.inflate(R.layout.assist_layer,null);
+    }
+
     class DoneReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index bdf3d4a..a7e7087 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -48,7 +48,7 @@
     protected BroadcastReceiver mReceiver;
     protected Bundle mAssistBundle;
     protected Context mContext;
-    protected CountDownLatch mLatch;
+    protected CountDownLatch mLatch, mAssistantReadyLatch;
     private String mTestName;
 
     public AssistTestBase() {
@@ -58,6 +58,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        mAssistantReadyLatch = new CountDownLatch(1);
         mContext = getInstrumentation().getTargetContext();
         SystemUtil.runShellCommand(getInstrumentation(),
             "settings put secure assist_structure_enabled 1");
@@ -71,6 +72,7 @@
         mContext.unregisterReceiver(mReceiver);
         mTestActivity.finish();
         super.tearDown();
+        mContext.sendBroadcast(new Intent(Utils.HIDE_SESSION));
     }
 
     protected void startTestActivity(String testName) {
@@ -84,9 +86,32 @@
     }
 
     /**
+     * Called when waiting for Assistant's Broadcast Receiver to be setup
+     */
+    public void waitForAssistantToBeReady() throws Exception {
+        Log.i(TAG, "waiting for assistant to be ready before continuing");
+        if (!mAssistantReadyLatch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Assistant was not ready before timeout of: " + Utils.TIMEOUT_MS + "msec");
+        }
+    }
+
+    /**
+     * Send broadcast to MainInteractionService to start a session
+     */
+    protected void startSession() {
+        mContext.sendBroadcast(new Intent(Utils.BROADCAST_INTENT_START_ASSIST));
+    }
+
+    /**
      * Called after startTestActivity
      */
     protected boolean waitForBroadcast() throws Exception {
+        mTestActivity.start3pApp(mTestName);
+        mTestActivity.startTest(mTestName);
+        return waitForContext();
+    }
+
+    protected boolean waitForContext() throws Exception {
         mLatch = new CountDownLatch(1);
         if (mReceiver != null) {
             mContext.unregisterReceiver(mReceiver);
@@ -95,8 +120,6 @@
         mContext.registerReceiver(mReceiver,
             new IntentFilter(Utils.BROADCAST_ASSIST_DATA_INTENT));
 
-        mTestActivity.start3pApp(mTestName);
-        mTestActivity.startTest(mTestName);
         if (!mLatch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
             fail("Failed to receive broadcast in " + Utils.TIMEOUT_MS + "msec");
             return false;
@@ -188,4 +211,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
index 04bea77..9b29407 100644
--- a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
+++ b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
@@ -97,4 +97,4 @@
 
         verifyAssistDataNullness(true, true, true, true);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
index fc14900..40bf7a7 100644
--- a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
@@ -17,8 +17,14 @@
 package android.assist.cts;
 
 import android.assist.common.Utils;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test we receive proper assist data (root assistStructure with no children) when the assistant is
@@ -26,10 +32,13 @@
  */
 public class FlagSecureTest extends AssistTestBase {
 
-    static final String TAG = "DisableContextTest";
+    static final String TAG = "FlagSecureTest";
 
     private static final String TEST_CASE_TYPE = Utils.FLAG_SECURE;
 
+    private BroadcastReceiver mReceiver;
+    private CountDownLatch mHasResumedLatch = new CountDownLatch(1);
+
     public FlagSecureTest() {
         super();
     }
@@ -37,13 +46,35 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        setUpAndRegisterReceiver();
         startTestActivity(TEST_CASE_TYPE);
-        waitForBroadcast();
     }
 
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+    }
+
+    private void setUpAndRegisterReceiver() {
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+        mReceiver = new FlagSecureTestBroadcastReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Utils.FLAG_SECURE_HASRESUMED);
+        filter.addAction(Utils.ASSIST_RECEIVER_REGISTERED);
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    private void waitForOnResume() throws Exception {
+        Log.i(TAG, "waiting for onResume() before continuing");
+        if (!mHasResumedLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity failed to resume in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+        }
     }
 
     public void testSecureActivity() throws Exception {
@@ -52,4 +83,16 @@
         // verify that we have only the root window and not its children.
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), true);
     }
-}
\ No newline at end of file
+
+    private class FlagSecureTestBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Utils.FLAG_SECURE_HASRESUMED)) {
+                mHasResumedLatch.countDown();
+            } else if (action.equals(Utils.ASSIST_RECEIVER_REGISTERED)) {
+                mAssistantReadyLatch.countDown();
+            }
+        }
+    }
+}
diff --git a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
new file mode 100644
index 0000000..19a1be5
--- /dev/null
+++ b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 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.assist.cts;
+
+import android.assist.TestStartActivity;
+import android.assist.common.Utils;
+
+import android.app.Activity;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.cts.util.SystemUtil;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.lang.Override;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Test we receive proper assist data when context is disabled or enabled */
+
+public class LifecycleTest extends AssistTestBase {
+    private static final String TAG = "LifecycleTest";
+    private static final String action_hasResumed = Utils.LIFECYCLE_HASRESUMED;
+    private static final String action_onPause = Utils.LIFECYCLE_ONPAUSE;
+    private static final String action_onStop = Utils.LIFECYCLE_ONSTOP;
+    private static final String action_onDestroy = Utils.LIFECYCLE_ONDESTROY;
+
+    private BroadcastReceiver mLifecycleTestBroadcastReceiver;
+    private CountDownLatch mHasResumedLatch = new CountDownLatch(1);
+    private CountDownLatch mActivityLifecycleLatch = new CountDownLatch(1);
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        setUpAndRegisterReceiver();
+        startTestActivity(Utils.LIFECYCLE);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mLifecycleTestBroadcastReceiver != null) {
+            mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
+            mLifecycleTestBroadcastReceiver = null;
+        }
+    }
+
+    private void setUpAndRegisterReceiver() {
+        if (mLifecycleTestBroadcastReceiver != null) {
+            mContext.unregisterReceiver(mLifecycleTestBroadcastReceiver);
+        }
+        mLifecycleTestBroadcastReceiver = new LifecycleTestReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(action_hasResumed);
+        filter.addAction(action_onPause);
+        filter.addAction(action_onStop);
+        filter.addAction(action_onDestroy);
+        filter.addAction(Utils.ASSIST_RECEIVER_REGISTERED);
+        mContext.registerReceiver(mLifecycleTestBroadcastReceiver, filter);
+
+    }
+
+    private void waitForOnResume() throws Exception {
+        Log.i(TAG, "waiting for onResume() before continuing");
+        if (!mHasResumedLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity failed to resume in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+        }
+    }
+
+    private void waitAndSeeIfLifecycleMethodsAreTriggered() throws Exception {
+        if (mActivityLifecycleLatch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("One or more lifecycle methods were called after triggering assist");
+        }
+    }
+
+    public void testLayerDoesNotTriggerLifecycleMethods() throws Exception {
+        mTestActivity.startTest(Utils.LIFECYCLE);
+        waitForAssistantToBeReady();
+        mTestActivity.start3pApp(Utils.LIFECYCLE);
+        waitForOnResume();
+        startSession();
+        waitForContext();
+        waitAndSeeIfLifecycleMethodsAreTriggered();
+    }
+
+    private class LifecycleTestReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(action_hasResumed)) {
+                mHasResumedLatch.countDown();
+            } else if (action.equals(action_onPause)) {
+                mActivityLifecycleLatch.countDown();
+            } else if (action.equals(action_onStop)) {
+                mActivityLifecycleLatch.countDown();
+            } else if (action.equals(action_onDestroy)) {
+                mActivityLifecycleLatch.countDown();
+            } else if (action.equals(Utils.ASSIST_RECEIVER_REGISTERED)) {
+                mAssistantReadyLatch.countDown();
+            }
+        }
+    }
+}
diff --git a/tests/tests/assist/testapp/AndroidManifest.xml b/tests/tests/assist/testapp/AndroidManifest.xml
index 371ae7b..8d6169c 100644
--- a/tests/tests/assist/testapp/AndroidManifest.xml
+++ b/tests/tests/assist/testapp/AndroidManifest.xml
@@ -39,5 +39,13 @@
                 <category android:name="android.intent.category.VOICE" />
             </intent-filter>
         </activity>
+        <activity android:name="LifecycleActivity"
+                  android:label="Life Cycle Check Activity"
+                  android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.TEST_APP_LIFECYCLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
new file mode 100644
index 0000000..4e1dc80
--- /dev/null
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/LifecycleActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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.assist.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class LifecycleActivity extends Activity {
+    private static final String TAG = "LifecycleActivity";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "LifecycleActivity created");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log.i(TAG, "Activity has resumed");
+        sendBroadcast(new Intent("android.intent.action.lifecycle_hasResumed"));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        Log.i(TAG, "activity was paused");
+        sendBroadcast(new Intent("android.intent.action.lifecycle_onpause"));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Log.i(TAG, "activity was stopped");
+        sendBroadcast(new Intent("android.intent.action.lifecycle_onstop"));
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Log.i(TAG, "activity was destroyed");
+        sendBroadcast(new Intent("android.intent.action.lifecycle_ondestroy"));
+    }
+}
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
index 83f7549..d9b2ff2 100644
--- a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
@@ -17,6 +17,7 @@
 package android.assist.testapp;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
 
@@ -24,7 +25,6 @@
 
 public class SecureActivity extends Activity {
     static final String TAG = "SecureActivity";
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -33,4 +33,11 @@
         getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
             WindowManager.LayoutParams.FLAG_SECURE);
     }
-}
\ No newline at end of file
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log.i(TAG, "Activity has resumed");     
+        sendBroadcast(new Intent("android.intent.action.flag_secure_hasResumed"));
+    }
+}
diff --git a/tests/tests/bluetooth/AndroidManifest.xml b/tests/tests/bluetooth/AndroidManifest.xml
index c9ad122..12838f3 100644
--- a/tests/tests/bluetooth/AndroidManifest.xml
+++ b/tests/tests/bluetooth/AndroidManifest.xml
@@ -19,6 +19,7 @@
 
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
     <application>
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index 08bbacd..a79df42 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -21,28 +21,33 @@
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.ScanCallback;
 import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.ParcelUuid;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
+import android.util.SparseArray;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Test cases for Bluetooth LE scans.
  * <p>
- * To run the test, the device must be placed in an environment that has at least 5 BLE beacons
- * broadcasting no slower than 1 HZ. The BLE beacons should be a combination of iBeacon devices and
- * non-iBeacon devices.
+ * To run the test, the device must be placed in an environment that has at least 3 beacons, all
+ * placed less than 5 meters away from the DUT.
  * <p>
  * Run 'run cts --class android.bluetooth.cts.BluetoothLeScanTest' in cts-tradefed to run the test
  * cases.
@@ -51,25 +56,26 @@
 
     private static final String TAG = "BluetoothLeScanTest";
 
-    private static final ScanFilter MANUFACTURER_DATA_FILTER =
-            new ScanFilter.Builder().setManufacturerData(0x004C, new byte[0], new byte[0]).build();
     private static final int SCAN_DURATION_MILLIS = 5000;
-    private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 10000;
+    private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 20000;
 
+    private BluetoothAdapter mBluetoothAdapter;
     private BluetoothLeScanner mScanner;
 
     @Override
     public void setUp() {
+        if (!isBleSupported())
+            return;
         BluetoothManager manager = (BluetoothManager) mContext.getSystemService(
                 Context.BLUETOOTH_SERVICE);
-        BluetoothAdapter adapter = manager.getAdapter();
-        if (!adapter.isEnabled()) {
-            adapter.enable();
+        mBluetoothAdapter = manager.getAdapter();
+        if (!mBluetoothAdapter.isEnabled()) {
             // Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check
             // bluetooth state.
-            sleep(2000);
+            mBluetoothAdapter.enable();
+            sleep(3000);
         }
-        mScanner = adapter.getBluetoothLeScanner();
+        mScanner = mBluetoothAdapter.getBluetoothLeScanner();
     }
 
     /**
@@ -77,13 +83,11 @@
      */
     @MediumTest
     public void testBasicBleScan() {
-        BleScanCallback regularLeScanCallback = new BleScanCallback();
+        if (!isBleSupported())
+            return;
         long scanStartMillis = SystemClock.elapsedRealtime();
-        mScanner.startScan(regularLeScanCallback);
-        sleep(SCAN_DURATION_MILLIS);
-        mScanner.stopScan(regularLeScanCallback);
+        Collection<ScanResult> scanResults = scan();
         long scanEndMillis = SystemClock.elapsedRealtime();
-        Collection<ScanResult> scanResults = regularLeScanCallback.getScanResults();
         assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty());
         verifyTimestamp(scanResults, scanStartMillis, scanEndMillis);
     }
@@ -94,8 +98,16 @@
      */
     @MediumTest
     public void testScanFilter() {
+        if (!isBleSupported())
+            return;
+
         List<ScanFilter> filters = new ArrayList<ScanFilter>();
-        filters.add(MANUFACTURER_DATA_FILTER);
+        ScanFilter filter = createScanFilter();
+        if (filter == null) {
+            Log.d(TAG, "no appropriate filter can be set");
+            return;
+        }
+        filters.add(filter);
 
         BleScanCallback filterLeScanCallback = new BleScanCallback();
         ScanSettings settings = new ScanSettings.Builder().setScanMode(
@@ -103,22 +115,54 @@
         mScanner.startScan(filters, settings, filterLeScanCallback);
         sleep(SCAN_DURATION_MILLIS);
         mScanner.stopScan(filterLeScanCallback);
+        sleep(1000);
         Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
-        assertTrue("No scan results", !scanResults.isEmpty());
         for (ScanResult result : scanResults) {
-            assertTrue(MANUFACTURER_DATA_FILTER.matches(result));
+            assertTrue(filter.matches(result));
         }
     }
 
+    // Create a scan filter based on the nearby beacon with highest signal strength.
+    private ScanFilter createScanFilter() {
+        // Get a list of nearby beacons.
+        List<ScanResult> scanResults = new ArrayList<ScanResult>(scan());
+        assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty());
+        // Find the beacon with strongest signal strength, which is the target device for filter
+        // scan.
+        Collections.sort(scanResults, new RssiComparator());
+        ScanResult result = scanResults.get(0);
+        ScanRecord record = result.getScanRecord();
+        if (record == null) {
+            return null;
+        }
+        Map<ParcelUuid, byte[]> serviceData = record.getServiceData();
+        if (serviceData != null && !serviceData.isEmpty()) {
+            ParcelUuid uuid = serviceData.keySet().iterator().next();
+            return new ScanFilter.Builder().setServiceData(uuid, new byte[] { 0 },
+                    new byte[] { 0 }).build();
+        }
+        SparseArray<byte[]> manufacturerSpecificData = record.getManufacturerSpecificData();
+        if (manufacturerSpecificData != null && manufacturerSpecificData.size() > 0) {
+            return new ScanFilter.Builder().setManufacturerData(manufacturerSpecificData.keyAt(0),
+                    new byte[] { 0 }, new byte[] { 0 }).build();
+        }
+        List<ParcelUuid> serviceUuids = record.getServiceUuids();
+        if (serviceUuids != null && !serviceUuids.isEmpty()) {
+            return new ScanFilter.Builder().setServiceUuid(serviceUuids.get(0)).build();
+        }
+        return null;
+    }
+
     /**
      * Test of opportunistic BLE scans.
      */
     @MediumTest
     public void testOpportunisticScan() {
-        ScanSettings opportunisticScanSettings =
-                new ScanSettings.Builder()
-                        .setScanMode(-1) // TODO: use constants in ScanSettings once it's unhiden.
-                        .build();
+        if (!isBleSupported())
+            return;
+        ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
+                .build();
         BleScanCallback emptyScanCallback = new BleScanCallback();
 
         // No scans are really started with opportunistic scans only.
@@ -128,18 +172,20 @@
         assertTrue(emptyScanCallback.getScanResults().isEmpty());
 
         BleScanCallback regularScanCallback = new BleScanCallback();
-        ScanSettings regularScanSettings =
-                new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        ScanSettings regularScanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
         List<ScanFilter> filters = new ArrayList<>();
-        filters.add(MANUFACTURER_DATA_FILTER);
+        ScanFilter filter = createScanFilter();
+        if (filter != null) {
+            filters.add(filter);
+        } else {
+            Log.d(TAG, "no appropriate filter can be set");
+        }
         mScanner.startScan(filters, regularScanSettings, regularScanCallback);
         sleep(SCAN_DURATION_MILLIS);
         // With normal BLE scan client, opportunistic scan client will get scan results.
         assertTrue("opportunistic scan results shouldn't be empty",
                 !emptyScanCallback.getScanResults().isEmpty());
-        assertTrue("opportunistic scan should see more results",
-                emptyScanCallback.getScanResults().size() >
-                regularScanCallback.getScanResults().size());
 
         // No more scan results for opportunistic scan clients once the normal BLE scan clients
         // stops.
@@ -157,20 +203,24 @@
      */
     @MediumTest
     public void testBatchScan() {
+        if (!isBleSupported() || !isBleBatchScanSupported()) {
+            Log.d(TAG, "BLE or BLE batching not suppported");
+            return;
+        }
         ScanSettings batchScanSettings = new ScanSettings.Builder()
                 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                 .setReportDelay(BATCH_SCAN_REPORT_DELAY_MILLIS).build();
         BleScanCallback batchScanCallback = new BleScanCallback();
-        long scanStartMillis = SystemClock.elapsedRealtime();
         mScanner.startScan(Collections.<ScanFilter> emptyList(), batchScanSettings,
                 batchScanCallback);
         sleep(SCAN_DURATION_MILLIS);
         mScanner.flushPendingScanResults(batchScanCallback);
         sleep(1000);
-        long scanEndMillis = SystemClock.elapsedRealtime();
         List<ScanResult> results = batchScanCallback.getBatchScanResults();
         assertTrue(!results.isEmpty());
-        verifyTimestamp(results, scanStartMillis, scanEndMillis);
+        long scanEndMillis = SystemClock.elapsedRealtime();
+        mScanner.stopScan(batchScanCallback);
+        verifyTimestamp(results, 0, scanEndMillis);
     }
 
     // Verify timestamp of all scan results are within [scanStartMillis, scanEndMillis].
@@ -178,8 +228,10 @@
             long scanEndMillis) {
         for (ScanResult result : results) {
             long timestampMillis = TimeUnit.NANOSECONDS.toMillis(result.getTimestampNanos());
-            assertTrue("Invalid timestamp", timestampMillis >= scanStartMillis);
-            assertTrue("Invalid timestamp", timestampMillis <= scanEndMillis);
+            assertTrue("Invalid timestamp: " + timestampMillis + " should be >= " + scanStartMillis,
+                    timestampMillis >= scanStartMillis);
+            assertTrue("Invalid timestamp: " + timestampMillis + " should be <= " + scanEndMillis,
+                    timestampMillis <= scanEndMillis);
         }
     }
 
@@ -207,8 +259,8 @@
         }
 
         // Return regular BLE scan results accumulated so far.
-        synchronized Collection<ScanResult> getScanResults() {
-            return Collections.unmodifiableCollection(mResults);
+        synchronized Set<ScanResult> getScanResults() {
+            return Collections.unmodifiableSet(mResults);
         }
 
         // Return batch scan results.
@@ -217,6 +269,25 @@
         }
     }
 
+    private class RssiComparator implements Comparator<ScanResult> {
+
+        @Override
+        public int compare(ScanResult lhs, ScanResult rhs) {
+            return rhs.getRssi() - lhs.getRssi();
+        }
+
+    }
+
+    // Perform a BLE scan to get results of nearby BLE devices.
+    private Set<ScanResult> scan() {
+        BleScanCallback regularLeScanCallback = new BleScanCallback();
+        mScanner.startScan(regularLeScanCallback);
+        sleep(SCAN_DURATION_MILLIS);
+        mScanner.stopScan(regularLeScanCallback);
+        sleep(1000);
+        return regularLeScanCallback.getScanResults();
+    }
+
     // Put the current thread to sleep.
     private void sleep(int sleepMillis) {
         try {
@@ -226,4 +297,15 @@
         }
     }
 
+    // Check if Bluetooth LE feature is supported on DUT.
+    private boolean isBleSupported() {
+        return getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+    }
+
+    // Returns whether offloaded scan batching is supported.
+    private boolean isBleBatchScanSupported() {
+        return mBluetoothAdapter.isOffloadedScanBatchingSupported();
+    }
+
 }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java b/tests/tests/hardware/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
index 1881774..8a217fd 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
@@ -32,6 +32,7 @@
     private SurfaceView mSurfaceView;
     private int currentWidth = 0;
     private int currentHeight = 0;
+    private final Object sizeLock = new Object();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -55,6 +56,12 @@
                             timeOutMs, expectWidth, expectHeight));
         }
 
+        synchronized(sizeLock) {
+            if (expectWidth == currentWidth && expectHeight == currentHeight) {
+                return true;
+            }
+        }
+
         int waitTimeMs = timeOutMs;
         boolean changeSucceeded = false;
         while (!changeSucceeded && waitTimeMs > 0) {
@@ -87,8 +94,10 @@
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         Log.i(TAG, "Surface Changed to: " + width + "x" + height);
-        currentWidth = width;
-        currentHeight = height;
+        synchronized (sizeLock) {
+            currentWidth = width;
+            currentHeight = height;
+        }
         surfaceChangedDone.open();
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
index b752c07..dc499ba 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -748,10 +748,10 @@
         resultKeys.add(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE);
         resultKeys.add(CaptureResult.LENS_POSE_ROTATION);
         resultKeys.add(CaptureResult.LENS_POSE_TRANSLATION);
-        resultKeys.add(CaptureResult.LENS_INTRINSIC_CALIBRATION);
-        resultKeys.add(CaptureResult.LENS_RADIAL_DISTORTION);
         resultKeys.add(CaptureResult.LENS_FOCUS_RANGE);
         resultKeys.add(CaptureResult.LENS_STATE);
+        resultKeys.add(CaptureResult.LENS_INTRINSIC_CALIBRATION);
+        resultKeys.add(CaptureResult.LENS_RADIAL_DISTORTION);
         resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
         resultKeys.add(CaptureResult.REQUEST_PIPELINE_DEPTH);
         resultKeys.add(CaptureResult.SCALER_CROP_REGION);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 80cd288..6d62067 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -738,6 +738,157 @@
     }
 
     /**
+     * Check depth output capability
+     */
+    public void testDepthOutputCharacteristics() {
+        int counter = 0;
+
+        for (CameraCharacteristics c : mCharacteristics) {
+            Log.i(TAG, "testDepthOutputCharacteristics: Testing camera ID " + mIds[counter]);
+
+            int[] capabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    capabilities);
+            boolean supportDepth = arrayContains(capabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT);
+            StreamConfigurationMap configs =
+                    c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+            int[] outputFormats = configs.getOutputFormats();
+            boolean hasDepth16 = arrayContains(outputFormats, ImageFormat.DEPTH16);
+
+            Boolean depthIsExclusive = c.get(CameraCharacteristics.DEPTH_DEPTH_IS_EXCLUSIVE);
+
+            float[] poseRotation = c.get(CameraCharacteristics.LENS_POSE_ROTATION);
+            float[] poseTranslation = c.get(CameraCharacteristics.LENS_POSE_TRANSLATION);
+            float[] cameraIntrinsics = c.get(CameraCharacteristics.LENS_INTRINSIC_CALIBRATION);
+            float[] radialDistortion = c.get(CameraCharacteristics.LENS_RADIAL_DISTORTION);
+
+            if (supportDepth) {
+                mCollector.expectTrue("Supports DEPTH_OUTPUT but does not support DEPTH16",
+                        hasDepth16);
+                if (hasDepth16) {
+                    Size[] depthSizes = configs.getOutputSizes(ImageFormat.DEPTH16);
+                    mCollector.expectTrue("Supports DEPTH_OUTPUT but no sizes for DEPTH16 supported!",
+                            depthSizes != null && depthSizes.length > 0);
+                    if (depthSizes != null) {
+                        for (Size depthSize : depthSizes) {
+                            mCollector.expectTrue("All depth16 sizes must be positive",
+                                    depthSize.getWidth() > 0 && depthSize.getHeight() > 0);
+                            long minFrameDuration = configs.getOutputMinFrameDuration(
+                                    ImageFormat.DEPTH16, depthSize);
+                            mCollector.expectTrue("Non-negative min frame duration for depth size "
+                                    + depthSize + " expected, got " + minFrameDuration,
+                                    minFrameDuration >= 0);
+                            long stallDuration = configs.getOutputStallDuration(
+                                    ImageFormat.DEPTH16, depthSize);
+                            mCollector.expectTrue("Non-negative stall duration for depth size "
+                                    + depthSize + " expected, got " + stallDuration,
+                                    stallDuration >= 0);
+                        }
+                    }
+                }
+                if (arrayContains(outputFormats, ImageFormat.DEPTH_POINT_CLOUD)) {
+                    Size[] depthCloudSizes = configs.getOutputSizes(ImageFormat.DEPTH_POINT_CLOUD);
+                    mCollector.expectTrue("Supports DEPTH_POINT_CLOUD " +
+                            "but no sizes for DEPTH_POINT_CLOUD supported!",
+                            depthCloudSizes != null && depthCloudSizes.length > 0);
+                    if (depthCloudSizes != null) {
+                        for (Size depthCloudSize : depthCloudSizes) {
+                            mCollector.expectTrue("All depth point cloud sizes must be nonzero",
+                                    depthCloudSize.getWidth() > 0);
+                            mCollector.expectTrue("All depth point cloud sizes must be N x 1",
+                                    depthCloudSize.getHeight() == 1);
+                            long minFrameDuration = configs.getOutputMinFrameDuration(
+                                    ImageFormat.DEPTH_POINT_CLOUD, depthCloudSize);
+                            mCollector.expectTrue("Non-negative min frame duration for depth size "
+                                    + depthCloudSize + " expected, got " + minFrameDuration,
+                                    minFrameDuration >= 0);
+                            long stallDuration = configs.getOutputStallDuration(
+                                    ImageFormat.DEPTH_POINT_CLOUD, depthCloudSize);
+                            mCollector.expectTrue("Non-negative stall duration for depth size "
+                                    + depthCloudSize + " expected, got " + stallDuration,
+                                    stallDuration >= 0);
+                        }
+                    }
+                }
+
+                mCollector.expectTrue("Supports DEPTH_OUTPUT but DEPTH_IS_EXCLUSIVE is not defined",
+                        depthIsExclusive != null);
+
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_POSE_ROTATION not right size",
+                        poseRotation != null && poseRotation.length == 4);
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_POSE_TRANSLATION not right size",
+                        poseTranslation != null && poseTranslation.length == 3);
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_INTRINSIC_CALIBRATION not right size",
+                        cameraIntrinsics != null && cameraIntrinsics.length == 5);
+                mCollector.expectTrue(
+                        "Supports DEPTH_OUTPUT but LENS_RADIAL_DISTORTION not right size",
+                        radialDistortion != null && radialDistortion.length == 6);
+
+                if (poseRotation != null && poseRotation.length == 4) {
+                    float normSq =
+                        poseRotation[0] * poseRotation[0] +
+                        poseRotation[1] * poseRotation[1] +
+                        poseRotation[2] * poseRotation[2] +
+                        poseRotation[3] * poseRotation[3];
+                    mCollector.expectTrue(
+                            "LENS_POSE_ROTATION quarternion must be unit-length",
+                            0.9999f < normSq && normSq < 1.0001f);
+
+                    // TODO: Cross-validate orientation/facing and poseRotation
+                    Integer orientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
+                    Integer facing = c.get(CameraCharacteristics.LENS_FACING);
+                }
+
+                if (poseTranslation != null && poseTranslation.length == 3) {
+                    float normSq =
+                        poseTranslation[0] * poseTranslation[0] +
+                        poseTranslation[1] * poseTranslation[1] +
+                        poseTranslation[2] * poseTranslation[2];
+                    mCollector.expectTrue("Pose translation is larger than 1 m",
+                            normSq < 1.f);
+                }
+
+                Rect precorrectionArray =
+                    c.get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
+                mCollector.expectTrue("Supports DEPTH_OUTPUT but does not have " +
+                        "precorrection active array defined", precorrectionArray != null);
+
+                if (cameraIntrinsics != null && precorrectionArray != null) {
+                    float fx = cameraIntrinsics[0];
+                    float fy = cameraIntrinsics[1];
+                    float cx = cameraIntrinsics[2];
+                    float cy = cameraIntrinsics[3];
+                    float s = cameraIntrinsics[4];
+                    mCollector.expectTrue("Optical center expected to be within precorrection array",
+                            0 <= cx && cx < precorrectionArray.width() &&
+                            0 <= cy && cy < precorrectionArray.height());
+
+                    // TODO: Verify focal lengths and skew are reasonable
+                }
+
+                if (radialDistortion != null) {
+                    // TODO: Verify radial distortion
+                }
+
+            } else {
+                boolean hasFields =
+                    hasDepth16 && (poseTranslation != null) &&
+                    (poseRotation != null) && (cameraIntrinsics != null) &&
+                    (radialDistortion != null) && (depthIsExclusive != null);
+
+                mCollector.expectTrue(
+                        "All necessary depth fields defined, but DEPTH_OUTPUT capability is not listed",
+                        !hasFields);
+            }
+        }
+    }
+
+    /**
      * Cross-check StreamConfigurationMap output
      */
     public void testStreamConfigurationMap() throws Exception {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
index 4415ecc..3e3e81e 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -397,6 +397,9 @@
             case REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING:
                 // Tested in ExtendedCameraCharacteristicsTest
                 return;
+            case REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT:
+                // Tested in ExtendedCameracharacteristicsTest
+                return;
             default:
                 capabilityName = "Unknown";
                 assertTrue(String.format("Unknown capability set: %d", capability),
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 7330a4c..c136b67 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -619,8 +619,8 @@
                 mPreviewSize.getHeight());
         assertTrue("wait for surface change to " + mPreviewSize.toString() + " timed out", res);
         mPreviewSurface = holder.getSurface();
-        assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
         assertNotNull("Preview surface is null", mPreviewSurface);
+        assertTrue("Preview surface is invalid", mPreviewSurface.isValid());
     }
 
     /**
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
index e5a5053..d246ec5 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerification.java
@@ -44,7 +44,8 @@
     // sensorType: threshold (% of expected period)
     private static final SparseIntArray DEFAULTS = new SparseIntArray(12);
     // Max allowed jitter (in percentage).
-    private static final int THRESHOLD_PERCENT_FOR_HIFI_SENSORS = 1;
+    private static final int GRACE_FACTOR = 2;
+    private static final int THRESHOLD_PERCENT_FOR_HIFI_SENSORS = 1 * GRACE_FACTOR;
     static {
         // Use a method so that the @deprecation warning can be set for that method only
         setDefaults();
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerification.java
index 20dd2d2..1e1c950 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerification.java
@@ -66,23 +66,31 @@
      */
     public static StandardDeviationVerification getDefault(TestSensorEnvironment environment) {
         int sensorType = environment.getSensor().getType();
+        float mGraceFactorAccelGyro = 2.0f;
+        float mGraceFactorMagPressure = 4.0f;
+        float mMaxBandWidth = (float) environment.getFrequencyHz();
+        float mAccelNoise = (float)(mGraceFactorAccelGyro * Math.sqrt(mMaxBandWidth) * (9.81 * 0.0004));
+        float mGyroNoise = (float)(mGraceFactorAccelGyro * Math.sqrt(mMaxBandWidth) * (Math.PI/180.0 * 0.014));
+        float mMagNoise = (float)((mGraceFactorMagPressure) * 0.5); // Allow extra grace for mag
+        float mPressureNoise = (float)(mGraceFactorMagPressure * 0.02 * (float)Math.sqrt(mMaxBandWidth)); // Allow extra grace for pressure
+
         if (!DEFAULTS.containsKey(sensorType)) {
             return null;
         }
+
         boolean hasHifiSensors = environment.getContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_HIFI_SENSORS);
 
         if (hasHifiSensors) {
-            // Max accelerometer deviation: 400uG/√Hz
-            DEFAULTS.put(Sensor.TYPE_ACCELEROMETER, new float[]{0.004f, 0.004f, 0.004f});
+
+            DEFAULTS.put(Sensor.TYPE_ACCELEROMETER, new float[]{mAccelNoise, mAccelNoise, mAccelNoise});
             // Max gyro deviation: 0.014°/s/√Hz
-            float deviationInRadians = (float) (0.014f * (Math.PI / 180));
             DEFAULTS.put(Sensor.TYPE_GYROSCOPE,
-                    new float[]{deviationInRadians,deviationInRadians, deviationInRadians});
+                    new float[]{mGyroNoise, mGyroNoise, mGyroNoise});
             // Max magnetometer deviation: 0.1uT/√Hz
-            DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD, new float[]{0.1f, 0.1f, 0.1f});
+            DEFAULTS.put(Sensor.TYPE_MAGNETIC_FIELD, new float[]{mMagNoise, mMagNoise, mMagNoise});
             // Max pressure deviation: 2Pa/√Hz
-            DEFAULTS.put(Sensor.TYPE_PRESSURE, new float[]{2.0f, 2.0f, 2.0f});
+            DEFAULTS.put(Sensor.TYPE_PRESSURE, new float[]{mPressureNoise,mPressureNoise,mPressureNoise});
         }
         return new StandardDeviationVerification(DEFAULTS.get(sensorType));
     }
diff --git a/tests/tests/os/src/android/os/cts/StatFsTest.java b/tests/tests/os/src/android/os/cts/StatFsTest.java
index 67afde9..a0653fd 100644
--- a/tests/tests/os/src/android/os/cts/StatFsTest.java
+++ b/tests/tests/os/src/android/os/cts/StatFsTest.java
@@ -46,15 +46,15 @@
         assertTrue(stat.getBlockSize() > 0);
         assertTrue(stat.getBlockCount() > 0);
         assertTrue(stat.getFreeBlocks() >= stat.getAvailableBlocks());
-        assertTrue(stat.getAvailableBlocks() > 0);
+        assertTrue(stat.getAvailableBlocks() >= 0);
 
         assertTrue(stat.getBlockSizeLong() > 0);
         assertTrue(stat.getBlockCountLong() > 0);
         assertTrue(stat.getFreeBlocksLong() >= stat.getAvailableBlocksLong());
-        assertTrue(stat.getAvailableBlocksLong() > 0);
+        assertTrue(stat.getAvailableBlocksLong() >= 0);
 
-        assertTrue(stat.getFreeBytes() > 0);
-        assertTrue(stat.getAvailableBytes() > 0);
+        assertTrue(stat.getFreeBytes() >= 0);
+        assertTrue(stat.getAvailableBytes() >= 0);
         assertTrue(stat.getTotalBytes() > 0);
     }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 7b4adb0..cf6a09d 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -371,11 +371,11 @@
         if (!arch.contains("arm") ||
             Integer.parseInt(osVersion[0]) < 2 ||
             (Integer.parseInt(osVersion[0]) == 3 &&
-             Integer.parseInt(osVersion[1]) < 10) ||
-             flavor.contains("sprout") || flavor.contains("sprout_b"))
+             Integer.parseInt(osVersion[1]) < 10))
             return;
         final File f = new File("/proc/device-tree/cpus");
-        assertTrue(f.exists());
+        if (!f.exists())
+            return;
         String[] dir = f.list(new FilenameFilter() {
             @Override
             public boolean accept(File pathname, String name) {
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index fa3a70d..8fc5df1 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -379,7 +379,7 @@
         values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp3");
         Uri fileUri = mResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
         // give media provider some time to realize there's no album art
-        //SystemClock.sleep(1000);
+        SystemClock.sleep(1000);
         // get its album id
         Cursor c = mResolver.query(fileUri, new String[] { MediaStore.Audio.Media.ALBUM_ID},
                 null, null, null);
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.java b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.java
index f646f07..961dafa 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.java
@@ -358,6 +358,204 @@
                 (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), errorFound);
     }
 
+    private void checkMaxFloat2FloatFloat2() {
+        Allocation inA = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 2, 0x8592438l, false);
+        Allocation inB = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0x8592439l, false);
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+            script.set_gAllocInB(inB);
+            script.forEach_testMaxFloat2FloatFloat2(inA, out);
+            verifyResultsMaxFloat2FloatFloat2(inA, inB, out, false);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxFloat2FloatFloat2: " + e.toString());
+        }
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+            scriptRelaxed.set_gAllocInB(inB);
+            scriptRelaxed.forEach_testMaxFloat2FloatFloat2(inA, out);
+            verifyResultsMaxFloat2FloatFloat2(inA, inB, out, true);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxFloat2FloatFloat2: " + e.toString());
+        }
+    }
+
+    private void verifyResultsMaxFloat2FloatFloat2(Allocation inA, Allocation inB, Allocation out, boolean relaxed) {
+        float[] arrayInA = new float[INPUTSIZE * 2];
+        inA.copyTo(arrayInA);
+        float[] arrayInB = new float[INPUTSIZE * 1];
+        inB.copyTo(arrayInB);
+        float[] arrayOut = new float[INPUTSIZE * 2];
+        out.copyTo(arrayOut);
+        for (int i = 0; i < INPUTSIZE; i++) {
+            for (int j = 0; j < 2 ; j++) {
+                // Extract the inputs.
+                ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+                args.inA = arrayInA[i * 2 + j];
+                args.inB = arrayInB[i];
+                // Figure out what the outputs should have been.
+                Target target = new Target(relaxed);
+                CoreMathVerifier.computeMax(args, target);
+                // Validate the outputs.
+                boolean valid = true;
+                if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+                    valid = false;
+                }
+                if (!valid) {
+                    StringBuilder message = new StringBuilder();
+                    message.append("Input inA: ");
+                    appendVariableToMessage(message, args.inA);
+                    message.append("\n");
+                    message.append("Input inB: ");
+                    appendVariableToMessage(message, args.inB);
+                    message.append("\n");
+                    message.append("Expected output out: ");
+                    appendVariableToMessage(message, args.out);
+                    message.append("\n");
+                    message.append("Actual   output out: ");
+                    appendVariableToMessage(message, arrayOut[i * 2 + j]);
+                    if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+                        message.append(" FAIL");
+                    }
+                    message.append("\n");
+                    assertTrue("Incorrect output for checkMaxFloat2FloatFloat2" +
+                            (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+                }
+            }
+        }
+    }
+
+    private void checkMaxFloat3FloatFloat3() {
+        Allocation inA = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 3, 0xf6c41894l, false);
+        Allocation inB = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0xf6c41895l, false);
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+            script.set_gAllocInB(inB);
+            script.forEach_testMaxFloat3FloatFloat3(inA, out);
+            verifyResultsMaxFloat3FloatFloat3(inA, inB, out, false);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxFloat3FloatFloat3: " + e.toString());
+        }
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+            scriptRelaxed.set_gAllocInB(inB);
+            scriptRelaxed.forEach_testMaxFloat3FloatFloat3(inA, out);
+            verifyResultsMaxFloat3FloatFloat3(inA, inB, out, true);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxFloat3FloatFloat3: " + e.toString());
+        }
+    }
+
+    private void verifyResultsMaxFloat3FloatFloat3(Allocation inA, Allocation inB, Allocation out, boolean relaxed) {
+        float[] arrayInA = new float[INPUTSIZE * 4];
+        inA.copyTo(arrayInA);
+        float[] arrayInB = new float[INPUTSIZE * 1];
+        inB.copyTo(arrayInB);
+        float[] arrayOut = new float[INPUTSIZE * 4];
+        out.copyTo(arrayOut);
+        for (int i = 0; i < INPUTSIZE; i++) {
+            for (int j = 0; j < 3 ; j++) {
+                // Extract the inputs.
+                ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+                args.inA = arrayInA[i * 4 + j];
+                args.inB = arrayInB[i];
+                // Figure out what the outputs should have been.
+                Target target = new Target(relaxed);
+                CoreMathVerifier.computeMax(args, target);
+                // Validate the outputs.
+                boolean valid = true;
+                if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                    valid = false;
+                }
+                if (!valid) {
+                    StringBuilder message = new StringBuilder();
+                    message.append("Input inA: ");
+                    appendVariableToMessage(message, args.inA);
+                    message.append("\n");
+                    message.append("Input inB: ");
+                    appendVariableToMessage(message, args.inB);
+                    message.append("\n");
+                    message.append("Expected output out: ");
+                    appendVariableToMessage(message, args.out);
+                    message.append("\n");
+                    message.append("Actual   output out: ");
+                    appendVariableToMessage(message, arrayOut[i * 4 + j]);
+                    if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                        message.append(" FAIL");
+                    }
+                    message.append("\n");
+                    assertTrue("Incorrect output for checkMaxFloat3FloatFloat3" +
+                            (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+                }
+            }
+        }
+    }
+
+    private void checkMaxFloat4FloatFloat4() {
+        Allocation inA = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 4, 0xe52f0cf0l, false);
+        Allocation inB = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0xe52f0cf1l, false);
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+            script.set_gAllocInB(inB);
+            script.forEach_testMaxFloat4FloatFloat4(inA, out);
+            verifyResultsMaxFloat4FloatFloat4(inA, inB, out, false);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxFloat4FloatFloat4: " + e.toString());
+        }
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+            scriptRelaxed.set_gAllocInB(inB);
+            scriptRelaxed.forEach_testMaxFloat4FloatFloat4(inA, out);
+            verifyResultsMaxFloat4FloatFloat4(inA, inB, out, true);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMaxFloat4FloatFloat4: " + e.toString());
+        }
+    }
+
+    private void verifyResultsMaxFloat4FloatFloat4(Allocation inA, Allocation inB, Allocation out, boolean relaxed) {
+        float[] arrayInA = new float[INPUTSIZE * 4];
+        inA.copyTo(arrayInA);
+        float[] arrayInB = new float[INPUTSIZE * 1];
+        inB.copyTo(arrayInB);
+        float[] arrayOut = new float[INPUTSIZE * 4];
+        out.copyTo(arrayOut);
+        for (int i = 0; i < INPUTSIZE; i++) {
+            for (int j = 0; j < 4 ; j++) {
+                // Extract the inputs.
+                ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+                args.inA = arrayInA[i * 4 + j];
+                args.inB = arrayInB[i];
+                // Figure out what the outputs should have been.
+                Target target = new Target(relaxed);
+                CoreMathVerifier.computeMax(args, target);
+                // Validate the outputs.
+                boolean valid = true;
+                if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                    valid = false;
+                }
+                if (!valid) {
+                    StringBuilder message = new StringBuilder();
+                    message.append("Input inA: ");
+                    appendVariableToMessage(message, args.inA);
+                    message.append("\n");
+                    message.append("Input inB: ");
+                    appendVariableToMessage(message, args.inB);
+                    message.append("\n");
+                    message.append("Expected output out: ");
+                    appendVariableToMessage(message, args.out);
+                    message.append("\n");
+                    message.append("Actual   output out: ");
+                    appendVariableToMessage(message, arrayOut[i * 4 + j]);
+                    if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                        message.append(" FAIL");
+                    }
+                    message.append("\n");
+                    assertTrue("Incorrect output for checkMaxFloat4FloatFloat4" +
+                            (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+                }
+            }
+        }
+    }
+
     public class ArgumentsCharCharChar {
         public byte inA;
         public byte inB;
@@ -2907,6 +3105,9 @@
         checkMaxFloat2Float2Float2();
         checkMaxFloat3Float3Float3();
         checkMaxFloat4Float4Float4();
+        checkMaxFloat2FloatFloat2();
+        checkMaxFloat3FloatFloat3();
+        checkMaxFloat4FloatFloat4();
         checkMaxCharCharChar();
         checkMaxChar2Char2Char2();
         checkMaxChar3Char3Char3();
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.rs b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.rs
index b582b0d..a9ca895 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.rs
+++ b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMax.rs
@@ -41,6 +41,21 @@
     return max(inA, inB);
 }
 
+float2 __attribute__((kernel)) testMaxFloat2FloatFloat2(float2 inA, unsigned int x) {
+    float inB = rsGetElementAt_float(gAllocInB, x);
+    return max(inA, inB);
+}
+
+float3 __attribute__((kernel)) testMaxFloat3FloatFloat3(float3 inA, unsigned int x) {
+    float inB = rsGetElementAt_float(gAllocInB, x);
+    return max(inA, inB);
+}
+
+float4 __attribute__((kernel)) testMaxFloat4FloatFloat4(float4 inA, unsigned int x) {
+    float inB = rsGetElementAt_float(gAllocInB, x);
+    return max(inA, inB);
+}
+
 char __attribute__((kernel)) testMaxCharCharChar(char inA, unsigned int x) {
     char inB = rsGetElementAt_char(gAllocInB, x);
     return max(inA, inB);
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.java b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.java
index 9151fd1..f71fac8 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.java
@@ -358,6 +358,204 @@
                 (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), errorFound);
     }
 
+    private void checkMinFloat2FloatFloat2() {
+        Allocation inA = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 2, 0x503b89a6l, false);
+        Allocation inB = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0x503b89a7l, false);
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+            script.set_gAllocInB(inB);
+            script.forEach_testMinFloat2FloatFloat2(inA, out);
+            verifyResultsMinFloat2FloatFloat2(inA, inB, out, false);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinFloat2FloatFloat2: " + e.toString());
+        }
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 2), INPUTSIZE);
+            scriptRelaxed.set_gAllocInB(inB);
+            scriptRelaxed.forEach_testMinFloat2FloatFloat2(inA, out);
+            verifyResultsMinFloat2FloatFloat2(inA, inB, out, true);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinFloat2FloatFloat2: " + e.toString());
+        }
+    }
+
+    private void verifyResultsMinFloat2FloatFloat2(Allocation inA, Allocation inB, Allocation out, boolean relaxed) {
+        float[] arrayInA = new float[INPUTSIZE * 2];
+        inA.copyTo(arrayInA);
+        float[] arrayInB = new float[INPUTSIZE * 1];
+        inB.copyTo(arrayInB);
+        float[] arrayOut = new float[INPUTSIZE * 2];
+        out.copyTo(arrayOut);
+        for (int i = 0; i < INPUTSIZE; i++) {
+            for (int j = 0; j < 2 ; j++) {
+                // Extract the inputs.
+                ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+                args.inA = arrayInA[i * 2 + j];
+                args.inB = arrayInB[i];
+                // Figure out what the outputs should have been.
+                Target target = new Target(relaxed);
+                CoreMathVerifier.computeMin(args, target);
+                // Validate the outputs.
+                boolean valid = true;
+                if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+                    valid = false;
+                }
+                if (!valid) {
+                    StringBuilder message = new StringBuilder();
+                    message.append("Input inA: ");
+                    appendVariableToMessage(message, args.inA);
+                    message.append("\n");
+                    message.append("Input inB: ");
+                    appendVariableToMessage(message, args.inB);
+                    message.append("\n");
+                    message.append("Expected output out: ");
+                    appendVariableToMessage(message, args.out);
+                    message.append("\n");
+                    message.append("Actual   output out: ");
+                    appendVariableToMessage(message, arrayOut[i * 2 + j]);
+                    if (!args.out.couldBe(arrayOut[i * 2 + j])) {
+                        message.append(" FAIL");
+                    }
+                    message.append("\n");
+                    assertTrue("Incorrect output for checkMinFloat2FloatFloat2" +
+                            (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+                }
+            }
+        }
+    }
+
+    private void checkMinFloat3FloatFloat3() {
+        Allocation inA = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 3, 0x3ea67e02l, false);
+        Allocation inB = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0x3ea67e03l, false);
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+            script.set_gAllocInB(inB);
+            script.forEach_testMinFloat3FloatFloat3(inA, out);
+            verifyResultsMinFloat3FloatFloat3(inA, inB, out, false);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinFloat3FloatFloat3: " + e.toString());
+        }
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 3), INPUTSIZE);
+            scriptRelaxed.set_gAllocInB(inB);
+            scriptRelaxed.forEach_testMinFloat3FloatFloat3(inA, out);
+            verifyResultsMinFloat3FloatFloat3(inA, inB, out, true);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinFloat3FloatFloat3: " + e.toString());
+        }
+    }
+
+    private void verifyResultsMinFloat3FloatFloat3(Allocation inA, Allocation inB, Allocation out, boolean relaxed) {
+        float[] arrayInA = new float[INPUTSIZE * 4];
+        inA.copyTo(arrayInA);
+        float[] arrayInB = new float[INPUTSIZE * 1];
+        inB.copyTo(arrayInB);
+        float[] arrayOut = new float[INPUTSIZE * 4];
+        out.copyTo(arrayOut);
+        for (int i = 0; i < INPUTSIZE; i++) {
+            for (int j = 0; j < 3 ; j++) {
+                // Extract the inputs.
+                ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+                args.inA = arrayInA[i * 4 + j];
+                args.inB = arrayInB[i];
+                // Figure out what the outputs should have been.
+                Target target = new Target(relaxed);
+                CoreMathVerifier.computeMin(args, target);
+                // Validate the outputs.
+                boolean valid = true;
+                if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                    valid = false;
+                }
+                if (!valid) {
+                    StringBuilder message = new StringBuilder();
+                    message.append("Input inA: ");
+                    appendVariableToMessage(message, args.inA);
+                    message.append("\n");
+                    message.append("Input inB: ");
+                    appendVariableToMessage(message, args.inB);
+                    message.append("\n");
+                    message.append("Expected output out: ");
+                    appendVariableToMessage(message, args.out);
+                    message.append("\n");
+                    message.append("Actual   output out: ");
+                    appendVariableToMessage(message, arrayOut[i * 4 + j]);
+                    if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                        message.append(" FAIL");
+                    }
+                    message.append("\n");
+                    assertTrue("Incorrect output for checkMinFloat3FloatFloat3" +
+                            (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+                }
+            }
+        }
+    }
+
+    private void checkMinFloat4FloatFloat4() {
+        Allocation inA = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 4, 0x2d11725el, false);
+        Allocation inB = createRandomAllocation(mRS, Element.DataType.FLOAT_32, 1, 0x2d11725fl, false);
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+            script.set_gAllocInB(inB);
+            script.forEach_testMinFloat4FloatFloat4(inA, out);
+            verifyResultsMinFloat4FloatFloat4(inA, inB, out, false);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinFloat4FloatFloat4: " + e.toString());
+        }
+        try {
+            Allocation out = Allocation.createSized(mRS, getElement(mRS, Element.DataType.FLOAT_32, 4), INPUTSIZE);
+            scriptRelaxed.set_gAllocInB(inB);
+            scriptRelaxed.forEach_testMinFloat4FloatFloat4(inA, out);
+            verifyResultsMinFloat4FloatFloat4(inA, inB, out, true);
+        } catch (Exception e) {
+            throw new RSRuntimeException("RenderScript. Can't invoke forEach_testMinFloat4FloatFloat4: " + e.toString());
+        }
+    }
+
+    private void verifyResultsMinFloat4FloatFloat4(Allocation inA, Allocation inB, Allocation out, boolean relaxed) {
+        float[] arrayInA = new float[INPUTSIZE * 4];
+        inA.copyTo(arrayInA);
+        float[] arrayInB = new float[INPUTSIZE * 1];
+        inB.copyTo(arrayInB);
+        float[] arrayOut = new float[INPUTSIZE * 4];
+        out.copyTo(arrayOut);
+        for (int i = 0; i < INPUTSIZE; i++) {
+            for (int j = 0; j < 4 ; j++) {
+                // Extract the inputs.
+                ArgumentsFloatFloatFloat args = new ArgumentsFloatFloatFloat();
+                args.inA = arrayInA[i * 4 + j];
+                args.inB = arrayInB[i];
+                // Figure out what the outputs should have been.
+                Target target = new Target(relaxed);
+                CoreMathVerifier.computeMin(args, target);
+                // Validate the outputs.
+                boolean valid = true;
+                if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                    valid = false;
+                }
+                if (!valid) {
+                    StringBuilder message = new StringBuilder();
+                    message.append("Input inA: ");
+                    appendVariableToMessage(message, args.inA);
+                    message.append("\n");
+                    message.append("Input inB: ");
+                    appendVariableToMessage(message, args.inB);
+                    message.append("\n");
+                    message.append("Expected output out: ");
+                    appendVariableToMessage(message, args.out);
+                    message.append("\n");
+                    message.append("Actual   output out: ");
+                    appendVariableToMessage(message, arrayOut[i * 4 + j]);
+                    if (!args.out.couldBe(arrayOut[i * 4 + j])) {
+                        message.append(" FAIL");
+                    }
+                    message.append("\n");
+                    assertTrue("Incorrect output for checkMinFloat4FloatFloat4" +
+                            (relaxed ? "_relaxed" : "") + ":\n" + message.toString(), valid);
+                }
+            }
+        }
+    }
+
     public class ArgumentsCharCharChar {
         public byte inA;
         public byte inB;
@@ -2907,6 +3105,9 @@
         checkMinFloat2Float2Float2();
         checkMinFloat3Float3Float3();
         checkMinFloat4Float4Float4();
+        checkMinFloat2FloatFloat2();
+        checkMinFloat3FloatFloat3();
+        checkMinFloat4FloatFloat4();
         checkMinCharCharChar();
         checkMinChar2Char2Char2();
         checkMinChar3Char3Char3();
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.rs b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.rs
index eec9fb6..dccff70 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.rs
+++ b/tests/tests/renderscript/src/android/renderscript/cts/generated/TestMin.rs
@@ -41,6 +41,21 @@
     return min(inA, inB);
 }
 
+float2 __attribute__((kernel)) testMinFloat2FloatFloat2(float2 inA, unsigned int x) {
+    float inB = rsGetElementAt_float(gAllocInB, x);
+    return min(inA, inB);
+}
+
+float3 __attribute__((kernel)) testMinFloat3FloatFloat3(float3 inA, unsigned int x) {
+    float inB = rsGetElementAt_float(gAllocInB, x);
+    return min(inA, inB);
+}
+
+float4 __attribute__((kernel)) testMinFloat4FloatFloat4(float4 inA, unsigned int x) {
+    float inB = rsGetElementAt_float(gAllocInB, x);
+    return min(inA, inB);
+}
+
 char __attribute__((kernel)) testMinCharCharChar(char inA, unsigned int x) {
     char inB = rsGetElementAt_char(gAllocInB, x);
     return min(inA, inB);
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index 6bef886..2d55fb6 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -30,10 +30,14 @@
 		android_security_cts_NativeCodeTest.cpp \
 		android_security_cts_SELinuxTest.cpp \
 		android_security_cts_MMapExecutableTest.cpp \
-		android_security_cts_AudioPolicyBinderTest.cpp
+		android_security_cts_AudioPolicyBinderTest.cpp \
+		android_security_cts_EncryptionTest.cpp
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
-LOCAL_SHARED_LIBRARIES := libnativehelper liblog libbinder libutils libmedia libselinux libdl
+LOCAL_SHARED_LIBRARIES := libnativehelper liblog libbinder libutils libmedia libselinux libdl libcutils libcrypto
+
+LOCAL_C_INCLUDES += ndk/sources/cpufeatures
+LOCAL_STATIC_LIBRARIES := cpufeatures
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index 1051a82..c60b866 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -25,6 +25,7 @@
 extern int register_android_security_cts_SELinuxTest(JNIEnv*);
 extern int register_android_security_cts_MMapExecutableTest(JNIEnv* env);
 extern int register_android_security_cts_AudioPolicyBinderTest(JNIEnv* env);
+extern int register_android_security_cts_EncryptionTest(JNIEnv* env);
 
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     JNIEnv *env = NULL;
@@ -65,5 +66,9 @@
         return JNI_ERR;
     }
 
+    if (register_android_security_cts_EncryptionTest(env)) {
+        return JNI_ERR;
+    }
+
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/security/jni/android_security_cts_EncryptionTest.cpp b/tests/tests/security/jni/android_security_cts_EncryptionTest.cpp
new file mode 100644
index 0000000..b9e390e
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_EncryptionTest.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include <cpu-features.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <jni.h>
+#include <JNIHelp.h>
+#include <openssl/aes.h>
+#include <openssl/cpu.h>
+#include <openssl/evp.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <new>
+
+#define TEST_EVP_CIPHER     EVP_aes_256_cbc()
+#define TEST_BUFSIZE        (1 * 1024 * 1024) /* 1 MiB */
+#define TEST_ITERATIONS     100 /* MiB */
+#define TEST_THRESHOLD      2000 /* ms */
+
+/*
+ * Function: deviceIsEncrypted
+ * Purpose: Check the device is encrypted
+ * Parameters: none
+ * Returns: boolean: (true) if encrypted, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_deviceIsEncrypted(JNIEnv *, jobject)
+{
+    char prop_value[PROP_VALUE_MAX];
+    property_get("ro.crypto.state", prop_value, "");
+
+    jboolean rc = !strcmp(prop_value, "encrypted");
+    ALOGE("EncryptionTest::deviceIsEncrypted: %d", rc);
+
+    return rc;
+}
+
+/*
+ * Function: cpuHasAes
+ * Purpose: Check if we have an ARM CPU with AES instruction
+ * Parameters: none
+ * Returns: boolean: (true) if AES is available, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_cpuHasAes(JNIEnv *, jobject)
+{
+    jboolean rc = false;
+    AndroidCpuFamily family = android_getCpuFamily();
+    uint64_t features = android_getCpuFeatures();
+
+    if (family == ANDROID_CPU_FAMILY_ARM) {
+        rc = (features & ANDROID_CPU_ARM_FEATURE_AES) != 0;
+    } else if (family == ANDROID_CPU_FAMILY_ARM64) {
+        rc = (features & ANDROID_CPU_ARM64_FEATURE_AES) != 0;
+    }
+
+    ALOGE("EncryptionTest::cpuHasAes: %d", rc);
+    return rc;
+}
+
+/*
+ * Function: cpuHasNeon
+ * Purpose: Check if we have an ARM CPU with NEON instructions
+ * Parameters: none
+ * Returns: boolean: (true) if NEON is available, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_cpuHasNeon(JNIEnv *, jobject)
+{
+    jboolean rc = false;
+    AndroidCpuFamily family = android_getCpuFamily();
+
+    if (family == ANDROID_CPU_FAMILY_ARM) {
+        rc = (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0;
+    } else {
+        rc = (family == ANDROID_CPU_FAMILY_ARM64);
+    }
+
+    ALOGE("EncryptionTest::cpuHasNeon: %d", rc);
+    return rc;
+}
+
+/*
+ * Function: neonIsEnabled
+ * Purpose: Check if libcrypto is compiled with NEON support
+ * Parameters: none
+ * Returns: boolean: (true) if NEON is available, (false) otherwise
+ * Exceptions: none
+ */
+static jboolean android_security_cts_EncryptionTest_neonIsEnabled(JNIEnv *, jobject)
+{
+#if defined(OPENSSL_ARM) || defined(OPENSSL_AARCH64)
+    jboolean rc = CRYPTO_is_NEON_capable();
+#else
+    jboolean rc = false;
+#endif
+
+    ALOGE("EncryptionTest::neonIsEnabled: %d", rc);
+    return rc;
+}
+
+static inline uint64_t ns()
+{
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
+}
+
+/*
+ * Function: aesIsFast
+ * Purpose: Test if AES performance is sufficient to require encryption
+ * Parameters: none
+ * Returns: boolean: (true) if AES performance is acceptable, (false) otherwise
+ * Exceptions: InvalidKeyException if EVP_DecryptInit fails, OutOfMemoryError
+ *             if memory allocation fails.
+ */
+static jboolean android_security_cts_EncryptionTest_aesIsFast(JNIEnv *env, jobject)
+{
+    EVP_CIPHER_CTX ctx;
+    uint8_t *buf;
+    uint8_t key[EVP_CIPHER_key_length(TEST_EVP_CIPHER)];
+    uint8_t iv[EVP_CIPHER_iv_length(TEST_EVP_CIPHER)];
+
+    memset(key, 0x42, sizeof(key));
+    memset(iv,  0x11, sizeof(iv));
+
+    EVP_CIPHER_CTX_init(&ctx);
+
+    if (!EVP_DecryptInit(&ctx, TEST_EVP_CIPHER, key, iv)) {
+        jniThrowException(env, "java/security/InvalidKeyException",
+            "EVP_DecryptInit failed");
+        return false;
+    }
+
+    buf = new (std::nothrow) uint8_t[TEST_BUFSIZE +
+                EVP_CIPHER_block_size(TEST_EVP_CIPHER)];
+
+    if (!buf) {
+        jniThrowException(env, "java/lang/OutOfMemoryError",
+            "Failed to allocate test buffer");
+        return false;
+    }
+
+    memset(buf, 0xF0, TEST_BUFSIZE);
+
+    int len;
+    uint64_t t = ns();
+
+    for (int i = 0; i < TEST_ITERATIONS; ++i) {
+        EVP_DecryptUpdate(&ctx, buf, &len, buf, TEST_BUFSIZE);
+    }
+
+    t = ns() - t;
+
+    delete[] buf;
+
+    unsigned long ms = (unsigned long)(t / 1000000);
+    double speed =
+        (double)(TEST_ITERATIONS * TEST_BUFSIZE / (1024 * 1024)) * 1000.0 / ms;
+
+    ALOGE("EncryptionTest::aesIsFast: %u iterations in %lu ms (%.01lf MiB/s) "
+        "(threshold %u ms)", TEST_ITERATIONS, ms, speed, TEST_THRESHOLD);
+
+    return ms < TEST_THRESHOLD;
+}
+
+static JNINativeMethod gMethods[] = {
+    { "deviceIsEncrypted", "()Z",
+            (void *) android_security_cts_EncryptionTest_deviceIsEncrypted },
+    { "cpuHasAes", "()Z",
+            (void *) android_security_cts_EncryptionTest_cpuHasAes },
+    { "cpuHasNeon", "()Z",
+            (void *) android_security_cts_EncryptionTest_cpuHasNeon },
+    { "neonIsEnabled", "()Z",
+            (void *) android_security_cts_EncryptionTest_neonIsEnabled },
+    { "aesIsFast", "()Z",
+            (void *) android_security_cts_EncryptionTest_aesIsFast }
+};
+
+int register_android_security_cts_EncryptionTest(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/security/cts/EncryptionTest");
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/security/src/android/security/cts/ClonedSecureRandomTest.java b/tests/tests/security/src/android/security/cts/ClonedSecureRandomTest.java
index 0ef08f0..d188aab 100644
--- a/tests/tests/security/src/android/security/cts/ClonedSecureRandomTest.java
+++ b/tests/tests/security/src/android/security/cts/ClonedSecureRandomTest.java
@@ -123,6 +123,7 @@
          */
         int firstPid = -1;
         int previousPid = -1;
+        int lastPid = -1;
         for (int i = 0; i < MAX_PID; i++) {
             byte[] output = new byte[RANDOM_BYTES_PER_PID];
             int pid;
@@ -205,7 +206,9 @@
                 firstPid = pid;
             }
 
-            if (i > PRIMING_ITERATIONS) {
+            if (i <= PRIMING_ITERATIONS) {
+                lastPid = pid;
+            } else if (pid > lastPid && (lastPid > firstPid || pid < firstPid)) {
                 wastePids(firstPid, previousPid);
             }
         }
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
new file mode 100644
index 0000000..bd9a458
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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.security.cts;
+
+import android.test.AndroidTestCase;
+import junit.framework.TestCase;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.util.Log;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class EncryptionTest extends AndroidTestCase {
+
+    static {
+        System.loadLibrary("ctssecurity_jni");
+    }
+
+    private static final String TAG = "EncryptionTest";
+
+    private static final String crypto = "/proc/crypto";
+
+    private static native boolean deviceIsEncrypted();
+
+    private static native boolean cpuHasAes();
+
+    private static native boolean cpuHasNeon();
+
+    private static native boolean neonIsEnabled();
+
+    private static native boolean aesIsFast();
+
+    private boolean hasKernelCrypto(String driver) throws Exception {
+        BufferedReader br = new BufferedReader(new FileReader(crypto));
+        Pattern p = Pattern.compile("driver\\s*:\\s*" + driver);
+
+        try {
+            String line;
+            while ((line = br.readLine()) != null) {
+                if (p.matcher(line).matches()) {
+                    Log.i(TAG, crypto + " has " + driver + " (" + line + ")");
+                    return true;
+                }
+            }
+       } finally {
+           br.close();
+       }
+
+       return false;
+    }
+
+    private boolean hasLowRAM() {
+        ActivityManager activityManager =
+            (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
+
+        return activityManager.isLowRamDevice();
+    }
+
+    public void testConfig() throws Exception {
+        if (cpuHasAes()) {
+            // If CPU has AES CE, it must be enabled in kernel
+            assertTrue(crypto + " is missing xts-aes-ce",
+                hasKernelCrypto("xts-aes-ce"));
+        } else if (cpuHasNeon()) {
+            // Otherwise, if CPU has NEON, it must be enabled
+            assertTrue(crypto + " is missing xts-aes-neon (or xts-aes-neonbs)",
+                hasKernelCrypto("xts-aes-neon") ||
+                hasKernelCrypto("xts-aes-neonbs") ||
+                hasKernelCrypto("aes-asm")); // Not recommended alone
+        }
+
+        if (cpuHasNeon()) {
+            assertTrue("libcrypto must have NEON", neonIsEnabled());
+        }
+    }
+
+    public void testEncryption() throws Exception {
+        if (deviceIsEncrypted()) {
+            return;
+        }
+
+        // Optional for low RAM devices
+        if (hasLowRAM()) {
+            Log.i(TAG, "hasLowRAM: true");
+            return;
+        }
+
+        // Required if performance is sufficient
+        assertFalse("Device encryption is required", aesIsFast());
+    }
+}
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
index 8fb61bf..4ba1160d 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
@@ -50,6 +50,8 @@
     public static final String RESOLUTION = "resolution";
     public static final String VERSION_SDK = "androidPlatformVersion";
     public static final String VERSION_RELEASE = "buildVersion";
+    public static final String VERSION_BASE_OS = "base_os";
+    public static final String VERSION_SECURITY_PATCH = "security_patch";
     public static final String BUILD_ABI = "build_abi";
     public static final String BUILD_ABI2 = "build_abi2";
     public static final String BUILD_ABIS = "build_abis";
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
index 5828259..51da850 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
@@ -80,6 +80,8 @@
 
         addResult(VERSION_RELEASE, Build.VERSION.RELEASE);
         addResult(VERSION_SDK, Build.VERSION.SDK);
+        addResult(VERSION_BASE_OS, Build.VERSION.BASE_OS);
+        addResult(VERSION_SECURITY_PATCH, Build.VERSION.SECURITY_PATCH);
 
         DisplayMetrics metrics = new DisplayMetrics();
         WindowManager wm = (WindowManager) getContext().getSystemService(