Add activity to manage the list of default apps.

This change adds the activity to manage the list of default apps.

Bug: 110557011
Test: manual
Change-Id: I6d21c0296b3518ef478d189135c1b2b53cac47d3
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml
index aace6df..a3bcda6 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -124,6 +124,19 @@
             </intent-filter>
         </activity>
 
+        <!-- TODO: STOPSHIP: Removed permissions because apps were able to launch it. Is this good? -->
+        <activity android:name="com.android.packageinstaller.role.ui.DefaultAppListActivity"
+                  android:label="@string/default_apps"
+                  android:theme="@style/Settings">
+            <!-- TODO: STOPSHIP: give this a priority greater than 1 to override Settings. -->
+            <intent-filter android:priority="0">
+                <action android:name="android.settings.MANAGE_DEFAULT_APPS_SETTINGS" />
+                <!-- TODO: Redirect into role page? -->
+                <action android:name="android.settings.HOME_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <provider android:name="com.android.packageinstaller.permission.service.PermissionSearchIndexablesProvider"
             android:authorities="com.android.permissioncontroller"
             android:multiprocess="false"
diff --git a/PermissionController/res/layout/settings.xml b/PermissionController/res/layout/settings.xml
new file mode 100644
index 0000000..bc34f2d
--- /dev/null
+++ b/PermissionController/res/layout/settings.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2018 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:id="@+id/loading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="vertical"
+        android:visibility="invisible">
+
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="?android:progressBarStyle" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:text="@string/loading"
+            android:textAppearance="@android:style/TextAppearance.Material.Body1" />
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/empty"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:visibility="invisible"
+        style="@android:style/TextAppearance.Material.Subhead" />
+</FrameLayout>
diff --git a/PermissionController/res/menu/settings.xml b/PermissionController/res/menu/settings.xml
new file mode 100644
index 0000000..74c0e3f
--- /dev/null
+++ b/PermissionController/res/menu/settings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2018 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.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@+id/action_search"
+        android:icon="@drawable/ic_search_24dp"
+        android:title="@string/search_menu"
+        android:showAsAction="always" />
+</menu>
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml
index 70ee605..c458a41 100644
--- a/PermissionController/res/values/strings.xml
+++ b/PermissionController/res/values/strings.xml
@@ -341,6 +341,16 @@
     <!-- The notification content for background location access reminder notification [CHAR LIMIT=none] -->
     <string name="background_location_access_reminder_notification_content">This app can always access your location. Tap to change.</string>
 
+    <!-- Title for page of managing default apps. [CHAR LIMIT=30] -->
+    <string name="default_apps">Default apps</string>
+
+    <!-- TODO: STOPSHIP: I cannot find its value in Settings. -->
+    <!-- Help URI, default apps [DO NOT TRANSLATE] -->
+    <string name="help_uri_default_apps" translatable="false"></string>
+
+    <!-- Label when there is no default apps [CHAR LIMIT=30] -->
+    <string name="no_default_apps">No default apps</string>
+
     <!-- TODO: STOPSHIP: Migrate all default app titles from packages/apps/Settings/res/values/strings.xml . -->
 
     <!-- Label for the dialer role. [CHAR LIMIT=30] -->
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java
index c81d139..af4b21b 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/handheld/PermissionsFrameFragment.java
@@ -16,17 +16,10 @@
 
 package com.android.packageinstaller.permission.ui.handheld;
 
-import static android.provider.Settings.ACTION_APP_SEARCH_SETTINGS;
-import static android.view.MenuItem.SHOW_AS_ACTION_ALWAYS;
-
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
 import android.os.Bundle;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.Animation;
@@ -37,12 +30,12 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.packageinstaller.permission.utils.Utils;
 import com.android.permissioncontroller.R;
 
 public abstract class PermissionsFrameFragment extends PreferenceFragmentCompat {
     private static final String LOG_TAG = PermissionsFrameFragment.class.getSimpleName();
 
-    private static final int MENU_SEARCH_SETTINGS = Menu.FIRST;
     static final int MENU_ALL_PERMS = Menu.FIRST + 1;
     static final int MENU_SHOW_SYSTEM = Menu.FIRST + 2;
     static final int MENU_HIDE_SYSTEM = Menu.FIRST + 3;
@@ -66,28 +59,7 @@
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         super.onCreateOptionsMenu(menu, inflater);
 
-        if (getContext().getPackageManager().resolveActivity(new Intent(ACTION_APP_SEARCH_SETTINGS),
-                0) != null) {
-            MenuItem searchItem = menu.add(Menu.NONE, MENU_SEARCH_SETTINGS, Menu.NONE,
-                    R.string.search_menu);
-            searchItem.setIcon(R.drawable.ic_search_24dp);
-            searchItem.setShowAsAction(SHOW_AS_ACTION_ALWAYS);
-        }
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_SEARCH_SETTINGS:
-                try {
-                    getActivity().startActivity(new Intent(ACTION_APP_SEARCH_SETTINGS));
-                } catch (ActivityNotFoundException e) {
-                    Log.e(LOG_TAG, "Cannot search settings", e);
-                }
-                break;
-        }
-
-        return super.onOptionsItemSelected(item);
+        Utils.prepareSearchMenuItem(menu, requireContext());
     }
 
     @Override
diff --git a/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java b/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java
index 85be02f..e4caec3 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java
@@ -31,6 +31,7 @@
 import static android.Manifest.permission_group.STORAGE;
 
 import android.Manifest;
+import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -42,12 +43,15 @@
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.Html;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.TypedValue;
+import android.view.Menu;
+import android.view.MenuItem;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -439,4 +443,28 @@
         long days = hours / 24;
         return context.getResources().getQuantityString(R.plurals.days, (int) days, days);
     }
+
+    /**
+     * Add a menu item for searching Settings, if there is an activity handling the action.
+     *
+     * @param menu the menu to add the menu item into
+     * @param context the context for checking whether there is an activity handling the action
+     */
+    public static void prepareSearchMenuItem(@NonNull Menu menu, @NonNull Context context) {
+        Intent intent = new Intent(Settings.ACTION_APP_SEARCH_SETTINGS);
+        if (context.getPackageManager().resolveActivity(intent, 0) == null) {
+            return;
+        }
+        MenuItem searchItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, R.string.search_menu);
+        searchItem.setIcon(R.drawable.ic_search_24dp);
+        searchItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        searchItem.setOnMenuItemClickListener(item -> {
+            try {
+                context.startActivity(intent);
+            } catch (ActivityNotFoundException e) {
+                Log.e(LOG_TAG, "Cannot start activity to search settings", e);
+            }
+            return true;
+        });
+    }
 }
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/AppIconPreference.java b/PermissionController/src/com/android/packageinstaller/role/ui/AppIconPreference.java
new file mode 100644
index 0000000..244f4c3
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/AppIconPreference.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+import androidx.annotation.StyleRes;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.permissioncontroller.R;
+
+/**
+ * {@link Preference} with its icon view set to a fixed size for app icons.
+ */
+public class AppIconPreference extends Preference {
+
+    private Mixin mMixin;
+
+    public AppIconPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        init();
+    }
+
+    public AppIconPreference(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        init();
+    }
+
+    public AppIconPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+
+        init();
+    }
+
+    public AppIconPreference(@NonNull Context context) {
+        super(context);
+
+        init();
+    }
+
+    private void init() {
+        mMixin = new Mixin(getContext());
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        mMixin.onBindViewHolder(holder);
+    }
+
+    /**
+     * Mixin for implementation of {@link AppIconPreference}.
+     */
+    public static class Mixin {
+
+        @Px
+        private int mIconSize;
+
+        public Mixin(@NonNull Context context) {
+            mIconSize = context.getResources().getDimensionPixelSize(
+                    R.dimen.secondary_app_icon_size);
+        }
+
+        /**
+         * Binds the view holder so that its icon view is set to a fixed size for app icons.
+         *
+         * @param holder the view holder passed in by {@link Preference#onBindViewHolder(
+         *               PreferenceViewHolder)}
+         *
+         * @see Preference#onBindViewHolder(PreferenceViewHolder)
+         */
+        public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+            View iconView = holder.findViewById(android.R.id.icon);
+            ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
+            boolean changed = false;
+            if (layoutParams.width != mIconSize) {
+                layoutParams.width = mIconSize;
+                changed = true;
+            }
+            if (layoutParams.height != mIconSize) {
+                layoutParams.height = mIconSize;
+                changed = true;
+            }
+            if (changed) {
+                iconView.requestLayout();
+            }
+        }
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/AsyncTaskLiveData.java b/PermissionController/src/com/android/packageinstaller/role/ui/AsyncTaskLiveData.java
new file mode 100644
index 0000000..970bfd3
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/AsyncTaskLiveData.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.os.AsyncTask;
+
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.LiveData;
+
+/**
+ * {@link LiveData} that uses {@link AsyncTask} to load value on a background thread.
+ *
+ * @param <T> type of the value
+ */
+public abstract class AsyncTaskLiveData<T> extends LiveData<T> {
+
+    /**
+     * Load the value on a background thread. The value will be reloaded even if already loaded.
+     */
+    public void loadValue() {
+        AsyncTask.execute(() -> postValue(loadValueInBackground()));
+    }
+
+    @WorkerThread
+    protected abstract T loadValueInBackground();
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/DefaultAppListActivity.java b/PermissionController/src/com/android/packageinstaller/role/ui/DefaultAppListActivity.java
new file mode 100644
index 0000000..66bee25
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/DefaultAppListActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * Activity for the list of default apps.
+ */
+public class DefaultAppListActivity extends FragmentActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            DefaultAppListFragment fragment = DefaultAppListFragment.newInstance();
+            getSupportFragmentManager().beginTransaction()
+                    .add(android.R.id.content, fragment)
+                    .commit();
+        }
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/DefaultAppListFragment.java b/PermissionController/src/com/android/packageinstaller/role/ui/DefaultAppListFragment.java
new file mode 100644
index 0000000..536252d
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/DefaultAppListFragment.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+import com.android.packageinstaller.permission.utils.IconDrawableFactory;
+import com.android.packageinstaller.permission.utils.Utils;
+import com.android.packageinstaller.role.model.Role;
+import com.android.permissioncontroller.R;
+
+import java.util.List;
+
+/**
+ * Fragment for the list of default apps.
+ */
+public class DefaultAppListFragment extends SettingsFragment
+        implements Preference.OnPreferenceClickListener {
+
+    private static final String LOG_TAG = DefaultAppListFragment.class.getSimpleName();
+
+    private RoleListViewModel mViewModel;
+
+    /**
+     * Create a new instance of this fragment.
+     *
+     * @return a new instance of this fragment
+     */
+    @NonNull
+    public static DefaultAppListFragment newInstance() {
+        return new DefaultAppListFragment();
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        mViewModel = ViewModelProviders.of(this, new RoleListViewModel.Factory(true,
+                requireActivity().getApplication())).get(RoleListViewModel.class);
+        mViewModel.getLiveData().observe(this, this::onRoleListChanged);
+    }
+
+    @Override
+    @StringRes
+    protected int getEmptyTextResource() {
+        return R.string.no_default_apps;
+    }
+
+    @Override
+    protected int getHelpUriResource() {
+        return R.string.help_uri_default_apps;
+    }
+
+    private void onRoleListChanged(@NonNull List<RoleItem> roleItems) {
+        PreferenceManager preferenceManager = getPreferenceManager();
+        Context context = preferenceManager.getContext();
+
+        PreferenceScreen preferenceScreen = getPreferenceScreen();
+        ArrayMap<String, Preference> oldPreferences = new ArrayMap<>();
+        if (preferenceScreen == null) {
+            preferenceScreen = preferenceManager.createPreferenceScreen(context);
+            setPreferenceScreen(preferenceScreen);
+        } else {
+            for (int i = preferenceScreen.getPreferenceCount() - 1; i >= 0; --i) {
+                Preference preference = preferenceScreen.getPreference(i);
+
+                preferenceScreen.removePreference(preference);
+                oldPreferences.put(preference.getKey(), preference);
+            }
+        }
+
+        int roleItemsSize = roleItems.size();
+        for (int roleItemsIndex = 0; roleItemsIndex < roleItemsSize; roleItemsIndex++) {
+            RoleItem roleItem = roleItems.get(roleItemsIndex);
+
+            List<ApplicationInfo> holderApplicationInfos = roleItem.getHolderApplicationInfos();
+            if (holderApplicationInfos.isEmpty()) {
+                // TODO: Handle Assistant which is visible even without holder.
+                continue;
+            }
+
+            Role role = roleItem.getRole();
+            Preference preference = oldPreferences.get(role.getName());
+            if (preference == null) {
+                preference = new AppIconPreference(context);
+                preference.setKey(role.getName());
+                preference.setIconSpaceReserved(true);
+                preference.setTitle(role.getLabelResource());
+                preference.setPersistent(false);
+                preference.setOnPreferenceClickListener(this);
+            }
+
+            ApplicationInfo holderApplicationInfo = holderApplicationInfos.get(0);
+            preference.setIcon(IconDrawableFactory.getBadgedIcon(context, holderApplicationInfo,
+                    UserHandle.getUserHandleForUid(holderApplicationInfo.uid)));
+            preference.setSummary(Utils.getAppLabel(holderApplicationInfo, context));
+
+            // TODO: Ordering?
+            preferenceScreen.addPreference(preference);
+        }
+
+        updateState();
+    }
+
+    @Override
+    public boolean onPreferenceClick(@NonNull Preference preference) {
+        // TODO
+        return true;
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/RequestRoleFragment.java b/PermissionController/src/com/android/packageinstaller/role/ui/RequestRoleFragment.java
index 955b01d..cac4bc5 100644
--- a/PermissionController/src/com/android/packageinstaller/role/ui/RequestRoleFragment.java
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/RequestRoleFragment.java
@@ -65,12 +65,12 @@
      */
     public static RequestRoleFragment newInstance(@NonNull String roleName,
             @NonNull String packageName) {
-        RequestRoleFragment instance = new RequestRoleFragment();
+        RequestRoleFragment fragment = new RequestRoleFragment();
         Bundle arguments = new Bundle();
         arguments.putString(RoleManager.EXTRA_REQUEST_ROLE_NAME, roleName);
         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
-        instance.setArguments(arguments);
-        return instance;
+        fragment.setArguments(arguments);
+        return fragment;
     }
 
     @Override
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/RoleItem.java b/PermissionController/src/com/android/packageinstaller/role/ui/RoleItem.java
new file mode 100644
index 0000000..01118e8
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/RoleItem.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.content.pm.ApplicationInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.packageinstaller.role.model.Role;
+
+import java.util.List;
+
+/**
+ * Information about a role to be displayed in a list of roles.
+ */
+public class RoleItem {
+
+    /**
+     * The {@link Role} for this role.
+     */
+    @NonNull
+    private final Role mRole;
+
+    /**
+     * The list of {@link ApplicationInfo} of applications holding this role.
+     */
+    @NonNull
+    private final List<ApplicationInfo> mHolderApplicationInfos;
+
+    public RoleItem(@NonNull Role role, @NonNull List<ApplicationInfo> holderApplicationInfos) {
+        mRole = role;
+        mHolderApplicationInfos = holderApplicationInfos;
+    }
+
+    @NonNull
+    public Role getRole() {
+        return mRole;
+    }
+
+    @NonNull
+    public List<ApplicationInfo> getHolderApplicationInfos() {
+        return mHolderApplicationInfos;
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/RoleListLiveData.java b/PermissionController/src/com/android/packageinstaller/role/ui/RoleListLiveData.java
new file mode 100644
index 0000000..af22753
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/RoleListLiveData.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.LiveData;
+
+import com.android.packageinstaller.role.model.Role;
+import com.android.packageinstaller.role.model.Roles;
+import com.android.packageinstaller.role.utils.PackageUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link LiveData} for a list of roles.
+ */
+public class RoleListLiveData extends AsyncTaskLiveData<List<RoleItem>> {
+
+    private static final String LOG_TAG = RoleListLiveData.class.getSimpleName();
+
+    private final boolean mExclusive;
+    private final Context mContext;
+
+    public RoleListLiveData(boolean exclusive, @NonNull Context context) {
+        mExclusive = exclusive;
+        mContext = context;
+
+        loadValue();
+    }
+
+    @Override
+    @WorkerThread
+    protected List<RoleItem> loadValueInBackground() {
+        ArrayMap<String, Role> roles = Roles.getRoles(mContext);
+
+        List<RoleItem> roleItems = new ArrayList<>();
+        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+        int rolesSize = roles.size();
+        for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
+            Role role = roles.valueAt(rolesIndex);
+
+            if (role.isExclusive() != mExclusive) {
+                continue;
+            }
+
+            List<ApplicationInfo> holderApplicationInfos = new ArrayList<>();
+            List<String> holderPackageNames = roleManager.getRoleHolders(role.getName());
+            int holderPackageNamesSize = holderPackageNames.size();
+            for (int holderPackageNamesIndex = 0; holderPackageNamesIndex < holderPackageNamesSize;
+                    holderPackageNamesIndex++) {
+                String holderPackageName = holderPackageNames.get(holderPackageNamesIndex);
+
+                ApplicationInfo holderApplicationInfo = PackageUtils.getApplicationInfo(
+                        holderPackageName, mContext);
+                if (holderApplicationInfo == null) {
+                    Log.w(LOG_TAG, "Cannot get ApplicationInfo for application, skipping: "
+                            + holderPackageName);
+                    continue;
+                }
+                holderApplicationInfos.add(holderApplicationInfo);
+            }
+
+            roleItems.add(new RoleItem(role, holderApplicationInfos));
+        }
+
+        return roleItems;
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/RoleListViewModel.java b/PermissionController/src/com/android/packageinstaller/role/ui/RoleListViewModel.java
new file mode 100644
index 0000000..de29928
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/RoleListViewModel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * {@link ViewModel} for a list of roles.
+ */
+public class RoleListViewModel extends AndroidViewModel {
+
+    @NonNull
+    private final RoleListLiveData mLiveData;
+
+    public RoleListViewModel(boolean exclusive, @NonNull Application application) {
+        super(application);
+
+        mLiveData = new RoleListLiveData(exclusive, application);
+    }
+
+    @NonNull
+    public RoleListLiveData getLiveData() {
+        return mLiveData;
+    }
+
+    /**
+     * {@link ViewModelProvider.Factory} for {@link RoleListViewModel}.
+     */
+    public static class Factory implements ViewModelProvider.Factory {
+
+        private boolean mExclusive;
+
+        @NonNull
+        private Application mApplication;
+
+        public Factory(boolean exclusive, @NonNull Application application) {
+            mExclusive = exclusive;
+            mApplication = application;
+        }
+
+        @NonNull
+        @Override
+        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+            //noinspection unchecked
+            return (T) new RoleListViewModel(mExclusive, mApplication);
+        }
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/ui/SettingsFragment.java b/PermissionController/src/com/android/packageinstaller/role/ui/SettingsFragment.java
new file mode 100644
index 0000000..c75bd04
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/ui/SettingsFragment.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.ui;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.packageinstaller.permission.utils.Utils;
+import com.android.packageinstaller.role.utils.UiUtils;
+import com.android.permissioncontroller.R;
+import com.android.settingslib.HelpUtils;
+
+/**
+ * Base class for settings fragments.
+ */
+public abstract class SettingsFragment extends PreferenceFragmentCompat {
+
+    private static final String LOG_TAG = SettingsFragment.class.getSimpleName();
+
+    private FrameLayout mContentLayout;
+    private LinearLayout mPreferenceLayout;
+    private View mLoadingView;
+    private TextView mEmptyText;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        mContentLayout = (FrameLayout) inflater.inflate(R.layout.settings, container, false);
+        mPreferenceLayout = (LinearLayout) super.onCreateView(inflater, container,
+                savedInstanceState);
+        mContentLayout.addView(mPreferenceLayout);
+        return mContentLayout;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        mLoadingView = mContentLayout.findViewById(R.id.loading);
+        mEmptyText = mContentLayout.findViewById(R.id.empty);
+    }
+
+    @Override
+    public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+        // We'll manually add preferences later.
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        requireActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
+
+        mEmptyText.setText(getEmptyTextResource());
+
+        updateState();
+    }
+
+    @StringRes
+    protected abstract int getEmptyTextResource();
+
+    @Override
+    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+
+        Utils.prepareSearchMenuItem(menu, requireContext());
+        int helpUriResource = getHelpUriResource();
+        if (helpUriResource != 0) {
+            HelpUtils.prepareHelpMenuItem(requireActivity(), menu, helpUriResource,
+                    getClass().getName());
+        }
+    }
+
+    @StringRes
+    protected int getHelpUriResource() {
+        return 0;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                requireActivity().finish();
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    protected void updateState() {
+        PreferenceScreen preferenceScreen = getPreferenceScreen();
+        boolean isLoading = preferenceScreen == null;
+        UiUtils.setViewShown(mLoadingView, isLoading);
+        boolean isEmpty = preferenceScreen != null && preferenceScreen.getPreferenceCount() == 0;
+        UiUtils.setViewShown(mEmptyText, isEmpty);
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/role/utils/UiUtils.java b/PermissionController/src/com/android/packageinstaller/role/utils/UiUtils.java
new file mode 100644
index 0000000..ded3e7f
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/role/utils/UiUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 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.packageinstaller.role.utils;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Utility methods about UI.
+ */
+public class UiUtils {
+
+    private UiUtils() {}
+
+    /**
+     * Set whether a view is shown.
+     *
+     * @param view the view to be set to shown or not
+     * @param shown whether the view should be shown
+     */
+    public static void setViewShown(@NonNull View view, boolean shown) {
+        if (shown && view.getVisibility() == View.VISIBLE && view.getAlpha() == 1) {
+            // This cancels any on-going animation.
+            view.animate()
+                    .alpha(1)
+                    .setDuration(0);
+            return;
+        } else if (!shown && (view.getVisibility() != View.VISIBLE || view.getAlpha() == 0)) {
+            // This cancels any on-going animation.
+            view.animate()
+                    .alpha(0)
+                    .setDuration(0);
+            view.setVisibility(View.INVISIBLE);
+            return;
+        }
+        if (shown && view.getVisibility() != View.VISIBLE) {
+            view.setAlpha(0);
+            view.setVisibility(View.VISIBLE);
+        }
+        int duration = view.getResources().getInteger(android.R.integer.config_mediumAnimTime);
+        Interpolator interpolator = AnimationUtils.loadInterpolator(view.getContext(), shown
+                ? android.R.interpolator.fast_out_slow_in
+                : android.R.interpolator.fast_out_linear_in);
+        view.animate()
+                .alpha(shown ? 1 : 0)
+                .setDuration(duration)
+                .setInterpolator(interpolator)
+                // Always update the listener or the view will try to reuse the previous one.
+                .setListener(shown ? null : new AnimatorListenerAdapter() {
+                    private boolean mCanceled = false;
+                    @Override
+                    public void onAnimationCancel(@NonNull Animator animator) {
+                        mCanceled = true;
+                    }
+                    @Override
+                    public void onAnimationEnd(@NonNull Animator animator) {
+                        if (!mCanceled) {
+                            view.setVisibility(View.INVISIBLE);
+                        }
+                    }
+                });
+    }
+}