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
This commit is contained in:
Fan Zhang
2018-03-20 18:07:31 -07:00
parent b5241a117f
commit 1ee2a88fd7
5 changed files with 117 additions and 44 deletions

View File

@@ -21,7 +21,6 @@ import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; 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_URL = "url";
public static final String ANNOTATION_ADMIN_DETAILS = "admin_details"; public static final String ANNOTATION_ADMIN_DETAILS = "admin_details";
public static final String KEY_FINGERPRINT_SETTINGS = "fingerprint_settings";
@Override @Override
public Intent getIntent() { public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent()); Intent modIntent = new Intent(super.getIntent());
@@ -158,20 +155,6 @@ public class FingerprintSettings extends SubSettings {
private FingerprintRemoveSidecar mRemovalSidecar; private FingerprintRemoveSidecar mRemovalSidecar;
private HashMap<Integer, String> mFingerprintsRenaming; private HashMap<Integer, String> 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 = FingerprintAuthenticateSidecar.Listener mAuthenticateListener =
new FingerprintAuthenticateSidecar.Listener() { new FingerprintAuthenticateSidecar.Listener() {
@Override @Override
@@ -360,11 +343,15 @@ public class FingerprintSettings extends SubSettings {
ANNOTATION_ADMIN_DETAILS, (view) -> { ANNOTATION_ADMIN_DETAILS, (view) -> {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(activity, admin); 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 pref.setTitle(AnnotationSpan.linkify(getText(admin != null
? R.string ? R.string
.security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled .security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled
: R.string.security_settings_fingerprint_enroll_disclaimer), : R.string.security_settings_fingerprint_enroll_disclaimer),
mUrlLinkInfo, adminLinkInfo)); linkInfo, adminLinkInfo));
} }
protected void removeFingerprintPreference(int fingerprintId) { protected void removeFingerprintPreference(int fingerprintId) {

View File

@@ -22,7 +22,6 @@ import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.FragmentManager; import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -33,7 +32,6 @@ import android.support.annotation.VisibleForTesting;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@@ -58,7 +56,7 @@ public class PrivateDnsModeDialogFragment extends InstrumentedDialogFragment imp
public static final String ANNOTATION_URL = "url"; public static final String ANNOTATION_URL = "url";
private static final String TAG = "PrivateDnsModeDialogFragment"; private static final String TAG = "PrivateDnsModeDialog";
// DNS_MODE -> RadioButton id // DNS_MODE -> RadioButton id
private static final Map<String, Integer> PRIVATE_DNS_MAP; private static final Map<String, Integer> PRIVATE_DNS_MAP;
@@ -83,21 +81,6 @@ public class PrivateDnsModeDialogFragment extends InstrumentedDialogFragment imp
@VisibleForTesting @VisibleForTesting
String mMode; 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) { public static void show(FragmentManager fragmentManager) {
if (fragmentManager.findFragmentByTag(TAG) == null) { if (fragmentManager.findFragmentByTag(TAG) == null) {
final PrivateDnsModeDialogFragment fragment = new PrivateDnsModeDialogFragment(); 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); final TextView helpTextView = view.findViewById(R.id.private_dns_help_info);
helpTextView.setMovementMethod(LinkMovementMethod.getInstance()); helpTextView.setMovementMethod(LinkMovementMethod.getInstance());
helpTextView.setText(AnnotationSpan.linkify( final Intent helpIntent = HelpUtils.getHelpIntent(context,
context.getText(R.string.private_dns_help_message), mUrlLinkInfo)); 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; return view;
} }

View File

@@ -16,11 +16,15 @@
package com.android.settings.utils; package com.android.settings.utils;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.text.Annotation; import android.text.Annotation;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextPaint; import android.text.TextPaint;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.util.Log;
import android.view.View; import android.view.View;
/** /**
@@ -28,6 +32,7 @@ import android.view.View;
* annotation. * annotation.
*/ */
public class AnnotationSpan extends URLSpan { public class AnnotationSpan extends URLSpan {
private final View.OnClickListener mClickListener; private final View.OnClickListener mClickListener;
private AnnotationSpan(View.OnClickListener lsn) { private AnnotationSpan(View.OnClickListener lsn) {
@@ -58,8 +63,8 @@ public class AnnotationSpan extends URLSpan {
int end = msg.getSpanEnd(annotation); int end = msg.getSpanEnd(annotation);
AnnotationSpan link = null; AnnotationSpan link = null;
for (LinkInfo linkInfo : linkInfos) { for (LinkInfo linkInfo : linkInfos) {
if (linkInfo.annotation.equals(key)) { if (linkInfo.mAnnotation.equals(key)) {
link = new AnnotationSpan(linkInfo.listener); link = new AnnotationSpan(linkInfo.mListener);
break; break;
} }
} }
@@ -74,12 +79,40 @@ public class AnnotationSpan extends URLSpan {
* Data class to store the annotation and the click action * Data class to store the annotation and the click action
*/ */
public static class LinkInfo { public static class LinkInfo {
public final String annotation; private static final String TAG = "AnnotationSpan.LinkInfo";
public final View.OnClickListener listener; private final String mAnnotation;
private final Boolean mActionable;
private final View.OnClickListener mListener;
public LinkInfo(String annotation, View.OnClickListener listener) { public LinkInfo(String annotation, View.OnClickListener listener) {
this.annotation = annotation; mAnnotation = annotation;
this.listener = listener; 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;
} }
} }
} }

View File

@@ -29,14 +29,17 @@ import android.widget.Button;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowHelpUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = ShadowHelpUtils.class)
public class PrivateDnsModeDialogFragmentTest { public class PrivateDnsModeDialogFragmentTest {
private static final String HOST_NAME = "192.168.1.1"; private static final String HOST_NAME = "192.168.1.1";

View File

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