Implement new "Open by default" page

- Use the new MainSwitch and TopIntro widgets.
- Create VerifiedLinksPreference and LeftSideCheckBoxPreference.
- Create ProgressDialogFragment and SupportedLinksDialogFragment.
- Retrieve the links info from DomainVerificationManager.
- Allow user to change the supported links into selected links.
- Refactor the ClearDefaultsPreference.
- Move AppLaunchSettings into intentpicker directory.

Bug: 177923646
Bug: 182530528
Test: manual test
Change-Id: I935a2fdd0f62cdb8b8d3210fb2f800f682249cb7
This commit is contained in:
Sunny Shao
2021-03-05 19:51:08 +08:00
parent 01f77d8e00
commit 81fa20a4f1
21 changed files with 1478 additions and 183 deletions

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<ProgressBar
xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:id="@+id/scan_links_progressbar"
android:paddingTop="8dp"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:paddingStart="?android:attr/dialogPreferredPadding"
android:paddingEnd="?android:attr/dialogPreferredPadding"
android:paddingTop="@*android:dimen/dialog_padding_top_material">
<TextView
android:id="@+id/dialog_title"
android:singleLine="true"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
style="?android:attr/windowTitleStyle" />
<TextView
android:paddingTop="12dp"
android:id="@+id/dialog_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.DialogMessage" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
android:scaleType="center"
android:src="@drawable/ic_info_outline_24"
android:contentDescription="@string/settings_button" />

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<CheckedTextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textAlignment="viewStart"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:textColor="?android:attr/textColorAlertDialogListItem"
android:drawableLeft="?android:attr/listChoiceIndicatorMultiple" />

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<include layout="@layout/preference_widget_info"
android:id="@+id/verified_links_widget" />
</LinearLayout>

View File

@@ -16,31 +16,59 @@
<PreferenceScreen <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/launch_by_default"> android:title="@string/launch_by_default">
<PreferenceCategory android:key="app_launch_domain_links" <com.android.settingslib.widget.MainSwitchPreference
android:title="@string/app_launch_domain_links_title"> android:key="open_by_default_supported_links"
android:title="@string/app_launch_open_domain_urls_title"/>
<Preference <com.android.settingslib.widget.TopIntroPreference
android:key="app_link_state" android:key="open_by_default_top_intro"
android:summary="@string/summary_placeholder" android:title="@string/app_launch_top_intro_message"/>
android:title="@string/app_launch_open_domain_urls_title"/>
<com.android.settings.applications.AppDomainsPreference <PreferenceCategory
android:key="app_launch_supported_domain_urls" android:layout="@layout/preference_category_no_label"
android:title="@string/app_launch_supported_domain_urls_title" android:key="open_by_default_main_category"
android:dependency="app_link_state" settings:searchable="false">
/>
<PreferenceCategory
android:title="@string/app_launch_links_category">
<com.android.settings.applications.intentpicker.VerifiedLinksPreference
android:key="open_by_default_verified_links"
android:title="@string/summary_placeholder"
android:order="-100"
settings:searchable="false"/>
<PreferenceCategory
android:layout="@layout/preference_category_no_label"
android:key="open_by_default_selected_links_category"
android:order="100"
settings:searchable="false"/>
<Preference
android:key="open_by_default_add_link"
android:title="@string/app_launch_add_link"
android:order="300"
android:icon="@drawable/ic_add_24dp"/>
</PreferenceCategory>
<PreferenceCategory android:key="app_launch_other_defaults"
android:title="@string/app_launch_other_defaults_title">
<com.android.settings.applications.ClearDefaultsPreference
android:key="app_launch_clear_defaults"
android:selectable="false"/>
</PreferenceCategory>
<com.android.settingslib.widget.FooterPreference
android:key="open_by_default_footer"
android:selectable="false"
settings:allowDividerAbove="true"
settings:searchable="false"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:key="app_launch_other_defaults"
android:title="@string/app_launch_other_defaults_title">
<com.android.settings.applications.ClearDefaultsPreference
android:key="app_launch_clear_defaults"
android:selectable="false"/>
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -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<String> 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;
}
}

View File

@@ -117,7 +117,7 @@ public class ClearDefaultsPreference extends Preference {
if (mUsbManager != null) { if (mUsbManager != null) {
final int userId = UserHandle.myUserId(); final int userId = UserHandle.myUserId();
mPm.clearPackagePreferredActivities(mPackageName); mPm.clearPackagePreferredActivities(mPackageName);
if (isDefaultBrowser(mPackageName)) { if (AppUtils.isDefaultBrowser(getContext(), mPackageName)) {
mPm.setDefaultBrowserPackageNameAsUser(null, userId); mPm.setDefaultBrowserPackageNameAsUser(null, userId);
} }
try { try {
@@ -141,7 +141,7 @@ public class ClearDefaultsPreference extends Preference {
TextView autoLaunchView = (TextView) view.findViewById(R.id.auto_launch); TextView autoLaunchView = (TextView) view.findViewById(R.id.auto_launch);
boolean autoLaunchEnabled = AppUtils.hasPreferredActivities(mPm, mPackageName) boolean autoLaunchEnabled = AppUtils.hasPreferredActivities(mPm, mPackageName)
|| isDefaultBrowser(mPackageName) || AppUtils.isDefaultBrowser(getContext(), mPackageName)
|| AppUtils.hasUsbDefaults(mUsbManager, mPackageName); || AppUtils.hasUsbDefaults(mUsbManager, mPackageName);
if (!autoLaunchEnabled && !hasBindAppWidgetPermission) { if (!autoLaunchEnabled && !hasBindAppWidgetPermission) {
resetLaunchDefaultsUi(autoLaunchView); resetLaunchDefaultsUi(autoLaunchView);
@@ -185,11 +185,6 @@ public class ClearDefaultsPreference extends Preference {
return true; return true;
} }
private boolean isDefaultBrowser(String packageName) {
final String defaultBrowser = mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId());
return packageName.equals(defaultBrowser);
}
private void resetLaunchDefaultsUi(TextView autoLaunchView) { private void resetLaunchDefaultsUi(TextView autoLaunchView) {
autoLaunchView.setText(R.string.auto_launch_disable_text); autoLaunchView.setText(R.string.auto_launch_disable_text);
// Disable clear activities button // Disable clear activities button

View File

@@ -19,6 +19,7 @@ package com.android.settings.applications;
import android.content.Intent; import android.content.Intent;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.applications.intentpicker.AppLaunchSettings;
public class InstalledAppOpenByDefaultActivity extends SettingsActivity { public class InstalledAppOpenByDefaultActivity extends SettingsActivity {

View File

@@ -28,7 +28,7 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.settings.SettingsPreferenceFragment; 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.AppUtils;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;

View File

@@ -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<String> 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<String> 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<String> 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<String> 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<String> linkList = IntentPickerUtils.getLinksList(
mDomainVerificationManager, mPackageName, state);
if (linkList == null) {
return 0;
}
return linkList.size();
}
}

View File

@@ -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<String> 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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<SupportedLinkWrapper> 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<String> 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<DomainOwner> 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;
}
}
}

View File

@@ -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<SupportedLinkWrapper> 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<SupportedLinkWrapper> wrapperList) {
mSupportedLinkWrapperList = wrapperList;
}
/** Get the list buffer of the {@link SupportedLinkWrapper}. */
public List<SupportedLinkWrapper> getSupportedLinkWrapperList() {
return mSupportedLinkWrapperList;
}
}

View File

@@ -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<DomainOwner> mOwnerList;
private boolean mIsEnabled;
private String mLastOwnerName;
private boolean mIsChecked;
public SupportedLinkWrapper(Context context, String host, List<DomainOwner> 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<String> 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;
}
}

View File

@@ -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<SupportedLinkWrapper> mWrapperList;
public SupportedLinksAdapter(Context context, List<SupportedLinkWrapper> 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;
}
}

View File

@@ -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<SupportedLinkWrapper> 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<String> 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<String> domainSet, boolean isEnabled) {
try {
manager.setDomainVerificationUserSelection(identifier, domainSet, isEnabled);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "addSelectedItems : " + e.getMessage());
}
}
private void displaySelectedItem() {
final List<Fragment> fragments = getActivity().getSupportFragmentManager().getFragments();
for (Fragment fragment : fragments) {
if (fragment instanceof AppLaunchSettings) {
((AppLaunchSettings) fragment).addSelectedLinksPreference();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -28,7 +28,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.applications.AppInfoBase; 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.settings.core.BasePreferenceController;
import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.AppEntry;

View File

@@ -37,7 +37,7 @@ import android.content.pm.ResolveInfo;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceScreen; 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.AppUtils;
import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.instantapps.InstantAppDataProvider; import com.android.settingslib.applications.instantapps.InstantAppDataProvider;