Merge "Restore app pinning." into qt-qpr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
599634a26b
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user