From 1ee2a88fd7a68f264bbd5799e39a22f7b19b5cfe Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 20 Mar 2018 18:07:31 -0700 Subject: [PATCH] Hide spannable link if it's not actionable Created a new constructor that detects if intent is actionable and set a flag in LinkInfo. In fragments, first check if linkInfo is actionable, if not, don't do anything with it. Change-Id: Ibda12ecac2545d696acc7c197fc315e423b984aa Fixes: 74726487 Test: make RunSettingsRoboTests -j --- .../fingerprint/FingerprintSettings.java | 23 ++----- .../network/PrivateDnsModeDialogFragment.java | 30 ++++------ .../settings/utils/AnnotationSpan.java | 45 ++++++++++++-- .../PrivateDnsModeDialogFragmentTest.java | 3 + .../settings/utils/AnnotationSpanTest.java | 60 +++++++++++++++++++ 5 files changed, 117 insertions(+), 44 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/utils/AnnotationSpanTest.java diff --git a/src/com/android/settings/fingerprint/FingerprintSettings.java b/src/com/android/settings/fingerprint/FingerprintSettings.java index caad9886664..6eaf3d2d556 100644 --- a/src/com/android/settings/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/fingerprint/FingerprintSettings.java @@ -21,7 +21,6 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.admin.DevicePolicyManager; -import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -99,8 +98,6 @@ public class FingerprintSettings extends SubSettings { public static final String ANNOTATION_URL = "url"; public static final String ANNOTATION_ADMIN_DETAILS = "admin_details"; - public static final String KEY_FINGERPRINT_SETTINGS = "fingerprint_settings"; - @Override public Intent getIntent() { Intent modIntent = new Intent(super.getIntent()); @@ -158,20 +155,6 @@ public class FingerprintSettings extends SubSettings { private FingerprintRemoveSidecar mRemovalSidecar; private HashMap mFingerprintsRenaming; - final AnnotationSpan.LinkInfo mUrlLinkInfo = new AnnotationSpan.LinkInfo( - ANNOTATION_URL, (view) -> { - final Context context = view.getContext(); - Intent intent = HelpUtils.getHelpIntent(context, getString(getHelpResource()), - context.getClass().getName()); - if (intent != null) { - try { - view.startActivityForResult(intent, 0); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Activity was not found for intent, " + intent.toString()); - } - } - }); - FingerprintAuthenticateSidecar.Listener mAuthenticateListener = new FingerprintAuthenticateSidecar.Listener() { @Override @@ -360,11 +343,15 @@ public class FingerprintSettings extends SubSettings { ANNOTATION_ADMIN_DETAILS, (view) -> { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(activity, admin); }); + final Intent helpIntent = HelpUtils.getHelpIntent( + activity, getString(getHelpResource()), activity.getClass().getName()); + final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo( + activity, ANNOTATION_URL, helpIntent); pref.setTitle(AnnotationSpan.linkify(getText(admin != null ? R.string .security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled : R.string.security_settings_fingerprint_enroll_disclaimer), - mUrlLinkInfo, adminLinkInfo)); + linkInfo, adminLinkInfo)); } protected void removeFingerprintPreference(int fingerprintId) { diff --git a/src/com/android/settings/network/PrivateDnsModeDialogFragment.java b/src/com/android/settings/network/PrivateDnsModeDialogFragment.java index 8b7ccce38cf..00950c3adc7 100644 --- a/src/com/android/settings/network/PrivateDnsModeDialogFragment.java +++ b/src/com/android/settings/network/PrivateDnsModeDialogFragment.java @@ -22,7 +22,6 @@ import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME import android.app.AlertDialog; import android.app.Dialog; import android.app.FragmentManager; -import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -33,7 +32,6 @@ import android.support.annotation.VisibleForTesting; import android.text.Editable; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; @@ -58,7 +56,7 @@ public class PrivateDnsModeDialogFragment extends InstrumentedDialogFragment imp public static final String ANNOTATION_URL = "url"; - private static final String TAG = "PrivateDnsModeDialogFragment"; + private static final String TAG = "PrivateDnsModeDialog"; // DNS_MODE -> RadioButton id private static final Map PRIVATE_DNS_MAP; @@ -83,21 +81,6 @@ public class PrivateDnsModeDialogFragment extends InstrumentedDialogFragment imp @VisibleForTesting String mMode; - private final AnnotationSpan.LinkInfo mUrlLinkInfo = new AnnotationSpan.LinkInfo( - ANNOTATION_URL, (widget) -> { - final Context context = widget.getContext(); - final Intent intent = HelpUtils.getHelpIntent(context, - getString(R.string.help_uri_private_dns), - context.getClass().getName()); - if (intent != null) { - try { - widget.startActivityForResult(intent, 0); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Activity was not found for intent, " + intent.toString()); - } - } - }); - public static void show(FragmentManager fragmentManager) { if (fragmentManager.findFragmentByTag(TAG) == null) { final PrivateDnsModeDialogFragment fragment = new PrivateDnsModeDialogFragment(); @@ -139,8 +122,15 @@ public class PrivateDnsModeDialogFragment extends InstrumentedDialogFragment imp final TextView helpTextView = view.findViewById(R.id.private_dns_help_info); helpTextView.setMovementMethod(LinkMovementMethod.getInstance()); - helpTextView.setText(AnnotationSpan.linkify( - context.getText(R.string.private_dns_help_message), mUrlLinkInfo)); + final Intent helpIntent = HelpUtils.getHelpIntent(context, + context.getString(R.string.help_uri_private_dns), + context.getClass().getName()); + final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo(context, + ANNOTATION_URL, helpIntent); + if (linkInfo.isActionable()) { + helpTextView.setText(AnnotationSpan.linkify( + context.getText(R.string.private_dns_help_message), linkInfo)); + } return view; } diff --git a/src/com/android/settings/utils/AnnotationSpan.java b/src/com/android/settings/utils/AnnotationSpan.java index 645351df0b9..c70cba53bee 100644 --- a/src/com/android/settings/utils/AnnotationSpan.java +++ b/src/com/android/settings/utils/AnnotationSpan.java @@ -16,11 +16,15 @@ package com.android.settings.utils; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; import android.text.Annotation; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextPaint; import android.text.style.URLSpan; +import android.util.Log; import android.view.View; /** @@ -28,6 +32,7 @@ import android.view.View; * annotation. */ public class AnnotationSpan extends URLSpan { + private final View.OnClickListener mClickListener; private AnnotationSpan(View.OnClickListener lsn) { @@ -58,8 +63,8 @@ public class AnnotationSpan extends URLSpan { int end = msg.getSpanEnd(annotation); AnnotationSpan link = null; for (LinkInfo linkInfo : linkInfos) { - if (linkInfo.annotation.equals(key)) { - link = new AnnotationSpan(linkInfo.listener); + if (linkInfo.mAnnotation.equals(key)) { + link = new AnnotationSpan(linkInfo.mListener); break; } } @@ -74,12 +79,40 @@ public class AnnotationSpan extends URLSpan { * Data class to store the annotation and the click action */ public static class LinkInfo { - public final String annotation; - public final View.OnClickListener listener; + private static final String TAG = "AnnotationSpan.LinkInfo"; + private final String mAnnotation; + private final Boolean mActionable; + private final View.OnClickListener mListener; public LinkInfo(String annotation, View.OnClickListener listener) { - this.annotation = annotation; - this.listener = listener; + mAnnotation = annotation; + mListener = listener; + mActionable = true; // assume actionable + } + + public LinkInfo(Context context, String annotation, Intent intent) { + mAnnotation = annotation; + if (intent != null) { + mActionable = context.getPackageManager() + .resolveActivity(intent, 0 /* flags */) != null; + } else { + mActionable = false; + } + if (!mActionable) { + mListener = null; + } else { + mListener = view -> { + try { + view.startActivityForResult(intent, 0); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity was not found for intent, " + intent); + } + }; + } + } + + public boolean isActionable() { + return mActionable; } } } diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogFragmentTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogFragmentTest.java index 93a96690cae..d490968ca62 100644 --- a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogFragmentTest.java @@ -29,14 +29,17 @@ import android.widget.Button; import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowHelpUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = ShadowHelpUtils.class) public class PrivateDnsModeDialogFragmentTest { private static final String HOST_NAME = "192.168.1.1"; diff --git a/tests/robotests/src/com/android/settings/utils/AnnotationSpanTest.java b/tests/robotests/src/com/android/settings/utils/AnnotationSpanTest.java new file mode 100644 index 00000000000..f1386a5ef89 --- /dev/null +++ b/tests/robotests/src/com/android/settings/utils/AnnotationSpanTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 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.utils; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; + +@RunWith(SettingsRobolectricTestRunner.class) +public class AnnotationSpanTest { + + private Intent mTestIntent; + private Context mContext; + private ShadowPackageManager mPackageManager; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mTestIntent = new Intent("test_action"); + } + + @Test + public void newLinkInfo_validIntent_isActionable() { + mPackageManager.addResolveInfoForIntent(mTestIntent, new ResolveInfo()); + assertThat(new AnnotationSpan.LinkInfo(mContext, "annotation", mTestIntent).isActionable()) + .isTrue(); + } + + @Test + public void newLinkInfo_invalidIntent_isNotActionable() { + assertThat(new AnnotationSpan.LinkInfo(mContext, "annotation", mTestIntent).isActionable()) + .isFalse(); + } +}