Merge "Reflect device encryption status" into oc-mr1-dev
diff --git a/src/com/android/settings/applications/PackageManagerWrapper.java b/src/com/android/settings/applications/PackageManagerWrapper.java
index 580b578..b0accdc 100644
--- a/src/com/android/settings/applications/PackageManagerWrapper.java
+++ b/src/com/android/settings/applications/PackageManagerWrapper.java
@@ -21,6 +21,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.UserHandle;
@@ -49,6 +50,11 @@
     List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId);
 
     /**
+     * Calls {@code PackageManager.getInstalledPackagesAsUser}
+     */
+    List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId);
+
+    /**
      * Calls {@code PackageManager.hasSystemFeature()}.
      *
      * @see android.content.pm.PackageManager#hasSystemFeature
diff --git a/src/com/android/settings/applications/PackageManagerWrapperImpl.java b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
index a47137c..021a80a 100644
--- a/src/com/android/settings/applications/PackageManagerWrapperImpl.java
+++ b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
@@ -21,6 +21,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.UserHandle;
@@ -47,6 +48,11 @@
     }
 
     @Override
+    public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+        return mPm.getInstalledPackagesAsUser(flags, userId);
+    }
+
+    @Override
     public boolean hasSystemFeature(String name) {
         return mPm.hasSystemFeature(name);
     }
diff --git a/src/com/android/settings/applications/PictureInPictureSettings.java b/src/com/android/settings/applications/PictureInPictureSettings.java
index b1c544a..79780d6 100644
--- a/src/com/android/settings/applications/PictureInPictureSettings.java
+++ b/src/com/android/settings/applications/PictureInPictureSettings.java
@@ -22,14 +22,16 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
 import android.support.v7.preference.PreferenceScreen;
-import android.util.ArrayMap;
+import android.util.IconDrawableFactory;
+import android.util.Pair;
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -37,8 +39,10 @@
 import com.android.settings.R;
 import com.android.settings.notification.EmptyTextSettings;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 public class PictureInPictureSettings extends EmptyTextSettings {
@@ -50,8 +54,38 @@
         IGNORE_PACKAGE_LIST.add("com.android.systemui");
     }
 
+    /**
+     * Comparator by name, then user id.
+     * {@see PackageItemInfo#DisplayNameComparator}
+     */
+    static class AppComparator implements Comparator<Pair<ApplicationInfo, Integer>> {
+
+        private final Collator mCollator = Collator.getInstance();
+        private final PackageManager mPm;
+
+        public AppComparator(PackageManager pm) {
+            mPm = pm;
+        }
+
+        public final int compare(Pair<ApplicationInfo, Integer> a,
+                Pair<ApplicationInfo, Integer> b) {
+            CharSequence  sa = a.first.loadLabel(mPm);
+            if (sa == null) sa = a.first.name;
+            CharSequence  sb = b.first.loadLabel(mPm);
+            if (sb == null) sb = b.first.name;
+            int nameCmp = mCollator.compare(sa.toString(), sb.toString());
+            if (nameCmp != 0) {
+                return nameCmp;
+            } else {
+                return a.second - b.second;
+            }
+        }
+    }
+
     private Context mContext;
-    private PackageManager mPackageManager;
+    private PackageManagerWrapper mPackageManager;
+    private UserManagerWrapper mUserManager;
+    private IconDrawableFactory mIconDrawableFactory;
 
     /**
      * @return true if the package has any activities that declare that they support
@@ -93,12 +127,23 @@
         return false;
     }
 
+    public PictureInPictureSettings() {
+        // Do nothing
+    }
+
+    public PictureInPictureSettings(PackageManagerWrapper pm, UserManagerWrapper um) {
+        mPackageManager = pm;
+        mUserManager = um;
+    }
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
         mContext = getActivity();
-        mPackageManager = mContext.getPackageManager();
+        mPackageManager = new PackageManagerWrapperImpl(mContext.getPackageManager());
+        mUserManager = new UserManagerWrapperImpl(mContext.getSystemService(UserManager.class));
+        mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext));
     }
 
@@ -110,33 +155,25 @@
         final PreferenceScreen screen = getPreferenceScreen();
         screen.removeAll();
 
-        // Fetch the set of applications which have at least one activity that declare that they
-        // support picture-in-picture
-        final ArrayMap<String, Boolean> packageToState = new ArrayMap<>();
-        final ArrayList<ApplicationInfo> pipApps = new ArrayList<>();
-        final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser(
-                GET_ACTIVITIES, UserHandle.myUserId());
-        for (PackageInfo packageInfo : installedPackages) {
-            if (checkPackageHasPictureInPictureActivities(packageInfo.packageName,
-                    packageInfo.activities)) {
-                final String packageName = packageInfo.applicationInfo.packageName;
-                final boolean state = PictureInPictureDetails.getEnterPipStateForPackage(
-                        mContext, packageInfo.applicationInfo.uid, packageName);
-                pipApps.add(packageInfo.applicationInfo);
-                packageToState.put(packageName, state);
-            }
-        }
-        Collections.sort(pipApps, new PackageItemInfo.DisplayNameComparator(mPackageManager));
+        // Fetch the set of applications for each profile which have at least one activity that
+        // declare that they support picture-in-picture
+        final PackageManager pm = mPackageManager.getPackageManager();
+        final ArrayList<Pair<ApplicationInfo, Integer>> pipApps =
+                collectPipApps(UserHandle.myUserId());
+        Collections.sort(pipApps, new AppComparator(pm));
 
         // Rebuild the list of prefs
         final Context prefContext = getPrefContext();
-        for (final ApplicationInfo appInfo : pipApps) {
+        for (final Pair<ApplicationInfo, Integer> appData : pipApps) {
+            final ApplicationInfo appInfo = appData.first;
+            final int userId = appData.second;
+            final UserHandle user = UserHandle.of(userId);
             final String packageName = appInfo.packageName;
-            final CharSequence label = appInfo.loadLabel(mPackageManager);
+            final CharSequence label = appInfo.loadLabel(pm);
 
             final Preference pref = new Preference(prefContext);
-            pref.setIcon(appInfo.loadIcon(mPackageManager));
-            pref.setTitle(label);
+            pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, userId));
+            pref.setTitle(pm.getUserBadgedLabel(label, user));
             pref.setSummary(PictureInPictureDetails.getPreferenceSummary(prefContext,
                     appInfo.uid, packageName));
             pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@@ -162,4 +199,28 @@
     public int getMetricsCategory() {
         return MetricsEvent.SETTINGS_MANAGE_PICTURE_IN_PICTURE;
     }
+
+    /**
+     * @return the list of applications for the given user and all their profiles that have
+     *         activities which support PiP.
+     */
+    ArrayList<Pair<ApplicationInfo, Integer>> collectPipApps(int userId) {
+        final ArrayList<Pair<ApplicationInfo, Integer>> pipApps = new ArrayList<>();
+        final ArrayList<Integer> userIds = new ArrayList<>();
+        for (UserInfo user : mUserManager.getProfiles(userId)) {
+            userIds.add(user.id);
+        }
+
+        for (int id : userIds) {
+            final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser(
+                    GET_ACTIVITIES, id);
+            for (PackageInfo packageInfo : installedPackages) {
+                if (checkPackageHasPictureInPictureActivities(packageInfo.packageName,
+                        packageInfo.activities)) {
+                    pipApps.add(new Pair<>(packageInfo.applicationInfo, id));
+                }
+            }
+        }
+        return pipApps;
+    }
 }
diff --git a/src/com/android/settings/applications/UserManagerWrapper.java b/src/com/android/settings/applications/UserManagerWrapper.java
index 5b4ed2a..5a86285 100644
--- a/src/com/android/settings/applications/UserManagerWrapper.java
+++ b/src/com/android/settings/applications/UserManagerWrapper.java
@@ -29,4 +29,5 @@
 public interface UserManagerWrapper {
     UserInfo getPrimaryUser();
     List<UserInfo> getUsers();
+    List<UserInfo> getProfiles(int userHandle);
 }
diff --git a/src/com/android/settings/applications/UserManagerWrapperImpl.java b/src/com/android/settings/applications/UserManagerWrapperImpl.java
index 14ea64a..26ffc3e 100644
--- a/src/com/android/settings/applications/UserManagerWrapperImpl.java
+++ b/src/com/android/settings/applications/UserManagerWrapperImpl.java
@@ -37,4 +37,9 @@
     public List<UserInfo> getUsers() {
         return mUserManager.getUsers();
     }
+
+    @Override
+    public List<UserInfo> getProfiles(int userHandle) {
+        return mUserManager.getProfiles(userHandle);
+    }
 }
diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java
index 78a0a74..72a4cc1 100644
--- a/src/com/android/settings/notification/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/AppNotificationSettings.java
@@ -21,6 +21,7 @@
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.Settings;
@@ -191,7 +192,8 @@
             }
 
             int deletedChannelCount = mBackend.getDeletedChannelCount(mAppRow.pkg, mAppRow.uid);
-            if (deletedChannelCount > 0) {
+            if (deletedChannelCount > 0 &&
+                    getPreferenceScreen().findPreference(KEY_DELETED) == null) {
                 mDeletedChannels = new FooterPreference(getPrefContext());
                 mDeletedChannels.setSelectable(false);
                 mDeletedChannels.setTitle(getResources().getQuantityString(
@@ -341,13 +343,20 @@
             case NotificationManager.IMPORTANCE_LOW:
                 return getContext().getString(R.string.notification_importance_low);
             case NotificationManager.IMPORTANCE_DEFAULT:
-                return getContext().getString(R.string.notification_importance_default);
+                if (hasValidSound(channel)) {
+                    return getContext().getString(R.string.notification_importance_default);
+                } else { // Silent
+                    return getContext().getString(R.string.notification_importance_low);
+                }
             case NotificationManager.IMPORTANCE_HIGH:
             case NotificationManager.IMPORTANCE_MAX:
             default:
-                return getContext().getString(R.string.notification_importance_high);
+                if (hasValidSound(channel)) {
+                    return getContext().getString(R.string.notification_importance_high);
+                } else { // Silent
+                    return getContext().getString(R.string.notification_importance_high_silent);
+                }
         }
-
     }
 
     private Comparator<NotificationChannel> mChannelComparator =
diff --git a/src/com/android/settings/notification/ChannelImportanceSettings.java b/src/com/android/settings/notification/ChannelImportanceSettings.java
index 26dd57f..9e9ffd6 100644
--- a/src/com/android/settings/notification/ChannelImportanceSettings.java
+++ b/src/com/android/settings/notification/ChannelImportanceSettings.java
@@ -17,6 +17,7 @@
 package com.android.settings.notification;
 
 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
+import static android.app.NotificationChannel.USER_LOCKED_SOUND;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -24,6 +25,7 @@
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 
 import android.content.Context;
+import android.media.RingtoneManager;
 import android.provider.SearchIndexableResource;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
@@ -125,6 +127,7 @@
 
     @Override
     public void onRadioButtonClicked(RadioButtonPreference clicked) {
+        int oldImportance = mChannel.getImportance();
         switch (clicked.getKey()) {
             case KEY_IMPORTANCE_HIGH:
                 mChannel.setImportance(IMPORTANCE_HIGH);
@@ -140,6 +143,17 @@
                 break;
         }
         updateRadioButtons(clicked.getKey());
+
+        // If you are moving from an importance level without sound to one with sound,
+        // but the sound you had selected was "Silence",
+        // then set sound for this channel to your default sound,
+        // because you probably intended to cause this channel to actually start making sound.
+        if (oldImportance < IMPORTANCE_DEFAULT && !hasValidSound(mChannel) &&
+                mChannel.getImportance() >= IMPORTANCE_DEFAULT) {
+            mChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
+                    mChannel.getAudioAttributes());
+            mChannel.lockFields(USER_LOCKED_SOUND);
+        }
         mChannel.lockFields(USER_LOCKED_IMPORTANCE);
         mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, mChannel);
     }
diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java
index 66e94fa..dd4850e 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -318,15 +318,19 @@
                 break;
             case NotificationManager.IMPORTANCE_DEFAULT:
                 title = getContext().getString(R.string.notification_importance_default_title);
-                if (hasValidSound()) {
+                if (hasValidSound(mChannel)) {
                     summary = getContext().getString(R.string.notification_importance_default);
+                } else {
+                    summary = getContext().getString(R.string.notification_importance_low);
                 }
                 break;
             case NotificationManager.IMPORTANCE_HIGH:
             case NotificationManager.IMPORTANCE_MAX:
                 title = getContext().getString(R.string.notification_importance_high_title);
-                if (hasValidSound()) {
+                if (hasValidSound(mChannel)) {
                     summary = getContext().getString(R.string.notification_importance_high);
+                } else {
+                    summary = getContext().getString(R.string.notification_importance_high_silent);
                 }
                 break;
             default:
@@ -369,10 +373,6 @@
                 Settings.System.NOTIFICATION_LIGHT_PULSE, 0) == 1;
     }
 
-    boolean hasValidSound() {
-        return mChannel.getSound() != null && !Uri.EMPTY.equals(mChannel.getSound());
-    }
-
     void updateDependents(boolean banned) {
         PreferenceGroup parent;
         if (mShowLegacyChannelConfig) {
diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java
index 36b263e..0d24afc 100644
--- a/src/com/android/settings/notification/NotificationSettingsBase.java
+++ b/src/com/android/settings/notification/NotificationSettingsBase.java
@@ -46,6 +46,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -506,4 +507,8 @@
             }
         }
     };
+
+    boolean hasValidSound(NotificationChannel channel) {
+        return channel.getSound() != null && !Uri.EMPTY.equals(channel.getSound());
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java
new file mode 100644
index 0000000..77acb46
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.UserInfo;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PictureInPictureSettingsTest {
+
+    private static final int PRIMARY_USER_ID = 0;
+    private static final int PROFILE_USER_ID = 10;
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private PictureInPictureSettings mFragment;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+    @Mock
+    private UserManagerWrapper mUserManager;
+    private ArrayList<PackageInfo> mPrimaryUserPackages;
+    private ArrayList<PackageInfo> mProfileUserPackages;
+    private ArrayList<UserInfo> mUsers;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mFragment = new PictureInPictureSettings(mPackageManager, mUserManager);
+        mPrimaryUserPackages = new ArrayList<>();
+        mProfileUserPackages = new ArrayList<>();
+        mUsers = new ArrayList<>();
+        when(mPackageManager.getInstalledPackagesAsUser(anyInt(), eq(PRIMARY_USER_ID)))
+                .thenReturn(mPrimaryUserPackages);
+        when(mPackageManager.getInstalledPackagesAsUser(anyInt(), eq(PROFILE_USER_ID)))
+                .thenReturn(mProfileUserPackages);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(mUsers);
+    }
+
+    @Test
+    public void testCollectPipApps() {
+        PackageInfo primaryP1 = createPackage("Calculator", true);
+        PackageInfo primaryP2 = createPackage("Clock", false);
+        PackageInfo profileP1 = createPackage("Calculator", false);
+        PackageInfo profileP2 = createPackage("Clock", true);
+
+        mPrimaryUserPackages.add(primaryP1);
+        mPrimaryUserPackages.add(primaryP2);
+        mProfileUserPackages.add(profileP1);
+        mProfileUserPackages.add(profileP2);
+
+        ArrayList<Pair<ApplicationInfo, Integer>> apps = mFragment.collectPipApps(PRIMARY_USER_ID);
+        assertThat(containsPackages(apps, primaryP1, profileP2));
+        assertThat(!containsPackages(apps, primaryP2, profileP1));
+    }
+
+    @Test
+    public void testAppSort() {
+        PackageInfo primaryP1 = createPackage("Android", true);
+        PackageInfo primaryP2 = createPackage("Boop", true);
+        PackageInfo primaryP3 = createPackage("Deck", true);
+        PackageInfo profileP1 = createPackage("Android", true);
+        PackageInfo profileP2 = createPackage("Cool", true);
+        PackageInfo profileP3 = createPackage("Fast", false);
+
+        mPrimaryUserPackages.add(primaryP1);
+        mPrimaryUserPackages.add(primaryP2);
+        mPrimaryUserPackages.add(primaryP3);
+        mProfileUserPackages.add(profileP1);
+        mProfileUserPackages.add(profileP2);
+        mProfileUserPackages.add(profileP3);
+
+        ArrayList<Pair<ApplicationInfo, Integer>> apps = mFragment.collectPipApps(PRIMARY_USER_ID);
+        Collections.sort(apps, new PictureInPictureSettings.AppComparator(null));
+        assertThat(isOrdered(apps, primaryP1, profileP1, primaryP2, profileP2));
+    }
+
+    private boolean containsPackages(ArrayList<Pair<ApplicationInfo, Integer>> apps,
+            PackageInfo... packages) {
+        for (int i = 0; i < packages.length; i++) {
+            boolean found = false;
+            for (int j = 0; j < apps.size(); j++) {
+                if (apps.get(j).first == packages[i].applicationInfo) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isOrdered(ArrayList<Pair<ApplicationInfo, Integer>> apps,
+            PackageInfo... packages) {
+        if (apps.size() != packages.length) {
+            return false;
+        }
+
+        for (int i = 0; i < packages.length; i++) {
+            if (packages[i].applicationInfo != apps.get(i).first) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private PackageInfo createPackage(String appTitle, boolean supportsPip) {
+        PackageInfo pi = new PackageInfo();
+        ActivityInfo ai = new ActivityInfo();
+        if (supportsPip) {
+            ai.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+        }
+        pi.activities = new ActivityInfo[1];
+        pi.activities[0] = ai;
+        pi.applicationInfo = new ApplicationInfo();
+        pi.applicationInfo.name = appTitle;
+        return pi;
+    }
+}