diff options
9 files changed, 301 insertions, 36 deletions
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 8856f99272b6..f361784e81d7 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -111,6 +111,7 @@ import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGett import com.android.internal.app.ResolverListAdapter.ViewHolder; import com.android.internal.app.chooser.ChooserTargetInfo; import com.android.internal.app.chooser.DisplayResolveInfo; +import com.android.internal.app.chooser.MultiDisplayResolveInfo; import com.android.internal.app.chooser.NotSelectableTargetInfo; import com.android.internal.app.chooser.SelectableTargetInfo; import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator; @@ -1352,17 +1353,31 @@ public class ChooserActivity extends ResolverActivity implements return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true); } - @Override - public void showTargetDetails(ResolveInfo ri) { - if (ri == null) { + void showTargetDetails(TargetInfo ti) { + if (ti == null) { return; } - - ComponentName name = ri.activityInfo.getComponentName(); + ComponentName name = ti.getResolveInfo().activityInfo.getComponentName(); boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false); - ResolverTargetActionsDialogFragment f = - new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()), - name, pinned); + + ResolverTargetActionsDialogFragment f; + + // For multiple targets, include info on all targets + if (ti instanceof MultiDisplayResolveInfo) { + MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti; + List<CharSequence> labels = new ArrayList<>(); + + for (TargetInfo innerInfo : mti.getTargets()) { + labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager())); + } + f = new ResolverTargetActionsDialogFragment( + mti.getResolveInfo().loadLabel(getPackageManager()), name, mti.getTargets(), + labels); + } else { + f = new ResolverTargetActionsDialogFragment( + ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned); + } + f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); } @@ -1416,8 +1431,26 @@ public class ChooserActivity extends ResolverActivity implements } final long selectionCost = System.currentTimeMillis() - mChooserShownTime; + + // Stacked apps get a disambiguation first + if (targetInfo instanceof MultiDisplayResolveInfo) { + MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo; + 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).getTargets(), labels); + + f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); + return; + } + super.startSelected(which, always, filtered); + if (currentListAdapter.getCount() > 0) { // Log the index of which type of target the user picked. // Lower values mean the ranking was better. @@ -2363,7 +2396,7 @@ public class ChooserActivity extends ResolverActivity implements itemView.setOnLongClickListener(v -> { showTargetDetails( mChooserMultiProfilePagerAdapter.getActiveListAdapter() - .resolveInfoForPosition(mListPosition, /* filtered */ true)); + .targetInfoForPosition(mListPosition, /* filtered */ true)); return true; }); } @@ -2615,7 +2648,7 @@ public class ChooserActivity extends ResolverActivity implements @Override public boolean onLongClick(View v) { showTargetDetails( - mChooserListAdapter.resolveInfoForPosition( + mChooserListAdapter.targetInfoForPosition( holder.getItemIndex(column), true)); return true; } diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 4eccf21677d5..a8a676d03971 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -38,17 +38,22 @@ import com.android.internal.R; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.chooser.ChooserTargetInfo; import com.android.internal.app.chooser.DisplayResolveInfo; +import com.android.internal.app.chooser.MultiDisplayResolveInfo; import com.android.internal.app.chooser.SelectableTargetInfo; import com.android.internal.app.chooser.TargetInfo; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class ChooserListAdapter extends ResolverListAdapter { private static final String TAG = "ChooserListAdapter"; private static final boolean DEBUG = false; + private boolean mEnableStackedApps = true; + public static final int NO_POSITION = -1; public static final int TARGET_BAD = -1; public static final int TARGET_CALLER = 0; @@ -218,7 +223,25 @@ public class ChooserListAdapter extends ResolverListAdapter { void updateAlphabeticalList() { mSortedList.clear(); - mSortedList.addAll(mDisplayList); + if (mEnableStackedApps) { + // Consolidate multiple targets from same app. + Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); + for (DisplayResolveInfo info : mDisplayList) { + String packageName = info.getResolvedComponentName().getPackageName(); + if (consolidated.get(packageName) != null) { + // create consolidated target + MultiDisplayResolveInfo multiDisplayResolveInfo = + new MultiDisplayResolveInfo(packageName, info); + multiDisplayResolveInfo.addTarget(consolidated.get(packageName)); + consolidated.put(packageName, multiDisplayResolveInfo); + } else { + consolidated.put(packageName, info); + } + } + mSortedList.addAll(consolidated.values()); + } else { + mSortedList.addAll(mDisplayList); + } Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext)); } @@ -270,7 +293,7 @@ public class ChooserListAdapter extends ResolverListAdapter { } int getAlphaTargetCount() { - int standardCount = super.getCount(); + int standardCount = mSortedList.size(); return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0; } diff --git a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java new file mode 100644 index 000000000000..ff6582d10535 --- /dev/null +++ b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 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 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 com.android.internal.app.chooser.DisplayResolveInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * 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 + implements DialogInterface.OnClickListener { + private static final String TITLE_KEY = "title"; + private static final String PINNED_KEY = "pinned"; + + private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>(); + private CharSequence[] mLabels; + + public ChooserStackedAppDialogFragment() { + } + + public ChooserStackedAppDialogFragment(CharSequence title) { + Bundle args = new Bundle(); + args.putCharSequence(TITLE_KEY, title); + setArguments(args); + } + + public ChooserStackedAppDialogFragment(CharSequence title, + List<DisplayResolveInfo> targets, CharSequence[] labels) { + Bundle args = new Bundle(); + args.putCharSequence(TITLE_KEY, title); + mTargetInfos = targets; + mLabels = labels; + 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(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final Bundle args = getArguments(); + mTargetInfos.get(which).start(getActivity(), null); + 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/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index cb7f2e424e7b..8dc3a072b47e 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1160,7 +1160,7 @@ public class ResolverActivity extends Activity implements return !target.isSuspended(); } - public void showTargetDetails(ResolveInfo ri) { + void showTargetDetails(ResolveInfo ri) { Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java index df91c4a1f88d..bdbe2109cf05 100644 --- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java +++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java @@ -24,14 +24,19 @@ import android.content.ComponentName; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; 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 + * Shows a dialog with actions to take on a chooser target. */ public class ResolverTargetActionsDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { @@ -43,6 +48,10 @@ public class ResolverTargetActionsDialogFragment extends DialogFragment private static final int TOGGLE_PIN_INDEX = 0; private static final int APP_INFO_INDEX = 1; + private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>(); + private List<CharSequence> mLabels = new ArrayList<>(); + private boolean[] mPinned; + public ResolverTargetActionsDialogFragment() { } @@ -55,15 +64,43 @@ public class ResolverTargetActionsDialogFragment extends DialogFragment setArguments(args); } + public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name, + List<DisplayResolveInfo> targets, List<CharSequence> labels) { + Bundle args = new Bundle(); + args.putCharSequence(TITLE_KEY, title); + args.putParcelable(NAME_KEY, name); + mTargetInfos = targets; + mLabels = labels; + setArguments(args); + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Bundle args = getArguments(); final int itemRes = args.getBoolean(PINNED_KEY, false) ? R.array.resolver_target_actions_unpin : R.array.resolver_target_actions_pin; + String[] defaultActions = getResources().getStringArray(itemRes); + CharSequence[] items; + + if (mTargetInfos == null || mTargetInfos.size() < 2) { + items = defaultActions; + } else { + // Pin item for each sub-item + items = new CharSequence[mTargetInfos.size() + 1]; + for (int i = 0; i < mTargetInfos.size(); i++) { + items[i] = mTargetInfos.get(i).isPinned() + ? getResources().getString(R.string.unpin_specific_target, mLabels.get(i)) + : getResources().getString(R.string.pin_specific_target, mLabels.get(i)); + } + // "App info" + items[mTargetInfos.size()] = defaultActions[1]; + } + + return new Builder(getContext()) .setCancelable(true) - .setItems(itemRes, this) + .setItems(items, this) .setTitle(args.getCharSequence(TITLE_KEY)) .create(); } @@ -72,27 +109,41 @@ public class ResolverTargetActionsDialogFragment extends DialogFragment public void onClick(DialogInterface dialog, int which) { final Bundle args = getArguments(); ComponentName name = args.getParcelable(NAME_KEY); - switch (which) { - case TOGGLE_PIN_INDEX: - 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(); - } - - // Force the chooser to requery and resort things - getActivity().recreate(); - break; - case APP_INFO_INDEX: - Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.fromParts("package", name.getPackageName(), null)) - .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - startActivity(in); - break; + if (which == 0 || (mTargetInfos.size() > 0 && which < mTargetInfos.size())) { + if (mTargetInfos == null || mTargetInfos.size() == 0) { + pinComponent(name); + } else { + pinComponent(mTargetInfos.get(which).getResolvedComponentName()); + } + // Force the chooser to requery and resort things + getActivity().recreate(); + } else { + // Last item in dialog is App Info + Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", name.getPackageName(), null)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + startActivity(in); + } + 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/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index f92637c1bf01..86a9af3db196 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -91,6 +91,16 @@ public class DisplayResolveInfo implements TargetInfo { mResolveInfoPresentationGetter = resolveInfoPresentationGetter; } + DisplayResolveInfo(DisplayResolveInfo other) { + mSourceIntents.addAll(other.getAllSourceIntents()); + mResolveInfo = other.mResolveInfo; + mDisplayLabel = other.mDisplayLabel; + mDisplayIcon = other.mDisplayIcon; + mExtendedInfo = other.mExtendedInfo; + mResolvedIntent = other.mResolvedIntent; + mResolveInfoPresentationGetter = other.mResolveInfoPresentationGetter; + } + public ResolveInfo getResolveInfo() { return mResolveInfo; } @@ -189,4 +199,5 @@ public class DisplayResolveInfo implements TargetInfo { public void setPinned(boolean pinned) { mPinned = pinned; } + } diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java new file mode 100644 index 000000000000..4c52411d6376 --- /dev/null +++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 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.chooser; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a "stack" of chooser targets for various activities within the same component. + */ +public class MultiDisplayResolveInfo extends DisplayResolveInfo { + + List<DisplayResolveInfo> mTargetInfos = new ArrayList<>(); + String mPackageName; + // We'll use this DRI for basic presentation info - eg icon, name. + final DisplayResolveInfo mBaseInfo; + + /** + * @param firstInfo A representative DRI to use for the main icon, title, etc for this Info. + */ + public MultiDisplayResolveInfo(String packageName, DisplayResolveInfo firstInfo) { + super(firstInfo); + mBaseInfo = firstInfo; + mTargetInfos.add(firstInfo); + } + + /** + * Add another DisplayResolveInfo to the list included for this target. + */ + public void addTarget(DisplayResolveInfo target) { + mTargetInfos.add(target); + } + + /** + * List of all DisplayResolveInfos included in this target. + */ + public List<DisplayResolveInfo> getTargets() { + return mTargetInfos; + } + +} diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index de1b5baec4b2..ab10738327b3 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4946,8 +4946,12 @@ <!-- Resolver target actions strings --> <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]--> <string name="pin_target">Pin</string> + <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]--> + <string name="pin_specific_target">Pin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string> + <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]--> + <string name="unpin_target">Unpin </string> <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]--> - <string name="unpin_target">Unpin</string> + <string name="unpin_specific_target">Unpin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string> <!-- View application info for a target. --> <string name="app_info">App info</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 011dc3961c4d..1631e0f00f07 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2958,6 +2958,8 @@ <!-- Resolver target actions --> <java-symbol type="array" name="resolver_target_actions_pin" /> <java-symbol type="array" name="resolver_target_actions_unpin" /> + <java-symbol type="string" name="pin_specific_target" /> + <java-symbol type="string" name="unpin_specific_target" /> <java-symbol type="array" name="non_removable_euicc_slots" /> |