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;
+    }
+}