Resolver - Improve flow for Intent URL handling

When a URL is detected and all resolved targets are browsers, allow
the user to set a default browser from the Resolver. If one of the
options is an App and not a browser (as defined by
ResolveInfo.handleAllWebDataURI) then direct the user to the settings
page where they can make changes to set the default app for particular
URLs.

On the settings page, highlight the correct setting to change so it's
clear to the user.

Bug: 132071949
Test: Manually resolve URLs with multiple browsers and apps like Reddit/Twitter
Change-Id: Ib8f867a020ecfbb73fa5f1d7db84890c72e4842d
This commit is contained in:
Matt Pietal
2019-06-27 13:27:52 -04:00
parent d701e951e0
commit 0d6834adfe
4 changed files with 90 additions and 79 deletions

View File

@@ -29,7 +29,6 @@ import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -111,7 +110,6 @@ public class ResolverActivity extends Activity {
protected AbsListView mAdapterView;
private Button mAlwaysButton;
private Button mOnceButton;
private Button mSettingsButton;
protected View mProfileView;
private int mIconDpi;
private int mLastSelected = AbsListView.INVALID_POSITION;
@@ -146,6 +144,10 @@ public class ResolverActivity extends Activity {
/** See {@link #setRetainInOnStop}. */
private boolean mRetainInOnStop;
private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state";
private final PackageMonitor mPackageMonitor = createPackageMonitor();
/**
@@ -196,9 +198,13 @@ public class ResolverActivity extends Activity {
// titles for layout that deals with http(s) intents
public static final int BROWSABLE_TITLE_RES =
com.android.internal.R.string.whichGiveAccessToApplication;
public static final int BROWSABLE_NAMED_TITLE_RES =
com.android.internal.R.string.whichGiveAccessToApplicationNamed;
com.android.internal.R.string.whichOpenLinksWith;
public static final int BROWSABLE_HOST_TITLE_RES =
com.android.internal.R.string.whichOpenHostLinksWith;
public static final int BROWSABLE_HOST_APP_TITLE_RES =
com.android.internal.R.string.whichOpenHostLinksWithApp;
public static final int BROWSABLE_APP_TITLE_RES =
com.android.internal.R.string.whichOpenLinksWithApp;
public final String action;
public final int titleRes;
@@ -322,9 +328,7 @@ public class ResolverActivity extends Activity {
? false
: isHttpSchemeAndViewAction(getTargetIntent());
// We don't want to support Always Use if browsable layout is being used,
// as to mitigate Intent Capturing vulnerability
mSupportsAlwaysUseOption = supportsAlwaysUseOption && !mUseLayoutForBrowsables;
mSupportsAlwaysUseOption = supportsAlwaysUseOption;
if (configureContentView(mIntents, initialIntents, rList)) {
return;
@@ -554,10 +558,21 @@ public class ResolverActivity extends Activity {
} else if (isHttpSchemeAndViewAction(intent)) {
// If the Intent's scheme is http(s) then we need to warn the user that
// they're giving access for the activity to open URLs from this specific host
return named
? getString(ActionTitle.BROWSABLE_NAMED_TITLE_RES, intent.getData().getHost(),
mAdapter.getFilteredItem().getDisplayLabel())
: getString(ActionTitle.BROWSABLE_TITLE_RES, intent.getData().getHost());
String dialogTitle = null;
if (named && !mUseLayoutForBrowsables) {
dialogTitle = getString(ActionTitle.BROWSABLE_APP_TITLE_RES,
mAdapter.getFilteredItem().getDisplayLabel());
} else if (named && mUseLayoutForBrowsables) {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_APP_TITLE_RES,
intent.getData().getHost(),
mAdapter.getFilteredItem().getDisplayLabel());
} else if (mAdapter.areAllTargetsBrowsers()) {
dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
} else {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
intent.getData().getHost());
}
return dialogTitle;
} else {
return named
? getString(title.namedTitleRes, mAdapter.getFilteredItem().getDisplayLabel())
@@ -856,6 +871,13 @@ public class ResolverActivity extends Activity {
} else {
enabled = true;
}
if (mUseLayoutForBrowsables && !ri.handleAllWebDataURI) {
mAlwaysButton.setText(getResources()
.getString(R.string.activity_resolver_set_always));
} else {
mAlwaysButton.setText(getResources()
.getString(R.string.activity_resolver_use_always));
}
}
mAlwaysButton.setEnabled(enabled);
}
@@ -866,26 +888,29 @@ public class ResolverActivity extends Activity {
? mAdapter.getFilteredPosition()
: mAdapterView.getCheckedItemPosition();
boolean hasIndexBeenFiltered = !mAdapter.hasFilteredItem();
if (id == R.id.button_app_settings) {
showSettingsForSelected(which, hasIndexBeenFiltered);
ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
if (!ri.handleAllWebDataURI && id == R.id.button_always) {
showSettingsForSelected(ri);
} else {
startSelected(which, id == R.id.button_always, hasIndexBeenFiltered);
}
}
private void showSettingsForSelected(int which, boolean hasIndexBeenFiltered) {
ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
private void showSettingsForSelected(ResolveInfo ri) {
Intent intent = new Intent();
// For browsers, we open the Default Browser page
final String packageName = ri.activityInfo.packageName;
Bundle showFragmentArgs = new Bundle();
showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, OPEN_LINKS_COMPONENT_KEY);
showFragmentArgs.putString("package", packageName);
// For regular apps, we open the Open by Default page
if (ri.handleAllWebDataURI) {
intent.setAction(Intent.ACTION_MANAGE_DEFAULT_APP)
.putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_BROWSER);
} else {
intent.setAction(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
.setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
}
intent.setAction(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS)
.setData(Uri.fromParts("package", packageName, null))
.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
.putExtra(EXTRA_FRAGMENT_ARG_KEY, OPEN_LINKS_COMPONENT_KEY)
.putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs);
startActivity(intent);
}
@@ -1332,41 +1357,15 @@ public class ResolverActivity extends Activity {
R.dimen.resolver_button_bar_spacing) + inset);
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
mSettingsButton = (Button) buttonLayout.findViewById(R.id.button_app_settings);
mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
if (mUseLayoutForBrowsables) {
resetSettingsOrOnceButtonBar();
} else {
resetAlwaysOrOnceButtonBar();
}
resetAlwaysOrOnceButtonBar();
} else {
Log.e(TAG, "Layout unexpectedly does not have a button bar");
}
}
private void resetSettingsOrOnceButtonBar() {
//unsetting always button
mAlwaysButton.setVisibility(View.GONE);
// When the items load in, if an item was already selected,
// enable the buttons
if (mAdapterView != null
&& mAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
mSettingsButton.setEnabled(true);
mOnceButton.setEnabled(true);
}
}
private void resetAlwaysOrOnceButtonBar() {
// This check needs to be made because layout with default
// doesn't have a settings button
if (mSettingsButton != null) {
//unsetting always button
mSettingsButton.setVisibility(View.GONE);
mSettingsButton = null;
}
if (useLayoutWithDefault()
&& mAdapter.getFilteredPosition() != ListView.INVALID_POSITION) {
setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
@@ -1626,6 +1625,7 @@ public class ResolverActivity extends Activity {
private DisplayResolveInfo mOtherProfile;
private ResolverListController mResolverListController;
private int mPlaceholderCount;
private boolean mAllTargetsAreBrowsers = false;
protected final LayoutInflater mInflater;
@@ -1700,6 +1700,14 @@ public class ResolverActivity extends Activity {
mResolverListController.updateChooserCounts(packageName, userId, action);
}
/**
* @return true if all items in the display list are defined as browsers by
* ResolveInfo.handleAllWebDataURI
*/
public boolean areAllTargetsBrowsers() {
return mAllTargetsAreBrowsers;
}
/**
* Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
* to complete.
@@ -1712,6 +1720,7 @@ public class ResolverActivity extends Activity {
mOtherProfile = null;
mLastChosen = null;
mLastChosenPosition = -1;
mAllTargetsAreBrowsers = false;
mDisplayList.clear();
if (mBaseResolveList != null) {
currentResolveList = mUnfilteredResolveList = new ArrayList<>();
@@ -1812,6 +1821,8 @@ public class ResolverActivity extends Activity {
private void processSortedList(List<ResolvedComponentInfo> sortedComponents) {
int N;
if (sortedComponents != null && (N = sortedComponents.size()) != 0) {
mAllTargetsAreBrowsers = mUseLayoutForBrowsables;
// First put the initial items at the top.
if (mInitialIntents != null) {
for (int i = 0; i < mInitialIntents.length; i++) {
@@ -1841,6 +1852,8 @@ public class ResolverActivity extends Activity {
ri.noResourceId = true;
ri.icon = 0;
}
mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
addResolveInfo(new DisplayResolveInfo(ii, ri,
ri.loadLabel(getPackageManager()), null, ii));
}
@@ -1850,6 +1863,8 @@ public class ResolverActivity extends Activity {
for (ResolvedComponentInfo rci : sortedComponents) {
final ResolveInfo ri = rci.getResolveInfoAt(0);
if (ri != null) {
mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
ResolveInfoPresentationGetter pg = makePresentationGetter(ri);
addResolveInfoWithAlternates(rci, pg.getSubLabel(), pg.getLabel());
}
@@ -2152,14 +2167,8 @@ public class ResolverActivity extends Activity {
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
if (!useLayoutWithDefault()
&& (!hasValidSelection || mLastSelected != checkedPos)
&& (mAlwaysButton != null || mSettingsButton != null)) {
if (mSettingsButton != null) {
// this implies that the layout for browsables is being used
mSettingsButton.setEnabled(true);
} else {
// this implies that mAlwaysButton != null
setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
}
&& mAlwaysButton != null) {
setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
mOnceButton.setEnabled(hasValidSelection);
if (hasValidSelection) {
mAdapterView.smoothScrollToPosition(checkedPos);

View File

@@ -129,18 +129,6 @@
android:enabled="false"
android:text="@string/activity_resolver_use_always"
android:onClick="onButtonClick" />
<Button
android:id="@+id/button_app_settings"
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
android:minHeight="@dimen/alert_dialog_button_bar_height"
style="?attr/buttonBarPositiveButtonStyle"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/activity_resolver_app_settings"
android:onClick="onButtonClick" />
</LinearLayout>
</com.android.internal.widget.ResolverDrawerLayout>

View File

@@ -3074,12 +3074,20 @@
<string name="whichViewApplicationNamed">Open with %1$s</string>
<!-- Label for a link to a intent resolver dialog to view something -->
<string name="whichViewApplicationLabel">Open</string>
<!-- Title of intent resolver dialog when selecting a viewer application that opens URI
<!-- Title of intent resolver dialog when selecting a browser/application that opens specific URIs
[CHAR LIMIT=128]. -->
<string name="whichGiveAccessToApplication">Give access to open <xliff:g id="host" example="mail.google.com">%1$s</xliff:g> links with</string>
<string name="whichOpenHostLinksWith">Open <xliff:g id="host" example="mail.google.com">%1$s</xliff:g> links with</string>
<!-- Title of intent resolver dialog when selecting a browser that opens URI
[CHAR LIMIT=128]. -->
<string name="whichOpenLinksWith">Open links with</string>
<!-- Title of intent resolver dialog when defaulting to a specific browser that opens URI
[CHAR LIMIT=128]. -->
<string name="whichOpenLinksWithApp">Open links with <xliff:g id="application" example="Chrome">%1$s</xliff:g></string>
<!-- Title of intent resolver dialog when defaulting to a specific browser that opens URI
[CHAR LIMIT=128]. -->
<string name="whichOpenHostLinksWithApp">Open <xliff:g id="host" example="mail.google.com">%1$s</xliff:g> links with <xliff:g id="application" example="Chrome">%2$s</xliff:g></string>
<!-- Title of intent resolver dialog when selecting a viewer application that opens URI
and a previously used application is known [CHAR LIMIT=128]. -->
<string name="whichGiveAccessToApplicationNamed">Give access to open <xliff:g id="host" example="mail.google.com">%1$s</xliff:g> links with <xliff:g id="application" example="Gmail">%2$s</xliff:g></string>
<!-- Label for a link to an intent resolver dialog to open URI [CHAR LIMIT=18] -->
<string name="whichGiveAccessToApplicationLabel">Give access</string>
<!-- Title of intent resolver dialog when selecting an editor application to run. -->
@@ -4211,6 +4219,10 @@
as the default in the activity resolver. [CHAR LIMIT=25] -->
<string name="activity_resolver_use_always">Always</string>
<!-- Title for a button to choose the currently selected activity
as the default in the activity resolver. [CHAR LIMIT=50] -->
<string name="activity_resolver_set_always">Set to always open</string>
<!-- Title for a button to choose the currently selected activity
from the activity resolver to use just this once. [CHAR LIMIT=25] -->
<string name="activity_resolver_use_once">Just once</string>

View File

@@ -2238,7 +2238,6 @@
<java-symbol type="id" name="resolver_list" />
<java-symbol type="id" name="button_once" />
<java-symbol type="id" name="button_always" />
<java-symbol type="id" name="button_app_settings" />
<java-symbol type="integer" name="config_globalActionsKeyTimeout" />
<java-symbol type="integer" name="config_screenshotChordKeyTimeout" />
<java-symbol type="integer" name="config_maxResolverActivityColumns" />
@@ -2623,14 +2622,17 @@
<java-symbol type="bool" name="config_use_voip_mode_for_ims" />
<java-symbol type="attr" name="touchscreenBlocksFocus" />
<java-symbol type="layout" name="resolver_list_with_default" />
<java-symbol type="string" name="activity_resolver_app_settings" />
<java-symbol type="string" name="activity_resolver_set_always" />
<java-symbol type="string" name="activity_resolver_use_always" />
<java-symbol type="string" name="whichApplicationNamed" />
<java-symbol type="string" name="whichApplicationLabel" />
<java-symbol type="string" name="whichViewApplication" />
<java-symbol type="string" name="whichViewApplicationNamed" />
<java-symbol type="string" name="whichViewApplicationLabel" />
<java-symbol type="string" name="whichGiveAccessToApplication" />
<java-symbol type="string" name="whichGiveAccessToApplicationNamed" />
<java-symbol type="string" name="whichOpenHostLinksWith" />
<java-symbol type="string" name="whichOpenHostLinksWithApp" />
<java-symbol type="string" name="whichOpenLinksWith" />
<java-symbol type="string" name="whichOpenLinksWithApp" />
<java-symbol type="string" name="whichGiveAccessToApplicationLabel" />
<java-symbol type="string" name="whichEditApplication" />
<java-symbol type="string" name="whichEditApplicationNamed" />