Merge "Update subtext for Security settings." into oc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b38cea7..02351e5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2645,6 +2645,10 @@
                 android:value="true" />
         </activity>
 
+        <!-- Confirmation dialog for enabling notification access from CompanionDeviceManager -->
+        <activity android:name=".notification.NotificationAccessConfirmationActivity"
+                  android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+
         <receiver android:name=".widget.SettingsAppWidgetProvider"
                 android:label="@string/gadget_title"
                 android:exported="false"
diff --git a/res/xml/app_storage_settings.xml b/res/xml/app_storage_settings.xml
index 6bd8ae3..1c71bfa 100644
--- a/res/xml/app_storage_settings.xml
+++ b/res/xml/app_storage_settings.xml
@@ -15,8 +15,14 @@
 -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
                   android:title="@string/application_info_label">
 
+    <com.android.settings.applications.LayoutPreference
+        android:key="header_view"
+        android:layout="@layout/app_action_buttons"
+        android:selectable="false" />
+
     <com.android.settings.applications.SpacePreference
         android:key="storage_space"
         android:layout_height="8dp" />
@@ -36,13 +42,9 @@
     <PreferenceCategory
         android:key="storage_category"
         android:layout="@layout/tall_preference_category"
-        android:title="@string/storage_label">
-
-        <Preference
-            android:key="total_size"
-            android:title="@string/total_size_label"
-            android:selectable="false"
-            android:layout="@layout/horizontal_preference" />
+        android:title="@string/app_info_storage_title"
+        settings:allowDividerAbove="false"
+        settings:allowDividerBelow="false">
 
         <Preference
             android:key="app_size"
@@ -56,32 +58,28 @@
             android:selectable="false"
             android:layout="@layout/horizontal_preference" />
 
-        <com.android.settings.applications.LayoutPreference
-            android:key="clear_data_button"
+        <Preference
+            android:key="cache_size"
+            android:title="@string/cache_size_label"
             android:selectable="false"
-            android:layout="@layout/single_button_panel" />
+            android:layout="@layout/horizontal_preference" />
+
+        <Preference
+            android:key="total_size"
+            android:title="@string/total_size_label"
+            android:selectable="false"
+            android:layout="@layout/horizontal_preference" />
+
+        <com.android.settings.applications.SpacePreference
+            android:layout_height="8dp" />
+
     </PreferenceCategory>
 
-    <com.android.settings.applications.SpacePreference
-        android:layout_height="8dp" />
-
-    <Preference
-        android:key="cache_size"
-        android:title="@string/cache_size_label"
-        android:selectable="false"
-        android:layout="@layout/horizontal_preference" />
-
-    <com.android.settings.applications.LayoutPreference
-        android:key="clear_cache_button"
-        android:selectable="false"
-        android:layout="@layout/single_button_panel" />
-
-    <com.android.settings.applications.SpacePreference
-        android:layout_height="8dp" />
-
     <PreferenceCategory
         android:key="uri_category"
-        android:layout="@layout/headerless_preference_category" >
+        android:layout="@layout/headerless_preference_category"
+        settings:allowDividerAbove="false"
+        settings:allowDividerBelow="false">
         <com.android.settings.applications.LayoutPreference
             android:key="clear_uri_button"
             android:layout="@layout/single_button_panel"
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
index 3e9304d..546b11e 100644
--- a/src/com/android/settings/ChooseLockGeneric.java
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -129,6 +129,7 @@
         private boolean mHideDrawer = false;
         private ManagedLockPasswordProvider mManagedPasswordProvider;
         private boolean mIsSetNewPassword = false;
+        private UserManager mUserManager;
 
         protected boolean mForFingerprint = false;
 
@@ -166,6 +167,7 @@
                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
             mForChangeCredRequiredForBoot = getArguments() != null && getArguments().getBoolean(
                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT);
+            mUserManager = UserManager.get(getActivity());
 
             if (savedInstanceState != null) {
                 mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
@@ -751,11 +753,10 @@
             if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
                 mFingerprintManager.setActiveUser(UserHandle.myUserId());
             }
-            final UserManager um = UserManager.get(getActivity());
             boolean hasChildProfile = false;
-            if (!um.getUserInfo(parentUserId).isManagedProfile()) {
+            if (!mUserManager.getUserInfo(parentUserId).isManagedProfile()) {
                 // Current user is primary profile, remove work profile fingerprints if necessary
-                final List<UserInfo> profiles = um.getProfiles(parentUserId);
+                final List<UserInfo> profiles = mUserManager.getProfiles(parentUserId);
                 final int profilesSize = profiles.size();
                 for (int i = 0; i < profilesSize; i++) {
                     final UserInfo userInfo = profiles.get(i);
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java
index 8d41558..0676f56 100644
--- a/src/com/android/settings/applications/AppStorageSettings.java
+++ b/src/com/android/settings/applications/AppStorageSettings.java
@@ -90,13 +90,10 @@
 
     private static final String KEY_TOTAL_SIZE = "total_size";
     private static final String KEY_APP_SIZE = "app_size";
-    private static final String KEY_EXTERNAL_CODE_SIZE = "external_code_size";
     private static final String KEY_DATA_SIZE = "data_size";
-    private static final String KEY_EXTERNAL_DATA_SIZE = "external_data_size";
     private static final String KEY_CACHE_SIZE = "cache_size";
 
-    private static final String KEY_CLEAR_DATA = "clear_data_button";
-    private static final String KEY_CLEAR_CACHE = "clear_cache_button";
+    private static final String KEY_HEADER_BUTTONS = "header_view";
 
     private static final String KEY_URI_CATEGORY = "uri_category";
     private static final String KEY_CLEAR_URI = "clear_uri_button";
@@ -119,16 +116,11 @@
     private boolean mCanClearData = true;
     private boolean mCacheCleared;
 
-    private AppStorageStats mLastResult;
     private AppStorageSizesController mSizeController;
 
     private ClearCacheObserver mClearCacheObserver;
     private ClearUserDataObserver mClearDataObserver;
 
-    // Resource strings
-    private CharSequence mInvalidSizeStr;
-    private CharSequence mComputingStr;
-
     private VolumeInfo[] mCandidates;
     private AlertDialog.Builder mDialogBuilder;
     private ApplicationInfo mInfo;
@@ -158,9 +150,6 @@
     }
 
     private void setupViews() {
-        mComputingStr = getActivity().getText(R.string.computing_size);
-        mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value);
-
         // Set default values on sizes
         mSizeController = new AppStorageSizesController.Builder()
                 .setTotalSizePreference(findPreference(KEY_TOTAL_SIZE))
@@ -171,8 +160,8 @@
                 .setErrorString(R.string.invalid_size_value)
                 .build();
 
-        mClearDataButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_DATA))
-                .findViewById(R.id.button);
+        mClearDataButton = (Button) ((LayoutPreference) findPreference(KEY_HEADER_BUTTONS))
+                .findViewById(R.id.left_button);
 
         mStorageUsed = findPreference(KEY_STORAGE_USED);
         mChangeStorageButton = (Button) ((LayoutPreference) findPreference(KEY_CHANGE_STORAGE))
@@ -182,8 +171,8 @@
 
         // Cache section
         mCacheSize = findPreference(KEY_CACHE_SIZE);
-        mClearCacheButton = (Button) ((LayoutPreference) findPreference(KEY_CLEAR_CACHE))
-                .findViewById(R.id.button);
+        mClearCacheButton = (Button) ((LayoutPreference) findPreference(KEY_HEADER_BUTTONS))
+                .findViewById(R.id.right_button);
         mClearCacheButton.setText(R.string.clear_cache_btn_text);
 
         // URI permissions section
@@ -267,7 +256,7 @@
         if (mAppEntry == null) {
             return false;
         }
-        updateUiWithSize(mLastResult);
+        updateUiWithSize(mSizeController.getLastResult());
         refreshGrantedUriPermissions();
 
         final VolumeInfo currentVol = getActivity().getPackageManager()
diff --git a/src/com/android/settings/applications/AppStorageSizesController.java b/src/com/android/settings/applications/AppStorageSizesController.java
index 94935bd..23a3eb2 100644
--- a/src/com/android/settings/applications/AppStorageSizesController.java
+++ b/src/com/android/settings/applications/AppStorageSizesController.java
@@ -110,6 +110,13 @@
         mCachedCleared = isCleared;
     }
 
+    /**
+     * Returns the last result calculated, if it exists. If it does not, returns null.
+     */
+    public StorageStatsSource.AppStorageStats getLastResult() {
+        return mLastResult;
+    }
+
     private String getSizeStr(Context context, long size) {
         return Formatter.formatFileSize(context, size);
     }
diff --git a/src/com/android/settings/applications/assist/DefaultAssistPreferenceController.java b/src/com/android/settings/applications/assist/DefaultAssistPreferenceController.java
index 747ce4d..b8d6a87 100644
--- a/src/com/android/settings/applications/assist/DefaultAssistPreferenceController.java
+++ b/src/com/android/settings/applications/assist/DefaultAssistPreferenceController.java
@@ -24,6 +24,7 @@
 import android.service.voice.VoiceInteractionService;
 import android.service.voice.VoiceInteractionServiceInfo;
 
+import android.support.annotation.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
 import com.android.settings.applications.defaultapps.DefaultAppInfo;
 import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
@@ -56,13 +57,10 @@
         if (services == null || services.isEmpty()) {
             return null;
         }
-        final ResolveInfo resolveInfo = services.get(0);
-        final VoiceInteractionServiceInfo voiceInfo =
-                new VoiceInteractionServiceInfo(pm, resolveInfo.serviceInfo);
-        if (!voiceInfo.getSupportsAssist()) {
+        final String activity = getAssistSettingsActivity(cn, services.get(0), pm);
+        if (activity == null) {
             return null;
         }
-        final String activity = voiceInfo.getSettingsActivity();
         return new Intent(Intent.ACTION_MAIN)
                 .setComponent(new ComponentName(cn.getPackageName(), activity));
     }
@@ -85,4 +83,14 @@
         }
         return new DefaultAppInfo(mPackageManager, mUserId, cn);
     }
+
+    @VisibleForTesting
+    String getAssistSettingsActivity(ComponentName cn, ResolveInfo resolveInfo, PackageManager pm) {
+        final VoiceInteractionServiceInfo voiceInfo =
+            new VoiceInteractionServiceInfo(pm, resolveInfo.serviceInfo);
+        if (!voiceInfo.getSupportsAssist()) {
+            return null;
+        }
+        return voiceInfo.getSettingsActivity();
+    }
 }
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index 602e65f..bc64ffa 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -117,7 +117,7 @@
     @Override
     public void onResume() {
         super.onResume();
-        getLoaderManager().initLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
+        getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
         getLoaderManager().initLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
     }
 
diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
index e83c5d2..cf0239d 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
@@ -79,8 +79,15 @@
         UserHandle myUser = UserHandle.of(userId);
         for (int i = 0, size = applicationInfos.size(); i < size; i++) {
             ApplicationInfo app = applicationInfos.get(i);
-            StorageStatsSource.AppStorageStats stats =
-                    mStatsManager.getStatsForPackage(mUuid, app.packageName, myUser);
+
+            StorageStatsSource.AppStorageStats stats;
+            try {
+                stats = mStatsManager.getStatsForPackage(mUuid, app.packageName, myUser);
+            } catch (IllegalStateException e) {
+                // This may happen if the package was removed during our calculation.
+                Log.w("App unexpectedly not found", e);
+                continue;
+            }
 
             long attributedAppSizeInBytes = stats.getDataBytes();
             // This matches how the package manager calculates sizes -- by zeroing out code sizes of
diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
new file mode 100644
index 0000000..a78fed7
--- /dev/null
+++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.settings.notification;
+
+import static com.android.internal.notification.NotificationAccessConfirmationActivityContract
+        .EXTRA_COMPONENT_NAME;
+import static com.android.internal.notification.NotificationAccessConfirmationActivityContract
+        .EXTRA_PACKAGE_TITLE;
+import static com.android.internal.notification.NotificationAccessConfirmationActivityContract
+        .EXTRA_USER_ID;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.SettingsStringUtil;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.settings.R;
+import com.android.settings.core.TouchOverlayManager;
+
+/** @hide */
+public class NotificationAccessConfirmationActivity extends Activity
+        implements DialogInterface {
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "NotificationAccessConfirmationActivity";
+
+    private int mUserId;
+    private ComponentName mComponentName;
+    private TouchOverlayManager mTouchOverlayManager;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mTouchOverlayManager = new TouchOverlayManager(this);
+
+        mComponentName = getIntent().getParcelableExtra(EXTRA_COMPONENT_NAME);
+        mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL);
+        String pkgTitle = getIntent().getStringExtra(EXTRA_PACKAGE_TITLE);
+
+        AlertController.AlertParams p = new AlertController.AlertParams(this);
+        p.mTitle = getString(
+                R.string.notification_listener_security_warning_title,
+                pkgTitle);
+        p.mMessage = getString(
+                R.string.notification_listener_security_warning_summary,
+                pkgTitle);
+        p.mPositiveButtonText = getString(R.string.allow);
+        p.mPositiveButtonListener = (a, b) -> onAllow();
+        p.mNegativeButtonText = getString(R.string.deny);
+        p.mNegativeButtonListener = (a, b) -> cancel();
+        AlertController
+                .create(this, this, getWindow())
+                .installContent(p);
+    }
+
+    private void onAllow() {
+        String requiredPermission = Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
+        try {
+            ServiceInfo serviceInfo = getPackageManager().getServiceInfo(mComponentName, 0);
+            if (!requiredPermission.equals(serviceInfo.permission)) {
+                Slog.e(LOG_TAG,
+                        "Service " + mComponentName + " lacks permission " + requiredPermission);
+                return;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(LOG_TAG, "Failed to get service info for " + mComponentName, e);
+            return;
+        }
+
+        final SettingsStringUtil.SettingStringHelper setting =
+                 new SettingsStringUtil.SettingStringHelper(
+                         getContentResolver(),
+                         Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                         mUserId);
+        setting.write(SettingsStringUtil.ComponentNameSet.add(setting.read(), mComponentName));
+
+        finish();
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return AlertActivity.dispatchPopulateAccessibilityEvent(this, event);
+    }
+
+    @Override
+    public void cancel() {
+        finish();
+    }
+
+    @Override
+    public void dismiss() {
+        // This is called after the click, since we finish when handling the
+        // click, don't do that again here.
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mTouchOverlayManager.setOverlayAllowed(false);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mTouchOverlayManager.setOverlayAllowed(true);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/assist/DefaultAssistPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/assist/DefaultAssistPreferenceControllerTest.java
index 1182762..728a8a5 100644
--- a/tests/robotests/src/com/android/settings/applications/assist/DefaultAssistPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/assist/DefaultAssistPreferenceControllerTest.java
@@ -16,7 +16,16 @@
 
 package com.android.settings.applications.assist;
 
+import android.Manifest;
+import android.app.SearchManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.provider.Settings;
 
 import com.android.settings.SettingsRobolectricTestRunner;
@@ -24,6 +33,8 @@
 import com.android.settings.applications.defaultapps.DefaultAppInfo;
 import com.android.settings.testutils.shadow.ShadowSecureSettings;
 
+import java.util.ArrayList;
+import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,6 +43,13 @@
 import org.robolectric.annotation.Config;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -39,6 +57,10 @@
 
     @Mock
     private Context mContext;
+    @Mock
+    private SearchManager mSearchManager;
+    @Mock
+    private PackageManager mPackageManager;
     private DefaultAssistPreferenceController mController;
 
     @Before
@@ -61,4 +83,33 @@
 
         assertThat(appInfo.getKey()).isEqualTo(flattenKey);
     }
+
+    @Test
+    public void getSettingIntent_noSettingsActivity_shouldNotCrash() {
+        final String flattenKey = "com.android.settings/assist";
+        ShadowSecureSettings.putString(null, Settings.Secure.ASSISTANT, flattenKey);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        DefaultAssistPreferenceController controller =
+            spy(new DefaultAssistPreferenceController(mContext));
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.name = "assist";
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        resolveInfo.activityInfo.applicationInfo.packageName = "com.android.settings";
+        when(mPackageManager.resolveActivityAsUser(any(Intent.class), anyInt(), anyInt()))
+            .thenReturn(resolveInfo);
+        when(mContext.getSystemService(Context.SEARCH_SERVICE)).thenReturn(mSearchManager);
+        when(mSearchManager.getAssistIntent(anyBoolean())).thenReturn(mock(Intent.class));
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.permission = Manifest.permission.BIND_VOICE_INTERACTION;
+        resolveInfo.serviceInfo = serviceInfo;
+        final List<ResolveInfo> services = new ArrayList<>();
+        services.add(resolveInfo);
+        when(mPackageManager.queryIntentServices(any(Intent.class), anyInt())).thenReturn(services);
+        doReturn(null).when(controller).getAssistSettingsActivity(
+            ComponentName.unflattenFromString(flattenKey), resolveInfo, mPackageManager);
+
+        controller.getSettingIntent(null);
+        // should not crash
+    }
 }
diff --git a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
index e82482e..f7131b3 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
@@ -180,6 +180,20 @@
         assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(0);
     }
 
+    @Test
+    public void testRemovedPackageDoesNotCrash() throws Exception {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = PACKAGE_NAME_1;
+        info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+        mInfo.add(info);
+        when(mSource.getStatsForPackage(anyString(), anyString(), any(UserHandle.class)))
+                .thenThrow(new IllegalStateException());
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        // Should not crash.
+    }
+
     private ApplicationInfo addPackage(
             String packageName, long cacheSize, long codeSize, long dataSize, int category) {
         StorageStatsSource.AppStorageStats storageStats =