Implement Web Action settings

Users can enable / disable Web Action from "Opening links" under
the apps setting.

Bug: 28140107
Change-Id: I4ed4b77953952a56316b780a54a482d0c564cbe4
Test: manual testing of the settings app
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cc3ee44..b2ab245 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7763,5 +7763,16 @@
 
     <string name="automatic_storage_manager_freed_bytes"><xliff:g id="size" example="3.25MB">%1$s</xliff:g> total made available\n\nLast ran on <xliff:g id="date" example="Jan 12">%2$s</xliff:g></string>
 
+    <!-- Title text for enabling web actions. [CHAR_LIMIT=60] -->
+    <string name="web_action_enable_title">Open links in apps</string>
 
+    <!-- Summary text for enabling web actions. [CHAR_LIMIT=250] -->
+    <string name="web_action_enable_summary">Open links in supported apps, even if the
+      apps aren’t installed on your device</string>
+
+    <!-- Section title for the Web Action preference [CHAR LIMIT=60] -->
+    <string name="web_action_section_title">Apps not installed</string>
+
+    <!-- Section title for the Domain URL app preference list [CHAR LIMIT=60]-->
+    <string name="domain_url_section_title">Installed apps</string>
 </resources>
diff --git a/res/xml/advanced_apps.xml b/res/xml/advanced_apps.xml
index 2673569..405d5af 100644
--- a/res/xml/advanced_apps.xml
+++ b/res/xml/advanced_apps.xml
@@ -30,10 +30,7 @@
         <PreferenceScreen
             android:key="domain_urls"
             android:title="@string/domain_urls_title"
-            android:fragment="com.android.settings.applications.ManageApplications">
-            <extra
-                android:name="classname"
-                android:value="com.android.settings.Settings$DomainsURLsAppListActivity" />
+            android:fragment="com.android.settings.applications.ManageDomainUrls">
         </PreferenceScreen>
 
         <Preference
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index feeabe7..68c4b86 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -119,7 +119,7 @@
     public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
     public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
     public static class OtherSoundSettingsActivity extends SettingsActivity { /* empty */ }
-    public static class DomainsURLsAppListActivity extends SettingsActivity { /* empty */ }
+    public static class ManageDomainUrls extends SettingsActivity { /* empty */ }
 
     public static class TopLevelSettings extends SettingsActivity { /* empty */ }
     public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index dae5d7a..a270ea3 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -57,7 +57,6 @@
 import com.android.settings.InstrumentedFragment;
 import com.android.settings.R;
 import com.android.settings.Settings.AllApplicationsActivity;
-import com.android.settings.Settings.DomainsURLsAppListActivity;
 import com.android.settings.Settings.HighPowerApplicationsActivity;
 import com.android.settings.Settings.NotificationAppListActivity;
 import com.android.settings.Settings.OverlaySettingsActivity;
@@ -136,7 +135,6 @@
     public static final int FILTER_APPS_PRIORITY = 9;
     public static final int FILTER_APPS_PERSONAL = 10;
     public static final int FILTER_APPS_WORK = 11;
-    public static final int FILTER_APPS_WITH_DOMAIN_URLS = 12;
     public static final int FILTER_APPS_USAGE_ACCESS = 13;
     public static final int FILTER_APPS_WITH_OVERLAY = 14;
     public static final int FILTER_APPS_WRITE_SETTINGS = 15;
@@ -217,7 +215,6 @@
 
     public static final int LIST_TYPE_MAIN = 0;
     public static final int LIST_TYPE_NOTIFICATION = 1;
-    public static final int LIST_TYPE_DOMAINS_URLS = 2;
     public static final int LIST_TYPE_STORAGE = 3;
     public static final int LIST_TYPE_USAGE_ACCESS = 4;
     public static final int LIST_TYPE_HIGH_POWER = 5;
@@ -251,8 +248,6 @@
         } else if (className.equals(NotificationAppListActivity.class.getName())) {
             mListType = LIST_TYPE_NOTIFICATION;
             mNotifBackend = new NotificationBackend();
-        } else if (className.equals(DomainsURLsAppListActivity.class.getName())) {
-            mListType = LIST_TYPE_DOMAINS_URLS;
         } else if (className.equals(StorageUseActivity.class.getName())) {
             if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
                 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
@@ -382,8 +377,6 @@
 
     private int getDefaultFilter() {
         switch (mListType) {
-            case LIST_TYPE_DOMAINS_URLS:
-                return FILTER_APPS_WITH_DOMAIN_URLS;
             case LIST_TYPE_USAGE_ACCESS:
                 return FILTER_APPS_USAGE_ACCESS;
             case LIST_TYPE_HIGH_POWER:
@@ -415,8 +408,6 @@
                 return MetricsEvent.MANAGE_APPLICATIONS;
             case LIST_TYPE_NOTIFICATION:
                 return MetricsEvent.MANAGE_APPLICATIONS_NOTIFICATIONS;
-            case LIST_TYPE_DOMAINS_URLS:
-                return MetricsEvent.MANAGE_DOMAIN_URLS;
             case LIST_TYPE_STORAGE:
                 return MetricsEvent.APPLICATIONS_STORAGE_APPS;
             case LIST_TYPE_USAGE_ACCESS:
@@ -502,9 +493,6 @@
                 startAppInfoFragment(AppNotificationSettings.class,
                         R.string.app_notifications_title);
                 break;
-            case LIST_TYPE_DOMAINS_URLS:
-                startAppInfoFragment(AppLaunchSettings.class, R.string.auto_launch_label);
-                break;
             case LIST_TYPE_USAGE_ACCESS:
                 startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
                 break;
@@ -537,9 +525,6 @@
 
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        if (mListType == LIST_TYPE_DOMAINS_URLS) {
-            return;
-        }
         HelpUtils.prepareHelpMenuItem(getActivity(), menu, mListType == LIST_TYPE_MAIN
                 ? R.string.help_uri_apps : R.string.help_uri_notifications, getClass().getName());
         mOptionsMenu = menu;
@@ -760,7 +745,7 @@
         private boolean mHasReceivedLoadEntries;
         private boolean mHasReceivedBridgeCallback;
 
-        private AlphabeticIndex.ImmutableIndex mIndex;
+        private AlphabeticIndex.ImmutableIndex<Locale> mIndex;
         private SectionInfo[] mSections = EMPTY_SECTIONS;
         private int[] mPositionToSectionIndex;
 
@@ -984,7 +969,7 @@
                     if (locales.size() == 0) {
                         locales = new LocaleList(Locale.ENGLISH);
                     }
-                    AlphabeticIndex index = new AlphabeticIndex<>(locales.get(0));
+                    AlphabeticIndex<Locale> index = new AlphabeticIndex<>(locales.get(0));
                     int localeCount = locales.size();
                     for (int i = 1; i < localeCount; i++) {
                         index.addLabels(locales.get(i));
@@ -1181,10 +1166,6 @@
                     }
                     break;
 
-                case LIST_TYPE_DOMAINS_URLS:
-                    holder.summary.setText(getDomainsSummary(holder.entry.info.packageName));
-                    break;
-
                 case LIST_TYPE_USAGE_ACCESS:
                     if (holder.entry.extraInfo != null) {
                         holder.summary.setText((new UsageState((PermissionState) holder.entry
@@ -1224,25 +1205,6 @@
             mActive.remove(view);
         }
 
-        private CharSequence getDomainsSummary(String packageName) {
-            // If the user has explicitly said "no" for this package, that's the
-            // string we should show.
-            int domainStatus = mPm.getIntentVerificationStatusAsUser(packageName, UserHandle.myUserId());
-            if (domainStatus == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-                return mContext.getString(R.string.domain_urls_summary_none);
-            }
-            // Otherwise, ask package manager for the domains for this package,
-            // and show the first one (or none if there aren't any).
-            ArraySet<String> result = Utils.getHandledDomains(mPm, packageName);
-            if (result.size() == 0) {
-                return mContext.getString(R.string.domain_urls_summary_none);
-            } else if (result.size() == 1) {
-                return mContext.getString(R.string.domain_urls_summary_one, result.valueAt(0));
-            } else {
-                return mContext.getString(R.string.domain_urls_summary_some, result.valueAt(0));
-            }
-        }
-
         @Override
         public Object[] getSections() {
             return mSections;
diff --git a/src/com/android/settings/applications/ManageDomainUrls.java b/src/com/android/settings/applications/ManageDomainUrls.java
new file mode 100644
index 0000000..9b3f09e
--- /dev/null
+++ b/src/com/android/settings/applications/ManageDomainUrls.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2016 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 android.app.Application;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.ArraySet;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+import com.android.settings.AppHeader;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.applications.AppStateBaseBridge;
+import com.android.settings.applications.InstalledAppDetails;
+import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import java.util.ArrayList;
+
+/**
+ * Activity to manage how Android handles URL resolution. Includes both per-app
+ * handling as well as system handling for Web Actions.
+ */
+public class ManageDomainUrls extends SettingsPreferenceFragment
+        implements ApplicationsState.Callbacks, OnPreferenceChangeListener {
+
+    // STOPSHIP; b/30256615
+    private static final boolean DISABLE_WEB_ACTIONS = !Build.IS_DEBUGGABLE;
+
+    private ApplicationsState mApplicationsState;
+    private ApplicationsState.Session mSession;
+    private PreferenceGroup mDomainAppList;
+    private SwitchPreference mWebAction;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setAnimationAllowed(true);
+        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
+        mApplicationsState = ApplicationsState.getInstance(
+                (Application) getContext().getApplicationContext());
+        mSession = mApplicationsState.newSession(this);
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        setLoading(true, false);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mSession.resume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mSession.pause();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mSession.release();
+    }
+
+    @Override
+    public void onRunningStateChanged(boolean running) {
+    }
+
+    @Override
+    public void onPackageListChanged() {
+    }
+
+    @Override
+    public void onRebuildComplete(ArrayList<AppEntry> apps) {
+        if (getContext() == null) {
+            return;
+        }
+
+        if (DISABLE_WEB_ACTIONS) {
+            mDomainAppList = getPreferenceScreen();
+        } else {
+            final PreferenceGroup preferenceScreen = getPreferenceScreen();
+            if (preferenceScreen.getPreferenceCount() == 0) {
+                // add preferences
+                final PreferenceCategory webActionCategory =
+                        new PreferenceCategory(getPrefContext());
+                webActionCategory.setTitle(R.string.web_action_section_title);
+                preferenceScreen.addPreference(webActionCategory);
+
+                // toggle to enable / disable Web Actions [aka Instant Apps]
+                mWebAction = new SwitchPreference(getPrefContext());
+                mWebAction.setTitle(R.string.web_action_enable_title);
+                mWebAction.setSummary(R.string.web_action_enable_summary);
+                mWebAction.setChecked(Settings.Secure.getInt(getContentResolver(),
+                        Settings.Secure.WEB_ACTION_ENABLED, 1) != 0);
+                mWebAction.setOnPreferenceChangeListener(this);
+                webActionCategory.addPreference(mWebAction);
+
+                // list to manage link handling per app
+                mDomainAppList = new PreferenceCategory(getPrefContext());
+                mDomainAppList.setTitle(R.string.domain_url_section_title);
+                preferenceScreen.addPreference(mDomainAppList);
+            }
+        }
+        rebuildAppList(mDomainAppList, apps);
+        setLoading(false, true);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (preference == mWebAction) {
+            final int enabled = (boolean) newValue ? 1 : 0;
+            Settings.Secure.putInt(
+                    getContentResolver(), Settings.Secure.WEB_ACTION_ENABLED, enabled);
+            return true;
+        }
+        return false;
+    }
+
+    private void rebuild() {
+        final ArrayList<AppEntry> apps = mSession.rebuild(
+                ApplicationsState.FILTER_WITH_DOMAIN_URLS, ApplicationsState.ALPHA_COMPARATOR);
+        if (apps != null) {
+            onRebuildComplete(apps);
+        }
+    }
+
+    private void rebuildAppList(PreferenceGroup group, ArrayList<AppEntry> apps) {
+        cacheRemoveAllPrefs(group);
+        final int N = apps.size();
+        for (int i = 0; i < N; i++) {
+            AppEntry entry = apps.get(i);
+            String key = entry.info.packageName + "|" + entry.info.uid;
+            DomainAppPreference preference = (DomainAppPreference) getCachedPreference(key);
+            if (preference == null) {
+                preference = new DomainAppPreference(getPrefContext(), entry);
+                preference.setKey(key);
+                group.addPreference(preference);
+            } else {
+                preference.reuse();
+            }
+            preference.setOrder(i);
+        }
+        removeCachedPrefs(group);
+    }
+
+    @Override
+    public void onPackageIconChanged() {
+    }
+
+    @Override
+    public void onPackageSizeChanged(String packageName) {
+    }
+
+    @Override
+    public void onAllSizesComputed() {
+    }
+
+    @Override
+    public void onLauncherInfoChanged() {
+    }
+
+    @Override
+    public void onLoadEntriesCompleted() {
+        rebuild();
+    }
+
+    @Override
+    protected int getMetricsCategory() {
+        return MetricsEvent.MANAGE_DOMAIN_URLS;
+    }
+
+    private class DomainAppPreference extends Preference {
+        private final AppEntry mEntry;
+        private final PackageManager mPm;
+
+        public DomainAppPreference(final Context context, AppEntry entry) {
+            super(context);
+            mPm = context.getPackageManager();
+            mEntry = entry;
+            mEntry.ensureLabel(getContext());
+            setState();
+            if (mEntry.icon != null) {
+                setIcon(mEntry.icon);
+            }
+        }
+
+        private void setState() {
+            setTitle(mEntry.label);
+            setSummary(getDomainsSummary(mEntry.info.packageName));
+        }
+
+        public void reuse() {
+            setState();
+            notifyChanged();
+        }
+
+        @Override
+        public void onBindViewHolder(PreferenceViewHolder holder) {
+            if (mEntry.icon == null) {
+                holder.itemView.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Ensure we have an icon before binding.
+                        mApplicationsState.ensureIcon(mEntry);
+                        // This might trigger us to bind again, but it gives an easy way to only
+                        // load the icon once its needed, so its probably worth it.
+                        setIcon(mEntry.icon);
+                    }
+                });
+            }
+            super.onBindViewHolder(holder);
+        }
+
+        private CharSequence getDomainsSummary(String packageName) {
+            // If the user has explicitly said "no" for this package, that's the
+            // string we should show.
+            int domainStatus =
+                    mPm.getIntentVerificationStatusAsUser(packageName, UserHandle.myUserId());
+            if (domainStatus == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+                return getContext().getString(R.string.domain_urls_summary_none);
+            }
+            // Otherwise, ask package manager for the domains for this package,
+            // and show the first one (or none if there aren't any).
+            ArraySet<String> result = Utils.getHandledDomains(mPm, packageName);
+            if (result.size() == 0) {
+                return getContext().getString(R.string.domain_urls_summary_none);
+            } else if (result.size() == 1) {
+                return getContext().getString(R.string.domain_urls_summary_one, result.valueAt(0));
+            } else {
+                return getContext().getString(R.string.domain_urls_summary_some, result.valueAt(0));
+            }
+        }
+    }
+}