Merge commit 'f256f02c2b88235d1a970d5055b253fd4a302ffe' into fp2-sibon-staging-16.11.0rc1
diff --git a/src/com/fairphone/fplauncher3/InstallShortcutReceiver.java b/src/com/fairphone/fplauncher3/InstallShortcutReceiver.java
index ce4c164..88f9966 100644
--- a/src/com/fairphone/fplauncher3/InstallShortcutReceiver.java
+++ b/src/com/fairphone/fplauncher3/InstallShortcutReceiver.java
@@ -22,8 +22,10 @@
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.os.Bundle;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@@ -31,6 +33,7 @@
import com.fairphone.fplauncher3.R;
import com.fairphone.fplauncher3.compat.UserHandleCompat;
+import com.fairphone.fplauncher3.util.PackageManagerHelper;
import org.json.JSONObject;
import org.json.JSONStringer;
@@ -65,6 +68,48 @@
private static final Object sLock = new Object();
+ /**
+ * Returns true if the intent is a valid launch intent for a launcher activity of an app.
+ * This is used to identify shortcuts which are different from the ones exposed by the
+ * applications' manifest file.
+ *
+ * @param launchIntent The intent that will be launched when the shortcut is clicked.
+ */
+ public static boolean isLauncherAppTarget(Intent launchIntent) {
+ if (launchIntent != null
+ && Intent.ACTION_MAIN.equals(launchIntent.getAction())
+ && launchIntent.getComponent() != null
+ && launchIntent.getCategories() != null
+ && launchIntent.getCategories().size() == 1
+ && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
+ && TextUtils.isEmpty(launchIntent.getDataString())) {
+ // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
+ Bundle extras = launchIntent.getExtras();
+ if (extras == null) {
+ return true;
+ } else {
+ Set<String> keys = extras.keySet();
+ return keys.size() == 1 && keys.contains(ItemInfo.EXTRA_PROFILE);
+ }
+ };
+ return false;
+ }
+
+ private static boolean isLauncherActivity(Context context, Intent launchIntent) {
+ if (!isLauncherAppTarget(launchIntent)) {
+ return false;
+ }
+
+ PackageManager pm = context.getPackageManager();
+ ResolveInfo info = pm.resolveActivity(launchIntent, 0);
+
+ if (info == null) {
+ return false;
+ }
+
+ return true;
+ }
+
private static void addToStringSet(SharedPreferences sharedPrefs,
SharedPreferences.Editor editor, String key, String value) {
Set<String> strings = sharedPrefs.getStringSet(key, null);
@@ -249,6 +294,18 @@
PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
info.icon = icon;
info.iconResource = iconResource;
+
+ if (info != null) {
+ if (!InstallShortcutReceiver.isLauncherActivity(context, intent)) {
+ // Since its a custom shortcut, verify that it is safe to launch.
+ if (!PackageManagerHelper.hasPermissionForActivity(
+ context, info.launchIntent, null)) {
+ // Target cannot be launched, or requires some special permission to launch
+ Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0));
+ return;
+ }
+ }
+ }
String spKey = LauncherAppState.getSharedPreferencesKey();
SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
diff --git a/src/com/fairphone/fplauncher3/Launcher.java b/src/com/fairphone/fplauncher3/Launcher.java
index 7079a29..7f1ae31 100644
--- a/src/com/fairphone/fplauncher3/Launcher.java
+++ b/src/com/fairphone/fplauncher3/Launcher.java
@@ -114,6 +114,7 @@
import com.fairphone.fplauncher3.edgeswipe.editor.AppDiscoverer;
import com.fairphone.fplauncher3.edgeswipe.editor.EditFavoritesActivity;
import com.fairphone.fplauncher3.oobe.OOBEActivity;
+import com.fairphone.fplauncher3.util.PackageManagerHelper;
import com.fairphone.fplauncher3.widgets.appswitcher.AppSwitcherManager;
import java.io.DataInputStream;
@@ -211,6 +212,8 @@
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
// Type: int
private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_COMPONENT = "launcher.add_component";
// Type: parcelable
private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
// Type: parcelable
@@ -277,7 +280,7 @@
private AppWidgetManagerCompat mAppWidgetManager;
private LauncherAppWidgetHost mAppWidgetHost;
- private final ItemInfo mPendingAddInfo = new ItemInfo();
+ PendingAddItemInfo mPendingAddInfo = new PendingAddItemInfo();
private AppWidgetProviderInfo mPendingAddWidgetInfo;
private int mPendingAddWidgetId = -1;
@@ -1241,6 +1244,8 @@
mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
+ mPendingAddInfo.componentName =
+ savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_COMPONENT);
mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
setWaitingForResult(true);
@@ -1453,9 +1458,15 @@
boolean foundCellSpan;
ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
- if (info == null) {
+ if (info == null || mPendingAddInfo.componentName == null) {
return;
}
+ if (!PackageManagerHelper.hasPermissionForActivity(
+ this, info.intent, mPendingAddInfo.componentName.getPackageName())) {
+ // The app is trying to add a shortcut without sufficient permissions
+ Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
+ return;
+ }
final View view = createShortcut(info);
// First we check if we already know the exact location where we want to add this item.
@@ -1998,6 +2009,8 @@
outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
+ outState.putParcelable(RUNTIME_STATE_PENDING_ADD_COMPONENT,
+ mPendingAddInfo.componentName);
outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
}
@@ -2224,6 +2237,7 @@
mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
mPendingAddInfo.dropPos = null;
+ mPendingAddInfo.componentName = null;
}
void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
@@ -2277,6 +2291,7 @@
mPendingAddInfo.container = container;
mPendingAddInfo.screenId = screenId;
mPendingAddInfo.dropPos = loc;
+ mPendingAddInfo.componentName = componentName;
if (cell != null) {
mPendingAddInfo.cellX = cell[0];
diff --git a/src/com/fairphone/fplauncher3/util/PackageManagerHelper.java b/src/com/fairphone/fplauncher3/util/PackageManagerHelper.java
new file mode 100644
index 0000000..379760c
--- /dev/null
+++ b/src/com/fairphone/fplauncher3/util/PackageManagerHelper.java
@@ -0,0 +1,78 @@
+/*
+* Copyright (C) 2016 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 ied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.fairphone.fplauncher3.util;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.text.TextUtils;
+
+public class PackageManagerHelper {
+ /**
+ * 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 static boolean hasPermissionForActivity(Context context, Intent intent,
+ String srcPackage) {
+ PackageManager pm = context.getPackageManager();
+ ResolveInfo target = pm.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(pm.checkPermission(target.activityInfo.permission, srcPackage) !=
+ PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ return true; /* We have to fix that once we go to M */
+ // if (!Utilities.ATLEAST_MARSHMALLOW) {
+ // // These checks are sufficient for below M devices.
+ // return true;
+ // }
+ //
+ // // 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 pm.getApplicationInfo(srcPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
+ // } catch (NameNotFoundException e) { }
+ //
+ // return false;
+ }
+}