diff --git a/res/values/strings.xml b/res/values/strings.xml index 28ad81b3224..55554c60eee 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9049,13 +9049,21 @@ - Open in this app + Allow app to open supported links Ask every time - Don’t open in this app + Don’t allow app to open links + + + App claims to handle %d link + App claims to handle %d links + + + + App claims to handle following links: Default diff --git a/res/xml/installed_app_launch_settings.xml b/res/xml/installed_app_launch_settings.xml index a2a0ca4bde5..6cf1955ad45 100644 --- a/res/xml/installed_app_launch_settings.xml +++ b/res/xml/installed_app_launch_settings.xml @@ -21,16 +21,16 @@ - + + android:key="app_launch_supported_domain_urls" + android:title="@string/app_launch_supported_domain_urls_title" + android:dependency="app_link_state" + /> @@ -38,7 +38,7 @@ android:title="@string/app_launch_other_defaults_title"> + android:key="app_launch_clear_defaults"/> diff --git a/res/xml/open_supported_links.xml b/res/xml/open_supported_links.xml new file mode 100644 index 00000000000..0f6e2ca8a16 --- /dev/null +++ b/res/xml/open_supported_links.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/applications/AppHeaderPreferenceController.java b/src/com/android/settings/applications/AppHeaderPreferenceController.java new file mode 100644 index 00000000000..8a77d6f07fd --- /dev/null +++ b/src/com/android/settings/applications/AppHeaderPreferenceController.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 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 com.android.settings.widget.EntityHeaderController.ActionType; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.IconDrawableFactory; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.widget.EntityHeaderController; +import com.android.settingslib.applications.AppUtils; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.widget.LayoutPreference; + +/** + * The header controller displays on the top of the page. + */ +public class AppHeaderPreferenceController extends BasePreferenceController implements + LifecycleObserver, OnResume { + private DashboardFragment mParent; + private PackageInfo mPackageInfo; + private Lifecycle mLifecycle; + private LayoutPreference mHeaderPreference; + + public AppHeaderPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * @param fragment set the parent fragment. + * @return return controller-self. + */ + public AppHeaderPreferenceController setParentFragment(DashboardFragment fragment) { + mParent = fragment; + return this; + } + + /** + * @param packageInfo set the {@link PackageInfo}. + * @return return controller-self. + */ + public AppHeaderPreferenceController setPackageInfo(PackageInfo packageInfo) { + mPackageInfo = packageInfo; + return this; + } + + /** + * @param lifeCycle set the {@link Lifecycle}. + * @return return controller-self. + */ + public AppHeaderPreferenceController setLifeCycle(Lifecycle lifeCycle) { + mLifecycle = lifeCycle; + return this; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mHeaderPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void onResume() { + final Activity activity = mParent.getActivity(); + final PackageManager packageManager = activity.getPackageManager(); + EntityHeaderController + .newInstance(activity, mParent, mHeaderPreference.findViewById(R.id.entity_header)) + .setRecyclerView(mParent.getListView(), mLifecycle) + .setIcon(IconDrawableFactory.newInstance(activity).getBadgedIcon( + mPackageInfo.applicationInfo)) + .setLabel(mPackageInfo.applicationInfo.loadLabel(packageManager)) + .setSummary(mPackageInfo) + .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo)) + .setPackageName(mPackageInfo.packageName) + .setUid(mPackageInfo.applicationInfo.uid) + .setButtonActions(ActionType.ACTION_NONE, ActionType.ACTION_NONE) + .done(mParent.getActivity(), true /* rebindActions */); + } +} diff --git a/src/com/android/settings/applications/AppLaunchSettings.java b/src/com/android/settings/applications/AppLaunchSettings.java index c61f5d126ad..17d8e67a0bb 100644 --- a/src/com/android/settings/applications/AppLaunchSettings.java +++ b/src/com/android/settings/applications/AppLaunchSettings.java @@ -17,14 +17,11 @@ 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_ALWAYS_ASK; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; import android.app.settings.SettingsEnums; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; -import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; @@ -36,24 +33,27 @@ import android.view.View; import android.view.View.OnClickListener; import androidx.appcompat.app.AlertDialog; -import androidx.preference.DropDownPreference; import androidx.preference.Preference; -import androidx.preference.Preference.OnPreferenceChangeListener; import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.core.SubSettingLauncher; import java.util.List; 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"; + + public static final String KEY_PACKAGE_INFO = "pkg_info"; private static final Intent sBrowserIntent; + static { sBrowserIntent = new Intent() .setAction(Intent.ACTION_VIEW) @@ -65,7 +65,7 @@ public class AppLaunchSettings extends AppInfoWithHeader implements OnClickListe private boolean mIsBrowser; private boolean mHasDomainUrls; - private DropDownPreference mAppLinkState; + private Preference mAppLinkState; private AppDomainsPreference mAppDomainUrls; private ClearDefaultsPreference mClearDefaultsPreference; @@ -76,7 +76,19 @@ public class AppLaunchSettings extends AppInfoWithHeader implements OnClickListe addPreferencesFromResource(R.xml.installed_app_launch_settings); mAppDomainUrls = (AppDomainsPreference) findPreference(KEY_SUPPORTED_DOMAIN_URLS); mClearDefaultsPreference = (ClearDefaultsPreference) findPreference(KEY_CLEAR_DEFAULTS); - mAppLinkState = (DropDownPreference) findPreference(KEY_APP_LINK_STATE); + mAppLinkState = findPreference(KEY_APP_LINK_STATE); + mAppLinkState.setOnPreferenceClickListener(preference -> { + final Bundle args = new Bundle(); + args.putParcelable(KEY_PACKAGE_INFO, this.mPackageInfo); + + new SubSettingLauncher(this.getContext()) + .setDestination(FRAGMENT_OPEN_SUPPORTED_LINKS) + .setArguments(args) + .setSourceMetricsCategory(SettingsEnums.APPLICATIONS_APP_LAUNCH) + .setTitleRes(-1) + .launch(); + return true; + }); mPm = getActivity().getPackageManager(); @@ -85,13 +97,17 @@ public class AppLaunchSettings extends AppInfoWithHeader implements OnClickListe (mAppEntry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0; if (!mIsBrowser) { - List iviList = mPm.getIntentFilterVerifications(mPackageName); - List filters = mPm.getAllIntentFilters(mPackageName); - CharSequence[] entries = getEntries(mPackageName, iviList, filters); + 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); } - buildStateDropDown(); } // An app is a "browser" if it has an activity resolution that wound up @@ -110,95 +126,35 @@ public class AppLaunchSettings extends AppInfoWithHeader implements OnClickListe return false; } - private void buildStateDropDown() { - if (mIsBrowser) { - // Browsers don't show the app-link prefs - mAppLinkState.setShouldDisableView(true); - mAppLinkState.setEnabled(false); - mAppDomainUrls.setShouldDisableView(true); - mAppDomainUrls.setEnabled(false); - } else { - // Designed order of states in the dropdown: - // - // * always - // * ask - // * never - // - // Make sure to update linkStateToIndex() if this presentation order is changed. - mAppLinkState.setEntries(new CharSequence[] { - getString(R.string.app_link_open_always), - getString(R.string.app_link_open_ask), - getString(R.string.app_link_open_never), - }); - mAppLinkState.setEntryValues(new CharSequence[] { - Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS), - Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK), - Integer.toString(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER), - }); - - mAppLinkState.setEnabled(mHasDomainUrls); - if (mHasDomainUrls) { - // Present 'undefined' as 'ask' because the OS treats them identically for - // purposes of the UI (and does the right thing around pending domain - // verifications that might arrive after the user chooses 'ask' in this UI). - final int state = mPm.getIntentVerificationStatusAsUser(mPackageName, UserHandle.myUserId()); - mAppLinkState.setValueIndex(linkStateToIndex(state)); - - // Set the callback only after setting the initial selected item - mAppLinkState.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - return updateAppLinkState(Integer.parseInt((String) newValue)); - } - }); - } - } - } - - private int linkStateToIndex(final int state) { + private int linkStateToResourceId(int state) { switch (state) { case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: - return 0; // Always + return R.string.app_link_open_always; // Always case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: - return 2; // Never + return R.string.app_link_open_never; // Never default: - return 1; // Ask + return R.string.app_link_open_ask; // Ask } } - private boolean updateAppLinkState(final int newState) { - if (mIsBrowser) { - // We shouldn't get into this state, but if we do make sure - // not to cause any permanent mayhem. - return false; - } - - final int userId = UserHandle.myUserId(); - final int priorState = mPm.getIntentVerificationStatusAsUser(mPackageName, userId); - - if (priorState == newState) { - return false; - } - - boolean success = mPm.updateIntentVerificationStatusAsUser(mPackageName, newState, userId); - if (success) { - // Read back the state to see if the change worked - final int updatedState = mPm.getIntentVerificationStatusAsUser(mPackageName, userId); - success = (newState == updatedState); - } else { - Log.e(TAG, "Couldn't update intent verification status!"); - } - return success; - } - - private CharSequence[] getEntries(String packageName, List iviList, - List filters) { + 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()); + Log.d("[sunny]", "setAppLinkStateSummary+ state=" + state); + 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; diff --git a/src/com/android/settings/applications/AppOpenSupportedLinksPreferenceController.java b/src/com/android/settings/applications/AppOpenSupportedLinksPreferenceController.java new file mode 100644 index 00000000000..479d5dd0b04 --- /dev/null +++ b/src/com/android/settings/applications/AppOpenSupportedLinksPreferenceController.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2020 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_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.widget.RadioButtonPreference; + +/** + * The radio group controller supports users to choose what kind supported links they need. + */ +public class AppOpenSupportedLinksPreferenceController extends BasePreferenceController + implements RadioButtonPreference.OnClickListener { + private static final String TAG = "OpenLinksPrefCtrl"; + private static final String KEY_LINK_OPEN_ALWAYS = "app_link_open_always"; + private static final String KEY_LINK_OPEN_ASK = "app_link_open_ask"; + private static final String KEY_LINK_OPEN_NEVER = "app_link_open_never"; + + private Context mContext; + private PackageManager mPackageManager; + private String mPackageName; + private int mCurrentIndex; + private PreferenceCategory mPreferenceCategory; + private String[] mRadioKeys = {KEY_LINK_OPEN_ALWAYS, KEY_LINK_OPEN_ASK, KEY_LINK_OPEN_NEVER}; + + @VisibleForTesting + RadioButtonPreference mAllowOpening; + @VisibleForTesting + RadioButtonPreference mAskEveryTime; + @VisibleForTesting + RadioButtonPreference mNotAllowed; + + public AppOpenSupportedLinksPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mContext = context; + mPackageManager = context.getPackageManager(); + } + + /** + * @param pkg selected package name. + * @return return controller-self. + */ + public AppOpenSupportedLinksPreferenceController setInit(String pkg) { + mPackageName = pkg; + return this; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreferenceCategory = screen.findPreference(getPreferenceKey()); + mAllowOpening = makeRadioPreference(KEY_LINK_OPEN_ALWAYS, R.string.app_link_open_always); + final int entriesNo = getEntriesNo(); + //This to avoid the summary line wrap + mAllowOpening.setAppendixVisibility(View.GONE); + mAllowOpening.setSummary( + mContext.getResources().getQuantityString(R.plurals.app_link_open_always_summary, + entriesNo, entriesNo)); + mAskEveryTime = makeRadioPreference(KEY_LINK_OPEN_ASK, R.string.app_link_open_ask); + mNotAllowed = makeRadioPreference(KEY_LINK_OPEN_NEVER, R.string.app_link_open_never); + + final int state = mPackageManager.getIntentVerificationStatusAsUser(mPackageName, + UserHandle.myUserId()); + mCurrentIndex = linkStateToIndex(state); + setRadioStatus(mCurrentIndex); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void onRadioButtonClicked(RadioButtonPreference preference) { + final int clickedIndex = preferenceKeyToIndex(preference.getKey()); + if (mCurrentIndex != clickedIndex) { + mCurrentIndex = clickedIndex; + setRadioStatus(mCurrentIndex); + updateAppLinkState(indexToLinkState(mCurrentIndex)); + } + } + + private RadioButtonPreference makeRadioPreference(String key, int resourceId) { + RadioButtonPreference pref = new RadioButtonPreference(mPreferenceCategory.getContext()); + pref.setKey(key); + pref.setTitle(resourceId); + pref.setOnClickListener(this); + mPreferenceCategory.addPreference(pref); + return pref; + } + + private int linkStateToIndex(int state) { + switch (state) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: + return 0; // Always + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: + return 2; // Never + default: + return 1; // Ask + } + } + + private int indexToLinkState(int index) { + switch (index) { + case 0: + return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; + case 2: + return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; + default: + return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; + } + } + + private int preferenceKeyToIndex(String key) { + for (int i = 0; i < mRadioKeys.length; i++) { + if (TextUtils.equals(key, mRadioKeys[i])) { + return i; + } + } + return 1; // Ask + } + + private void setRadioStatus(int index) { + mAllowOpening.setChecked(index == 0 ? true : false); + mAskEveryTime.setChecked(index == 1 ? true : false); + mNotAllowed.setChecked(index == 2 ? true : false); + } + + private boolean updateAppLinkState(final int newState) { + final int userId = UserHandle.myUserId(); + final int priorState = mPackageManager.getIntentVerificationStatusAsUser(mPackageName, + userId); + + if (priorState == newState) { + return false; + } + + boolean success = mPackageManager.updateIntentVerificationStatusAsUser(mPackageName, + newState, userId); + if (success) { + // Read back the state to see if the change worked + final int updatedState = mPackageManager.getIntentVerificationStatusAsUser(mPackageName, + userId); + success = (newState == updatedState); + } else { + Log.e(TAG, "Couldn't update intent verification status!"); + } + return success; + } + + @VisibleForTesting + int getEntriesNo() { + return Utils.getHandledDomains(mPackageManager, mPackageName).size(); + } +} diff --git a/src/com/android/settings/applications/OpenSupportedLinks.java b/src/com/android/settings/applications/OpenSupportedLinks.java new file mode 100644 index 00000000000..0e1531f8d0c --- /dev/null +++ b/src/com/android/settings/applications/OpenSupportedLinks.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.applications; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.os.Bundle; +import android.util.ArraySet; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.widget.FooterPreference; + +/** + * Display the Open Supported Links page. Allow users choose what kind supported links they need. + */ +public class OpenSupportedLinks extends DashboardFragment { + private static final String TAG = "OpenSupportedLinks"; + private static final String FOOTER_KEY = "supported_links_footer"; + + @VisibleForTesting + PackageInfo mPackageInfo; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + final Bundle args = getArguments(); + mPackageInfo = (args != null) ? args.getParcelable(AppLaunchSettings.KEY_PACKAGE_INFO) + : null; + if (mPackageInfo == null) { + Log.w(TAG, "Missing PackageInfo; maybe reinstalling?"); + return; + } + use(AppHeaderPreferenceController.class).setParentFragment(this).setPackageInfo( + mPackageInfo).setLifeCycle(getSettingsLifecycle()); + use(AppOpenSupportedLinksPreferenceController.class).setInit(mPackageInfo.packageName); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + super.onCreatePreferences(savedInstanceState, rootKey); + final FooterPreference footer = findPreference(FOOTER_KEY); + if (footer == null) { + Log.w(TAG, "Can't find the footer preference."); + return; + } + addLinksToFooter(footer); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.open_supported_links; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.OPEN_SUPPORTED_LINKS; + } + + @VisibleForTesting + void addLinksToFooter(FooterPreference footer) { + final ArraySet result = Utils.getHandledDomains(getPackageManager(), + mPackageInfo.packageName); + if (result.isEmpty()) { + Log.w(TAG, "Can't find any app links."); + return; + } + CharSequence title = footer.getTitle() + System.lineSeparator(); + for (String link : result) { + title = title + System.lineSeparator() + link; + } + footer.setTitle(title); + } +} diff --git a/tests/robotests/src/com/android/settings/applications/AppOpenSupportedLinksPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/AppOpenSupportedLinksPreferenceControllerTest.java new file mode 100644 index 00000000000..93167354206 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/AppOpenSupportedLinksPreferenceControllerTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 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_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.ArraySet; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.shadow.ShadowUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +public class AppOpenSupportedLinksPreferenceControllerTest { + private static final String TEST_KEY = "test_key"; + private static final String TEST_DOMAIN_LINK = "aaa.bbb.ccc"; + private static final String TEST_PACKAGE = "ssl.test.package.com"; + + @Mock + private PackageManager mPackageManager; + + private Context mContext; + private PreferenceManager mPreferenceManager; + private PreferenceScreen mScreen; + private PreferenceCategory mCategory; + private AppOpenSupportedLinksPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + doReturn(mPackageManager).when(mContext).getPackageManager(); + mPreferenceManager = new PreferenceManager(mContext); + mScreen = spy(mPreferenceManager.createPreferenceScreen(mContext)); + mCategory = spy(new PreferenceCategory(mContext)); + mController = spy(new AppOpenSupportedLinksPreferenceController(mContext, TEST_KEY)); + mController.setInit(TEST_PACKAGE); + } + + @Test + public void displayPreference_statusAlways_allowOpenChecked() { + init(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS); + + mController.displayPreference(mScreen); + + assertThat(mController.mAllowOpening.isChecked()).isTrue(); + assertThat(mController.mAskEveryTime.isChecked()).isFalse(); + assertThat(mController.mNotAllowed.isChecked()).isFalse(); + } + + @Test + public void displayPreference_statusAsk_askEveryTimeChecked() { + init(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK); + + mController.displayPreference(mScreen); + + assertThat(mController.mAllowOpening.isChecked()).isFalse(); + assertThat(mController.mAskEveryTime.isChecked()).isTrue(); + assertThat(mController.mNotAllowed.isChecked()).isFalse(); + } + + @Test + public void displayPreference_statusNever_notAllowedChecked() { + init(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER); + + mController.displayPreference(mScreen); + + assertThat(mController.mAllowOpening.isChecked()).isFalse(); + assertThat(mController.mAskEveryTime.isChecked()).isFalse(); + assertThat(mController.mNotAllowed.isChecked()).isTrue(); + } + + @Test + @Config(shadows = ShadowUtils.class) + public void getEntriesNo_oneHandledDomains_returnOne() { + initHandledDomains(); + + assertThat(mController.getEntriesNo()).isEqualTo(1); + } + + private void init(int status) { + doReturn(mCategory).when(mScreen).findPreference(any(CharSequence.class)); + doReturn(true).when(mCategory).addPreference(any(Preference.class)); + when(mPackageManager.getIntentVerificationStatusAsUser(anyString(), anyInt())).thenReturn( + status); + } + + private void initHandledDomains() { + final ArraySet domainLinks = new ArraySet<>(); + domainLinks.add(TEST_DOMAIN_LINK); + ShadowUtils.setHandledDomains(domainLinks); + } +} diff --git a/tests/robotests/src/com/android/settings/applications/OpenSupportedLinksTest.java b/tests/robotests/src/com/android/settings/applications/OpenSupportedLinksTest.java new file mode 100644 index 00000000000..f9d4ca83a41 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/OpenSupportedLinksTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 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 com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.ArraySet; + +import com.android.settings.testutils.shadow.ShadowUtils; +import com.android.settingslib.widget.FooterPreference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowUtils.class) +public class OpenSupportedLinksTest { + private static final String TEST_FOOTER_TITLE = "FooterTitle"; + private static final String TEST_DOMAIN_LINK = "aaa.bbb.ccc"; + + private Context mContext; + private TestFragment mSettings; + private FooterPreference mFooter; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mSettings = spy(new TestFragment(mContext)); + mFooter = new FooterPreference.Builder(mContext).setTitle(TEST_FOOTER_TITLE).build(); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + } + + @Test + public void addLinksToFooter_noHandledDomains_returnDefaultFooterTitle() { + mSettings.addLinksToFooter(mFooter); + + assertThat(mFooter.getTitle()).isEqualTo(TEST_FOOTER_TITLE); + } + + @Test + public void addLinksToFooter_oneHandledDomains_returnDomainsFooterTitle() { + final ArraySet domainLinks = new ArraySet<>(); + domainLinks.add(TEST_DOMAIN_LINK); + ShadowUtils.setHandledDomains(domainLinks); + + mSettings.addLinksToFooter(mFooter); + + assertThat(mFooter.getTitle().toString()).contains(TEST_DOMAIN_LINK); + } + + public static class TestFragment extends OpenSupportedLinks { + private final Context mContext; + + public TestFragment(Context context) { + mContext = context; + mPackageInfo = new PackageInfo(); + mPackageInfo.packageName = "ssl.test.package.com"; + } + + @Override + protected PackageManager getPackageManager() { + return mContext.getPackageManager(); + } + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java index cf96aba3c81..c1f33c6bdde 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java @@ -18,9 +18,11 @@ package com.android.settings.testutils.shadow; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; import android.os.UserManager; +import android.util.ArraySet; import com.android.settings.Utils; @@ -40,6 +42,7 @@ public class ShadowUtils { private static Map sAppNameMap; private static boolean sIsSystemAlertWindowEnabled; private static boolean sIsVoiceCapable; + private static ArraySet sResultLinks = new ArraySet<>(); @Implementation protected static int enforceSameOwner(Context context, int userId) { @@ -60,6 +63,7 @@ public class ShadowUtils { sIsUserAMonkey = false; sIsDemoUser = false; sIsVoiceCapable = false; + sResultLinks = new ArraySet<>(); } public static void setIsDemoUser(boolean isDemoUser) { @@ -134,4 +138,13 @@ public class ShadowUtils { public static void setIsVoiceCapable(boolean isVoiceCapable) { sIsVoiceCapable = isVoiceCapable; } + + @Implementation + protected static ArraySet getHandledDomains(PackageManager pm, String packageName) { + return sResultLinks; + } + + public static void setHandledDomains(ArraySet links) { + sResultLinks = links; + } }