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;