Merge "Restore app pinning." into qt-qpr1-dev

This commit is contained in:
TreeHugger Robot
2019-11-20 00:06:52 +00:00
committed by Android (Google) Code Review
9 changed files with 267 additions and 8 deletions

View File

@@ -30,6 +30,7 @@ import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -37,7 +38,7 @@ import java.util.List;
/**
* Used to sort resolved activities in {@link ResolverListController}.
*/
abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
public abstract class AbstractResolverComparator implements Comparator<ResolvedComponentInfo> {
private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
private static final boolean DEBUG = false;
@@ -62,6 +63,8 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen
// predicting ranking scores.
private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
private final Comparator<ResolveInfo> mAzComparator;
protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -90,7 +93,7 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen
}
};
AbstractResolverComparator(Context context, Intent intent) {
public AbstractResolverComparator(Context context, Intent intent) {
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mContentType = intent.getType();
@@ -100,6 +103,7 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen
mDefaultBrowserPackageName = mHttp
? mPm.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId())
: null;
mAzComparator = new AzInfoComparator(context);
}
// get annotations of content from intent.
@@ -168,6 +172,20 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen
return lhsSpecific ? -1 : 1;
}
}
final boolean lPinned = lhsp.isPinned();
final boolean rPinned = rhsp.isPinned();
// Pinned items always receive priority.
if (lPinned && !rPinned) {
return -1;
} else if (!lPinned && rPinned) {
return 1;
} else if (lPinned && rPinned) {
// If both items are pinned, resolve the tie alphabetically.
return mAzComparator.compare(lhsp.getResolveInfoAt(0), rhsp.getResolveInfoAt(0));
}
return compare(lhs, rhs);
}
@@ -258,4 +276,25 @@ abstract class AbstractResolverComparator implements Comparator<ResolvedComponen
}
return false;
}
/**
* Sort intents alphabetically based on package name.
*/
class AzInfoComparator implements Comparator<ResolveInfo> {
Collator mCollator;
AzInfoComparator(Context context) {
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
}
@Override
public int compare(ResolveInfo lhsp, ResolveInfo rhsp) {
if (lhsp == null) {
return -1;
} else if (rhsp == null) {
return 1;
}
return mCollator.compare(lhsp.activityInfo.packageName, rhsp.activityInfo.packageName);
}
}
}

View File

@@ -43,6 +43,7 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.LabeledIntent;
@@ -68,6 +69,7 @@ import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -77,6 +79,7 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
import android.provider.DocumentsContract;
import android.provider.Downloads;
@@ -119,6 +122,7 @@ import com.android.internal.widget.ResolverDrawerLayout;
import com.google.android.collect.Lists;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -252,6 +256,9 @@ public class ChooserActivity extends ResolverActivity {
private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
private SharedPreferences mPinnedSharedPrefs;
private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
private boolean mListViewDataChanged = false;
@Retention(SOURCE)
@@ -596,6 +603,8 @@ public class ChooserActivity extends ResolverActivity {
Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
setSafeForwardingMode(true);
mPinnedSharedPrefs = getPinnedSharedPrefs(this);
pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
if (pa != null) {
ComponentName[] names = new ComponentName[pa.length];
@@ -727,6 +736,23 @@ public class ChooserActivity extends ResolverActivity {
}
}
static SharedPreferences getPinnedSharedPrefs(Context context) {
// The code below is because in the android:ui process, no one can hear you scream.
// The package info in the context isn't initialized in the way it is for normal apps,
// so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
// build the path manually below using the same policy that appears in ContextImpl.
// This fails silently under the hood if there's a problem, so if we find ourselves in
// the case where we don't have access to credential encrypted storage we just won't
// have our pinned target info.
final File prefsFile = new File(new File(
Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
context.getUserId(), context.getPackageName()),
"shared_prefs"),
PINNED_SHARED_PREFS_NAME + ".xml");
return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
}
/**
* Returns true if app prediction service is defined and the component exists on device.
*/
@@ -1273,9 +1299,10 @@ public class ChooserActivity extends ResolverActivity {
}
ComponentName name = ri.activityInfo.getComponentName();
boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
ResolverTargetActionsDialogFragment f =
new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
name);
name, pinned);
f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
}
@@ -1952,6 +1979,12 @@ public class ChooserActivity extends ResolverActivity {
}
return false;
}
@Override
public boolean isComponentPinned(ComponentName name) {
return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
}
}
@Override
@@ -2085,6 +2118,10 @@ public class ChooserActivity extends ResolverActivity {
public boolean isSuspended() {
return false;
}
public boolean isPinned() {
return false;
}
}
final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
@@ -2173,6 +2210,10 @@ public class ChooserActivity extends ResolverActivity {
return mIsSuspended;
}
public boolean isPinned() {
return mSourceInfo != null && mSourceInfo.isPinned();
}
/**
* Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
* the call to LauncherApps#getShortcuts(ShortcutQuery).

View File

@@ -1413,6 +1413,7 @@ public class ResolverActivity extends Activity {
private final Intent mResolvedIntent;
private final List<Intent> mSourceIntents = new ArrayList<>();
private boolean mIsSuspended;
private boolean mPinned = false;
public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
CharSequence pInfo, Intent pOrigIntent) {
@@ -1516,6 +1517,15 @@ public class ResolverActivity extends Activity {
public boolean isSuspended() {
return mIsSuspended;
}
@Override
public boolean isPinned() {
return mPinned;
}
public void setPinned(boolean pinned) {
mPinned = pinned;
}
}
List<DisplayResolveInfo> getDisplayList() {
@@ -1616,6 +1626,11 @@ public class ResolverActivity extends Activity {
* @return true if this target can be selected by the user
*/
boolean isSuspended();
/**
* @return true if this target should be pinned to the front by the request of the user
*/
boolean isPinned();
}
public class ResolveListAdapter extends BaseAdapter {
@@ -1922,6 +1937,10 @@ public class ResolverActivity extends Activity {
final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
extraInfo, replaceIntent);
dri.setPinned(rci.isPinned());
if (rci.isPinned()) {
Log.i(TAG, "Pinned item: " + rci.name);
}
addResolveInfo(dri);
if (replaceIntent == intent) {
// Only add alternates if we didn't get a specific replacement from
@@ -2090,6 +2109,7 @@ public class ResolverActivity extends Activity {
public final ComponentName name;
private final List<Intent> mIntents = new ArrayList<>();
private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
private boolean mPinned;
public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) {
this.name = name;
@@ -2130,6 +2150,15 @@ public class ResolverActivity extends Activity {
}
return -1;
}
public boolean isPinned() {
return mPinned;
}
public void setPinned(boolean pinned) {
mPinned = pinned;
}
}
static class ViewHolder {

View File

@@ -156,11 +156,22 @@ public class ResolverListController {
newInfo.activityInfo.packageName, newInfo.activityInfo.name);
final ResolverActivity.ResolvedComponentInfo rci =
new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
rci.setPinned(isComponentPinned(name));
into.add(rci);
}
}
}
/**
* Whether this component is pinned by the user. Always false for resolver; overridden in
* Chooser.
*/
public boolean isComponentPinned(ComponentName name) {
return false;
}
// Filter out any activities that the launched uid does not have permission for.
// To preserve the inputList, optionally will return the original list if any modification has
// been made.

View File

@@ -23,6 +23,7 @@ import android.app.DialogFragment;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
@@ -36,26 +37,33 @@ public class ResolverTargetActionsDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
private static final String NAME_KEY = "componentName";
private static final String TITLE_KEY = "title";
private static final String PINNED_KEY = "pinned";
// Sync with R.array.resolver_target_actions_* resources
private static final int APP_INFO_INDEX = 0;
private static final int TOGGLE_PIN_INDEX = 0;
private static final int APP_INFO_INDEX = 1;
public ResolverTargetActionsDialogFragment() {
}
public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name) {
public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
boolean pinned) {
Bundle args = new Bundle();
args.putCharSequence(TITLE_KEY, title);
args.putParcelable(NAME_KEY, name);
args.putBoolean(PINNED_KEY, pinned);
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;
return new Builder(getContext())
.setCancelable(true)
.setItems(R.array.resolver_target_actions, this)
.setItems(itemRes, this)
.setTitle(args.getCharSequence(TITLE_KEY))
.create();
}
@@ -65,6 +73,19 @@ public class ResolverTargetActionsDialogFragment extends DialogFragment
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))

View File

@@ -175,7 +175,15 @@
</array>
<!-- Used in ResolverTargetActionsDialogFragment -->
<string-array name="resolver_target_actions">
<!-- Used in ResolverTargetActionsDialogFragment -->
<string-array name="resolver_target_actions_pin">
<item>@string/pin_target</item>
<item>@string/app_info</item>
</string-array>
<string-array name="resolver_target_actions_unpin">
<item>@string/unpin_target</item>
<item>@string/app_info</item>
</string-array>

View File

@@ -5072,6 +5072,10 @@
<string name="usb_mtp_launch_notification_description">Tap to view files</string>
<!-- Resolver target actions strings -->
<!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
<string name="pin_target">Pin</string>
<!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
<string name="unpin_target">Unpin</string>
<!-- View application info for a target. -->
<string name="app_info">App info</string>

View File

@@ -3047,7 +3047,8 @@
<java-symbol type="color" name="notification_material_background_color" />
<!-- Resolver target actions -->
<java-symbol type="array" name="resolver_target_actions" />
<java-symbol type="array" name="resolver_target_actions_pin" />
<java-symbol type="array" name="resolver_target_actions_unpin" />
<java-symbol type="array" name="non_removable_euicc_slots" />

View File

@@ -0,0 +1,105 @@
/*
* 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 static junit.framework.Assert.assertEquals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.os.Message;
import androidx.test.InstrumentationRegistry;
import org.junit.Test;
import java.util.List;
public class AbstractResolverComparatorTest {
@Test
public void testPinned() {
ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
new ComponentName("package", "class"), new Intent(), new ResolveInfo()
);
r1.setPinned(true);
ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
new ComponentName("zackage", "zlass"), new Intent(), new ResolveInfo()
);
Context context = InstrumentationRegistry.getTargetContext();
AbstractResolverComparator comparator = getTestComparator(context);
assertEquals("Pinned ranks over unpinned", -1, comparator.compare(r1, r2));
assertEquals("Unpinned ranks under pinned", 1, comparator.compare(r2, r1));
}
@Test
public void testBothPinned() {
ResolveInfo pmInfo1 = new ResolveInfo();
pmInfo1.activityInfo = new ActivityInfo();
pmInfo1.activityInfo.packageName = "aaa";
ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
new ComponentName("package", "class"), new Intent(), pmInfo1);
r1.setPinned(true);
ResolveInfo pmInfo2 = new ResolveInfo();
pmInfo2.activityInfo = new ActivityInfo();
pmInfo2.activityInfo.packageName = "zzz";
ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
new ComponentName("zackage", "zlass"), new Intent(), pmInfo2);
r2.setPinned(true);
Context context = InstrumentationRegistry.getTargetContext();
AbstractResolverComparator comparator = getTestComparator(context);
assertEquals("Both pinned should rank alphabetically", -1, comparator.compare(r1, r2));
}
private AbstractResolverComparator getTestComparator(Context context) {
Intent intent = new Intent();
AbstractResolverComparator testComparator =
new AbstractResolverComparator(context, intent) {
@Override
int compare(ResolveInfo lhs, ResolveInfo rhs) {
// Used for testing pinning, so we should never get here --- the overrides should
// determine the result instead.
return 1;
}
@Override
void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
@Override
float getScore(ComponentName name) {
return 0;
}
@Override
void handleResultMessage(Message message) {}
};
return testComparator;
}
}