From dc29479df4e8c9930730660fa4315d1023bc4365 Mon Sep 17 00:00:00 2001 From: Mike Digman Date: Wed, 27 May 2020 10:31:16 -0700 Subject: [PATCH] Add icons to Sharesheet pin and group selection UI Merging menu dialogs to simplify code. Fixed a long-press selection bug. Rtl now works as expected. Fixes: 150405021 Test: manual Change-Id: I2c9ea58f2641046700ac1a627a7db9ea20e7092a --- .../android/internal/app/ChooserActivity.java | 45 ++--- .../app/ChooserStackedAppDialogFragment.java | 54 ++---- .../ChooserTargetActionsDialogFragment.java | 173 ++++++++++++++++++ .../ResolverTargetActionsDialogFragment.java | 117 ------------ .../res/drawable/ic_chooser_pin_dialog.xml | 25 +++ core/res/res/layout/chooser_dialog_item.xml | 44 +++++ core/res/res/values/symbols.xml | 3 + 7 files changed, 284 insertions(+), 177 deletions(-) create mode 100644 core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java delete mode 100644 core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java create mode 100644 core/res/res/drawable/ic_chooser_pin_dialog.xml create mode 100644 core/res/res/layout/chooser_dialog_item.xml diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 8b0dd8ae18ce9..f4863cc35a3aa 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1635,7 +1635,7 @@ public class ChooserActivity extends ResolverActivity implements targetList = Collections.singletonList(ti); } - ResolverTargetActionsDialogFragment f = new ResolverTargetActionsDialogFragment( + ChooserTargetActionsDialogFragment f = new ChooserTargetActionsDialogFragment( targetList, mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); @@ -1695,15 +1695,9 @@ public class ChooserActivity extends ResolverActivity implements if (targetInfo instanceof MultiDisplayResolveInfo) { MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo; if (!mti.hasSelected()) { - // Stacked apps get a disambiguation first - CharSequence[] labels = new CharSequence[mti.getTargets().size()]; - int i = 0; - for (TargetInfo ti : mti.getTargets()) { - labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager()); - } ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment( - targetInfo.getDisplayLabel(), - ((MultiDisplayResolveInfo) targetInfo), labels, which); + mti, which, + mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); return; @@ -2995,16 +2989,16 @@ public class ChooserActivity extends ResolverActivity implements itemView.setOnClickListener(v -> startSelected(mListPosition, false/* always */, true/* filterd */)); - TargetInfo ti = mChooserMultiProfilePagerAdapter.getActiveListAdapter() - .targetInfoForPosition(mListPosition, /* filtered */ true); + itemView.setOnLongClickListener(v -> { + final TargetInfo ti = mChooserMultiProfilePagerAdapter.getActiveListAdapter() + .targetInfoForPosition(mListPosition, /* filtered */ true); - // This should always be the case for ItemViewHolder, check for sanity - if (ti instanceof DisplayResolveInfo) { - itemView.setOnLongClickListener(v -> { + // This should always be the case for ItemViewHolder, check for sanity + if (ti instanceof DisplayResolveInfo) { showTargetDetails((DisplayResolveInfo) ti); - return true; - }); - } + } + return true; + }); } } } @@ -3332,16 +3326,15 @@ public class ChooserActivity extends ResolverActivity implements // Direct Share targets should not show any menu if (!isDirectShare) { - final TargetInfo ti = mChooserListAdapter.targetInfoForPosition( - holder.getItemIndex(column), true); - - // This should always be the case for non-DS targets, check for sanity - if (ti instanceof DisplayResolveInfo) { - v.setOnLongClickListener(v1 -> { + v.setOnLongClickListener(v1 -> { + final TargetInfo ti = mChooserListAdapter.targetInfoForPosition( + holder.getItemIndex(column), true); + // This should always be the case for non-DS targets, check for sanity + if (ti instanceof DisplayResolveInfo) { showTargetDetails((DisplayResolveInfo) ti); - return true; - }); - } + } + return true; + }); } holder.addView(i, v); diff --git a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java index f4c69a51ece15..fdeba8f67cc10 100644 --- a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java +++ b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java @@ -17,64 +17,50 @@ package com.android.internal.app; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.DialogFragment; import android.content.DialogInterface; -import android.content.res.Configuration; -import android.os.Bundle; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import com.android.internal.app.chooser.DisplayResolveInfo; import com.android.internal.app.chooser.MultiDisplayResolveInfo; /** * Shows individual actions for a "stacked" app target - such as an app with multiple posting * streams represented in the Sharesheet. */ -public class ChooserStackedAppDialogFragment extends DialogFragment +public class ChooserStackedAppDialogFragment extends ChooserTargetActionsDialogFragment implements DialogInterface.OnClickListener { - private static final String TITLE_KEY = "title"; - private static final String PINNED_KEY = "pinned"; - private MultiDisplayResolveInfo mTargetInfos; - private CharSequence[] mLabels; + private MultiDisplayResolveInfo mMultiDisplayResolveInfo; private int mParentWhich; public ChooserStackedAppDialogFragment() { } - public ChooserStackedAppDialogFragment(CharSequence title, - MultiDisplayResolveInfo targets, CharSequence[] labels, int parentWhich) { - Bundle args = new Bundle(); - args.putCharSequence(TITLE_KEY, title); - mTargetInfos = targets; - mLabels = labels; + public ChooserStackedAppDialogFragment(MultiDisplayResolveInfo targets, + int parentWhich, UserHandle userHandle) { + super(targets.getTargets(), userHandle); + mMultiDisplayResolveInfo = targets; mParentWhich = parentWhich; - setArguments(args); } @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Bundle args = getArguments(); - return new Builder(getContext()) - .setCancelable(true) - .setItems(mLabels, this) - .setTitle(args.getCharSequence(TITLE_KEY)) - .create(); + protected CharSequence getItemLabel(DisplayResolveInfo dri) { + final PackageManager pm = getContext().getPackageManager(); + return dri.getResolveInfo().loadLabel(pm); + } + + @Override + protected Drawable getItemIcon(DisplayResolveInfo dri) { + // Show no icon for the group disambig dialog, null hides the imageview + return null; } @Override public void onClick(DialogInterface dialog, int which) { - final Bundle args = getArguments(); - mTargetInfos.setSelected(which); + mMultiDisplayResolveInfo.setSelected(which); ((ChooserActivity) getActivity()).startSelected(mParentWhich, false, true); dismiss(); } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - // Dismiss on config changed (eg: rotation) - // TODO: Maintain state on config change - super.onConfigurationChanged(newConfig); - dismiss(); - } } diff --git a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java new file mode 100644 index 0000000000000..3991a7674f38b --- /dev/null +++ b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2016 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.internal.app; + +import static android.content.Context.ACTIVITY_SERVICE; + +import static com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter; + +import static java.util.stream.Collectors.toList; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.UserHandle; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.internal.R; +import com.android.internal.app.chooser.DisplayResolveInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Shows a dialog with actions to take on a chooser target. + */ +public class ChooserTargetActionsDialogFragment extends DialogFragment + implements DialogInterface.OnClickListener { + + protected List mTargetInfos = new ArrayList<>(); + protected UserHandle mUserHandle; + + public ChooserTargetActionsDialogFragment() { + } + + public ChooserTargetActionsDialogFragment(List targets, + UserHandle userHandle) { + mUserHandle = userHandle; + mTargetInfos = targets; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + // Fetch UI details from target info + List> items = mTargetInfos.stream().map(dri -> { + return new Pair<>(getItemLabel(dri), getItemIcon(dri)); + }).collect(toList()); + + final ResolveInfoPresentationGetter pg = getProvidingAppPresentationGetter(); + return new Builder(getContext()) + .setTitle(pg.getLabel()) + .setIcon(pg.getIcon(mUserHandle)) + .setCancelable(true) + .setAdapter(getAdapterForContent(items), this) + .create(); + } + + protected ArrayAdapter> getAdapterForContent( + List> items) { + return new ArrayAdapter>(getContext(), + R.layout.chooser_dialog_item, R.id.text, items) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); // super recycles views + TextView label = v.findViewById(R.id.text); + ImageView icon = v.findViewById(R.id.icon); + + Pair pair = getItem(position); + label.setText(pair.first); + + // Hide icon view if one isn't available + if (pair.second == null) { + icon.setVisibility(View.GONE); + } else { + icon.setImageDrawable(pair.second); + icon.setVisibility(View.VISIBLE); + } + + return v; + } + }; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + pinComponent(mTargetInfos.get(which).getResolvedComponentName()); + ((ChooserActivity) getActivity()).handlePackagesChanged(); + dismiss(); + } + + private void pinComponent(ComponentName name) { + SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext()); + final String key = name.flattenToString(); + boolean currentVal = sp.getBoolean(name.flattenToString(), false); + if (currentVal) { + sp.edit().remove(key).apply(); + } else { + sp.edit().putBoolean(key, true).apply(); + } + } + + private Drawable getPinIcon(boolean isPinned) { + return isPinned + ? getContext().getDrawable(R.drawable.ic_close) + : getContext().getDrawable(R.drawable.ic_chooser_pin_dialog); + } + + private CharSequence getPinLabel(boolean isPinned, CharSequence targetLabel) { + return isPinned + ? getResources().getString(R.string.unpin_specific_target, targetLabel) + : getResources().getString(R.string.pin_specific_target, targetLabel); + } + + @NonNull + protected CharSequence getItemLabel(DisplayResolveInfo dri) { + final PackageManager pm = getContext().getPackageManager(); + return getPinLabel(dri.isPinned(), dri.getResolveInfo().loadLabel(pm)); + } + + @Nullable + protected Drawable getItemIcon(DisplayResolveInfo dri) { + return getPinIcon(dri.isPinned()); + } + + private ResolveInfoPresentationGetter getProvidingAppPresentationGetter() { + final ActivityManager am = (ActivityManager) getContext() + .getSystemService(ACTIVITY_SERVICE); + final int iconDpi = am.getLauncherLargeIconDensity(); + + // Use the matching application icon and label for the title, any TargetInfo will do + return new ResolveInfoPresentationGetter(getContext(), iconDpi, + mTargetInfos.get(0).getResolveInfo()); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + // Dismiss on config changed (eg: rotation) + // TODO: Maintain state on config change + super.onConfigurationChanged(newConfig); + dismiss(); + } + +} diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java deleted file mode 100644 index cdc600c51451e..0000000000000 --- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2016 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.internal.app; - -import static android.content.Context.ACTIVITY_SERVICE; - -import static com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter; - -import android.app.ActivityManager; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.ComponentName; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.UserHandle; - -import com.android.internal.R; -import com.android.internal.app.chooser.DisplayResolveInfo; -import com.android.internal.app.chooser.TargetInfo; - -import java.util.ArrayList; -import java.util.List; - -/** - * Shows a dialog with actions to take on a chooser target. - */ -public class ResolverTargetActionsDialogFragment extends DialogFragment - implements DialogInterface.OnClickListener { - - private List mTargetInfos = new ArrayList<>(); - private UserHandle mUserHandle; - - public ResolverTargetActionsDialogFragment() { - } - - public ResolverTargetActionsDialogFragment(List targets, - UserHandle userHandle) { - mUserHandle = userHandle; - mTargetInfos = targets; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Bundle args = getArguments(); - final PackageManager pm = getContext().getPackageManager(); - - // Pin item for each sub-item - CharSequence[] items = new CharSequence[mTargetInfos.size()]; - for (int i = 0; i < mTargetInfos.size(); i++) { - final TargetInfo ti = mTargetInfos.get(i); - final CharSequence label = ti.getResolveInfo().loadLabel(pm); - items[i] = ti.isPinned() - ? getResources().getString(R.string.unpin_specific_target, label) - : getResources().getString(R.string.pin_specific_target, label); - } - - // Use the matching application icon and label for the title, any TargetInfo will do - final ActivityManager am = (ActivityManager) getContext() - .getSystemService(ACTIVITY_SERVICE); - final int iconDpi = am.getLauncherLargeIconDensity(); - final ResolveInfoPresentationGetter pg = new ResolveInfoPresentationGetter(getContext(), - iconDpi, mTargetInfos.get(0).getResolveInfo()); - - return new Builder(getContext()) - .setTitle(pg.getLabel()) - .setIcon(pg.getIcon(mUserHandle)) - .setCancelable(true) - .setItems(items, this) - .create(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - pinComponent(mTargetInfos.get(which).getResolvedComponentName()); - ((ChooserActivity) getActivity()).handlePackagesChanged(); - dismiss(); - } - - private void pinComponent(ComponentName name) { - SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext()); - final String key = name.flattenToString(); - boolean currentVal = sp.getBoolean(name.flattenToString(), false); - if (currentVal) { - sp.edit().remove(key).apply(); - } else { - sp.edit().putBoolean(key, true).apply(); - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - // Dismiss on config changed (eg: rotation) - // TODO: Maintain state on config change - super.onConfigurationChanged(newConfig); - dismiss(); - } - -} diff --git a/core/res/res/drawable/ic_chooser_pin_dialog.xml b/core/res/res/drawable/ic_chooser_pin_dialog.xml new file mode 100644 index 0000000000000..2ac01c79e3169 --- /dev/null +++ b/core/res/res/drawable/ic_chooser_pin_dialog.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/core/res/res/layout/chooser_dialog_item.xml b/core/res/res/layout/chooser_dialog_item.xml new file mode 100644 index 0000000000000..1d6369793bfbc --- /dev/null +++ b/core/res/res/layout/chooser_dialog_item.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 61a65249a464f..78e61f8a9f577 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2748,6 +2748,7 @@ + @@ -3835,10 +3836,12 @@ + +