Add 'miniresolver' UI for cross-profile single app resolution.

When the current profile only has web browsers and the other profile has
a single non-browser app to handle the given intent, simplify the
resolver UI to present the user with a yes/no question about how to
handle.

Canonical example of this is "clicking a youtube link in your work
profile".

Bug: 175433480
Test: atest ResolverActivityTest
Change-Id: I213313ca87a8d1db5e586fe074f953060a91fc19
This commit is contained in:
Matt Casey
2022-01-26 17:13:46 -05:00
parent 915de65194
commit 81e474d7a7
5 changed files with 217 additions and 4 deletions

View File

@@ -1011,7 +1011,9 @@ public class ResolverActivity extends Activity implements
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
ViewPager viewPager = findViewById(R.id.profile_pager);
outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
if (viewPager != null) {
outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
}
}
@Override
@@ -1019,7 +1021,9 @@ public class ResolverActivity extends Activity implements
super.onRestoreInstanceState(savedInstanceState);
resetButtonBar();
ViewPager viewPager = findViewById(R.id.profile_pager);
viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
if (viewPager != null) {
viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
}
mMultiProfilePagerAdapter.clearInactiveProfileCache();
}
@@ -1568,6 +1572,11 @@ public class ResolverActivity extends Activity implements
rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
}
if (shouldUseMiniResolver()) {
configureMiniResolverContent();
return false;
}
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
} else {
@@ -1578,6 +1587,72 @@ public class ResolverActivity extends Activity implements
return postRebuildList(rebuildCompleted);
}
private void configureMiniResolverContent() {
mLayoutId = R.layout.miniresolver;
setContentView(mLayoutId);
DisplayResolveInfo sameProfileResolveInfo =
mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0);
boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
DisplayResolveInfo otherProfileResolveInfo =
mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList.get(0);
ImageView icon = findViewById(R.id.icon);
// TODO: Set icon drawable to app icon.
((TextView) findViewById(R.id.open_cross_profile)).setText(
getResources().getString(
inWorkProfile ? R.string.miniresolver_open_in_personal
: R.string.miniresolver_open_in_work,
otherProfileResolveInfo.getDisplayLabel()));
((Button) findViewById(R.id.use_same_profile_browser)).setText(
inWorkProfile ? R.string.miniresolver_use_work_browser
: R.string.miniresolver_use_personal_browser);
findViewById(R.id.use_same_profile_browser).setOnClickListener(
v -> safelyStartActivity(sameProfileResolveInfo));
findViewById(R.id.button_open).setOnClickListener(v -> {
Intent intent = otherProfileResolveInfo.getResolvedIntent();
if (intent != null) {
prepareIntentForCrossProfileLaunch(intent);
}
safelyStartActivityInternal(otherProfileResolveInfo,
mMultiProfilePagerAdapter.getInactiveListAdapter().mResolverListController
.getUserHandle());
});
}
private boolean shouldUseMiniResolver() {
if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
|| mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
return false;
}
List<DisplayResolveInfo> sameProfileList =
mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList;
List<DisplayResolveInfo> otherProfileList =
mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList;
if (otherProfileList.size() != 1) {
Log.d(TAG, "Found " + otherProfileList.size() + " resolvers in the other profile");
return false;
}
if (otherProfileList.get(0).getResolveInfo().handleAllWebDataURI) {
Log.d(TAG, "Other profile is a web browser");
return false;
}
for (DisplayResolveInfo info : sameProfileList) {
if (!info.getResolveInfo().handleAllWebDataURI) {
Log.d(TAG, "Non-browser found in this profile");
return false;
}
}
return true;
}
/**
* Finishing procedures to be performed after the list has been rebuilt.
* </p>Subclasses must call postRebuildListInternal at the end of postRebuildList.

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<com.android.internal.widget.ResolverDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="@dimen/resolver_max_collapsed_height"
android:maxCollapsedHeightSmall="56dp"
android:id="@id/contentPanel">
<RelativeLayout
android:id="@+id/title_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:elevation="@dimen/resolver_elevation"
android:paddingTop="@dimen/resolver_small_margin"
android:paddingStart="@dimen/resolver_edge_margin"
android:paddingEnd="@dimen/resolver_edge_margin"
android:paddingBottom="@dimen/resolver_title_padding_bottom"
android:background="@drawable/bottomsheet_background">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
/>
<TextView
android:id="@+id/open_cross_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/icon"
android:layout_centerHorizontal="true"
android:textColor="?android:textColorPrimary"
/>
</RelativeLayout>
<LinearLayout
android:id="@+id/button_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:orientation="vertical"
android:background="?attr/colorBackground"
android:layout_ignoreOffset="true">
<View
android:id="@+id/resolver_button_bar_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorBackground"
android:foreground="?attr/dividerVertical" />
<RelativeLayout
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_ignoreOffset="true"
android:layout_hasNestedScrollIndicator="true"
android:gravity="end|center_vertical"
android:orientation="horizontal"
android:layoutDirection="locale"
android:measureWithLargestChild="true"
android:paddingTop="@dimen/resolver_button_bar_spacing"
android:paddingBottom="@dimen/resolver_button_bar_spacing"
android:paddingStart="@dimen/resolver_edge_margin"
android:paddingEnd="@dimen/resolver_small_margin"
android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/use_same_profile_browser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:maxLines="2"
style="@android:style/Widget.DeviceDefault.Button.Borderless"
android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:textAllCaps="false"
android:text="@string/activity_resolver_use_once"
/>
<Button
android:id="@+id/button_open"
android:layout_width="wrap_content"
android:layout_alignParentEnd="true"
android:maxLines="2"
style="@android:style/Widget.DeviceDefault.Button.Colored"
android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:textAllCaps="false"
android:layout_height="wrap_content"
android:text="@string/whichViewApplicationLabel"
/>
</RelativeLayout>
</LinearLayout>
</com.android.internal.widget.ResolverDrawerLayout>

View File

@@ -5949,9 +5949,9 @@
<string name="resolver_no_personal_apps_available">No personal apps</string>
<!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string>
<string name="miniresolver_open_in_personal">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your personal profile?</string>
<!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string>
<string name="miniresolver_open_in_work">Open <xliff:g id="app" example="YouTube">%s</xliff:g> in your work profile?</string>
<!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_use_personal_browser">Use personal browser</string>
<!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] -->

View File

@@ -1597,6 +1597,13 @@
<java-symbol type="layout" name="resolver_list_per_profile" />
<java-symbol type="layout" name="chooser_list_per_profile" />
<java-symbol type="layout" name="resolver_empty_states" />
<java-symbol type="id" name="open_cross_profile" />
<java-symbol type="string" name="miniresolver_open_in_personal" />
<java-symbol type="string" name="miniresolver_open_in_work" />
<java-symbol type="string" name="miniresolver_use_personal_browser" />
<java-symbol type="string" name="miniresolver_use_work_browser" />
<java-symbol type="id" name="button_open" />
<java-symbol type="id" name="use_same_profile_browser" />
<java-symbol type="anim" name="slide_in_child_bottom" />
<java-symbol type="anim" name="slide_in_right" />
@@ -2705,6 +2712,7 @@
<java-symbol type="bool" name="config_allow_ussd_over_ims" />
<java-symbol type="attr" name="touchscreenBlocksFocus" />
<java-symbol type="layout" name="resolver_list_with_default" />
<java-symbol type="layout" name="miniresolver" />
<java-symbol type="string" name="activity_resolver_use_always" />
<java-symbol type="string" name="whichApplicationNamed" />
<java-symbol type="string" name="whichApplicationLabel" />

View File

@@ -730,6 +730,25 @@ public class ResolverActivityTest {
.check(matches(isDisplayed()));
}
@Test
public void testMiniResolver() {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTest(1);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(1);
// Personal profile only has a browser
personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
mActivityRule.launchActivity(sendIntent);
waitForIdle();
onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed()));
}
@Test
public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
// enable the work tab feature flag