Merge "Sharesheet - Lock direct share row"

This commit is contained in:
TreeHugger Robot
2019-03-06 23:04:14 +00:00
committed by Android (Google) Code Review
8 changed files with 300 additions and 65 deletions

View File

@@ -18,6 +18,11 @@ package com.android.internal.app;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.app.Activity;
import android.app.ActivityManager;
@@ -90,6 +95,8 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
@@ -152,11 +159,17 @@ public class ChooserActivity extends ResolverActivity {
private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
/**
* The transition time between placeholders for direct share to a message
* indicating that non are available.
*/
private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
// TODO(b/121287224): Re-evaluate this limit
private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
private static final int WATCHDOG_TIMEOUT_MILLIS = 3000;
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
@@ -172,6 +185,8 @@ public class ChooserActivity extends ResolverActivity {
private ChooserListAdapter mChooserListAdapter;
private ChooserRowAdapter mChooserRowAdapter;
private Drawable mChooserRowLayer;
private int mChooserRowServiceSpacing;
private SharedPreferences mPinnedSharedPrefs;
private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
@@ -220,7 +235,6 @@ public class ChooserActivity extends ResolverActivity {
sri.connection.destroy();
mServiceConnections.remove(sri.connection);
if (mServiceConnections.isEmpty()) {
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
sendVoiceChoicesIfNeeded();
mChooserListAdapter.setShowServiceTargets(true);
}
@@ -230,8 +244,12 @@ public class ChooserActivity extends ResolverActivity {
if (DEBUG) {
Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
}
if (isDestroyed()) {
break;
}
unbindRemainingServices();
sendVoiceChoicesIfNeeded();
mChooserListAdapter.completeServiceTargetLoading();
mChooserListAdapter.setShowServiceTargets(true);
break;
@@ -399,11 +417,17 @@ public class ChooserActivity extends ResolverActivity {
.setExtras(extras)
.build());
mAppPredictorCallback = resultList -> {
if (isFinishing() || isDestroyed()) {
return;
}
final List<DisplayResolveInfo> driList =
getDisplayResolveInfos(mChooserListAdapter);
final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
new ArrayList<>();
for (AppTarget appTarget : resultList) {
if (appTarget.getShortcutInfo() == null) {
continue;
}
shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
appTarget.getShortcutInfo(),
new ComponentName(
@@ -414,6 +438,10 @@ public class ChooserActivity extends ResolverActivity {
mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
}
mChooserRowLayer = getResources().getDrawable(R.drawable.chooser_row_layer_list, null);
mChooserRowServiceSpacing = getResources()
.getDimensionPixelSize(R.dimen.chooser_service_spacing);
if (DEBUG) {
Log.d(TAG, "System Time Cost is " + systemCost);
}
@@ -919,6 +947,10 @@ public class ChooserActivity extends ResolverActivity {
@Override
protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
if (target instanceof NotSelectableTargetInfo) {
return false;
}
if (mRefinementIntentSender != null) {
final Intent fillIn = new Intent();
final List<Intent> sourceIntents = target.getAllSourceIntents();
@@ -1064,14 +1096,14 @@ public class ChooserActivity extends ResolverActivity {
}
}
if (!mServiceConnections.isEmpty()) {
if (DEBUG) {
Log.d(TAG, "queryTargets setting watchdog timer for "
+ WATCHDOG_TIMEOUT_MILLIS + "ms");
}
mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
WATCHDOG_TIMEOUT_MILLIS);
} else {
if (DEBUG) {
Log.d(TAG, "queryTargets setting watchdog timer for "
+ WATCHDOG_TIMEOUT_MILLIS + "ms");
}
mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
WATCHDOG_TIMEOUT_MILLIS);
if (mServiceConnections.isEmpty()) {
sendVoiceChoicesIfNeeded();
}
}
@@ -1213,7 +1245,6 @@ public class ChooserActivity extends ResolverActivity {
conn.destroy();
}
mServiceConnections.clear();
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
}
public void onSetupVoiceInteraction() {
@@ -1420,7 +1451,93 @@ public class ChooserActivity extends ResolverActivity {
return null;
}
final class ChooserTargetInfo implements TargetInfo {
interface ChooserTargetInfo extends TargetInfo {
float getModifiedScore();
ChooserTarget getChooserTarget();
}
/**
* Distinguish between targets that selectable by the user, vs those that are
* placeholders for the system while information is loading in an async manner.
*/
abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
public Intent getResolvedIntent() {
return null;
}
public ComponentName getResolvedComponentName() {
return null;
}
public boolean start(Activity activity, Bundle options) {
return false;
}
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
return false;
}
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
return false;
}
public ResolveInfo getResolveInfo() {
return null;
}
public CharSequence getDisplayLabel() {
return null;
}
public CharSequence getExtendedInfo() {
return null;
}
public Drawable getBadgeIcon() {
return null;
}
public CharSequence getBadgeContentDescription() {
return null;
}
public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
return null;
}
public List<Intent> getAllSourceIntents() {
return null;
}
public boolean isPinned() {
return false;
}
public float getModifiedScore() {
return 0.1f;
}
public ChooserTarget getChooserTarget() {
return null;
}
}
final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
public Drawable getDisplayIcon() {
return getDrawable(R.drawable.resolver_icon_placeholder);
}
}
final class EmptyTargetInfo extends NotSelectableTargetInfo {
public Drawable getDisplayIcon() {
return null;
}
}
final class SelectableTargetInfo implements ChooserTargetInfo {
private final DisplayResolveInfo mSourceInfo;
private final ResolveInfo mBackupResolveInfo;
private final ChooserTarget mChooserTarget;
@@ -1431,7 +1548,7 @@ public class ChooserActivity extends ResolverActivity {
private final int mFillInFlags;
private final float mModifiedScore;
public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
float modifiedScore) {
mSourceInfo = sourceInfo;
mChooserTarget = chooserTarget;
@@ -1460,7 +1577,7 @@ public class ChooserActivity extends ResolverActivity {
mFillInFlags = 0;
}
private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) {
private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
mSourceInfo = other.mSourceInfo;
mBackupResolveInfo = other.mBackupResolveInfo;
mChooserTarget = other.mChooserTarget;
@@ -1616,7 +1733,7 @@ public class ChooserActivity extends ResolverActivity {
@Override
public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
return new ChooserTargetInfo(this, fillInIntent, flags);
return new SelectableTargetInfo(this, fillInIntent, flags);
}
@Override
@@ -1644,7 +1761,9 @@ public class ChooserActivity extends ResolverActivity {
private static final int MAX_SERVICE_TARGETS = 4;
private static final int MAX_TARGETS_PER_SERVICE = 2;
private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
// Reserve spots for incoming direct share targets by adding placeholders
private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
private List<ChooserTargetInfo> mServiceTargets;
private final List<TargetInfo> mCallerTargets = new ArrayList<>();
private boolean mShowServiceTargets;
@@ -1663,6 +1782,8 @@ public class ChooserActivity extends ResolverActivity {
super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
resolverListController);
mServiceTargets = createPlaceHolders();
if (initialIntents != null) {
final PackageManager pm = getPackageManager();
for (int i = 0; i < initialIntents.length; i++) {
@@ -1715,6 +1836,14 @@ public class ChooserActivity extends ResolverActivity {
}
}
private List<ChooserTargetInfo> createPlaceHolders() {
List<ChooserTargetInfo> list = new ArrayList<>();
for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
list.add(mPlaceHolderTargetInfo);
}
return list;
}
@Override
public boolean showsExtendedInfo(TargetInfo info) {
// We have badges so we don't need this text shown.
@@ -1770,22 +1899,33 @@ public class ChooserActivity extends ResolverActivity {
@Override
public int getCount() {
return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
return super.getCount() + getSelectableServiceTargetCount() + getCallerTargetCount();
}
@Override
public int getUnfilteredCount() {
return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
return super.getUnfilteredCount() + getSelectableServiceTargetCount()
+ getCallerTargetCount();
}
public int getCallerTargetCount() {
return mCallerTargets.size();
}
public int getServiceTargetCount() {
if (!mShowServiceTargets) {
return 0;
/**
* Filter out placeholders and non-selectable service targets
*/
public int getSelectableServiceTargetCount() {
int count = 0;
for (ChooserTargetInfo info : mServiceTargets) {
if (info instanceof SelectableTargetInfo) {
count++;
}
}
return count;
}
public int getServiceTargetCount() {
return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
}
@@ -1831,7 +1971,8 @@ public class ChooserActivity extends ResolverActivity {
}
offset += callerTargetCount;
final int serviceTargetCount = getServiceTargetCount();
final int serviceTargetCount = filtered ? getServiceTargetCount() :
getSelectableServiceTargetCount();
if (position - offset < serviceTargetCount) {
return mServiceTargets.get(position - offset);
}
@@ -1850,8 +1991,14 @@ public class ChooserActivity extends ResolverActivity {
if (mTargetsNeedPruning && targets.size() > 0) {
// First proper update since we got an onListRebuilt() with (transient) 0 items.
// Clear out the target list and rebuild.
mServiceTargets.clear();
mServiceTargets = createPlaceHolders();
mTargetsNeedPruning = false;
// Add back any app-supplied direct share targets that may have been
// wiped by this clear
if (mCallerChooserTargets != null) {
addServiceResults(null, Lists.newArrayList(mCallerChooserTargets));
}
}
final float parentScore = getScore(origTarget);
@@ -1867,7 +2014,7 @@ public class ChooserActivity extends ResolverActivity {
// This incents ChooserTargetServices to define what's truly better.
targetScore = lastScore * 0.95f;
}
insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
insertServiceTarget(new SelectableTargetInfo(origTarget, target, targetScore));
if (DEBUG) {
Log.d(TAG, " => " + target.toString() + " score=" + targetScore
@@ -1905,11 +2052,33 @@ public class ChooserActivity extends ResolverActivity {
}
}
/**
* Calling this marks service target loading complete, and will attempt to no longer
* update the direct share area.
*/
public void completeServiceTargetLoading() {
mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
if (mServiceTargets.isEmpty()) {
mServiceTargets.add(new EmptyTargetInfo());
}
notifyDataSetChanged();
}
private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
// Avoid inserting any potentially late results
if (mServiceTargets.size() == 1
&& mServiceTargets.get(0) instanceof EmptyTargetInfo) {
return;
}
final float newScore = chooserTargetInfo.getModifiedScore();
for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
if (newScore > serviceTarget.getModifiedScore()) {
if (serviceTarget == null) {
mServiceTargets.set(i, chooserTargetInfo);
return;
} else if (newScore > serviceTarget.getModifiedScore()) {
mServiceTargets.add(i, chooserTargetInfo);
return;
}
@@ -1968,7 +2137,7 @@ public class ChooserActivity extends ResolverActivity {
// There can be at most one row of service targets.
public int getServiceTargetRowCount() {
return (int) mChooserListAdapter.getServiceTargetCount() == 0 ? 0 : 1;
return 1;
}
@Override
@@ -2054,58 +2223,81 @@ public class ChooserActivity extends ResolverActivity {
final int start = getFirstRowPosition(rowPosition);
final int startType = mChooserListAdapter.getPositionTargetType(start);
final int lastStartType = mChooserListAdapter.getPositionTargetType(
getFirstRowPosition(rowPosition - 1));
if (startType != lastStartType || rowPosition == 0) {
holder.row.setBackground(mChooserRowLayer);
setVertPadding(holder, mChooserRowServiceSpacing, 0);
} else {
holder.row.setBackground(null);
setVertPadding(holder, 0, 0);
}
int end = start + mColumnCount - 1;
while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
end--;
}
if (startType == ChooserListAdapter.TARGET_SERVICE) {
int nextStartType = mChooserListAdapter.getPositionTargetType(
getFirstRowPosition(rowPosition + 1));
int serviceSpacing = holder.row.getContext().getResources()
.getDimensionPixelSize(R.dimen.chooser_service_spacing);
if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
// if the row is the only row for target service
setVertPadding(holder, 0, 0);
} else {
int top = rowPosition == 0 ? serviceSpacing : 0;
if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
setVertPadding(holder, top, serviceSpacing);
} else {
setVertPadding(holder, top, 0);
}
}
} else {
holder.row.setBackgroundColor(Color.TRANSPARENT);
int lastStartType = mChooserListAdapter.getPositionTargetType(
getFirstRowPosition(rowPosition - 1));
if (lastStartType == ChooserListAdapter.TARGET_SERVICE || rowPosition == 0) {
int serviceSpacing = holder.row.getContext().getResources()
.getDimensionPixelSize(R.dimen.chooser_service_spacing);
setVertPadding(holder, serviceSpacing, 0);
} else {
setVertPadding(holder, 0, 0);
}
}
if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
final TextView textView = holder.row.findViewById(R.id.chooser_row_text_option);
final int oldHeight = holder.row.getLayoutParams().height;
holder.row.getLayoutParams().height = Math.max(1, holder.measuredRowHeight);
if (holder.row.getLayoutParams().height != oldHeight) {
holder.row.requestLayout();
if (textView.getVisibility() != View.VISIBLE) {
textView.setAlpha(0.0f);
textView.setVisibility(View.VISIBLE);
textView.setText(R.string.chooser_no_direct_share_targets);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
float translationInPx = getResources().getDimensionPixelSize(
R.dimen.chooser_row_text_option_translate);
textView.setTranslationY(translationInPx);
ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
0.0f);
translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
animSet.playTogether(fadeAnim, translateAnim);
animSet.start();
}
}
for (int i = 0; i < mColumnCount; i++) {
final View v = holder.cells[i];
if (start + i <= end) {
v.setVisibility(View.VISIBLE);
setCellVisibility(holder, i, View.VISIBLE);
holder.itemIndices[i] = start + i;
mChooserListAdapter.bindView(holder.itemIndices[i], v);
} else {
v.setVisibility(View.INVISIBLE);
setCellVisibility(holder, i, View.INVISIBLE);
}
}
}
private void setCellVisibility(RowViewHolder holder, int i, int visibility) {
final View v = holder.cells[i];
if (visibility == View.VISIBLE) {
holder.cellVisibility[i] = true;
v.setVisibility(visibility);
v.setAlpha(1.0f);
} else if (visibility == View.INVISIBLE && holder.cellVisibility[i]) {
holder.cellVisibility[i] = false;
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
v.setVisibility(View.INVISIBLE);
}
});
fadeAnim.start();
}
}
private void setVertPadding(RowViewHolder holder, int top, int bottom) {
holder.row.setPadding(holder.row.getPaddingLeft(), top,
holder.row.getPaddingRight(), bottom);
@@ -2132,14 +2324,16 @@ public class ChooserActivity extends ResolverActivity {
}
static class RowViewHolder {
final View[] cells;
final ViewGroup row;
public final View[] cells;
public final boolean [] cellVisibility;
public final ViewGroup row;
int measuredRowHeight;
int[] itemIndices;
public RowViewHolder(ViewGroup row, int cellCount) {
this.row = row;
this.cells = new View[cellCount];
this.cellVisibility = new boolean[cellCount];
this.itemIndices = new int[cellCount];
}
@@ -2217,8 +2411,6 @@ public class ChooserActivity extends ResolverActivity {
mChooserActivity.unbindService(this);
mChooserActivity.mServiceConnections.remove(this);
if (mChooserActivity.mServiceConnections.isEmpty()) {
mChooserActivity.mChooserHandler.removeMessages(
CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
mChooserActivity.sendVoiceChoicesIfNeeded();
}
mConnectedComponent = null;

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 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.
*/
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:bottom="-2dp" android:left="-2dp" android:right="-2dp">
<shape android:shape="rectangle">
<stroke android:width="1dp" android:color="@color/chooser_row_divider"/>
</shape>
</item>
</layer-list>

View File

@@ -23,6 +23,12 @@
android:gravity="start|top"
android:paddingStart="@dimen/chooser_grid_padding"
android:paddingEnd="@dimen/chooser_grid_padding">
<TextView
android:id="@+id/chooser_row_text_option"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:visibility="gone" />
</LinearLayout>

View File

@@ -215,4 +215,6 @@
<!-- Magnifier -->
<color name="default_magnifier_color_overlay">#00FFFFFF</color>
<color name="chooser_row_divider">#1f000000</color>
</resources>

View File

@@ -717,6 +717,7 @@
<!-- chooser (sharesheet) spacing -->
<dimen name="chooser_corner_radius">8dp</dimen>
<dimen name="chooser_row_text_option_translate">25dp</dimen>
<dimen name="chooser_view_spacing">18dp</dimen>
<dimen name="chooser_edge_margin_thin">16dp</dimen>
<dimen name="chooser_edge_margin_normal">24dp</dimen>

View File

@@ -5340,4 +5340,8 @@
<item quantity="one"><xliff:g id="file_name">%s</xliff:g> + <xliff:g id="count">%d</xliff:g> file</item>
<item quantity="other"><xliff:g id="file_name">%s</xliff:g> + <xliff:g id="count">%d</xliff:g> files</item>
</plurals>
<!-- ChooserActivity - No direct share targets are available. [CHAR LIMIT=NONE] -->
<string name="chooser_no_direct_share_targets">Direct share not available</string>
</resources>

View File

@@ -2752,12 +2752,16 @@
<java-symbol type="id" name="month_view" />
<java-symbol type="integer" name="config_zen_repeat_callers_threshold" />
<java-symbol type="dimen" name="chooser_corner_radius" />
<java-symbol type="string" name="chooser_no_direct_share_targets" />
<java-symbol type="drawable" name="chooser_row_layer_list" />
<java-symbol type="dimen" name="chooser_view_spacing" />
<java-symbol type="dimen" name="chooser_edge_margin_thin" />
<java-symbol type="dimen" name="chooser_edge_margin_normal" />
<java-symbol type="dimen" name="chooser_preview_image_font_size"/>
<java-symbol type="dimen" name="chooser_preview_width" />
<java-symbol type="layout" name="chooser_grid" />
<java-symbol type="id" name="chooser_row_text_option" />
<java-symbol type="dimen" name="chooser_row_text_option_translate" />
<java-symbol type="layout" name="resolve_grid_item" />
<java-symbol type="id" name="day_picker_view_pager" />
<java-symbol type="layout" name="day_picker_content_material" />

View File

@@ -740,6 +740,7 @@ public class ChooserActivityTest {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
sendIntent.setType("text/plain");
return sendIntent;
}