Merge "Removing support for adding legacy shortcuts." into udc-dev
This commit is contained in:
@@ -2,8 +2,11 @@
|
||||
// Note: Comments are not supported in JSON schema, but android parser is lenient.
|
||||
|
||||
// Maximum DB version supported by this schema
|
||||
"version" : 31,
|
||||
|
||||
"version" : 32,
|
||||
"downgrade_to_31" : [
|
||||
"ALTER TABLE favorites ADD COLUMN iconPackage TEXT;",
|
||||
"ALTER TABLE favorites ADD COLUMN iconResource TEXT;"
|
||||
],
|
||||
"downgrade_to_30" : [],
|
||||
"downgrade_to_29" : [],
|
||||
"downgrade_to_28" : [
|
||||
|
||||
@@ -192,9 +192,6 @@
|
||||
<string name="permdesc_write_settings">Allows the app to change the settings and
|
||||
shortcuts in home.</string>
|
||||
|
||||
<!-- Toast shown on clicking a direct call shortcut. [CHAR_LIMIT=80] -->
|
||||
<string name="msg_no_phone_permission"><xliff:g id="app_name" example="Launcher3">%1$s</xliff:g> is not allowed to make phone calls</string>
|
||||
|
||||
<!-- Widgets: -->
|
||||
<skip />
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Color;
|
||||
@@ -92,7 +91,6 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import android.os.StrictMode;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
@@ -115,7 +113,6 @@ import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
import android.widget.Toast;
|
||||
import android.window.BackEvent;
|
||||
import android.window.OnBackAnimationCallback;
|
||||
|
||||
@@ -160,7 +157,6 @@ import com.android.launcher3.logging.InstanceIdSequence;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.model.ItemInstallQueue;
|
||||
import com.android.launcher3.model.ModelUtils;
|
||||
import com.android.launcher3.model.ModelWriter;
|
||||
import com.android.launcher3.model.StringCache;
|
||||
import com.android.launcher3.model.WidgetsModel;
|
||||
@@ -192,7 +188,6 @@ import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.PendingRequestArgs;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
@@ -263,8 +258,6 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
|
||||
public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
|
||||
|
||||
private static final int REQUEST_PERMISSION_CALL_PHONE = 14;
|
||||
|
||||
private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
|
||||
|
||||
/**
|
||||
@@ -975,33 +968,6 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
handleActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||
int[] grantResults) {
|
||||
PendingRequestArgs pendingArgs = mPendingRequestArgs;
|
||||
if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null
|
||||
&& pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) {
|
||||
setWaitingForResult(null);
|
||||
|
||||
View v = null;
|
||||
CellPos cellPos = getCellPosMapper().mapModelToPresenter(pendingArgs);
|
||||
CellLayout layout = getCellLayout(pendingArgs.container, cellPos.screenId);
|
||||
if (layout != null) {
|
||||
v = layout.getChildAt(cellPos.cellX, cellPos.cellY);
|
||||
}
|
||||
Intent intent = pendingArgs.getPendingIntent();
|
||||
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startActivitySafely(v, intent, null);
|
||||
} else {
|
||||
// TODO: Show a snack bar with link to settings
|
||||
Toast.makeText(this, getString(R.string.msg_no_phone_permission,
|
||||
getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a given screen id exists. If not, create it at the end, return the new id.
|
||||
*
|
||||
@@ -1417,21 +1383,9 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
|
||||
WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
|
||||
this, PinRequestHelper.getPinItemRequest(data), 0);
|
||||
|
||||
if (info == null) {
|
||||
// Legacy shortcuts are only supported for primary profile.
|
||||
info = Process.myUserHandle().equals(args.user)
|
||||
? ModelUtils.fromLegacyShortcutIntent(this, data) : null;
|
||||
|
||||
if (info == null) {
|
||||
Log.e(TAG, "Unable to parse a valid custom shortcut result");
|
||||
return;
|
||||
} else if (!new PackageManagerHelper(this).hasPermissionForActivity(
|
||||
info.intent, args.getPendingIntent().getComponent().getPackageName())) {
|
||||
// The app is trying to add a shortcut without sufficient permissions
|
||||
Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "Unable to parse a valid shortcut result");
|
||||
return;
|
||||
}
|
||||
|
||||
if (container < 0) {
|
||||
@@ -2151,27 +2105,6 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
|
||||
// Due to legacy reasons, direct call shortcuts require Launchers to have the
|
||||
// corresponding permission. Show the appropriate permission prompt if that
|
||||
// is the case.
|
||||
if (intent.getComponent() == null
|
||||
&& Intent.ACTION_CALL.equals(intent.getAction())
|
||||
&& checkSelfPermission(android.Manifest.permission.CALL_PHONE) !=
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
setWaitingForResult(PendingRequestArgs
|
||||
.forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info));
|
||||
requestPermissions(new String[]{android.Manifest.permission.CALL_PHONE},
|
||||
REQUEST_PERMISSION_CALL_PHONE);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
|
||||
if (!hasBeenResumed()) {
|
||||
|
||||
@@ -130,18 +130,6 @@ public class LauncherSettings {
|
||||
*/
|
||||
public static final int ITEM_TYPE_SEARCH_ACTION = 9;
|
||||
|
||||
/**
|
||||
* The icon package name in Intent.ShortcutIconResource
|
||||
* <P>Type: TEXT</P>
|
||||
*/
|
||||
public static final String ICON_PACKAGE = "iconPackage";
|
||||
|
||||
/**
|
||||
* The icon resource name in Intent.ShortcutIconResource
|
||||
* <P>Type: TEXT</P>
|
||||
*/
|
||||
public static final String ICON_RESOURCE = "iconResource";
|
||||
|
||||
/**
|
||||
* The custom icon bitmap.
|
||||
* <P>Type: BLOB</P>
|
||||
@@ -357,8 +345,6 @@ public class LauncherSettings {
|
||||
"spanY INTEGER," +
|
||||
"itemType INTEGER," +
|
||||
"appWidgetId INTEGER NOT NULL DEFAULT -1," +
|
||||
"iconPackage TEXT," +
|
||||
"iconResource TEXT," +
|
||||
"icon BLOB," +
|
||||
"appWidgetProvider TEXT," +
|
||||
"modified INTEGER NOT NULL DEFAULT 0," +
|
||||
|
||||
@@ -72,7 +72,7 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
|
||||
* Represents the schema of the database. Changes in scheme need not be backwards compatible.
|
||||
* When increasing the scheme version, ensure that downgrade_schema.json is updated
|
||||
*/
|
||||
public static final int SCHEMA_VERSION = 31;
|
||||
public static final int SCHEMA_VERSION = 32;
|
||||
private static final String TAG = "DatabaseHelper";
|
||||
private static final boolean LOGD = false;
|
||||
|
||||
@@ -327,6 +327,10 @@ public class DatabaseHelper extends NoLocaleSQLiteHelper implements
|
||||
return;
|
||||
}
|
||||
case 31: {
|
||||
LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
|
||||
}
|
||||
// Fall through
|
||||
case 32: {
|
||||
// DB Upgraded successfully
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,8 +72,18 @@ public class DbDowngradeHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a helper from the provided file
|
||||
*/
|
||||
public static DbDowngradeHelper parse(File file) throws JSONException, IOException {
|
||||
JSONObject obj = new JSONObject(new String(IOUtils.toByteArray(file)));
|
||||
return parse(IOUtils.toByteArray(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a helper from the provided bytes
|
||||
*/
|
||||
public static DbDowngradeHelper parse(byte[] fileData) throws JSONException {
|
||||
JSONObject obj = new JSONObject(new String(fileData));
|
||||
DbDowngradeHelper helper = new DbDowngradeHelper(obj.getInt(KEY_VERSION));
|
||||
for (int version = helper.version - 1; version > 0; version--) {
|
||||
if (obj.has(KEY_DOWNGRADE_TO + version)) {
|
||||
|
||||
@@ -75,8 +75,6 @@ public class LoaderCursor extends CursorWrapper {
|
||||
private final IntArray mRestoredRows = new IntArray();
|
||||
private final IntSparseArrayMap<GridOccupancy> mOccupied = new IntSparseArrayMap<>();
|
||||
|
||||
private final int mIconPackageIndex;
|
||||
private final int mIconResourceIndex;
|
||||
private final int mIconIndex;
|
||||
public final int mTitleIndex;
|
||||
|
||||
@@ -122,8 +120,6 @@ public class LoaderCursor extends CursorWrapper {
|
||||
|
||||
// Init column indices
|
||||
mIconIndex = getColumnIndexOrThrow(Favorites.ICON);
|
||||
mIconPackageIndex = getColumnIndexOrThrow(Favorites.ICON_PACKAGE);
|
||||
mIconResourceIndex = getColumnIndexOrThrow(Favorites.ICON_RESOURCE);
|
||||
mTitleIndex = getColumnIndexOrThrow(Favorites.TITLE);
|
||||
|
||||
mIdIndex = getColumnIndexOrThrow(Favorites._ID);
|
||||
@@ -200,23 +196,25 @@ public class LoaderCursor extends CursorWrapper {
|
||||
|
||||
public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
|
||||
WorkspaceItemInfo wai, boolean useLowResIcon) {
|
||||
String packageName = itemType == Favorites.ITEM_TYPE_SHORTCUT
|
||||
? getString(mIconPackageIndex) : null;
|
||||
String resourceName = itemType == Favorites.ITEM_TYPE_SHORTCUT
|
||||
? getString(mIconResourceIndex) : null;
|
||||
byte[] iconBlob = itemType == Favorites.ITEM_TYPE_SHORTCUT
|
||||
|| itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
|| restoreFlag != 0
|
||||
? getBlob(mIconIndex) : null;
|
||||
? getIconBlob() : null;
|
||||
|
||||
return new IconRequestInfo<>(
|
||||
wai, mActivityInfo, packageName, resourceName, iconBlob, useLowResIcon);
|
||||
return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the icon data for at the current position
|
||||
*/
|
||||
public byte[] getIconBlob() {
|
||||
return getBlob(mIconIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title or empty string
|
||||
*/
|
||||
private String getTitle() {
|
||||
public String getTitle() {
|
||||
return Utilities.trim(getString(mTitleIndex));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,18 +15,10 @@
|
||||
*/
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
@@ -99,60 +91,4 @@ public class ModelUtils {
|
||||
IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a workspace item info for the legacy shortcut intent
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static WorkspaceItemInfo fromLegacyShortcutIntent(Context context, Intent data) {
|
||||
if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class)
|
||||
|| !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
|
||||
Intent.ShortcutIconResource.class))
|
||||
|| !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
|
||||
|
||||
Log.e(TAG, "Invalid install shortcut intent");
|
||||
return null;
|
||||
}
|
||||
|
||||
Intent launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
|
||||
String label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
|
||||
if (launchIntent == null || label == null) {
|
||||
Log.e(TAG, "Invalid install shortcut intent");
|
||||
return null;
|
||||
}
|
||||
|
||||
final WorkspaceItemInfo info = new WorkspaceItemInfo();
|
||||
info.user = Process.myUserHandle();
|
||||
|
||||
BitmapInfo iconInfo = null;
|
||||
try (LauncherIcons li = LauncherIcons.obtain(context)) {
|
||||
Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
|
||||
if (bitmap != null) {
|
||||
iconInfo = li.createIconBitmap(bitmap);
|
||||
} else {
|
||||
info.iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
|
||||
if (info.iconResource != null) {
|
||||
iconInfo = li.createIconBitmap(info.iconResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iconInfo == null) {
|
||||
Log.e(TAG, "Invalid icon by the app");
|
||||
return null;
|
||||
}
|
||||
info.bitmap = iconInfo;
|
||||
info.contentDescription = info.title = Utilities.trim(label);
|
||||
info.intent = launchIntent;
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the extra is either null or is of type {@param type}
|
||||
*/
|
||||
private static boolean isValidExtraType(Intent intent, String key, Class type) {
|
||||
Object extra = intent.getParcelableExtra(key);
|
||||
return extra == null || type.isInstance(extra);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,7 @@ import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
import com.android.launcher3.logging.FileLog;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
@@ -204,18 +202,6 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
|
||||
boolean infoUpdated = false;
|
||||
boolean shortcutUpdated = false;
|
||||
|
||||
// Update shortcuts which use iconResource.
|
||||
if ((si.iconResource != null)
|
||||
&& packageSet.contains(si.iconResource.packageName)) {
|
||||
LauncherIcons li = LauncherIcons.obtain(context);
|
||||
BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
|
||||
li.recycle();
|
||||
if (iconInfo != null) {
|
||||
si.bitmap = iconInfo;
|
||||
infoUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
ComponentName cn = si.getTargetComponent();
|
||||
if (cn != null && matcher.test(si)) {
|
||||
String packageName = cn.getPackageName();
|
||||
|
||||
@@ -18,16 +18,12 @@ package com.android.launcher3.model.data;
|
||||
import static android.graphics.BitmapFactory.decodeByteArray;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherActivityInfo;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
|
||||
/**
|
||||
@@ -42,8 +38,6 @@ public class IconRequestInfo<T extends ItemInfoWithIcon> {
|
||||
|
||||
@NonNull public final T itemInfo;
|
||||
@Nullable public final LauncherActivityInfo launcherActivityInfo;
|
||||
@Nullable public final String packageName;
|
||||
@Nullable public final String resourceName;
|
||||
@Nullable public final byte[] iconBlob;
|
||||
public final boolean useLowResIcon;
|
||||
|
||||
@@ -54,8 +48,6 @@ public class IconRequestInfo<T extends ItemInfoWithIcon> {
|
||||
this(
|
||||
itemInfo,
|
||||
launcherActivityInfo,
|
||||
/* packageName= */ null,
|
||||
/* resourceName= */ null,
|
||||
/* iconBlob= */ null,
|
||||
useLowResIcon);
|
||||
}
|
||||
@@ -63,14 +55,10 @@ public class IconRequestInfo<T extends ItemInfoWithIcon> {
|
||||
public IconRequestInfo(
|
||||
@NonNull T itemInfo,
|
||||
@Nullable LauncherActivityInfo launcherActivityInfo,
|
||||
@Nullable String packageName,
|
||||
@Nullable String resourceName,
|
||||
@Nullable byte[] iconBlob,
|
||||
boolean useLowResIcon) {
|
||||
this.itemInfo = itemInfo;
|
||||
this.launcherActivityInfo = launcherActivityInfo;
|
||||
this.packageName = packageName;
|
||||
this.resourceName = resourceName;
|
||||
this.iconBlob = iconBlob;
|
||||
this.useLowResIcon = useLowResIcon;
|
||||
}
|
||||
@@ -87,31 +75,16 @@ public class IconRequestInfo<T extends ItemInfoWithIcon> {
|
||||
|
||||
try (LauncherIcons li = LauncherIcons.obtain(context)) {
|
||||
WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo;
|
||||
if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
|
||||
if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
|
||||
info.iconResource = new Intent.ShortcutIconResource();
|
||||
info.iconResource.packageName = packageName;
|
||||
info.iconResource.resourceName = resourceName;
|
||||
BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
|
||||
if (iconInfo != null) {
|
||||
info.bitmap = iconInfo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to load from resource, try loading from DB.
|
||||
try {
|
||||
if (iconBlob == null) {
|
||||
return false;
|
||||
}
|
||||
info.bitmap = li.createIconBitmap(decodeByteArray(
|
||||
iconBlob, 0, iconBlob.length));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to decode byte array for info " + info, e);
|
||||
if (iconBlob == null) {
|
||||
return false;
|
||||
}
|
||||
info.bitmap = li.createIconBitmap(decodeByteArray(
|
||||
iconBlob, 0, iconBlob.length));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to decode byte array for info " + itemInfo, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +78,6 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
|
||||
@NonNull
|
||||
public Intent intent;
|
||||
|
||||
/**
|
||||
* If isShortcut=true and customIcon=false, this contains a reference to the
|
||||
* shortcut icon as an application's resource.
|
||||
*/
|
||||
public Intent.ShortcutIconResource iconResource;
|
||||
|
||||
/**
|
||||
* A message to display when the user tries to start a disabled shortcut.
|
||||
* This is currently only used for deep shortcuts.
|
||||
@@ -109,7 +103,6 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
|
||||
super(info);
|
||||
title = info.title;
|
||||
intent = new Intent(info.intent);
|
||||
iconResource = info.iconResource;
|
||||
status = info.status;
|
||||
personKeys = info.personKeys.clone();
|
||||
}
|
||||
@@ -141,10 +134,6 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon {
|
||||
if (!usingLowResIcon()) {
|
||||
writer.putIcon(bitmap, user);
|
||||
}
|
||||
if (iconResource != null) {
|
||||
writer.put(Favorites.ICON_PACKAGE, iconResource.packageName)
|
||||
.put(Favorites.ICON_RESOURCE, iconResource.resourceName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,8 +24,10 @@ import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.LauncherApps.PinItemRequest;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.os.Build;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -63,17 +65,10 @@ public class PinRequestHelper {
|
||||
}
|
||||
} else {
|
||||
// Block the worker thread until the accept() is called.
|
||||
MODEL_EXECUTOR.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(acceptDelay);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (request.isValid()) {
|
||||
request.accept();
|
||||
}
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
SystemClock.sleep(acceptDelay);
|
||||
if (request.isValid()) {
|
||||
request.accept();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -95,4 +90,13 @@ public class PinRequestHelper {
|
||||
Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);
|
||||
return extra instanceof PinItemRequest ? (PinItemRequest) extra : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PinItemRequest corresponding to the provided ShortcutInfo
|
||||
*/
|
||||
public static PinItemRequest createRequestForShortcut(Context context, ShortcutInfo info) {
|
||||
return context.getSystemService(LauncherApps.class)
|
||||
.getPinItemRequest(context.getSystemService(ShortcutManager.class)
|
||||
.createShortcutResultIntent(info));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +151,6 @@ public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAn
|
||||
public static List<ShortcutConfigActivityInfo> queryList(
|
||||
Context context, @Nullable PackageUserKey packageUser) {
|
||||
List<ShortcutConfigActivityInfo> result = new ArrayList<>();
|
||||
UserHandle myUser = Process.myUserHandle();
|
||||
|
||||
final List<UserHandle> users;
|
||||
final String packageName;
|
||||
if (packageUser == null) {
|
||||
@@ -164,11 +162,9 @@ public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAn
|
||||
}
|
||||
LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
|
||||
for (UserHandle user : users) {
|
||||
boolean ignoreTargetSdk = myUser.equals(user);
|
||||
for (LauncherActivityInfo activityInfo :
|
||||
launcherApps.getShortcutConfigActivityList(packageName, user)) {
|
||||
if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
|
||||
>= Build.VERSION_CODES.O) {
|
||||
if (activityInfo.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
|
||||
result.add(new ShortcutConfigActivityInfoVO(activityInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,34 @@
|
||||
|
||||
package com.android.launcher3.provider;
|
||||
|
||||
import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Binder;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.Process;
|
||||
import android.os.UserManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.LoaderCursor;
|
||||
import com.android.launcher3.model.UserManagerState;
|
||||
import com.android.launcher3.pm.PinRequestHelper;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
|
||||
/**
|
||||
* A set of utility methods for Launcher DB used for DB updates and migration.
|
||||
@@ -72,6 +91,78 @@ public class LauncherDbUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the legacy shortcuts to deep shortcuts pinned under Launcher.
|
||||
* Removes any invalid shortcut or any shortcut which requires some permission to launch
|
||||
*/
|
||||
public static void migrateLegacyShortcuts(Context context, SQLiteDatabase db) {
|
||||
Cursor c = db.query(
|
||||
Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null);
|
||||
UserManagerState ums = new UserManagerState();
|
||||
ums.init(UserCache.INSTANCE.get(context),
|
||||
context.getSystemService(UserManager.class));
|
||||
LoaderCursor lc = new LoaderCursor(c, null, LauncherAppState.getInstance(context), ums);
|
||||
IntSet deletedShortcuts = new IntSet();
|
||||
|
||||
while (lc.moveToNext()) {
|
||||
if (lc.user != Process.myUserHandle()) {
|
||||
deletedShortcuts.add(lc.id);
|
||||
continue;
|
||||
}
|
||||
Intent intent = lc.parseIntent();
|
||||
if (intent == null) {
|
||||
deletedShortcuts.add(lc.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure the target intent can be launched without any permissions. Otherwise remove
|
||||
// the shortcut
|
||||
ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0);
|
||||
if (ri == null || !TextUtils.isEmpty(ri.activityInfo.permission)) {
|
||||
deletedShortcuts.add(lc.id);
|
||||
continue;
|
||||
}
|
||||
PersistableBundle extras = new PersistableBundle();
|
||||
extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, ri.activityInfo.packageName);
|
||||
ShortcutInfo.Builder infoBuilder = new ShortcutInfo.Builder(
|
||||
context, "migrated_shortcut-" + lc.id)
|
||||
.setIntent(intent)
|
||||
.setExtras(extras)
|
||||
.setShortLabel(lc.getTitle());
|
||||
|
||||
Bitmap bitmap = null;
|
||||
byte[] iconData = lc.getIconBlob();
|
||||
if (iconData != null) {
|
||||
bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.length);
|
||||
}
|
||||
if (bitmap != null) {
|
||||
infoBuilder.setIcon(Icon.createWithBitmap(bitmap));
|
||||
}
|
||||
|
||||
ShortcutInfo info = infoBuilder.build();
|
||||
if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
|
||||
deletedShortcuts.add(lc.id);
|
||||
continue;
|
||||
}
|
||||
ContentValues update = new ContentValues();
|
||||
update.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
|
||||
update.put(Favorites.INTENT,
|
||||
ShortcutKey.makeIntent(info.getId(), context.getPackageName()).toUri(0));
|
||||
db.update(Favorites.TABLE_NAME, update, "_id = ?",
|
||||
new String[] {Integer.toString(lc.id)});
|
||||
}
|
||||
lc.close();
|
||||
if (!deletedShortcuts.isEmpty()) {
|
||||
db.delete(Favorites.TABLE_NAME,
|
||||
Utilities.createDbSelectionQuery(Favorites._ID, deletedShortcuts.getArray()),
|
||||
null);
|
||||
}
|
||||
|
||||
// Drop the unused columns
|
||||
db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconPackage;");
|
||||
db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconResource;");
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to simplify managing sqlite transactions
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -30,7 +29,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
@@ -139,48 +137,6 @@ public class PackageManagerHelper {
|
||||
return (info.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if {@param srcPackage} has the permission required to start the activity from
|
||||
* {@param intent}. If {@param srcPackage} is null, then the activity should not need
|
||||
* any permissions
|
||||
*/
|
||||
public boolean hasPermissionForActivity(Intent intent, String srcPackage) {
|
||||
ResolveInfo target = mPm.resolveActivity(intent, 0);
|
||||
if (target == null) {
|
||||
// Not a valid target
|
||||
return false;
|
||||
}
|
||||
if (TextUtils.isEmpty(target.activityInfo.permission)) {
|
||||
// No permission is needed
|
||||
return true;
|
||||
}
|
||||
if (TextUtils.isEmpty(srcPackage)) {
|
||||
// The activity requires some permission but there is no source.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Source does not have sufficient permissions.
|
||||
if(mPm.checkPermission(target.activityInfo.permission, srcPackage) !=
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// On M and above also check AppOpsManager for compatibility mode permissions.
|
||||
if (TextUtils.isEmpty(AppOpsManager.permissionToOp(target.activityInfo.permission))) {
|
||||
// There is no app-op for this permission, which could have been disabled.
|
||||
return true;
|
||||
}
|
||||
|
||||
// There is no direct way to check if the app-op is allowed for a particular app. Since
|
||||
// app-op is only enabled for apps running in compatibility mode, simply block such apps.
|
||||
|
||||
try {
|
||||
return mPm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
|
||||
} catch (NameNotFoundException e) { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Intent getMarketIntent(String packageName) {
|
||||
return new Intent(Intent.ACTION_VIEW)
|
||||
.setData(new Uri.Builder()
|
||||
|
||||
@@ -437,9 +437,7 @@ public interface ActivityContext {
|
||||
StrictMode.setVmPolicy(oldPolicy);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
if (!onErrorStartingShortcut(intent, info)) {
|
||||
throw e;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,16 +457,6 @@ public interface ActivityContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a shortcut fails to launch.
|
||||
* @param intent Shortcut intent that failed to start.
|
||||
* @param info Shortcut information.
|
||||
* @return {@code true} if the error is handled by this callback.
|
||||
*/
|
||||
default boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default CellPosMapper getCellPosMapper() {
|
||||
return CellPosMapper.DEFAULT;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,21 @@ public class DbDowngradeHelperTest {
|
||||
assertEquals(0, mSchemaFile.lastModified());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDowngrade_success_v31() throws Exception {
|
||||
setupTestDb();
|
||||
|
||||
try (SQLiteOpenHelper helper = new MyDatabaseHelper()) {
|
||||
assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), "iconPackage"));
|
||||
assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), "iconResource"));
|
||||
}
|
||||
|
||||
try (TestOpenHelper helper = new TestOpenHelper(24)) {
|
||||
assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), "iconPackage"));
|
||||
assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), "iconResource"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDowngrade_success_v24() throws Exception {
|
||||
setupTestDb();
|
||||
@@ -121,34 +136,18 @@ public class DbDowngradeHelperTest {
|
||||
public void testDowngrade_success_v22() throws Exception {
|
||||
setupTestDb();
|
||||
|
||||
SQLiteOpenHelper helper = new TestOpenHelper(22);
|
||||
assertEquals(22, helper.getWritableDatabase().getVersion());
|
||||
|
||||
// Check column does not exist
|
||||
try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME,
|
||||
null, null, null, null, null, null)) {
|
||||
assertEquals(-1, c.getColumnIndex(Favorites.OPTIONS));
|
||||
|
||||
// Check data is present
|
||||
assertEquals(10, c.getCount());
|
||||
try (SQLiteOpenHelper helper = new TestOpenHelper(22)) {
|
||||
assertEquals(22, helper.getWritableDatabase().getVersion());
|
||||
assertFalse(hasFavoritesColumn(helper.getWritableDatabase(), Favorites.OPTIONS));
|
||||
assertEquals(10, getFavoriteDataCount(helper.getWritableDatabase()));
|
||||
}
|
||||
helper.close();
|
||||
|
||||
helper = new DatabaseHelper(mContext, DB_FILE, false) {
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) { }
|
||||
};
|
||||
assertEquals(DatabaseHelper.SCHEMA_VERSION, helper.getWritableDatabase().getVersion());
|
||||
|
||||
try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME,
|
||||
null, null, null, null, null, null)) {
|
||||
// Check column exists
|
||||
assertNotSame(-1, c.getColumnIndex(Favorites.OPTIONS));
|
||||
|
||||
// Check data is present
|
||||
assertEquals(10, c.getCount());
|
||||
try (SQLiteOpenHelper helper = new MyDatabaseHelper()) {
|
||||
assertEquals(DatabaseHelper.SCHEMA_VERSION,
|
||||
helper.getWritableDatabase().getVersion());
|
||||
assertTrue(hasFavoritesColumn(helper.getWritableDatabase(), Favorites.OPTIONS));
|
||||
assertEquals(10, getFavoriteDataCount(helper.getWritableDatabase()));
|
||||
}
|
||||
helper.close();
|
||||
}
|
||||
|
||||
@Test(expected = DowngradeFailException.class)
|
||||
@@ -165,10 +164,7 @@ public class DbDowngradeHelperTest {
|
||||
|
||||
DbDowngradeHelper.updateSchemaFile(mSchemaFile, DatabaseHelper.SCHEMA_VERSION, mContext);
|
||||
|
||||
DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) {
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) { }
|
||||
};
|
||||
DatabaseHelper dbHelper = new MyDatabaseHelper();
|
||||
// Insert mock data
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ContentValues values = new ContentValues();
|
||||
@@ -210,4 +206,26 @@ public class DbDowngradeHelperTest {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasFavoritesColumn(SQLiteDatabase db, String columnName) {
|
||||
try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
return c.getColumnIndex(columnName) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getFavoriteDataCount(SQLiteDatabase db) {
|
||||
try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
return c.getCount();
|
||||
}
|
||||
}
|
||||
|
||||
private class MyDatabaseHelper extends DatabaseHelper {
|
||||
|
||||
MyDatabaseHelper() {
|
||||
super(mContext, DB_FILE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ICON;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
@@ -97,11 +95,10 @@ public class LoaderCursorTest {
|
||||
mApp = LauncherAppState.getInstance(mContext);
|
||||
|
||||
mCursor = new MatrixCursor(new String[] {
|
||||
ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE,
|
||||
_ID, CONTAINER, ITEM_TYPE, PROFILE_ID,
|
||||
SCREEN, CELLX, CELLY, RESTORED, INTENT,
|
||||
APPWIDGET_ID, APPWIDGET_PROVIDER, SPANX,
|
||||
SPANY, RANK, OPTIONS, APPWIDGET_SOURCE
|
||||
ICON, TITLE, _ID, CONTAINER, ITEM_TYPE,
|
||||
PROFILE_ID, SCREEN, CELLX, CELLY, RESTORED,
|
||||
INTENT, APPWIDGET_ID, APPWIDGET_PROVIDER,
|
||||
SPANX, SPANY, RANK, OPTIONS, APPWIDGET_SOURCE
|
||||
});
|
||||
|
||||
UserManagerState ums = new UserManagerState();
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.launcher3.provider;
|
||||
|
||||
import static android.content.pm.LauncherApps.EXTRA_PIN_ITEM_REQUEST;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps.PinItemRequest;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Process;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.DatabaseHelper;
|
||||
import com.android.launcher3.model.DbDowngradeHelper;
|
||||
import com.android.launcher3.settings.SettingsActivity;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.IOUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Test for {@link LauncherDbUtils}.
|
||||
*/
|
||||
public class LauncherDbUtilsTest {
|
||||
|
||||
private Context mContext;
|
||||
private ArgumentCaptor<ShortcutInfo> mInfoArgumentCaptor;
|
||||
private boolean mPinningAllowed = true;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
PinItemRequest req = mock(PinItemRequest.class);
|
||||
doAnswer(i -> mPinningAllowed).when(req).accept();
|
||||
|
||||
mInfoArgumentCaptor = ArgumentCaptor.forClass(ShortcutInfo.class);
|
||||
ShortcutManager sm = mock(ShortcutManager.class);
|
||||
when(sm.createShortcutResultIntent(mInfoArgumentCaptor.capture()))
|
||||
.thenReturn(new Intent().putExtra(EXTRA_PIN_ITEM_REQUEST, req));
|
||||
|
||||
mContext = new ContextWrapper(getInstrumentation().getTargetContext()) {
|
||||
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
return SHORTCUT_SERVICE.equals(name) ? sm : super.getSystemService(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateLegacyShortcuts_invalidIntent() throws Exception {
|
||||
SQLiteDatabase db = setupLegacyShortcut(null);
|
||||
assertEquals(1, getFavoriteDataCount(db));
|
||||
|
||||
LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
|
||||
|
||||
assertEquals(0, getFavoriteDataCount(db));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateLegacyShortcuts_protectedIntent() throws Exception {
|
||||
Intent intent = new Intent(Intent.ACTION_CALL)
|
||||
.setData(Uri.parse("tel:0000000000"));
|
||||
SQLiteDatabase db = setupLegacyShortcut(intent);
|
||||
assertEquals(1, getFavoriteDataCount(db));
|
||||
|
||||
LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
|
||||
|
||||
assertEquals(0, getFavoriteDataCount(db));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateLegacyShortcuts_pinningDisabled() throws Exception {
|
||||
mPinningAllowed = false;
|
||||
Intent intent = new Intent(mContext, SettingsActivity.class);
|
||||
SQLiteDatabase db = setupLegacyShortcut(intent);
|
||||
assertEquals(1, getFavoriteDataCount(db));
|
||||
|
||||
LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
|
||||
|
||||
assertEquals(0, getFavoriteDataCount(db));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateLegacyShortcuts_success() throws Exception {
|
||||
Intent intent = new Intent(mContext, SettingsActivity.class);
|
||||
SQLiteDatabase db = setupLegacyShortcut(intent);
|
||||
assertEquals(1, getFavoriteDataCount(db));
|
||||
|
||||
LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
|
||||
|
||||
assertEquals(1, getFavoriteDataCount(db));
|
||||
ShortcutInfo info = mInfoArgumentCaptor.getValue();
|
||||
assertNotNull(info);
|
||||
assertEquals("Hello", info.getTitle());
|
||||
try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
c.moveToNext();
|
||||
assertEquals(Favorites.ITEM_TYPE_DEEP_SHORTCUT, c.getInt(c.getColumnIndex(ITEM_TYPE)));
|
||||
|
||||
ShortcutKey key = ShortcutKey.fromIntent(
|
||||
Intent.parseUri(c.getString(c.getColumnIndex(INTENT)), 0),
|
||||
Process.myUserHandle());
|
||||
|
||||
assertEquals(info.getId(), key.getId());
|
||||
assertEquals(info.getPackage(), key.getPackageName());
|
||||
}
|
||||
}
|
||||
|
||||
private SQLiteDatabase setupLegacyShortcut(Intent intent) throws Exception {
|
||||
SQLiteDatabase db = new MyDatabaseHelper().getWritableDatabase();
|
||||
try (InputStream in = mContext.getResources().openRawResource(R.raw.downgrade_schema)) {
|
||||
DbDowngradeHelper.parse(IOUtils.toByteArray(in)).onDowngrade(db, db.getVersion(), 31);
|
||||
}
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("itemType", 1);
|
||||
cv.put("title", "Hello");
|
||||
cv.put("intent", intent == null ? null : intent.toUri(0));
|
||||
db.insert(Favorites.TABLE_NAME, null, cv);
|
||||
return db;
|
||||
}
|
||||
|
||||
public static int getFavoriteDataCount(SQLiteDatabase db) {
|
||||
try (Cursor c = db.query(Favorites.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
return c.getCount();
|
||||
}
|
||||
}
|
||||
|
||||
private class MyDatabaseHelper extends DatabaseHelper {
|
||||
|
||||
MyDatabaseHelper() {
|
||||
super(mContext, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
|
||||
|
||||
protected void onEmptyDbCreated() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user