diff --git a/res/layout/app_launch_progress.xml b/res/layout/app_launch_progress.xml
new file mode 100644
index 00000000000..28fe4060788
--- /dev/null
+++ b/res/layout/app_launch_progress.xml
@@ -0,0 +1,25 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/app_launch_verified_links_title.xml b/res/layout/app_launch_verified_links_title.xml
new file mode 100644
index 00000000000..a230edc1d5b
--- /dev/null
+++ b/res/layout/app_launch_verified_links_title.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/preference_widget_info.xml b/res/layout/preference_widget_info.xml
new file mode 100644
index 00000000000..39c2aca561a
--- /dev/null
+++ b/res/layout/preference_widget_info.xml
@@ -0,0 +1,28 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/supported_links_dialog_item.xml b/res/layout/supported_links_dialog_item.xml
new file mode 100644
index 00000000000..bbd2857dafd
--- /dev/null
+++ b/res/layout/supported_links_dialog_item.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/res/layout/verified_links_widget.xml b/res/layout/verified_links_widget.xml
new file mode 100644
index 00000000000..322ddd58f36
--- /dev/null
+++ b/res/layout/verified_links_widget.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/xml/installed_app_launch_settings.xml b/res/xml/installed_app_launch_settings.xml
index b77794984de..d631e56eaa0 100644
--- a/res/xml/installed_app_launch_settings.xml
+++ b/res/xml/installed_app_launch_settings.xml
@@ -16,31 +16,59 @@
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
diff --git a/src/com/android/settings/applications/AppLaunchSettings.java b/src/com/android/settings/applications/AppLaunchSettings.java
deleted file mode 100644
index 86bc7ed9c5a..00000000000
--- a/src/com/android/settings/applications/AppLaunchSettings.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.applications;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-
-import android.app.settings.SettingsEnums;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-import com.android.settings.Utils;
-import com.android.settings.core.SubSettingLauncher;
-import com.android.settingslib.applications.AppUtils;
-
-public class AppLaunchSettings extends AppInfoWithHeader implements OnClickListener,
- Preference.OnPreferenceChangeListener {
- private static final String TAG = "AppLaunchSettings";
- private static final String KEY_APP_LINK_STATE = "app_link_state";
- private static final String KEY_SUPPORTED_DOMAIN_URLS = "app_launch_supported_domain_urls";
- private static final String KEY_CLEAR_DEFAULTS = "app_launch_clear_defaults";
- private static final String FRAGMENT_OPEN_SUPPORTED_LINKS =
- "com.android.settings.applications.OpenSupportedLinks";
-
- private PackageManager mPm;
-
- private boolean mIsBrowser;
- private boolean mHasDomainUrls;
- private Preference mAppLinkState;
- private AppDomainsPreference mAppDomainUrls;
- private ClearDefaultsPreference mClearDefaultsPreference;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.installed_app_launch_settings);
- mAppDomainUrls = (AppDomainsPreference) findPreference(KEY_SUPPORTED_DOMAIN_URLS);
- mClearDefaultsPreference = (ClearDefaultsPreference) findPreference(KEY_CLEAR_DEFAULTS);
- mAppLinkState = findPreference(KEY_APP_LINK_STATE);
- mAppLinkState.setOnPreferenceClickListener(preference -> {
- final Bundle args = new Bundle();
- args.putString(ARG_PACKAGE_NAME, mPackageName);
- args.putInt(ARG_PACKAGE_UID, mUserId);
-
- new SubSettingLauncher(this.getContext())
- .setDestination(FRAGMENT_OPEN_SUPPORTED_LINKS)
- .setArguments(args)
- .setSourceMetricsCategory(SettingsEnums.APPLICATIONS_APP_LAUNCH)
- .setTitleRes(-1)
- .launch();
- return true;
- });
-
- mPm = getActivity().getPackageManager();
-
- mIsBrowser = AppUtils.isBrowserApp(this.getContext(), mPackageName, UserHandle.myUserId());
- mHasDomainUrls =
- (mAppEntry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
-
- if (!mIsBrowser) {
- CharSequence[] entries = getEntries(mPackageName);
- mAppDomainUrls.setTitles(entries);
- mAppDomainUrls.setValues(new int[entries.length]);
- mAppLinkState.setEnabled(mHasDomainUrls);
- } else {
- // Browsers don't show the app-link prefs
- mAppLinkState.setShouldDisableView(true);
- mAppLinkState.setEnabled(false);
- mAppDomainUrls.setShouldDisableView(true);
- mAppDomainUrls.setEnabled(false);
- }
- }
-
- private int linkStateToResourceId(int state) {
- switch (state) {
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
- return R.string.app_link_open_always; // Always
- case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
- return R.string.app_link_open_never; // Never
- default:
- return R.string.app_link_open_ask; // Ask
- }
- }
-
- private CharSequence[] getEntries(String packageName) {
- ArraySet result = Utils.getHandledDomains(mPm, packageName);
- return result.toArray(new CharSequence[result.size()]);
- }
-
- private void setAppLinkStateSummary() {
- final int state = mPm.getIntentVerificationStatusAsUser(mPackageName,
- UserHandle.myUserId());
- mAppLinkState.setSummary(linkStateToResourceId(state));
- }
-
- @Override
- protected boolean refreshUi() {
- if (mHasDomainUrls) {
- //Update the summary after return from the OpenSupportedLinks
- setAppLinkStateSummary();
- }
- mClearDefaultsPreference.setPackageName(mPackageName);
- mClearDefaultsPreference.setAppEntry(mAppEntry);
- return true;
- }
-
- @Override
- protected AlertDialog createDialog(int id, int errorCode) {
- // No dialogs for preferred launch settings.
- return null;
- }
-
- @Override
- public void onClick(View v) {
- // Nothing to do
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- // actual updates are handled by the app link dropdown callback
- return true;
- }
-
- @Override
- public int getMetricsCategory() {
- return SettingsEnums.APPLICATIONS_APP_LAUNCH;
- }
-}
diff --git a/src/com/android/settings/applications/ClearDefaultsPreference.java b/src/com/android/settings/applications/ClearDefaultsPreference.java
index f9466364ff2..0952b9c8ca7 100644
--- a/src/com/android/settings/applications/ClearDefaultsPreference.java
+++ b/src/com/android/settings/applications/ClearDefaultsPreference.java
@@ -117,7 +117,7 @@ public class ClearDefaultsPreference extends Preference {
if (mUsbManager != null) {
final int userId = UserHandle.myUserId();
mPm.clearPackagePreferredActivities(mPackageName);
- if (isDefaultBrowser(mPackageName)) {
+ if (AppUtils.isDefaultBrowser(getContext(), mPackageName)) {
mPm.setDefaultBrowserPackageNameAsUser(null, userId);
}
try {
@@ -141,7 +141,7 @@ public class ClearDefaultsPreference extends Preference {
TextView autoLaunchView = (TextView) view.findViewById(R.id.auto_launch);
boolean autoLaunchEnabled = AppUtils.hasPreferredActivities(mPm, mPackageName)
- || isDefaultBrowser(mPackageName)
+ || AppUtils.isDefaultBrowser(getContext(), mPackageName)
|| AppUtils.hasUsbDefaults(mUsbManager, mPackageName);
if (!autoLaunchEnabled && !hasBindAppWidgetPermission) {
resetLaunchDefaultsUi(autoLaunchView);
@@ -185,11 +185,6 @@ public class ClearDefaultsPreference extends Preference {
return true;
}
- private boolean isDefaultBrowser(String packageName) {
- final String defaultBrowser = mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId());
- return packageName.equals(defaultBrowser);
- }
-
private void resetLaunchDefaultsUi(TextView autoLaunchView) {
autoLaunchView.setText(R.string.auto_launch_disable_text);
// Disable clear activities button
diff --git a/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java b/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java
index cd30d792efd..799389e2672 100644
--- a/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java
+++ b/src/com/android/settings/applications/InstalledAppOpenByDefaultActivity.java
@@ -19,6 +19,7 @@ package com.android.settings.applications;
import android.content.Intent;
import com.android.settings.SettingsActivity;
+import com.android.settings.applications.intentpicker.AppLaunchSettings;
public class InstalledAppOpenByDefaultActivity extends SettingsActivity {
diff --git a/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
index f9c88fc6956..d8c190fb730 100644
--- a/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
@@ -28,7 +28,7 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppLaunchSettings;
+import com.android.settings.applications.intentpicker.AppLaunchSettings;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
diff --git a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
new file mode 100644
index 00000000000..7d6abe7ca0f
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications.intentpicker;
+
+import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE;
+import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED;
+import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationUserState;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.AppInfoBase;
+import com.android.settings.applications.ClearDefaultsPreference;
+import com.android.settings.utils.AnnotationSpan;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.MainSwitchPreference;
+import com.android.settingslib.widget.OnMainSwitchChangeListener;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/** The page of the Open by default */
+public class AppLaunchSettings extends AppInfoBase implements
+ Preference.OnPreferenceChangeListener, OnMainSwitchChangeListener {
+ private static final String TAG = "AppLaunchSettings";
+ // Preference keys
+ private static final String MAIN_SWITCH_PREF_KEY = "open_by_default_supported_links";
+ private static final String VERIFIED_LINKS_PREF_KEY = "open_by_default_verified_links";
+ private static final String ADD_LINK_PREF_KEY = "open_by_default_add_link";
+ private static final String CLEAR_DEFAULTS_PREF_KEY = "app_launch_clear_defaults";
+ private static final String FOOTER_PREF_KEY = "open_by_default_footer";
+
+ private static final String MAIN_PREF_CATEGORY_KEY = "open_by_default_main_category";
+ private static final String SELECTED_LINKS_CATEGORY_KEY =
+ "open_by_default_selected_links_category";
+ private static final String OTHER_DETAILS_PREF_CATEGORY_KEY = "app_launch_other_defaults";
+
+ // Url and Uri
+ private static final String ANNOTATION_URL = "url";
+ private static final String LEARN_MORE_URI =
+ "https://developer.android.com/training/app-links/verify-site-associations";
+
+ // Dialogs id
+ private static final int DLG_VERIFIED_LINKS = DLG_BASE + 1;
+
+ // Arguments key
+ public static final String APP_PACKAGE_KEY = "app_package";
+
+ private ClearDefaultsPreference mClearDefaultsPreference;
+ private MainSwitchPreference mMainSwitchPreference;
+ private PreferenceCategory mMainPreferenceCategory;
+ private PreferenceCategory mSelectedLinksPreferenceCategory;
+ private PreferenceCategory mOtherDefaultsPreferenceCategory;
+
+ private boolean mActivityCreated;
+
+ @VisibleForTesting
+ Context mContext;
+ @VisibleForTesting
+ DomainVerificationManager mDomainVerificationManager;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mContext = context;
+ mActivityCreated = false;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.installed_app_launch_settings);
+ mDomainVerificationManager = mContext.getSystemService(DomainVerificationManager.class);
+ initUIComponents();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ createHeaderPreference();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.APPLICATIONS_APP_LAUNCH;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ if (id == DLG_VERIFIED_LINKS) {
+ return createVerifiedLinksDialog();
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ mClearDefaultsPreference.setPackageName(mPackageName);
+ mClearDefaultsPreference.setAppEntry(mAppEntry);
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean isChecked = (boolean) newValue;
+ IntentPickerUtils.logd(
+ "onPreferenceChange: " + preference.getTitle() + " isChecked: " + isChecked);
+ if ((preference instanceof LeftSideCheckBoxPreference) && !isChecked) {
+ final Set domainSet = new ArraySet<>();
+ domainSet.add(preference.getTitle().toString());
+ removePreference(preference.getKey());
+ final DomainVerificationUserState userState =
+ IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
+ mPackageName);
+ if (userState == null) {
+ return false;
+ }
+ setDomainVerificationUserSelection(userState.getIdentifier(), domainSet, /* enabled= */
+ false);
+ }
+ return true;
+ }
+
+ @Override
+ public void onSwitchChanged(Switch switchView, boolean isChecked) {
+ IntentPickerUtils.logd("onSwitchChanged: isChecked=" + isChecked);
+ if (mMainSwitchPreference != null) { //mMainSwitchPreference synced with Switch
+ mMainSwitchPreference.setChecked(isChecked);
+ }
+ if (mMainPreferenceCategory != null) {
+ mMainPreferenceCategory.setVisible(isChecked);
+ }
+ if (mDomainVerificationManager != null) {
+ try {
+ mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(mPackageName,
+ isChecked);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "onSwitchChanged: " + e.getMessage());
+ }
+ }
+ }
+
+ private void createHeaderPreference() {
+ if (mActivityCreated) {
+ Log.w(TAG, "onParentActivityCreated: ignoring duplicate call.");
+ return;
+ }
+ mActivityCreated = true;
+ if (mPackageInfo == null) {
+ Log.w(TAG, "onParentActivityCreated: PakcageInfo is null.");
+ return;
+ }
+ final Activity activity = getActivity();
+ final Preference pref = EntityHeaderController
+ .newInstance(activity, this, null /* header */)
+ .setRecyclerView(getListView(), getSettingsLifecycle())
+ .setIcon(Utils.getBadgedIcon(mContext, mPackageInfo.applicationInfo))
+ .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
+ .setSummary("" /* summary */) // no version number
+ .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo))
+ .setPackageName(mPackageName)
+ .setUid(mPackageInfo.applicationInfo.uid)
+ .setHasAppInfoLink(true)
+ .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
+ EntityHeaderController.ActionType.ACTION_NONE)
+ .done(activity, getPrefContext());
+ getPreferenceScreen().addPreference(pref);
+ }
+
+ private void initUIComponents() {
+ initMainSwitchAndCategories();
+ if (canUpdateMainSwitchAndCategories()) {
+ initVerifiedLinksPreference();
+ addSelectedLinksPreference();
+ initAddLinkPreference();
+ initFooter();
+ }
+ }
+
+ private void initMainSwitchAndCategories() {
+ mMainSwitchPreference = (MainSwitchPreference) findPreference(MAIN_SWITCH_PREF_KEY);
+ mMainPreferenceCategory = findPreference(MAIN_PREF_CATEGORY_KEY);
+ mSelectedLinksPreferenceCategory = findPreference(SELECTED_LINKS_CATEGORY_KEY);
+ // Initialize the "Other Default Category" section
+ initOtherDefaultsSection();
+ }
+
+ private boolean canUpdateMainSwitchAndCategories() {
+ final DomainVerificationUserState userState =
+ IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
+ mPackageName);
+ if (userState == null) {
+ disabledPreference();
+ return false;
+ }
+
+ IntentPickerUtils.logd("isLinkHandlingAllowed() : " + userState.isLinkHandlingAllowed());
+ mMainSwitchPreference.updateStatus(userState.isLinkHandlingAllowed());
+ mMainSwitchPreference.addOnSwitchChangeListener(this);
+ mMainPreferenceCategory.setVisible(userState.isLinkHandlingAllowed());
+ return true;
+ }
+
+ /** Initialize verified links preference */
+ private void initVerifiedLinksPreference() {
+ final VerifiedLinksPreference verifiedLinksPreference =
+ (VerifiedLinksPreference) mMainPreferenceCategory.findPreference(
+ VERIFIED_LINKS_PREF_KEY);
+ verifiedLinksPreference.setWidgetFrameClickListener(l -> {
+ showVerifiedLinksDialog();
+ });
+ final int verifiedLinksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
+ verifiedLinksPreference.setTitle(getVerifiedLinksTitle(verifiedLinksNo));
+ verifiedLinksPreference.setCheckBoxVisible(verifiedLinksNo > 0);
+ verifiedLinksPreference.setEnabled(verifiedLinksNo > 0);
+ }
+
+ private void showVerifiedLinksDialog() {
+ final int linksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
+ if (linksNo == 0) {
+ return;
+ }
+ showDialogInner(DLG_VERIFIED_LINKS, /* moveErrorCode= */ 0);
+ }
+
+ private AlertDialog createVerifiedLinksDialog() {
+ final int linksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
+
+ final View titleView = LayoutInflater.from(mContext).inflate(
+ R.layout.app_launch_verified_links_title, /* root= */ null);
+ ((TextView) titleView.findViewById(R.id.dialog_title)).setText(
+ getVerifiedLinksTitle(linksNo));
+ ((TextView) titleView.findViewById(R.id.dialog_message)).setText(
+ getVerifiedLinksMessage(linksNo));
+
+ final List verifiedLinksList = IntentPickerUtils.getLinksList(
+ mDomainVerificationManager, mPackageName, DOMAIN_STATE_VERIFIED);
+ return new AlertDialog.Builder(mContext)
+ .setCustomTitle(titleView)
+ .setCancelable(true)
+ .setItems(verifiedLinksList.toArray(new String[0]), /* listener= */ null)
+ .setPositiveButton(R.string.app_launch_dialog_ok, /* listener= */ null)
+ .create();
+ }
+
+ @VisibleForTesting
+ String getVerifiedLinksTitle(int linksNo) {
+ return getResources().getQuantityString(
+ R.plurals.app_launch_verified_links_title, linksNo, linksNo);
+ }
+
+ private String getVerifiedLinksMessage(int linksNo) {
+ return getResources().getQuantityString(
+ R.plurals.app_launch_verified_links_message, linksNo, linksNo);
+ }
+
+ /** Add selected links items */
+ public void addSelectedLinksPreference() {
+ if (getLinksNumber(DOMAIN_STATE_SELECTED) == 0) {
+ return;
+ }
+ mSelectedLinksPreferenceCategory.removeAll();
+ final List selectedLinks = IntentPickerUtils.getLinksList(
+ mDomainVerificationManager, mPackageName, DOMAIN_STATE_SELECTED);
+ for (String host : selectedLinks) {
+ generateCheckBoxPreference(mSelectedLinksPreferenceCategory, host);
+ }
+ }
+
+ /** Initialize add link preference */
+ private void initAddLinkPreference() {
+ final Preference addLinkPreference = findPreference(ADD_LINK_PREF_KEY);
+ if (addLinkPreference != null) {
+ addLinkPreference.setEnabled(getLinksNumber(DOMAIN_STATE_NONE) > 0);
+ addLinkPreference.setOnPreferenceClickListener(preference -> {
+ final int stateNoneLinksNo = getLinksNumber(DOMAIN_STATE_NONE);
+ IntentPickerUtils.logd("The number of the state none links: " + stateNoneLinksNo);
+ if (stateNoneLinksNo > 0) {
+ showProgressDialogFragment();
+ }
+ return true;
+ });
+ }
+ }
+
+ private void showProgressDialogFragment() {
+ final Bundle args = new Bundle();
+ args.putString(APP_PACKAGE_KEY, mPackageName);
+ final ProgressDialogFragment dialogFragment = new ProgressDialogFragment();
+ dialogFragment.setArguments(args);
+ dialogFragment.showDialog(getActivity().getSupportFragmentManager());
+ }
+
+ private void disabledPreference() {
+ mMainSwitchPreference.updateStatus(false);
+ mMainSwitchPreference.setSelectable(false);
+ mMainSwitchPreference.setEnabled(false);
+ mMainPreferenceCategory.setVisible(false);
+ }
+
+ /** Init OTHER DEFAULTS category */
+ private void initOtherDefaultsSection() {
+ mOtherDefaultsPreferenceCategory = findPreference(OTHER_DETAILS_PREF_CATEGORY_KEY);
+ mOtherDefaultsPreferenceCategory.setVisible(isClearDefaultsEnabled());
+ mClearDefaultsPreference = (ClearDefaultsPreference) findPreference(
+ CLEAR_DEFAULTS_PREF_KEY);
+ }
+
+ private void initFooter() {
+ // learn more
+ final AnnotationSpan.LinkInfo linkInfo =
+ new AnnotationSpan.LinkInfo(ANNOTATION_URL, v -> {
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(LEARN_MORE_URI));
+ mContext.startActivity(intent);
+ });
+ final CharSequence footerText = mContext.getText(R.string.app_launch_footer);
+ final FooterPreference footerPreference = (FooterPreference) findPreference(
+ FOOTER_PREF_KEY);
+ footerPreference.setTitle(AnnotationSpan.linkify(footerText, linkInfo));
+ }
+
+ private boolean isClearDefaultsEnabled() {
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+ final boolean hasBindAppWidgetPermission =
+ appWidgetManager.hasBindAppWidgetPermission(mAppEntry.info.packageName);
+
+ final boolean isAutoLaunchEnabled = AppUtils.hasPreferredActivities(mPm, mPackageName)
+ || AppUtils.isDefaultBrowser(mContext, mPackageName)
+ || AppUtils.hasUsbDefaults(mUsbManager, mPackageName);
+
+ IntentPickerUtils.logd("isClearDefaultsEnabled hasBindAppWidgetPermission : "
+ + hasBindAppWidgetPermission);
+ IntentPickerUtils.logd(
+ "isClearDefaultsEnabled isAutoLaunchEnabled : " + isAutoLaunchEnabled);
+ return (isAutoLaunchEnabled || hasBindAppWidgetPermission);
+ }
+
+ private void setDomainVerificationUserSelection(UUID identifier, Set domainSet,
+ boolean isEnabled) {
+ try {
+ mDomainVerificationManager.setDomainVerificationUserSelection(identifier, domainSet,
+ isEnabled);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "addSelectedItems : " + e.getMessage());
+ }
+ }
+
+ private void generateCheckBoxPreference(PreferenceCategory parent, String title) {
+ final LeftSideCheckBoxPreference checkBoxPreference = new LeftSideCheckBoxPreference(
+ parent.getContext(), /* isChecked= */ true);
+ checkBoxPreference.setTitle(title);
+ checkBoxPreference.setOnPreferenceChangeListener(this);
+ checkBoxPreference.setKey(UUID.randomUUID().toString());
+ parent.addPreference(checkBoxPreference);
+ }
+
+ /** get the number of the specify links */
+ private int getLinksNumber(@DomainVerificationUserState.DomainState int state) {
+ final List linkList = IntentPickerUtils.getLinksList(
+ mDomainVerificationManager, mPackageName, state);
+ if (linkList == null) {
+ return 0;
+ }
+ return linkList.size();
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java b/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java
new file mode 100644
index 00000000000..5b14bc7ce3e
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationUserState;
+import android.os.Build;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.style.AlignmentSpan;
+import android.util.Log;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** The common APIs for intent picker */
+public class IntentPickerUtils {
+ private static final String TAG = "IntentPickerUtils";
+ private static final boolean DEBUG = Build.IS_DEBUGGABLE;
+
+ private IntentPickerUtils() {
+ }
+
+ /**
+ * Gets the centralized title.
+ *
+ * @param title The title of the dialog box.
+ * @return The spannable string with centralized title.
+ */
+ public static SpannableString getCentralizedDialogTitle(String title) {
+ final SpannableString dialogTitle = new SpannableString(title);
+ dialogTitle.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), /* start= */
+ 0, title.length(), /* flags= */ 0);
+ return dialogTitle;
+ }
+
+ /**
+ * Gets the {@link DomainVerificationUserState} for specific application.
+ *
+ * @param manager The {@link DomainVerificationManager}.
+ * @param pkgName The package name of the target application.
+ */
+ public static DomainVerificationUserState getDomainVerificationUserState(
+ DomainVerificationManager manager, String pkgName) {
+ try {
+ final DomainVerificationUserState domainVerificationUserState =
+ manager.getDomainVerificationUserState(pkgName);
+ return domainVerificationUserState;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Gets the links list by {@link DomainVerificationUserState.DomainState}
+ *
+ * @param manager The {@link DomainVerificationManager}.
+ * @param pkgName The package name of the target application.
+ * @param state The user state you want to query.
+ * @return A links list.
+ */
+ public static List getLinksList(DomainVerificationManager manager, String pkgName,
+ @DomainVerificationUserState.DomainState int state) {
+ final DomainVerificationUserState userStage = getDomainVerificationUserState(manager,
+ pkgName);
+ if (userStage == null) {
+ return null;
+ }
+ return userStage.getHostToStateMap()
+ .entrySet()
+ .stream()
+ .filter(it -> it.getValue() == state)
+ .map(it -> it.getKey())
+ .collect(Collectors.toList());
+ }
+
+ /** Logs the message in debug ROM. */
+ public static void logd(String msg) {
+ if (DEBUG) {
+ Log.d(TAG, msg);
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/LeftSideCheckBoxPreference.java b/src/com/android/settings/applications/intentpicker/LeftSideCheckBoxPreference.java
new file mode 100644
index 00000000000..fdb6d2541e0
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/LeftSideCheckBoxPreference.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.CheckBox;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settingslib.widget.TwoTargetPreference;
+
+/** This preference has a check box in the left side. */
+public class LeftSideCheckBoxPreference extends TwoTargetPreference {
+ private boolean mChecked;
+ private CheckBox mCheckBox;
+
+ public LeftSideCheckBoxPreference(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setLayoutResource(R.layout.preference_checkable_two_target);
+ }
+
+ public LeftSideCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LeftSideCheckBoxPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LeftSideCheckBoxPreference(Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public LeftSideCheckBoxPreference(Context context, boolean isChecked) {
+ super(context);
+ mChecked = isChecked;
+ setLayoutResource(R.layout.preference_checkable_two_target);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+ mCheckBox = (CheckBox) view.findViewById(com.android.internal.R.id.checkbox);
+ if (mCheckBox != null) {
+ mCheckBox.setChecked(mChecked);
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ if (mCheckBox != null) {
+ mChecked = !mChecked;
+ mCheckBox.setChecked(mChecked);
+ callChangeListener(mChecked);
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/ProgressDialogFragment.java b/src/com/android/settings/applications/intentpicker/ProgressDialogFragment.java
new file mode 100644
index 00000000000..53a6b04e66e
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/ProgressDialogFragment.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.verify.domain.DomainOwner;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.android.settings.R;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** A customized {@link DialogFragment} with a progress bar. */
+public class ProgressDialogFragment extends DialogFragment {
+ private static final String TAG = "ProgressDialogFragment";
+ private static final String DLG_ID = "ProgressDialog";
+ private static final int PROGRESS_BAR_STEPPING_TIME = 20;
+
+ private ProgressAlertDialog mProgressAlertDialog;
+ private DomainVerificationManager mDomainVerificationManager;
+ private List mSupportedLinkWrapperList;
+ private SupportedLinkViewModel mViewModel;
+ private Handler mHandle;
+ private String mPackage;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mViewModel = ViewModelProviders.of(this.getActivity()).get(SupportedLinkViewModel.class);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mPackage = getArguments().getString(AppLaunchSettings.APP_PACKAGE_KEY);
+ mDomainVerificationManager = getActivity().getSystemService(
+ DomainVerificationManager.class);
+ mHandle = new Handler(Looper.getMainLooper());
+ mProgressAlertDialog = createProgressAlertDialog();
+ return mProgressAlertDialog;
+ }
+
+ private ProgressAlertDialog createProgressAlertDialog() {
+ final Context context = getActivity();
+ final ProgressAlertDialog progressDialog = new ProgressAlertDialog(context);
+ final String title = context.getResources().getString(
+ R.string.app_launch_checking_links_title);
+ progressDialog.setTitle(IntentPickerUtils.getCentralizedDialogTitle(title));
+ progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getText(R.string.app_launch_dialog_cancel),
+ (dialog, which) -> {
+ if (which == DialogInterface.BUTTON_NEGATIVE) {
+ dialog.cancel();
+ }
+ });
+ progressDialog.setCanceledOnTouchOutside(true);
+ return progressDialog;
+ }
+
+ /** Display the {@link ProgressAlertDialog}. */
+ public void showDialog(FragmentManager manager) {
+ show(manager, DLG_ID);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ generateProgressAlertDialog();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mProgressAlertDialog != null && mProgressAlertDialog.isShowing()) {
+ mProgressAlertDialog.cancel();
+ }
+ }
+
+ /**
+ * To generate a progress alter dialog and invoke the supported links dialog.
+ */
+ private void generateProgressAlertDialog() {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ final long start = SystemClock.elapsedRealtime();
+ queryLinksInBackground();
+ IntentPickerUtils.logd(
+ "queryLinksInBackground take time: " + (SystemClock.elapsedRealtime() - start));
+ if (mProgressAlertDialog.isShowing()) {
+ mHandle.post(() -> {
+ synchronized (mHandle) {
+ if (mProgressAlertDialog.isShowing()) {
+ mProgressAlertDialog.dismiss();
+ IntentPickerUtils.logd("mProgressAlertDialog.dismiss() and isShowing: "
+ + mProgressAlertDialog.isShowing());
+ launchSupportedLinksDialogFragment();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ private void queryLinksInBackground() {
+ final List links = IntentPickerUtils.getLinksList(mDomainVerificationManager,
+ mPackage, DOMAIN_STATE_NONE);
+ final int linksNo = links.size();
+ int index = 0;
+ mSupportedLinkWrapperList = new ArrayList<>();
+ for (String host : links) {
+ final List ownerList =
+ mDomainVerificationManager.getOwnersForDomain(host);
+ mSupportedLinkWrapperList.add(new SupportedLinkWrapper(getActivity(), host, ownerList));
+ index++;
+ // The cancel was clicked while progressing to collect data.
+ if (!mProgressAlertDialog.isShowing()) {
+ Log.w(TAG, "Exit the background thread!!!");
+ // clear buffer
+ mSupportedLinkWrapperList.clear();
+ break;
+ }
+ int progress = (int) (index * 100) / linksNo;
+ mHandle.post(() -> {
+ synchronized (mHandle) {
+ if (!mProgressAlertDialog.isShowing()) {
+ Log.w(TAG, "Exit the UI thread");
+ return;
+ }
+ mProgressAlertDialog.getProgressBar().setProgress(progress);
+ }
+ });
+ if (ownerList.size() == 0) {
+ SystemClock.sleep(PROGRESS_BAR_STEPPING_TIME);
+ }
+ }
+ IntentPickerUtils.logd("queryLinksInBackground : SupportedLinkWrapperList size="
+ + mSupportedLinkWrapperList.size());
+ Collections.sort(mSupportedLinkWrapperList);
+ }
+
+ private void launchSupportedLinksDialogFragment() {
+ if (mSupportedLinkWrapperList.size() > 0) {
+ mViewModel.setSupportedLinkWrapperList(mSupportedLinkWrapperList);
+ final Bundle args = new Bundle();
+ args.putString(AppLaunchSettings.APP_PACKAGE_KEY, mPackage);
+ final SupportedLinksDialogFragment dialogFragment = new SupportedLinksDialogFragment();
+ dialogFragment.setArguments(args);
+ dialogFragment.showDialog(getActivity().getSupportFragmentManager());
+ }
+ }
+
+ /** Create a custom {@link AlertDialog} with a {@link ProgressBar}. */
+ static class ProgressAlertDialog extends AlertDialog {
+ private ProgressBar mProgressBar;
+
+ protected ProgressAlertDialog(@NonNull Context context) {
+ this(context, 0);
+ }
+
+ protected ProgressAlertDialog(@NonNull Context context, int themeResId) {
+ super(context, themeResId);
+ init(context);
+ }
+
+ private void init(Context context) {
+ final View view = LayoutInflater.from(context).inflate(
+ R.layout.app_launch_progress, /* root= */ null);
+ mProgressBar = view.findViewById(R.id.scan_links_progressbar);
+ mProgressBar.setProgress(0);
+ mProgressBar.setMax(100);
+ setView(view);
+ }
+
+ ProgressBar getProgressBar() {
+ return mProgressBar;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/SupportedLinkViewModel.java b/src/com/android/settings/applications/intentpicker/SupportedLinkViewModel.java
new file mode 100644
index 00000000000..c374b8c23d9
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/SupportedLinkViewModel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import android.app.Application;
+
+import androidx.lifecycle.AndroidViewModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This {@link AndroidViewModel} provides supported link wrapper data
+ * between multiple fragments.
+ */
+public class SupportedLinkViewModel extends AndroidViewModel {
+ private List mSupportedLinkWrapperList;
+
+ public SupportedLinkViewModel(Application application) {
+ super(application);
+ }
+
+ /** Clear the list buffer of the {@link SupportedLinkWrapper}. */
+ public void clearSupportedLinkWrapperList() {
+ mSupportedLinkWrapperList = new ArrayList<>();
+ }
+
+ /** Set the list buffer of the {@link SupportedLinkWrapper}. */
+ public void setSupportedLinkWrapperList(List wrapperList) {
+ mSupportedLinkWrapperList = wrapperList;
+ }
+
+ /** Get the list buffer of the {@link SupportedLinkWrapper}. */
+ public List getSupportedLinkWrapperList() {
+ return mSupportedLinkWrapperList;
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/SupportedLinkWrapper.java b/src/com/android/settings/applications/intentpicker/SupportedLinkWrapper.java
new file mode 100644
index 00000000000..0acc2bc823a
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/SupportedLinkWrapper.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainOwner;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.settings.R;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * A buffer of the supported link data. This {@link SupportedLinkWrapper} wraps the host, enabled
+ * and a list of {@link DomainOwner}.
+ */
+public class SupportedLinkWrapper implements Comparable {
+ private static final String TAG = "SupportedLinkWrapper";
+
+ private String mHost;
+ private List mOwnerList;
+ private boolean mIsEnabled;
+ private String mLastOwnerName;
+ private boolean mIsChecked;
+
+ public SupportedLinkWrapper(Context context, String host, List ownerList) {
+ mHost = host;
+ mOwnerList = ownerList;
+ mIsEnabled = true;
+ mLastOwnerName = "";
+ mIsChecked = false;
+ init(context);
+ }
+
+ private void init(Context context) {
+ if (mOwnerList.size() > 0) {
+ final long nonOverirideableNo = mOwnerList.stream()
+ .filter(it -> !it.isOverrideable())
+ .count();
+ mIsEnabled = (nonOverirideableNo == 0L);
+ if (nonOverirideableNo > 0L) {
+ mLastOwnerName = getLastPackageLabel(context, false);
+ } else {
+ mLastOwnerName = getLastPackageLabel(context, true);
+ }
+ }
+ }
+
+ private String getLastPackageLabel(Context context, boolean isOverrideable) {
+ final List labelList = mOwnerList.stream()
+ .filter(it -> it.isOverrideable() == isOverrideable)
+ .map(it -> getLabel(context, it.getPackageName()))
+ .filter(label -> label != null)
+ .collect(Collectors.toList());
+ return labelList.get(labelList.size() - 1);
+ }
+
+ private String getLabel(Context context, String pkg) {
+ try {
+ final PackageManager pm = context.getPackageManager();
+ return pm.getApplicationInfo(pkg, /* flags= */ 0).loadLabel(pm).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "getLabel error : " + e.getMessage());
+ return null;
+ }
+ }
+
+ /** Returns the enabled/disabled value for list item. */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /** Returns the display format of list item in the Supported Links dialog */
+ public String getDisplayTitle(Context context) {
+ if (TextUtils.isEmpty(mLastOwnerName) || context == null) {
+ return mHost;
+ }
+ return mHost + System.lineSeparator() + context.getString(
+ R.string.app_launch_supported_links_subtext, mLastOwnerName);
+ }
+
+ /** Returns the host name. */
+ public String getHost() {
+ return mHost;
+ }
+
+ /** Returns the checked value for list item. */
+ public boolean isChecked() {
+ return mIsChecked;
+ }
+
+ /** Set the checked value. */
+ public void setChecked(boolean isChecked) {
+ mIsChecked = isChecked;
+ }
+
+ @Override
+ public int compareTo(Object o) {
+ final SupportedLinkWrapper that = (SupportedLinkWrapper) o;
+ if (this.mIsEnabled != that.mIsEnabled) {
+ return this.mIsEnabled ? -1 : 1;
+ }
+ if (TextUtils.isEmpty(this.mLastOwnerName) != TextUtils.isEmpty(that.mLastOwnerName)) {
+ return TextUtils.isEmpty(this.mLastOwnerName) ? -1 : 1;
+ }
+ return 0;
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/SupportedLinksAdapter.java b/src/com/android/settings/applications/intentpicker/SupportedLinksAdapter.java
new file mode 100644
index 00000000000..9288d52c504
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/SupportedLinksAdapter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CheckedTextView;
+
+import com.android.settings.R;
+
+import java.util.List;
+
+/** This adapter is for supported links dialog. */
+public class SupportedLinksAdapter extends BaseAdapter {
+ private final Context mContext;
+ private final List mWrapperList;
+
+ public SupportedLinksAdapter(Context context, List list) {
+ mContext = context;
+ mWrapperList = list;
+ }
+
+ @Override
+ public int getCount() {
+ return mWrapperList.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ if (position < mWrapperList.size()) {
+ return mWrapperList.get(position);
+ }
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(mContext).inflate(
+ R.layout.supported_links_dialog_item, /* root= */ null);
+ }
+ final CheckedTextView textView = convertView.findViewById(android.R.id.text1);
+ textView.setText(mWrapperList.get(position).getDisplayTitle(mContext));
+ textView.setEnabled(mWrapperList.get(position).isEnabled());
+ textView.setChecked(mWrapperList.get(position).isChecked());
+ textView.setOnClickListener(l -> {
+ textView.toggle();
+ mWrapperList.get(position).setChecked(textView.isChecked());
+ });
+ return convertView;
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/SupportedLinksDialogFragment.java b/src/com/android/settings/applications/intentpicker/SupportedLinksDialogFragment.java
new file mode 100644
index 00000000000..52511c9540c
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/SupportedLinksDialogFragment.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationUserState;
+import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.android.settings.R;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/** A customized {@link DialogFragment} with multiple checkboxes. */
+public class SupportedLinksDialogFragment extends DialogFragment {
+ private static final String TAG = "SupportedLinksDialogFrg";
+ private static final String DLG_ID = "SupportedLinksDialog";
+
+ private SupportedLinkViewModel mViewModel;
+ private List mSupportedLinkWrapperList;
+ private String mPackage;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mPackage = getArguments().getString(AppLaunchSettings.APP_PACKAGE_KEY);
+ mViewModel = ViewModelProviders.of(this.getActivity()).get(SupportedLinkViewModel.class);
+ mSupportedLinkWrapperList = mViewModel.getSupportedLinkWrapperList();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final SupportedLinksAdapter adapter = new SupportedLinksAdapter(context,
+ mSupportedLinkWrapperList);
+ final AlertDialog.Builder builder = new AlertDialog
+ .Builder(context)
+ .setTitle(IntentPickerUtils.getCentralizedDialogTitle(getSupportedLinksTitle()))
+ .setAdapter(adapter, /* listener= */ null)
+ .setCancelable(true)
+ .setPositiveButton(R.string.app_launch_supported_links_add, (dialog, id) -> {
+ // addSelectedItems(((AlertDialog) dialog).getListView());
+ doSelectedAction();
+ })
+ .setNegativeButton(R.string.app_launch_dialog_cancel, /* listener= */ null);
+ return builder.create();
+ }
+
+ /** Display the dialog. */
+ public void showDialog(FragmentManager manager) {
+ show(manager, DLG_ID);
+ }
+
+ private String getSupportedLinksTitle() {
+ final int supportedLinksNo = mSupportedLinkWrapperList.size();
+ return getResources().getQuantityString(
+ R.plurals.app_launch_supported_links_title, supportedLinksNo, supportedLinksNo);
+ }
+
+ private void doSelectedAction() {
+ final DomainVerificationManager manager = getActivity().getSystemService(
+ DomainVerificationManager.class);
+ final DomainVerificationUserState userState =
+ IntentPickerUtils.getDomainVerificationUserState(manager, mPackage);
+ if (userState == null || mSupportedLinkWrapperList == null) {
+ return;
+ }
+
+ updateUserSelection(manager, userState);
+ displaySelectedItem();
+ }
+
+ private void updateUserSelection(DomainVerificationManager manager,
+ DomainVerificationUserState userState) {
+ final Set domainSet = new ArraySet<>();
+ for (SupportedLinkWrapper wrapper : mSupportedLinkWrapperList) {
+ if (wrapper.isChecked()) {
+ domainSet.add(wrapper.getHost());
+ }
+ }
+ if (domainSet.size() > 0) {
+ setDomainVerificationUserSelection(manager, userState.getIdentifier(),
+ domainSet, /* enabled= */true);
+ }
+ }
+
+ private void setDomainVerificationUserSelection(DomainVerificationManager manager,
+ UUID identifier, Set domainSet, boolean isEnabled) {
+ try {
+ manager.setDomainVerificationUserSelection(identifier, domainSet, isEnabled);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "addSelectedItems : " + e.getMessage());
+ }
+ }
+
+ private void displaySelectedItem() {
+ final List fragments = getActivity().getSupportFragmentManager().getFragments();
+ for (Fragment fragment : fragments) {
+ if (fragment instanceof AppLaunchSettings) {
+ ((AppLaunchSettings) fragment).addSelectedLinksPreference();
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/intentpicker/VerifiedLinksPreference.java b/src/com/android/settings/applications/intentpicker/VerifiedLinksPreference.java
new file mode 100644
index 00000000000..5452a2a307d
--- /dev/null
+++ b/src/com/android/settings/applications/intentpicker/VerifiedLinksPreference.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.intentpicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+import com.android.settingslib.widget.TwoTargetPreference;
+
+/** This customized VerifiedLinksPreference was belonged to Open by default page */
+public class VerifiedLinksPreference extends TwoTargetPreference {
+ private Context mContext;
+ private View.OnClickListener mOnWidgetClickListener;
+ private boolean mShowCheckBox;
+
+ public VerifiedLinksPreference(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context);
+ }
+
+ public VerifiedLinksPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, /* defStyleRes= */0);
+ }
+
+ public VerifiedLinksPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, /* defStyleAttr= */ 0);
+ }
+
+ public VerifiedLinksPreference(Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ private void init(Context context) {
+ mContext = context;
+ mOnWidgetClickListener = null;
+ mShowCheckBox = true;
+ setLayoutResource(R.layout.preference_checkable_two_target);
+ setWidgetLayoutResource(R.layout.verified_links_widget);
+ }
+
+ /**
+ * Register a callback to be invoked when this widget is clicked.
+ *
+ * @param listener The callback that will run.
+ */
+ public void setWidgetFrameClickListener(View.OnClickListener listener) {
+ mOnWidgetClickListener = listener;
+ }
+
+ /** Determine the visibility of the {@link CheckBox}. */
+ public void setCheckBoxVisible(boolean isVisible) {
+ mShowCheckBox = isVisible;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+ final View settingsWidget = view.findViewById(android.R.id.widget_frame);
+ final View divider = view.findViewById(R.id.two_target_divider);
+ divider.setVisibility(View.VISIBLE);
+ settingsWidget.setVisibility(View.VISIBLE);
+ if (mOnWidgetClickListener != null) {
+ settingsWidget.setOnClickListener(mOnWidgetClickListener);
+ }
+ final View checkboxContainer = view.findViewById(R.id.checkbox_container);
+ final View parentView = (View) checkboxContainer.getParent();
+ parentView.setEnabled(false);
+ parentView.setClickable(false);
+ CheckBox checkBox = (CheckBox) view.findViewById(com.android.internal.R.id.checkbox);
+ if (checkBox != null) {
+ checkBox.setChecked(true);
+ checkBox.setVisibility(mShowCheckBox ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/managedomainurls/DomainAppPreferenceController.java b/src/com/android/settings/applications/managedomainurls/DomainAppPreferenceController.java
index c859db204e5..07c4858ea54 100644
--- a/src/com/android/settings/applications/managedomainurls/DomainAppPreferenceController.java
+++ b/src/com/android/settings/applications/managedomainurls/DomainAppPreferenceController.java
@@ -28,7 +28,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
-import com.android.settings.applications.AppLaunchSettings;
+import com.android.settings.applications.intentpicker.AppLaunchSettings;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java
index 849f2acbf3a..10732566b58 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java
@@ -37,7 +37,7 @@ import android.content.pm.ResolveInfo;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import com.android.settings.applications.AppLaunchSettings;
+import com.android.settings.applications.intentpicker.AppLaunchSettings;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.instantapps.InstantAppDataProvider;