Add empty state screens.

The added screens are for the following scenarios:
- No resolved apps on the visible tab
- Sharing not enabled by admin for the visible tab for both
directions, work->personal and personal->work
- Work profile is disabled

Other changes in this CL:
- Make the intent resolver height suitable for the empty states
- Handle landscape mode gracefully by hiding the content preview
if the tabs are shown
- Show the content preview view as part of the list view if there
are no tabs present, and above the tabs if tabs are present
- Remove elevation if showing the tabbed view

What's left:
- Finalize strings

Test: manually tested all conditions for the empty state screens
Test: atest com.android.internal.app.ChooserActivityTest
Test: atest com.android.internal.app.ResolverActivityTest
Bug: 142537267
Bug: 142538125

Change-Id: I4e5ab5fa368d2b907e3fa74b00a82d8739e4f229
This commit is contained in:
arangelov
2020-02-11 18:05:42 +00:00
parent 20024529db
commit 590fba384a
23 changed files with 437 additions and 102 deletions

View File

@@ -15,15 +15,25 @@
*/
package com.android.internal.app;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.app.AppGlobals;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.PagerAdapter;
import com.android.internal.widget.ViewPager;
@@ -213,26 +223,115 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
abstract @Nullable ViewGroup getInactiveAdapterView();
boolean rebuildActiveTab(boolean post) {
return rebuildTab(getActiveListAdapter(), post);
/**
* Rebuilds the tab that is currently visible to the user.
* <p>Returns {@code true} if rebuild has completed.
*/
boolean rebuildActiveTab(boolean doPostProcessing) {
return rebuildTab(getActiveListAdapter(), doPostProcessing);
}
boolean rebuildInactiveTab(boolean post) {
/**
* Rebuilds the tab that is not currently visible to the user, if such one exists.
* <p>Returns {@code true} if rebuild has completed.
*/
boolean rebuildInactiveTab(boolean doPostProcessing) {
if (getItemCount() == 1) {
return false;
}
return rebuildTab(getInactiveListAdapter(), post);
return rebuildTab(getInactiveListAdapter(), doPostProcessing);
}
private int userHandleToPageIndex(UserHandle userHandle) {
if (userHandle == getPersonalListAdapter().mResolverListController.getUserHandle()) {
return PROFILE_PERSONAL;
} else {
return PROFILE_WORK;
}
}
private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
UserHandle listUserHandle = activeListAdapter.getUserHandle();
if (UserHandle.myUserId() != listUserHandle.getIdentifier() &&
!hasAppsInOtherProfile(activeListAdapter)) {
// TODO(arangelov): Show empty state UX here
UserManager userManager = mContext.getSystemService(UserManager.class);
if (listUserHandle == mWorkProfileUserHandle
&& userManager.isQuietModeEnabled(mWorkProfileUserHandle)) {
showEmptyState(activeListAdapter,
R.drawable.ic_work_apps_off,
R.string.resolver_turn_on_work_apps,
R.string.resolver_turn_on_work_apps_explanation,
(View.OnClickListener) v ->
userManager.requestQuietModeEnabled(false, mWorkProfileUserHandle));
return false;
} else {
return activeListAdapter.rebuildList(doPostProcessing);
}
if (UserHandle.myUserId() != listUserHandle.getIdentifier()) {
if (!hasCrossProfileIntents(activeListAdapter.getIntents(),
UserHandle.myUserId(), listUserHandle.getIdentifier())) {
if (listUserHandle == mPersonalProfileUserHandle) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
R.string.resolver_cant_share_with_personal_apps,
R.string.resolver_cant_share_cross_profile_explanation);
} else {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
R.string.resolver_cant_share_with_work_apps,
R.string.resolver_cant_share_cross_profile_explanation);
}
return false;
}
}
return activeListAdapter.rebuildList(doPostProcessing);
}
void showEmptyState(ResolverListAdapter listAdapter) {
UserHandle listUserHandle = listAdapter.getUserHandle();
if (UserHandle.myUserId() == listUserHandle.getIdentifier()
|| !hasAppsInOtherProfile(listAdapter)) {
showEmptyState(listAdapter,
R.drawable.ic_no_apps,
R.string.resolver_no_apps_available,
R.string.resolver_no_apps_available_explanation);
}
}
private void showEmptyState(ResolverListAdapter activeListAdapter,
@DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes) {
showEmptyState(activeListAdapter, iconRes, titleRes, subtitleRes, /* buttonOnClick */ null);
}
private void showEmptyState(ResolverListAdapter activeListAdapter,
@DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes,
View.OnClickListener buttonOnClick) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
View emptyStateView = descriptor.rootView.findViewById(R.id.resolver_empty_state);
emptyStateView.setVisibility(View.VISIBLE);
ImageView icon = emptyStateView.findViewById(R.id.resolver_empty_state_icon);
icon.setImageResource(iconRes);
TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title);
title.setText(titleRes);
TextView subtitle = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
subtitle.setText(subtitleRes);
Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
button.setOnClickListener(buttonOnClick);
}
private boolean hasCrossProfileIntents(List<Intent> intents, int source, int target) {
IPackageManager packageManager = AppGlobals.getPackageManager();
ContentResolver contentResolver = mContext.getContentResolver();
for (Intent intent : intents) {
if (IntentForwarderActivity.canForward(intent, source, target, packageManager,
contentResolver) != null) {
return true;
}
}
return false;
}
private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {

View File

@@ -373,7 +373,11 @@ public class ChooserActivity extends ResolverActivity implements
Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
+ " within " + mImageLoadTimeoutMillis + "ms.");
collapseParentView();
hideContentPreview();
if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
hideStickyContentPreview();
} else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().hideContentPreview();
}
mHideParentOnFail = false;
}
}
@@ -829,7 +833,12 @@ public class ChooserActivity extends ResolverActivity implements
@Override
protected boolean postRebuildList(boolean rebuildCompleted) {
updateContentPreview();
updateStickyContentPreview();
if (shouldShowStickyContentPreview()
|| mChooserMultiProfilePagerAdapter
.getCurrentRootAdapter().getContentPreviewRowCount() != 0) {
logActionShareWithPreview();
}
return postRebuildListInternal(rebuildCompleted);
}
@@ -978,6 +987,8 @@ public class ChooserActivity extends ResolverActivity implements
updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
findViewById(R.id.content_preview_container)
.setVisibility(shouldShowStickyContentPreview() ? View.VISIBLE : View.GONE);
}
private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
@@ -2413,14 +2424,14 @@ public class ChooserActivity extends ResolverActivity implements
// still zero? then use a default height and leave, which
// can happen when there are no targets to show
if (rowsToShow == 0 && !shouldShowContentPreview()) {
if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
offset += getResources().getDimensionPixelSize(
R.dimen.chooser_max_collapsed_height);
mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
return;
}
if (shouldShowContentPreview()) {
if (shouldShowStickyContentPreview()) {
offset += findViewById(R.id.content_preview_container).getHeight();
}
@@ -2552,7 +2563,7 @@ public class ChooserActivity extends ResolverActivity implements
}
private void setupScrollListener() {
if (mResolverDrawerLayout == null) {
if (mResolverDrawerLayout == null || (hasWorkProfile() && ENABLE_TABBED_VIEW)) {
return;
}
final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
@@ -2597,28 +2608,36 @@ public class ChooserActivity extends ResolverActivity implements
return false;
}
private boolean shouldShowContentPreview() {
return mMultiProfilePagerAdapter.getActiveListAdapter().getCount() > 0
&& isSendAction(getTargetIntent());
/**
* The sticky content preview is shown only when we have a tabbed view. It's shown above
* the tabs so it is not part of the scrollable list. If we are not in tabbed view,
* we instead show the content preview as a regular list item.
*/
private boolean shouldShowStickyContentPreview() {
return hasWorkProfile()
&& ENABLE_TABBED_VIEW
&& mMultiProfilePagerAdapter.getListAdapterForUserHandle(
UserHandle.of(UserHandle.myUserId())).getCount() > 0
&& isSendAction(getTargetIntent())
&& getResources().getBoolean(R.bool.sharesheet_show_content_preview);
}
private void updateContentPreview() {
if (shouldShowContentPreview()) {
showContentPreview();
private void updateStickyContentPreview() {
if (shouldShowStickyContentPreview()) {
showStickyContentPreview();
} else {
hideContentPreview();
hideStickyContentPreview();
}
}
private void showContentPreview() {
private void showStickyContentPreview() {
ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
contentPreviewContainer.setVisibility(View.VISIBLE);
ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
contentPreviewContainer.addView(contentPreviewView);
logActionShareWithPreview();
}
private void hideContentPreview() {
private void hideStickyContentPreview() {
ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
contentPreviewContainer.removeAllViews();
contentPreviewContainer.setVisibility(View.GONE);
@@ -2634,6 +2653,7 @@ public class ChooserActivity extends ResolverActivity implements
/**
* Used to bind types of individual item including
* {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
* {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
* {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
* and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
*/
@@ -2695,16 +2715,18 @@ public class ChooserActivity extends ResolverActivity implements
private int mChooserTargetWidth = 0;
private boolean mShowAzLabelIfPoss;
private boolean mHideContentPreview = false;
private boolean mLayoutRequested = false;
private int mFooterHeight = 0;
private static final int VIEW_TYPE_DIRECT_SHARE = 0;
private static final int VIEW_TYPE_NORMAL = 1;
private static final int VIEW_TYPE_PROFILE = 2;
private static final int VIEW_TYPE_AZ_LABEL = 3;
private static final int VIEW_TYPE_CALLER_AND_RANK = 4;
private static final int VIEW_TYPE_FOOTER = 5;
private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
private static final int VIEW_TYPE_PROFILE = 3;
private static final int VIEW_TYPE_AZ_LABEL = 4;
private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
private static final int VIEW_TYPE_FOOTER = 6;
private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
@@ -2765,6 +2787,17 @@ public class ChooserActivity extends ResolverActivity implements
return maxTargets;
}
/**
* Hides the list item content preview.
* <p>Not to be confused with the sticky content preview which is above the
* personal and work tabs.
*/
public void hideContentPreview() {
mHideContentPreview = true;
mLayoutRequested = true;
notifyDataSetChanged();
}
public boolean consumeLayoutRequest() {
boolean oldValue = mLayoutRequested;
mLayoutRequested = false;
@@ -2773,7 +2806,8 @@ public class ChooserActivity extends ResolverActivity implements
public int getRowCount() {
return (int) (
getProfileRowCount()
getContentPreviewRowCount()
+ getProfileRowCount()
+ getServiceTargetRowCount()
+ getCallerAndRankedTargetRowCount()
+ getAzLabelRowCount()
@@ -2783,7 +2817,33 @@ public class ChooserActivity extends ResolverActivity implements
);
}
/**
* Returns either {@code 0} or {@code 1} depending on whether we want to show the list item
* content preview. Not to be confused with the sticky content preview which is above the
* personal and work tabs.
*/
public int getContentPreviewRowCount() {
// For the tabbed case we show the sticky content preview above the tabs,
// please refer to shouldShowStickyContentPreview
if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
return 0;
}
if (!isSendAction(getTargetIntent())) {
return 0;
}
if (mHideContentPreview || mChooserListAdapter == null
|| mChooserListAdapter.getCount() == 0) {
return 0;
}
return 1;
}
public int getProfileRowCount() {
if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
return 0;
}
return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
}
@@ -2815,7 +2875,8 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public int getItemCount() {
return (int) (
getProfileRowCount()
getContentPreviewRowCount()
+ getProfileRowCount()
+ getServiceTargetRowCount()
+ getCallerAndRankedTargetRowCount()
+ getAzLabelRowCount()
@@ -2827,6 +2888,8 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_CONTENT_PREVIEW:
return new ItemViewHolder(createContentPreviewView(parent), false);
case VIEW_TYPE_PROFILE:
return new ItemViewHolder(createProfileView(parent), false);
case VIEW_TYPE_AZ_LABEL:
@@ -2866,7 +2929,10 @@ public class ChooserActivity extends ResolverActivity implements
public int getItemViewType(int position) {
int count;
int countSum = (count = getProfileRowCount());
int countSum = (count = getContentPreviewRowCount());
if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
countSum += (count = getProfileRowCount());
if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
countSum += (count = getServiceTargetRowCount());
@@ -3082,7 +3148,7 @@ public class ChooserActivity extends ResolverActivity implements
}
int getListPosition(int position) {
position -= getProfileRowCount();
position -= getContentPreviewRowCount() + getProfileRowCount();
final int serviceCount = mChooserListAdapter.getServiceTargetCount();
final int serviceRows = (int) Math.ceil((float) serviceCount

View File

@@ -26,6 +26,7 @@ import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
@@ -108,7 +109,8 @@ public class IntentForwarderActivity extends Activity {
}
final int callingUserId = getUserId();
final Intent newIntent = canForward(intentReceived, targetUserId);
final Intent newIntent = canForward(intentReceived, getUserId(), targetUserId,
mInjector.getIPackageManager(), getContentResolver());
if (newIntent != null) {
if (Intent.ACTION_CHOOSER.equals(newIntent.getAction())) {
Intent innerIntent = newIntent.getParcelableExtra(Intent.EXTRA_INTENT);
@@ -191,7 +193,8 @@ public class IntentForwarderActivity extends Activity {
* Check whether the intent can be forwarded to target user. Return the intent used for
* forwarding if it can be forwarded, {@code null} otherwise.
*/
Intent canForward(Intent incomingIntent, int targetUserId) {
static Intent canForward(Intent incomingIntent, int sourceUserId, int targetUserId,
IPackageManager packageManager, ContentResolver contentResolver) {
Intent forwardIntent = new Intent(incomingIntent);
forwardIntent.addFlags(
Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
@@ -220,11 +223,11 @@ public class IntentForwarderActivity extends Activity {
if (forwardIntent.getSelector() != null) {
intentToCheck = forwardIntent.getSelector();
}
String resolvedType = intentToCheck.resolveTypeIfNeeded(getContentResolver());
String resolvedType = intentToCheck.resolveTypeIfNeeded(contentResolver);
sanitizeIntent(intentToCheck);
try {
if (mInjector.getIPackageManager().
canForwardTo(intentToCheck, resolvedType, getUserId(), targetUserId)) {
if (packageManager.canForwardTo(
intentToCheck, resolvedType, sourceUserId, targetUserId)) {
return forwardIntent;
}
} catch (RemoteException e) {
@@ -267,7 +270,7 @@ public class IntentForwarderActivity extends Activity {
/**
* Sanitize the intent in place.
*/
private void sanitizeIntent(Intent intent) {
private static void sanitizeIntent(Intent intent) {
// Apps should not be allowed to target a specific package/ component in the target user.
intent.setPackage(null);
intent.setComponent(null);

View File

@@ -387,6 +387,11 @@ public class ResolverActivity extends Activity implements
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
rdl.setMaxCollapsedHeight(hasWorkProfile() && ENABLE_TABBED_VIEW
? getResources().getDimensionPixelSize(
R.dimen.resolver_empty_state_height_with_tabs)
: getResources().getDimensionPixelSize(R.dimen.resolver_empty_state_height));
mResolverDrawerLayout = rdl;
}
@@ -548,13 +553,6 @@ public class ResolverActivity extends Activity implements
applyFooterView(mSystemWindowInsets.bottom);
}
View emptyView = findViewById(R.id.empty);
if (emptyView != null) {
emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+ getResources().getDimensionPixelSize(
R.dimen.chooser_edge_margin_normal) * 2);
}
return insets.consumeSystemWindowInsets();
}
@@ -941,10 +939,13 @@ public class ResolverActivity extends Activity implements
}
@Override // ResolverListCommunicator
public void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing) {
public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing) {
if (isAutolaunching() || maybeAutolaunchActivity()) {
return;
}
if (shouldShowEmptyState(listAdapter)) {
mMultiProfilePagerAdapter.showEmptyState(listAdapter);
}
if (doPostProcessing) {
if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
== UserHandle.myUserId()) {
@@ -1497,15 +1498,17 @@ public class ResolverActivity extends Activity implements
}
private void setupViewVisibilities() {
int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
boolean shouldShowEmptyState = count == 0
&& mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0;
//TODO(arangelov): Handle empty state
if (!shouldShowEmptyState) {
addUseDifferentAppLabelIfNecessary(mMultiProfilePagerAdapter.getActiveListAdapter());
ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
if (!shouldShowEmptyState(activeListAdapter)) {
addUseDifferentAppLabelIfNecessary(activeListAdapter);
}
}
private boolean shouldShowEmptyState(ResolverListAdapter listAdapter) {
int count = listAdapter.getUnfilteredCount();
return count == 0 && listAdapter.getPlaceholderCount() == 0;
}
/**
* Add a label to signify that the user can pick a different app.
* @param adapter The adapter used to provide data to item views.

View File

@@ -607,6 +607,10 @@ public class ResolverListAdapter extends BaseAdapter {
mIntents, userHandle);
}
protected List<Intent> getIntents() {
return mIntents;
}
/**
* Necessary methods to communicate between {@link ResolverListAdapter}
* and {@link ResolverActivity}.

View File

@@ -57,10 +57,15 @@ public class ResolverViewPager extends ViewPager {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
int height = getMeasuredHeight();
if (getChildCount() > 0) {
View firstChild = getChildAt(0);
firstChild.measure(widthMeasureSpec,
View child = getChildAt(0);
child.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
height = firstChild.getMeasuredHeight();
if (height > child.getMeasuredHeight()) {
height = child.getMeasuredHeight();
}
}
if (height < getMinimumHeight()) {
height = getMinimumHeight();
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

View File

@@ -66,6 +66,12 @@ public class ResolverDrawerLayout extends ViewGroup {
*/
private int mMaxCollapsedHeightSmall;
/**
* Whether {@code mMaxCollapsedHeightSmall} was set explicitly as a layout attribute or
* inferred by {@code mMaxCollapsedHeight}.
*/
private final boolean mIsMaxCollapsedHeightSmallExplicit;
private boolean mSmallCollapsed;
/**
@@ -146,6 +152,8 @@ public class ResolverDrawerLayout extends ViewGroup {
mMaxCollapsedHeightSmall = a.getDimensionPixelSize(
R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall,
mMaxCollapsedHeight);
mIsMaxCollapsedHeightSmallExplicit =
a.hasValue(R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall);
mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false);
a.recycle();
@@ -162,6 +170,21 @@ public class ResolverDrawerLayout extends ViewGroup {
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
/**
* Dynamically set the max collapsed height. Note this also updates the small collapsed
* height if it wasn't specified explicitly.
*/
public void setMaxCollapsedHeight(int heightInPixels) {
if (heightInPixels == mMaxCollapsedHeight) {
return;
}
mMaxCollapsedHeight = heightInPixels;
if (!mIsMaxCollapsedHeightSmallExplicit) {
mMaxCollapsedHeightSmall = mMaxCollapsedHeight;
}
requestLayout();
}
public void setSmallCollapsed(boolean smallCollapsed) {
mSmallCollapsed = smallCollapsed;
requestLayout();

View File

@@ -0,0 +1,4 @@
<vector android:height="32dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/resolver_empty_state_icon" android:fillType="evenOdd" android:pathData="M18.8123,20.0145L21.3999,22.6021L22.602,21.4L2.602,1.4L1.3999,2.6021L3.9873,5.1895C3.6248,5.552 3.3998,6.052 3.3998,6.602C3.3998,7.702 4.2998,8.602 5.3998,8.602C5.9498,8.602 6.4498,8.377 6.8123,8.0145L9.9873,11.1895C9.6248,11.552 9.3998,12.052 9.3998,12.602C9.3998,13.702 10.2998,14.602 11.3998,14.602C11.9498,14.602 12.4498,14.377 12.8123,14.0145L15.9873,17.1895C15.6248,17.552 15.3998,18.052 15.3998,18.602C15.3998,19.702 16.2998,20.602 17.3998,20.602C17.9498,20.602 18.4498,20.377 18.8123,20.0145ZM17.3998,8.602C16.2998,8.602 15.3998,7.7021 15.3998,6.602C15.3998,5.502 16.2998,4.602 17.3998,4.602C18.4998,4.602 19.3998,5.502 19.3998,6.602C19.3998,7.7021 18.4998,8.602 17.3998,8.602ZM5.3998,14.6021C6.4998,14.6021 7.3998,13.7021 7.3998,12.6021C7.3998,11.5021 6.4998,10.6021 5.3998,10.6021C4.2998,10.6021 3.3998,11.5021 3.3998,12.6021C3.3998,13.7021 4.2998,14.6021 5.3998,14.6021ZM7.3998,18.6021C7.3998,19.7021 6.4998,20.6021 5.3998,20.6021C4.2998,20.6021 3.3998,19.7021 3.3998,18.6021C3.3998,17.5021 4.2998,16.6021 5.3998,16.6021C6.4998,16.6021 7.3998,17.5021 7.3998,18.6021ZM13.3998,18.6021C13.3998,19.7021 12.4998,20.6021 11.3998,20.6021C10.2998,20.6021 9.3998,19.7021 9.3998,18.6021C9.3998,17.5021 10.2998,16.6021 11.3998,16.6021C12.4998,16.6021 13.3998,17.5021 13.3998,18.6021ZM13.3999,6.602C13.3999,7.547 12.7357,8.3444 11.8511,8.5504L9.4516,6.1509C9.6576,5.2663 10.4549,4.602 11.3999,4.602C12.4999,4.602 13.3999,5.502 13.3999,6.602ZM17.8511,14.5504C18.7357,14.3444 19.3999,13.547 19.3999,12.6021C19.3999,11.5021 18.4999,10.6021 17.3999,10.6021C16.4549,10.6021 15.6576,11.2663 15.4516,12.1509L17.8511,14.5504Z"/>
</vector>

View File

@@ -0,0 +1,4 @@
<vector android:height="32dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/resolver_empty_state_icon" android:fillType="evenOdd" android:pathData="M19.7225,20.9245L21.2011,22.4031L22.4032,21.201L2.8022,1.6L1.6001,2.8021L8.1265,9.3284L7.64,9.612C7.1,9.112 6.39,8.802 5.6,8.802C3.94,8.802 2.6,10.142 2.6,11.802C2.6,13.462 3.94,14.802 5.6,14.802C6.39,14.802 7.1,14.492 7.64,13.992L14.69,18.112C14.64,18.332 14.6,18.562 14.6,18.802C14.6,20.462 15.94,21.802 17.6,21.802C18.43,21.802 19.18,21.467 19.7225,20.9245ZM16.8938,18.0958L18.3063,19.5083C18.125,19.6895 17.875,19.802 17.6,19.802C17.05,19.802 16.6,19.352 16.6,18.802C16.6,18.527 16.7125,18.277 16.8938,18.0958ZM15.1871,16.3891L9.3881,10.5901L8.51,11.102C8.56,11.332 8.6,11.562 8.6,11.802C8.6,12.042 8.56,12.272 8.51,12.502L15.1871,16.3891ZM15.56,6.992L12.4382,8.8119L11.1766,7.5503L14.69,5.502C14.64,5.282 14.6,5.042 14.6,4.802C14.6,3.142 15.94,1.802 17.6,1.802C19.26,1.802 20.6,3.142 20.6,4.802C20.6,6.462 19.26,7.802 17.6,7.802C16.81,7.802 16.09,7.492 15.56,6.992ZM18.6,4.802C18.6,4.252 18.15,3.802 17.6,3.802C17.05,3.802 16.6,4.252 16.6,4.802C16.6,5.352 17.05,5.802 17.6,5.802C18.15,5.802 18.6,5.352 18.6,4.802ZM5.6,12.802C5.05,12.802 4.6,12.352 4.6,11.802C4.6,11.252 5.05,10.802 5.6,10.802C6.15,10.802 6.6,11.252 6.6,11.802C6.6,12.352 6.15,12.802 5.6,12.802Z"/>
</vector>

View File

@@ -0,0 +1,4 @@
<vector android:height="32dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/resolver_empty_state_icon" android:pathData="M22,7.95c0.05,-1.11 -0.84,-2 -1.95,-1.95L16,6L16,3.95c0,-1.11 -0.84,-2 -1.95,-1.95h-4C8.94,1.95 8,2.84 8,3.95v0.32l14,14L22,7.95zM14,6h-4L10,4h4v2zM21.54,20.28l-7.56,-7.56v0.01l-1.7,-1.7h0.01L7.21,5.95 3.25,1.99 1.99,3.27 4.69,6h-0.64c-1.11,0 -1.99,0.86 -1.99,1.97l-0.01,11.02c0,1.11 0.89,2.01 2,2.01h15.64l2.05,2.02L23,21.75l-1.46,-1.47z"/>
</vector>

View File

@@ -75,7 +75,8 @@
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:visibility="gone">
</TabWidget>
<FrameLayout
android:id="@android:id/tabcontent"
@@ -89,15 +90,4 @@
</LinearLayout>
</TabHost>
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:background="?attr/colorBackgroundFloating"
android:text="@string/noApplications"
android:padding="@dimen/chooser_edge_margin_normal"
android:gravity="center"
android:elevation="1dp"
android:visibility="gone"/>
</com.android.internal.widget.ResolverDrawerLayout>

View File

@@ -13,15 +13,21 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<com.android.internal.widget.RecyclerView
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutManager="com.android.internal.widget.GridLayoutManager"
android:id="@+id/resolver_list"
android:clipToPadding="false"
android:background="?attr/colorBackgroundFloating"
android:scrollbars="none"
android:elevation="1dp"
android:nestedScrollingEnabled="true"/>
android:layout_marginTop="8dp">
<com.android.internal.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutManager="com.android.internal.widget.GridLayoutManager"
android:id="@+id/resolver_list"
android:clipToPadding="false"
android:background="?attr/colorBackgroundFloating"
android:scrollbars="none"
android:elevation="1dp"
android:nestedScrollingEnabled="true" />
<include layout="@layout/resolver_empty_states" />
</RelativeLayout>

View File

@@ -0,0 +1,58 @@
<!--
~ 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/resolver_empty_state"
android:layout_width="match_parent"
android:layout_height="@dimen/resolver_empty_state_height"
android:orientation="vertical"
android:layout_centerInParent="true"
android:gravity="center"
android:visibility="gone"
android:paddingStart="24dp"
android:paddingEnd="24dp">
<ImageView
android:id="@+id/resolver_empty_state_icon"
android:layout_width="32dp"
android:layout_height="32dp"/>
<TextView
android:id="@+id/resolver_empty_state_title"
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceMedium"
android:textColor="@color/resolver_empty_state_text"
android:textSize="18sp"/>
<TextView
android:id="@+id/resolver_empty_state_subtitle"
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/resolver_empty_state_text"
android:textSize="14sp"
android:gravity="center_horizontal" />
<Button
android:id="@+id/resolver_empty_state_button"
android:layout_marginTop="24dp"
android:text="@string/resolver_switch_on_work"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:textAppearance="?attr/textAppearanceMedium"
android:textSize="14sp"
android:textColor="@color/resolver_tabs_active_color"/>
</LinearLayout>

View File

@@ -21,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="192dp"
android:maxCollapsedHeight="@dimen/resolver_empty_state_height"
android:maxCollapsedHeightSmall="56dp"
android:id="@id/contentPanel">
@@ -91,7 +91,8 @@
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:visibility="gone">
</TabWidget>
<FrameLayout
android:id="@android:id/tabcontent"
@@ -101,6 +102,7 @@
android:id="@+id/profile_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/resolver_empty_state_height"
android:divider="?attr/dividerVertical"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
@@ -116,17 +118,6 @@
android:background="?attr/colorBackgroundFloating"
android:foreground="?attr/dividerVertical" />
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorBackgroundFloating"
android:elevation="@dimen/resolver_elevation"
android:layout_alwaysShow="true"
android:text="@string/noApplications"
android:padding="32dp"
android:gravity="center"
android:visibility="gone" />
<LinearLayout
android:id="@+id/button_bar"
android:visibility="gone"

View File

@@ -14,18 +14,26 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<ListView
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
android:background="?attr/colorBackgroundFloating"
android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
android:scrollbarStyle="outsideOverlay"
android:scrollIndicators="top|bottom"
android:divider="@null"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
android:dividerHeight="0dp" />
android:layout_height="match_parent"
android:layout_marginTop="8dp">
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
android:background="?attr/colorBackgroundFloating"
android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
android:scrollbarStyle="outsideOverlay"
android:scrollIndicators="top|bottom"
android:divider="@null"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
android:dividerHeight="0dp" />
<include layout="@layout/resolver_empty_states" />
</RelativeLayout>

View File

@@ -183,6 +183,7 @@
android:id="@+id/profile_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/resolver_empty_state_height"
android:dividerHeight="1dp"
android:divider="?attr/dividerVertical"
android:footerDividersEnabled="false"

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<bool name="sharesheet_show_content_preview">true</bool>
</resources>

View File

@@ -34,4 +34,6 @@
<color name="chooser_gradient_highlight">@color/loading_gradient_highlight_color_dark</color>
<color name="resolver_tabs_active_color">#FF8AB4F8</color>
<color name="resolver_empty_state_text">#FFFFFF</color>
<color name="resolver_empty_state_icon">#FFFFFF</color>
</resources>

View File

@@ -29,4 +29,5 @@
<p>The main purpose is for OEMs to customize the rendering of the
lockscreen, setting this to true should come with customized drawables. -->
<bool name="use_lock_pattern_drawable">false</bool>
<bool name="sharesheet_show_content_preview">false</bool>
</resources>

View File

@@ -226,4 +226,6 @@
<color name="resolver_text_color_secondary_dark">#ffC4C6C6</color>
<color name="resolver_tabs_active_color">#FF1A73E8</color>
<color name="resolver_tabs_inactive_color">#FF80868B</color>
<color name="resolver_empty_state_text">#FF202124</color>
<color name="resolver_empty_state_icon">#FF5F6368</color>
</resources>

View File

@@ -775,6 +775,8 @@
<dimen name="resolver_small_margin">18dp</dimen>
<dimen name="resolver_edge_margin">24dp</dimen>
<dimen name="resolver_elevation">1dp</dimen>
<dimen name="resolver_empty_state_height">212dp</dimen>
<dimen name="resolver_empty_state_height_with_tabs">268dp</dimen>
<dimen name="chooser_action_button_icon_size">18dp</dimen>

View File

@@ -5341,6 +5341,24 @@
<xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string>
<!-- ResolverActivity - profile tabs -->
<!-- Label for the perosnal tab of the share sheet and intent resolver [CHAR LIMIT=NONE] -->
<string name="resolver_personal_tab">Personal</string>
<!-- Label for the work tab of the share sheet and intent resolver [CHAR LIMIT=NONE] -->
<string name="resolver_work_tab">Work</string>
<!-- Label to indicate that the user cannot share with work apps [CHAR LIMIT=NONE] -->
<string name="resolver_cant_share_with_work_apps">Can\u2019t share with work apps</string>
<!-- Label to indicate that the user cannot share with personal apps [CHAR LIMIT=NONE] -->
<string name="resolver_cant_share_with_personal_apps">Can\u2019t share with personal apps</string>
<!-- Label to explain to the user that sharing between personal and work apps is not enabled by the IT admin [CHAR LIMIT=NONE] -->
<string name="resolver_cant_share_cross_profile_explanation">Your IT admin blocked sharing between personal and work apps</string>
<!-- Label to indicate that the user has to turn on work apps in order to access work data [CHAR LIMIT=NONE] -->
<string name="resolver_turn_on_work_apps">Turn on work apps</string>
<!-- Label to explain that the user has to turn on work apps in order to access work data [CHAR LIMIT=NONE] -->
<string name="resolver_turn_on_work_apps_explanation">Turn on work apps to access work apps &amp; contacts</string>
<!-- Label to indicate that no apps are resolved [CHAR LIMIT=NONE] -->
<string name="resolver_no_apps_available">No apps available</string>
<!-- Label to explain that no apps are resolved [CHAR LIMIT=NONE] -->
<string name="resolver_no_apps_available_explanation">We couldn\u2019t find any apps</string>
<!-- Button which switches on the disabled work profile [CHAR LIMIT=NONE] -->
<string name="resolver_switch_on_work">Switch on work</string>
</resources>

View File

@@ -1560,6 +1560,7 @@
<java-symbol type="layout" name="notification_material_media_seekbar" />
<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="anim" name="slide_in_child_bottom" />
<java-symbol type="anim" name="slide_in_right" />
@@ -3862,6 +3863,26 @@
<java-symbol type="string" name="resolver_personal_tab" />
<java-symbol type="string" name="resolver_work_tab" />
<java-symbol type="id" name="stub" />
<java-symbol type="id" name="resolver_empty_state" />
<java-symbol type="id" name="resolver_empty_state_icon" />
<java-symbol type="id" name="resolver_empty_state_title" />
<java-symbol type="id" name="resolver_empty_state_subtitle" />
<java-symbol type="id" name="resolver_empty_state_button" />
<java-symbol type="string" name="resolver_cant_share_with_work_apps" />
<java-symbol type="string" name="resolver_cant_share_with_personal_apps" />
<java-symbol type="string" name="resolver_cant_share_cross_profile_explanation" />
<java-symbol type="string" name="resolver_turn_on_work_apps" />
<java-symbol type="string" name="resolver_turn_on_work_apps_explanation" />
<java-symbol type="string" name="resolver_no_apps_available" />
<java-symbol type="string" name="resolver_no_apps_available_explanation" />
<java-symbol type="string" name="resolver_no_apps_available_explanation" />
<java-symbol type="string" name="resolver_switch_on_work" />
<java-symbol type="drawable" name="ic_work_apps_off" />
<java-symbol type="drawable" name="ic_sharing_disabled" />
<java-symbol type="drawable" name="ic_no_apps" />
<java-symbol type="dimen" name="resolver_empty_state_height" />
<java-symbol type="dimen" name="resolver_empty_state_height_with_tabs" />
<java-symbol type="bool" name="sharesheet_show_content_preview" />
<!-- Toast message for background started foreground service while-in-use permission restriction feature -->
<java-symbol type="string" name="allow_while_in_use_permission_in_fgs" />