Added Application Suggestions.

Added in custom Resolver to handle providing suggestions.

Added in Service to handle providing suggestions to custom resolver.

Added in ability to provider suggestions through a Proxy to another
application which must be installed during compile time if one is
to be used. This is a similar implementation to how the Location
Services work.

Change-Id: Id960260596b7bb6485caa1e1d07744e387a4c6e9
diff --git a/api/cm_current.txt b/api/cm_current.txt
index 1d0f4f7..daf5fea 100644
--- a/api/cm_current.txt
+++ b/api/cm_current.txt
@@ -463,6 +463,7 @@
 
   public static final class Manifest.permission {
     ctor public Manifest.permission();
+    field public static final java.lang.String ACCESS_APP_SUGGESTIONS = "cyanogenmod.permission.ACCESS_APP_SUGGESTIONS";
     field public static final java.lang.String HARDWARE_ABSTRACTION_ACCESS = "cyanogenmod.permission.HARDWARE_ABSTRACTION_ACCESS";
     field public static final java.lang.String MANAGE_ALARMS = "cyanogenmod.permission.MANAGE_ALARMS";
     field public static final java.lang.String MANAGE_PERSISTENT_STORAGE = "cyanogenmod.permission.MANAGE_PERSISTENT_STORAGE";
@@ -482,10 +483,18 @@
     ctor public R();
   }
 
+  public static final class R.array {
+    ctor public R.array();
+  }
+
   public static final class R.attr {
     ctor public R.attr();
   }
 
+  public static final class R.bool {
+    ctor public R.bool();
+  }
+
   public static final class R.drawable {
     ctor public R.drawable();
   }
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestManagerService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestManagerService.java
new file mode 100644
index 0000000..d7a6ad4
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestManagerService.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 org.cyanogenmod.platform.internal;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.Slog;
+import com.android.server.SystemService;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+import cyanogenmod.app.suggest.IAppSuggestManager;
+import cyanogenmod.platform.Manifest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppSuggestManagerService extends SystemService {
+    private static final String TAG = "AppSgstMgrService";
+    public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    public static final String NAME = "appsuggest";
+
+    public static final String ACTION = "org.cyanogenmod.app.suggest";
+
+    private AppSuggestProviderInterface mImpl;
+
+    private final IBinder mService = new IAppSuggestManager.Stub() {
+        public boolean handles(Intent intent) {
+            if (mImpl == null) return false;
+
+            return mImpl.handles(intent);
+        }
+
+        public List<ApplicationSuggestion> getSuggestions(Intent intent) {
+            if (mImpl == null) return new ArrayList<>(0);
+
+            return mImpl.getSuggestions(intent);
+        }
+    };
+
+    public AppSuggestManagerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        mImpl = AppSuggestProviderProxy.createAndBind(mContext, TAG, ACTION,
+                R.bool.config_enableAppSuggestOverlay,
+                R.string.config_appSuggestProviderPackageName,
+                R.array.config_appSuggestProviderPackageNames);
+        if (mImpl == null) {
+            Slog.e(TAG, "no app suggest provider found");
+        } else {
+            Slog.i(TAG, "Bound to to suggest provider");
+        }
+
+        publishBinderService(CMContextConstants.CM_APP_SUGGEST_SERVICE, mService);
+    }
+}
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestProviderInterface.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestProviderInterface.java
new file mode 100644
index 0000000..da815ce
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestProviderInterface.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 org.cyanogenmod.platform.internal;
+
+import android.content.Intent;
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+import java.util.List;
+
+/**
+ * App Suggestion Manager's interface for Applicaiton Suggestion Providers.
+ *
+ * @hide
+ */
+public interface AppSuggestProviderInterface {
+    boolean handles(Intent intent);
+    List<ApplicationSuggestion> getSuggestions(Intent intent);
+}
diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestProviderProxy.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestProviderProxy.java
new file mode 100644
index 0000000..0357f73
--- /dev/null
+++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/AppSuggestProviderProxy.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 org.cyanogenmod.platform.internal;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import com.android.server.ServiceWatcher;
+
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+import cyanogenmod.app.suggest.IAppSuggestProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class AppSuggestProviderProxy implements AppSuggestProviderInterface {
+    private static final String TAG = AppSuggestProviderProxy.class.getSimpleName();
+    private static final boolean DEBUG = AppSuggestManagerService.DEBUG;
+
+    public static AppSuggestProviderProxy createAndBind(
+            Context context, String name, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId) {
+        AppSuggestProviderProxy proxy = new AppSuggestProviderProxy(context, name, action,
+                overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId);
+        if (proxy.bind()) {
+            return proxy;
+        } else {
+            return null;
+        }
+    }
+
+    private Context mContext;
+    private ServiceWatcher mServiceWatcher;
+
+    private AppSuggestProviderProxy(Context context, String name, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId) {
+        mContext = context;
+        mServiceWatcher = new ServiceWatcher(mContext, TAG + "-" + name, action, overlaySwitchResId,
+                defaultServicePackageNameResId, initialPackageNamesResId, null, null);
+    }
+
+    private boolean bind() {
+        return mServiceWatcher.start();
+    }
+
+    private IAppSuggestProvider getService() {
+        return IAppSuggestProvider.Stub.asInterface(mServiceWatcher.getBinder());
+    }
+
+    @Override
+    public boolean handles(Intent intent) {
+        IAppSuggestProvider service = getService();
+        if (service == null) return false;
+
+        try {
+            return service.handles(intent);
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+        return false;
+    }
+
+    @Override
+    public List<ApplicationSuggestion> getSuggestions(Intent intent) {
+        IAppSuggestProvider service = getService();
+        if (service == null) return new ArrayList<>(0);
+
+        try {
+            return service.getSuggestions(intent);
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+        return new ArrayList<>(0);
+    }
+}
diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml
index 8c40827..ec801cd 100644
--- a/cm/res/AndroidManifest.xml
+++ b/cm/res/AndroidManifest.xml
@@ -120,6 +120,13 @@
                 android:description="@string/permdesc_managePersistentStorage"
                 android:protectionLevel="system|signature" />
 
+    <!-- Permission for accessing a provider of app suggestions
+         @hide -->
+    <permission android:name="cyanogenmod.permission.ACCESS_APP_SUGGESTIONS"
+                android:label="@string/permlab_accessAppSuggestions"
+                android:description="@string/permdesc_accessAppSuggestions"
+                android:protectionLevel="signature|system|development" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/cm/res/res/values/config.xml b/cm/res/res/values/config.xml
new file mode 100644
index 0000000..eb8982d
--- /dev/null
+++ b/cm/res/res/values/config.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The CyanogenMod 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.
+-->
+<resources>
+    <!-- Whether to enable app suggest overlay which allows app suggest
+         provider to be replaced by an app at run-time. When disabled, only
+         the config_appSuggestProviderPackageName will be searched for app
+         suggest provider, otherwise packages whos signature matches the
+         signature of config_appSuggestProviderPackageNames will be searched,
+         and the service with the highest version number will be picked.
+         Anyone who wants to disable the overlay mechanism can set it to false.
+
+         Note: There appears to be an issue with false if we reinstall the provider which causes
+         it to not get the update and fail to reconnect on package update.  It's safer to just
+         use the list version with config_appSuggestProviderPackageNames.
+         -->
+    <bool name="config_enableAppSuggestOverlay" translatable="false">true</bool>
+
+    <!-- Package name providing app suggest support. Used only when
+         config_enableAppSuggestOverlay is false. -->
+    <string name="config_appSuggestProviderPackageName" translatable="false">com.cyanogen.app.suggest</string>
+
+    <!-- List of packages providing app suggest support. Used only when
+    config_enableAppSuggestOverlay is true. -->
+    <string-array name="config_appSuggestProviderPackageNames" translatable="false">
+        <item>com.cyanogen.app.suggest</item>
+    </string-array>
+</resources>
\ No newline at end of file
diff --git a/cm/res/res/values/strings.xml b/cm/res/res/values/strings.xml
index e727080..bcfed5a 100644
--- a/cm/res/res/values/strings.xml
+++ b/cm/res/res/values/strings.xml
@@ -70,6 +70,10 @@
     <string name="permlab_managePersistentStorage">manage persistent storage</string>
     <string name="permdesc_managePersistentStorage">Allows an app to read or write properties which may persist thrοugh a factory reset.</string>
 
+    <!-- Labels for the ACCESS_APP_SUGGESTIONS permission -->
+    <string name="permlab_accessAppSuggestions">access application suggestions</string>
+    <string name="permdesc_accessAppSuggestions">Allows an app to access application suggestions.</string>
+
     <!-- Label to show for a service that is running because it is observing the user's custom tiles. -->
     <string name="custom_tile_listener_binding_label">Custom tile listener</string>
 
diff --git a/cm/res/res/values/symbols.xml b/cm/res/res/values/symbols.xml
index 3dcf497..7977939 100644
--- a/cm/res/res/values/symbols.xml
+++ b/cm/res/res/values/symbols.xml
@@ -19,6 +19,10 @@
          SDK.  Instead, put them here. -->
     <private-symbols package="org.cyanogenmod.platform.internal" />
 
+    <java-symbol type="bool" name="config_enableAppSuggestOverlay"/>
+    <java-symbol type="string" name="config_appSuggestProviderPackageName"/>
+    <java-symbol type="array" name="config_appSuggestProviderPackageNames"/>
+
     <java-symbol type="string" name="custom_tile_listener_binding_label" />
 
     <!-- Profiles -->
diff --git a/packages/CMResolver/Android.mk b/packages/CMResolver/Android.mk
new file mode 100644
index 0000000..b11027c
--- /dev/null
+++ b/packages/CMResolver/Android.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2015 The CyanogenMod 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+src_dir := src
+res_dir := res
+
+LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dir))
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dir))
+
+LOCAL_PACKAGE_NAME := CMResolver
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    org.cyanogenmod.platform.sdk
+
+include $(BUILD_PACKAGE)
+
+########################
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/CMResolver/AndroidManifest.xml b/packages/CMResolver/AndroidManifest.xml
new file mode 100644
index 0000000..47ce4bf
--- /dev/null
+++ b/packages/CMResolver/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The CyanogenMod 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:internal="http://schemas.android.com/apk/prv/res/android"
+          package="org.cyanogenmod.resolver"
+          coreApp="true"
+          android:sharedUserId="android.uid.system">
+    <!-- It is necessary to be a system app in order to update table versions in SystemProperties for
+         CMSettings to know whether or not the client side cache is up to date. It is also necessary
+         to run in the system process in order to start the content provider prior to running migration
+         for CM settings on user starting -->
+
+    <uses-permission android:name="cyanogenmod.permission.ACCESS_APP_SUGGESTIONS" />
+
+    <application android:icon="@drawable/icon"
+                 android:label="@string/app_name"
+                 android:killAfterRestore="false"
+                 android:allowClearUserData="false"
+                 android:supportsRtl="true"
+                 android:enabled="true">
+
+        <activity android:name="org.cyanogenmod.resolver.ResolverActivity"
+                android:theme="@style/AppTheme.DeviceDefault.Resolver"
+                android:exported="true"/>
+
+    </application>
+</manifest>
diff --git a/packages/CMResolver/res/drawable-hdpi/play_download.png b/packages/CMResolver/res/drawable-hdpi/play_download.png
new file mode 100644
index 0000000..a0e280c
--- /dev/null
+++ b/packages/CMResolver/res/drawable-hdpi/play_download.png
Binary files differ
diff --git a/packages/CMResolver/res/drawable-mdpi/play_download.png b/packages/CMResolver/res/drawable-mdpi/play_download.png
new file mode 100644
index 0000000..e9ccbaa
--- /dev/null
+++ b/packages/CMResolver/res/drawable-mdpi/play_download.png
Binary files differ
diff --git a/packages/CMResolver/res/drawable-xhdpi/play_download.png b/packages/CMResolver/res/drawable-xhdpi/play_download.png
new file mode 100644
index 0000000..bd2ccc3
--- /dev/null
+++ b/packages/CMResolver/res/drawable-xhdpi/play_download.png
Binary files differ
diff --git a/packages/CMResolver/res/drawable-xxhdpi/play_download.png b/packages/CMResolver/res/drawable-xxhdpi/play_download.png
new file mode 100644
index 0000000..5f6d062
--- /dev/null
+++ b/packages/CMResolver/res/drawable-xxhdpi/play_download.png
Binary files differ
diff --git a/packages/CMResolver/res/drawable-xxxhdpi/play_download.png b/packages/CMResolver/res/drawable-xxxhdpi/play_download.png
new file mode 100644
index 0000000..81ac1de
--- /dev/null
+++ b/packages/CMResolver/res/drawable-xxxhdpi/play_download.png
Binary files differ
diff --git a/packages/CMResolver/res/drawable/icon.png b/packages/CMResolver/res/drawable/icon.png
new file mode 100644
index 0000000..08ee50d
--- /dev/null
+++ b/packages/CMResolver/res/drawable/icon.png
Binary files differ
diff --git a/packages/CMResolver/res/layout/suggest_list_item.xml b/packages/CMResolver/res/layout/suggest_list_item.xml
new file mode 100644
index 0000000..1747203
--- /dev/null
+++ b/packages/CMResolver/res/layout/suggest_list_item.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/any/layout/resolve_list_item.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/suggest_item_container"
+              android:orientation="horizontal"
+              android:layout_height="wrap_content"
+              android:layout_width="match_parent"
+              android:minHeight="?android:attr/listPreferredItemHeightSmall"
+              android:paddingTop="4dp"
+              android:paddingBottom="4dp"
+              android:background="?android:attr/activatedBackgroundIndicator">
+
+    <!-- Activity icon when presenting dialog
+         Size will be filled in by ResolverActivity -->
+    <ImageView android:id="@android:id/icon"
+               android:layout_width="24dp"
+               android:layout_height="24dp"
+               android:layout_gravity="start|center_vertical"
+               android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+               android:layout_marginTop="12dp"
+               android:layout_marginBottom="12dp"
+               android:layout_alignParentStart="true"
+               android:scaleType="fitCenter" />
+
+    <!-- Activity name -->
+    <TextView android:id="@android:id/text1"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_toEndOf="@android:id/icon"
+              android:layout_toStartOf="@android:id/icon2"
+              android:layout_centerVertical="true"
+              android:textAppearance="?android:attr/textAppearanceMedium"
+              android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+              android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+              android:gravity="start|center_vertical"
+              android:textColor="?android:attr/textColorPrimary"
+              android:minLines="1"
+              android:maxLines="1"
+              android:ellipsize="marquee" />
+
+    <ImageView android:id="@android:id/icon2"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_gravity="start|center_vertical"
+            android:layout_marginEnd="?android:attr/listPreferredItemPaddingStart"
+            android:layout_marginTop="12dp"
+            android:layout_marginBottom="12dp"
+            android:layout_alignParentEnd="true"
+            android:scaleType="fitCenter"
+            android:src="@drawable/play_download"/>
+</RelativeLayout>
+
diff --git a/packages/CMResolver/res/values/strings.xml b/packages/CMResolver/res/values/strings.xml
new file mode 100644
index 0000000..7f4f705
--- /dev/null
+++ b/packages/CMResolver/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014-2015 The CyanogenMod 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.
+-->
+
+<resources>
+    <string name="app_name">CyanogenMod Resolver</string>
+    <string name="download_and_open_with">Download and open with</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CMResolver/res/values/themes.xml b/packages/CMResolver/res/values/themes.xml
new file mode 100644
index 0000000..c138bea
--- /dev/null
+++ b/packages/CMResolver/res/values/themes.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014-2015 The CyanogenMod 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.
+-->
+
+<resources>
+    <style name="AppTheme.DeviceDefault.Resolver" parent="@android:style/Theme.Material.Light">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:backgroundDimEnabled">true</item>
+        <item name="android:windowTranslucentStatus">false</item>
+        <item name="android:windowTranslucentNavigation">false</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:colorControlActivated">?android:attr/colorControlHighlight</item>
+        <item name="android:listPreferredItemPaddingStart">?android:attr/dialogPreferredPadding</item>
+        <item name="android:listPreferredItemPaddingEnd">?android:attr/dialogPreferredPadding</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CMResolver/src/org/cyanogenmod/resolver/ResolverActivity.java b/packages/CMResolver/src/org/cyanogenmod/resolver/ResolverActivity.java
new file mode 100644
index 0000000..8c9c1e3
--- /dev/null
+++ b/packages/CMResolver/src/org/cyanogenmod/resolver/ResolverActivity.java
@@ -0,0 +1,1447 @@
+/*
+ * Copyright (C) 2008 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 org.cyanogenmod.resolver;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
+import android.app.AppGlobals;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PatternMatcher;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.internal.R;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.widget.ResolverDrawerLayout;
+import cyanogenmod.app.suggest.AppSuggestManager;
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+
+/**
+ * This activity is displayed when the system attempts to start an Intent for
+ * which there is more than one matching activity, allowing the user to decide
+ * which to go to.  It is not normally used directly by application developers.
+ */
+public class ResolverActivity extends Activity implements AdapterView.OnItemClickListener {
+    private static final String TAG = "ResolverActivity";
+    private static final boolean DEBUG = false;
+
+    private int mLaunchedFromUid;
+    private ResolveListAdapter mAdapter;
+    private ApplicationSuggestionAdapter mSuggestAdapter;
+    private AppSuggestManager mSuggest;
+    private PackageManager mPm;
+    private boolean mSafeForwardingMode;
+    private boolean mAlwaysUseOption;
+    private boolean mShowExtended;
+    private ListView mListView;
+    private boolean mHasSuggestions;
+    private ViewGroup mFilteredItemContainer;
+    private Button mAlwaysButton;
+    private Button mOnceButton;
+    private View mProfileView;
+    private int mIconDpi;
+    private int mIconSize;
+    private int mMaxColumns;
+    private int mLastSelected = ListView.INVALID_POSITION;
+    private boolean mResolvingHome = false;
+    private int mProfileSwitchMessageId = -1;
+    private Intent mIntent;
+
+    private boolean mUsingSuggestions;
+
+    private UsageStatsManager mUsm;
+    private Map<String, UsageStats> mStats;
+    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
+
+    private boolean mRegistered;
+    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+        @Override public void onSomePackagesChanged() {
+            mAdapter.handlePackagesChanged();
+            mSuggestAdapter.handlePackagesChanged();
+            if (mAdapter.getCount() == 0 && !mHasSuggestions) {
+                // We no longer have any items...  just finish the activity.
+                finish();
+            } else {
+                ListAdapter d = mListView.getAdapter();
+                if (mHasSuggestions) {
+                    if (d != mSuggestAdapter) {
+                        mListView.setAdapter(mSuggestAdapter);
+                    }
+                } else if (d != mAdapter) {
+                    mListView.setAdapter(mAdapter);
+                } else {
+                    // keep using the same adapter
+                }
+            }
+            if (mProfileView != null) {
+                bindProfileView();
+            }
+        }
+    };
+
+    private enum ActionTitle {
+        VIEW(Intent.ACTION_VIEW,
+                R.string.whichViewApplication,
+                R.string.whichViewApplicationNamed),
+        EDIT(Intent.ACTION_EDIT,
+                R.string.whichEditApplication,
+                R.string.whichEditApplicationNamed),
+        SEND(Intent.ACTION_SEND,
+                R.string.whichSendApplication,
+                R.string.whichSendApplicationNamed),
+        SENDTO(Intent.ACTION_SENDTO,
+                R.string.whichSendApplication,
+                R.string.whichSendApplicationNamed),
+        SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
+                R.string.whichSendApplication,
+                R.string.whichSendApplicationNamed),
+        DEFAULT(null,
+                R.string.whichApplication,
+                R.string.whichApplicationNamed),
+        HOME(Intent.ACTION_MAIN,
+                R.string.whichHomeApplication,
+                R.string.whichHomeApplicationNamed);
+
+        public final String action;
+        public final int titleRes;
+        public final int namedTitleRes;
+
+        ActionTitle(String action, int titleRes, int namedTitleRes) {
+            this.action = action;
+            this.titleRes = titleRes;
+            this.namedTitleRes = namedTitleRes;
+        }
+
+        public static ActionTitle forAction(String action) {
+            for (ActionTitle title : values()) {
+                if (title != HOME && action != null && action.equals(title.action)) {
+                    return title;
+                }
+            }
+            return DEFAULT;
+        }
+    }
+
+    private Intent makeMyIntent() {
+        Intent intent = new Intent(getIntent());
+        intent.setComponent(null);
+        // The resolver activity is set to be hidden from recent tasks.
+        // we don't want this attribute to be propagated to the next activity
+        // being launched.  Note that if the original Intent also had this
+        // flag set, we are now losing it.  That should be a very rare case
+        // and we can live with this.
+        intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        return intent;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Use a specialized prompt when we're handling the 'Home' app startActivity()
+        final Intent intent = makeMyIntent();
+        final Set<String> categories = intent.getCategories();
+        if (Intent.ACTION_MAIN.equals(intent.getAction())
+                && categories != null
+                && categories.size() == 1
+                && categories.contains(Intent.CATEGORY_HOME)) {
+            // Note: this field is not set to true in the compatibility version.
+            mResolvingHome = true;
+        }
+
+        setSafeForwardingMode(true);
+
+        onCreate(savedInstanceState, intent, null, 0, null, null, true);
+    }
+
+    /**
+     * Compatibility version for other bundled services that use this ocerload without
+     * a default title resource
+     */
+    protected void onCreate(Bundle savedInstanceState, Intent intent,
+            CharSequence title, Intent[] initialIntents,
+            List<ResolveInfo> rList, boolean alwaysUseOption) {
+        onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, alwaysUseOption);
+    }
+
+    protected void onCreate(Bundle savedInstanceState, Intent intent,
+            CharSequence title, int defaultTitleRes, Intent[] initialIntents,
+            List<ResolveInfo> rList, boolean alwaysUseOption) {
+        super.onCreate(savedInstanceState);
+
+        mSuggest = AppSuggestManager.getInstance(this);
+        // Determine whether we should show that intent is forwarded
+        // from managed profile to owner or other way around.
+        setProfileSwitchMessageId(intent.getContentUserHint());
+
+        try {
+            mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
+                    getActivityToken());
+        } catch (RemoteException e) {
+            mLaunchedFromUid = -1;
+        }
+        mPm = getPackageManager();
+        mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+
+        final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD;
+        mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis());
+
+        mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
+
+        mPackageMonitor.register(this, getMainLooper(), false);
+        mRegistered = true;
+
+        final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+        mIconDpi = am.getLauncherLargeIconDensity();
+        mIconSize = am.getLauncherLargeIconSize();
+
+        mIntent = new Intent(intent);
+        mAdapter = new ResolveListAdapter(this, initialIntents, rList,
+                mLaunchedFromUid, alwaysUseOption);
+
+        mSuggestAdapter = new ApplicationSuggestionAdapter(this);
+
+        final int layoutId;
+        final boolean useHeader;
+        if (mAdapter.hasFilteredItem()) {
+            layoutId = R.layout.resolver_list_with_default;
+            alwaysUseOption = false;
+            useHeader = true;
+        } else {
+            useHeader = false;
+            layoutId = R.layout.resolver_list;
+        }
+        mAlwaysUseOption = alwaysUseOption;
+
+        if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
+            // Gulp!
+            finish();
+            return;
+        }
+
+        mHasSuggestions = mSuggest.handles(mIntent);
+
+        int count = mAdapter.mList.size();
+        if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) {
+            setContentView(layoutId);
+            mListView = (ListView) findViewById(R.id.resolver_list);
+            mListView.setAdapter(mAdapter);
+            mListView.setOnItemClickListener(this);
+            mListView.setOnItemLongClickListener(new ItemLongClickListener());
+
+            if (alwaysUseOption) {
+                mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            }
+
+            if (useHeader) {
+                mListView.addHeaderView(LayoutInflater.from(this).inflate(
+                        R.layout.resolver_different_item_header, mListView, false));
+            }
+
+            mUsingSuggestions = false;
+        } else if (count == 1 && !mHasSuggestions) {
+            safelyStartActivity(mAdapter.intentForPosition(0, false));
+            mPackageMonitor.unregister();
+            mRegistered = false;
+            finish();
+            return;
+        } else {
+            setContentView(R.layout.resolver_list);
+
+            mListView = (ListView) findViewById(R.id.resolver_list);
+
+            if (!mHasSuggestions) {
+                final TextView empty = (TextView) findViewById(R.id.empty);
+                empty.setVisibility(View.VISIBLE);
+                mListView.setVisibility(View.GONE);
+                mUsingSuggestions = false;
+            } else {
+                mListView.setVisibility(View.VISIBLE);
+                mListView.setAdapter(mSuggestAdapter);
+                mListView.setOnItemClickListener(this);
+                mUsingSuggestions = true;
+            }
+        }
+        // Prevent the Resolver window from becoming the top fullscreen window and thus from taking
+        // control of the system bars.
+        getWindow().clearFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR);
+
+        final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel);
+        if (rdl != null) {
+            rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
+                @Override
+                public void onDismissed() {
+                    finish();
+                }
+            });
+        }
+
+        if (title == null) {
+            if (!mUsingSuggestions) {
+                title = getTitleForAction(intent.getAction(), defaultTitleRes);
+            } else {
+                title = getString(org.cyanogenmod.resolver.R.string.download_and_open_with);
+            }
+        }
+        if (!TextUtils.isEmpty(title)) {
+            final TextView titleView = (TextView) findViewById(R.id.title);
+            if (titleView != null) {
+                titleView.setText(title);
+            }
+            setTitle(title);
+        }
+
+        final ImageView iconView = (ImageView) findViewById(R.id.icon);
+        final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
+        if (iconView != null && iconInfo != null) {
+            new LoadIconIntoViewTask(iconView).execute(iconInfo);
+        }
+
+        if (alwaysUseOption || mAdapter.hasFilteredItem()) {
+            final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
+            if (buttonLayout != null && !mUsingSuggestions) {
+                buttonLayout.setVisibility(View.VISIBLE);
+                mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
+                mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
+                mAlwaysButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        onButtonClick(v);
+                    }
+                });
+                mOnceButton.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        onButtonClick(v);
+                    }
+                });
+            } else {
+                mAlwaysUseOption = false;
+            }
+        }
+
+        if (mAdapter.hasFilteredItem()) {
+            mFilteredItemContainer = (ViewGroup) findViewById(R.id.filtered_item_container);
+            mFilteredItemContainer.setOnLongClickListener(new View.OnLongClickListener() {
+                @Override
+                public boolean onLongClick(View v) {
+                    DisplayResolveInfo filteredItem = mAdapter.getFilteredItem();
+
+                    if (filteredItem == null) {
+                        return false;
+                    }
+
+                    showAppDetails(filteredItem.ri);
+                    return true;
+                }
+            });
+
+            setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
+            mOnceButton.setEnabled(true);
+        }
+
+        mProfileView = findViewById(R.id.profile_button);
+        if (mProfileView != null) {
+            mProfileView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    final DisplayResolveInfo dri = mAdapter.getOtherProfile();
+                    if (dri == null) {
+                        return;
+                    }
+
+                    final Intent intent = intentForDisplayResolveInfo(dri);
+                    onIntentSelected(dri.ri, intent, false);
+                    finish();
+                }
+            });
+            bindProfileView();
+        }
+    }
+
+    void bindProfileView() {
+        final DisplayResolveInfo dri = mAdapter.getOtherProfile();
+        if (dri != null) {
+            mProfileView.setVisibility(View.VISIBLE);
+            final ImageView icon = (ImageView) mProfileView.findViewById(R.id.icon);
+            final TextView text = (TextView) mProfileView.findViewById(R.id.text1);
+            if (dri.displayIcon == null) {
+                new LoadIconTask().execute(dri);
+            }
+            icon.setImageDrawable(dri.displayIcon);
+            text.setText(dri.displayLabel);
+        } else {
+            mProfileView.setVisibility(View.GONE);
+        }
+    }
+
+    private void setProfileSwitchMessageId(int contentUserHint) {
+        if (contentUserHint != UserHandle.USER_CURRENT &&
+                contentUserHint != UserHandle.myUserId()) {
+            UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+            UserInfo originUserInfo = userManager.getUserInfo(contentUserHint);
+            boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile()
+                    : false;
+            boolean targetIsManaged = userManager.isManagedProfile();
+            if (originIsManaged && !targetIsManaged) {
+                mProfileSwitchMessageId = R.string.forward_intent_to_owner;
+            } else if (!originIsManaged && targetIsManaged) {
+                mProfileSwitchMessageId = R.string.forward_intent_to_work;
+            }
+        }
+    }
+
+    /**
+     * Turn on launch mode that is safe to use when forwarding intents received from
+     * applications and running in system processes.  This mode uses Activity.startActivityAsCaller
+     * instead of the normal Activity.startActivity for launching the activity selected
+     * by the user.
+     *
+     * <p>This mode is set to true by default if the activity is initialized through
+     * {@link #onCreate(Bundle)}.  If a subclass calls one of the other onCreate
+     * methods, it is set to false by default.  You must set it before calling one of the
+     * more detailed onCreate methods, so that it will be set correctly in the case where
+     * there is only one intent to resolve and it is thus started immediately.</p>
+     */
+    public void setSafeForwardingMode(boolean safeForwarding) {
+        mSafeForwardingMode = safeForwarding;
+    }
+
+    protected CharSequence getTitleForAction(String action, int defaultTitleRes) {
+        final ActionTitle title = mResolvingHome ? ActionTitle.HOME : ActionTitle.forAction(action);
+        final boolean named = mAdapter.hasFilteredItem();
+        if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
+            return getString(defaultTitleRes);
+        } else {
+            return named ? getString(title.namedTitleRes, mAdapter.getFilteredItem().displayLabel) :
+                    getString(title.titleRes);
+        }
+    }
+
+    void dismiss() {
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    Drawable getIcon(Resources res, int resId) {
+        Drawable result;
+        try {
+            result = res.getDrawableForDensity(resId, mIconDpi);
+        } catch (Resources.NotFoundException e) {
+            result = null;
+        }
+
+        return result;
+    }
+
+    Drawable loadIconForResolveInfo(ResolveInfo ri) {
+        Drawable dr;
+        try {
+            if (ri.resolvePackageName != null && ri.icon != 0) {
+                dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon);
+                if (dr != null) {
+                    return dr;
+                }
+            }
+            final int iconRes = ri.getIconResource();
+            if (iconRes != 0) {
+                dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes);
+                if (dr != null) {
+                    return dr;
+                }
+            }
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Couldn't find resources for package", e);
+        }
+        return ri.loadIcon(mPm);
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        if (!mRegistered) {
+            mPackageMonitor.register(this, getMainLooper(), false);
+            mRegistered = true;
+        }
+        mAdapter.handlePackagesChanged();
+        mSuggestAdapter.handlePackagesChanged();
+        if (mProfileView != null) {
+            bindProfileView();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (mRegistered) {
+            mPackageMonitor.unregister();
+            mRegistered = false;
+        }
+        if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+            // This resolver is in the unusual situation where it has been
+            // launched at the top of a new task.  We don't let it be added
+            // to the recent tasks shown to the user, and we need to make sure
+            // that each time we are launched we get the correct launching
+            // uid (not re-using the same resolver from an old launching uid),
+            // so we will now finish ourself since being no longer visible,
+            // the user probably can't get back to us.
+            if (!isChangingConfigurations()) {
+                finish();
+            }
+        }
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        if (mAlwaysUseOption) {
+            final int checkedPos = mListView.getCheckedItemPosition();
+            final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
+            mLastSelected = checkedPos;
+            setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
+            mOnceButton.setEnabled(hasValidSelection);
+            if (hasValidSelection) {
+                mListView.setSelection(checkedPos);
+            }
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        position -= mListView.getHeaderViewsCount();
+        if (position < 0) {
+            // Header views don't count.
+            return;
+        }
+        ListAdapter d = mListView.getAdapter();
+        if (d == mAdapter) {
+            ResolveInfo resolveInfo = mAdapter.resolveInfoForPosition(position, true);
+            if (mResolvingHome && hasManagedProfile()
+                    && !supportsManagedProfiles(resolveInfo)) {
+                Toast.makeText(this, String.format(getResources().getString(
+                                        R.string.activity_resolver_work_profiles_support),
+                                resolveInfo.activityInfo.loadLabel(getPackageManager()).toString()),
+                        Toast.LENGTH_LONG).show();
+                return;
+            }
+            final int checkedPos = mListView.getCheckedItemPosition();
+            final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
+            if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
+                setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
+                mOnceButton.setEnabled(hasValidSelection);
+                if (hasValidSelection) {
+                    mListView.smoothScrollToPosition(checkedPos);
+                }
+                mLastSelected = checkedPos;
+            } else {
+                startSelected(position, false, true);
+            }
+        } else {
+            showMarket(mSuggestAdapter.getItem(position).suggestion);
+        }
+    }
+
+    private void showMarket(ApplicationSuggestion item) {
+        Intent in = new Intent().setAction(Intent.ACTION_VIEW)
+                .setData(item.getDownloadUri())
+                .addFlags((Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET));
+        startActivity(in);
+    }
+
+    private boolean hasManagedProfile() {
+        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        if (userManager == null) {
+            return false;
+        }
+
+        try {
+            List<UserInfo> profiles = userManager.getProfiles(getUserId());
+            for (UserInfo userInfo : profiles) {
+                if (userInfo != null && userInfo.isManagedProfile()) {
+                    return true;
+                }
+            }
+        } catch (SecurityException e) {
+            return false;
+        }
+        return false;
+    }
+
+    private boolean supportsManagedProfiles(ResolveInfo resolveInfo) {
+        try {
+            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
+                    resolveInfo.activityInfo.packageName, 0 /* default flags */);
+            return versionNumberAtLeastL(appInfo.targetSdkVersion);
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private boolean versionNumberAtLeastL(int versionNumber) {
+        return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
+    }
+
+    private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
+            boolean filtered) {
+        boolean enabled = false;
+        if (hasValidSelection) {
+            ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered);
+            if (ri.targetUserId == UserHandle.USER_CURRENT) {
+                enabled = true;
+            }
+        }
+        mAlwaysButton.setEnabled(enabled);
+    }
+
+    public void onButtonClick(View v) {
+        final int id = v.getId();
+        switch(id) {
+            case R.id.button_always:
+            case R.id.button_once:
+            case R.id.filtered_item_container: {
+                startSelected(mAlwaysUseOption ?
+                                mListView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
+                        id == R.id.button_always,
+                        mAlwaysUseOption);
+                dismiss();
+                break;
+            } case org.cyanogenmod.resolver.R.id.suggest_item_container: {
+                DisplayApplicationSuggestion s = mSuggestAdapter.getRecommended();
+                if (s != null) {
+                    showMarket(s.suggestion);
+                }
+                break;
+            }
+        }
+    }
+
+    void startSelected(int which, boolean always, boolean filtered) {
+        if (isFinishing()) {
+            return;
+        }
+        ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
+        Intent intent = mAdapter.intentForPosition(which, filtered);
+        onIntentSelected(ri, intent, always);
+        finish();
+    }
+
+    /**
+     * Replace me in subclasses!
+     */
+    public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
+        return defIntent;
+    }
+
+    protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
+        if ((mAlwaysUseOption || mAdapter.hasFilteredItem()) && mAdapter.mOrigResolveList != null) {
+            // Build a reasonable intent filter, based on what matched.
+            IntentFilter filter = new IntentFilter();
+
+            if (intent.getAction() != null) {
+                filter.addAction(intent.getAction());
+            }
+            Set<String> categories = intent.getCategories();
+            if (categories != null) {
+                for (String cat : categories) {
+                    filter.addCategory(cat);
+                }
+            }
+            filter.addCategory(Intent.CATEGORY_DEFAULT);
+
+            int cat = ri.match&IntentFilter.MATCH_CATEGORY_MASK;
+            Uri data = intent.getData();
+            if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
+                String mimeType = intent.resolveType(this);
+                if (mimeType != null) {
+                    try {
+                        filter.addDataType(mimeType);
+                    } catch (IntentFilter.MalformedMimeTypeException e) {
+                        Log.w("ResolverActivity", e);
+                        filter = null;
+                    }
+                }
+            }
+            if (data != null && data.getScheme() != null) {
+                // We need the data specification if there was no type,
+                // OR if the scheme is not one of our magical "file:"
+                // or "content:" schemes (see IntentFilter for the reason).
+                if (cat != IntentFilter.MATCH_CATEGORY_TYPE
+                        || (!"file".equals(data.getScheme())
+                                && !"content".equals(data.getScheme()))) {
+                    filter.addDataScheme(data.getScheme());
+
+                    // Look through the resolved filter to determine which part
+                    // of it matched the original Intent.
+                    Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
+                    if (pIt != null) {
+                        String ssp = data.getSchemeSpecificPart();
+                        while (ssp != null && pIt.hasNext()) {
+                            PatternMatcher p = pIt.next();
+                            if (p.match(ssp)) {
+                                filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
+                                break;
+                            }
+                        }
+                    }
+                    Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
+                    if (aIt != null) {
+                        while (aIt.hasNext()) {
+                            IntentFilter.AuthorityEntry a = aIt.next();
+                            if (a.match(data) >= 0) {
+                                int port = a.getPort();
+                                filter.addDataAuthority(a.getHost(),
+                                        port >= 0 ? Integer.toString(port) : null);
+                                break;
+                            }
+                        }
+                    }
+                    pIt = ri.filter.pathsIterator();
+                    if (pIt != null) {
+                        String path = data.getPath();
+                        while (path != null && pIt.hasNext()) {
+                            PatternMatcher p = pIt.next();
+                            if (p.match(path)) {
+                                filter.addDataPath(p.getPath(), p.getType());
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (filter != null) {
+                final int N = mAdapter.mOrigResolveList.size();
+                ComponentName[] set = new ComponentName[N];
+                int bestMatch = 0;
+                for (int i=0; i<N; i++) {
+                    ResolveInfo r = mAdapter.mOrigResolveList.get(i);
+                    set[i] = new ComponentName(r.activityInfo.packageName,
+                            r.activityInfo.name);
+                    if (r.match > bestMatch) bestMatch = r.match;
+                }
+                if (alwaysCheck) {
+                    getPackageManager().addPreferredActivity(filter, bestMatch, set,
+                            intent.getComponent());
+                } else {
+                    try {
+                        AppGlobals.getPackageManager().setLastChosenActivity(intent,
+                                intent.resolveTypeIfNeeded(getContentResolver()),
+                                PackageManager.MATCH_DEFAULT_ONLY,
+                                filter, bestMatch, intent.getComponent());
+                    } catch (RemoteException re) {
+                        Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
+                    }
+                }
+            }
+        }
+
+        if (intent != null) {
+            safelyStartActivity(intent);
+        }
+    }
+
+    public void safelyStartActivity(Intent intent) {
+        // If needed, show that intent is forwarded
+        // from managed profile to owner or other way around.
+        if (mProfileSwitchMessageId != -1) {
+            Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show();
+        }
+        if (!mSafeForwardingMode) {
+            startActivity(intent);
+            onActivityStarted(intent);
+            return;
+        }
+        try {
+            startActivityAsCaller(intent, null, UserHandle.USER_NULL);
+            onActivityStarted(intent);
+        } catch (RuntimeException e) {
+            String launchedFromPackage;
+            try {
+                launchedFromPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage(
+                        getActivityToken());
+            } catch (RemoteException e2) {
+                launchedFromPackage = "??";
+            }
+            Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid
+                    + " package " + launchedFromPackage + ", while running in "
+                    + ActivityThread.currentProcessName(), e);
+        }
+    }
+
+    public void onActivityStarted(Intent intent) {
+        // Do nothing
+    }
+
+    void showAppDetails(ResolveInfo ri) {
+        Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+                .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        startActivity(in);
+    }
+
+    Intent intentForDisplayResolveInfo(DisplayResolveInfo dri) {
+        Intent intent = new Intent(dri.origIntent != null ? dri.origIntent :
+                getReplacementIntent(dri.ri.activityInfo, mIntent));
+        intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+                |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+        ActivityInfo ai = dri.ri.activityInfo;
+        intent.setComponent(new ComponentName(
+                ai.applicationInfo.packageName, ai.name));
+        return intent;
+    }
+
+    private final class DisplayResolveInfo {
+        ResolveInfo ri;
+        CharSequence displayLabel;
+        Drawable displayIcon;
+        CharSequence extendedInfo;
+        Intent origIntent;
+
+        DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel,
+                CharSequence pInfo, Intent pOrigIntent) {
+            ri = pri;
+            displayLabel = pLabel;
+            extendedInfo = pInfo;
+            origIntent = pOrigIntent;
+        }
+    }
+
+    private final class ResolveListAdapter extends BaseAdapter {
+        private final Intent[] mInitialIntents;
+        private final List<ResolveInfo> mBaseResolveList;
+        private ResolveInfo mLastChosen;
+        private DisplayResolveInfo mOtherProfile;
+        private final int mLaunchedFromUid;
+        private final LayoutInflater mInflater;
+
+        List<DisplayResolveInfo> mList;
+        List<ResolveInfo> mOrigResolveList;
+
+        private int mLastChosenPosition = -1;
+        private boolean mFilterLastUsed;
+
+        public ResolveListAdapter(Context context, Intent[] initialIntents,
+                List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) {
+            mInitialIntents = initialIntents;
+            mBaseResolveList = rList;
+            mLaunchedFromUid = launchedFromUid;
+            mInflater = LayoutInflater.from(context);
+            mList = new ArrayList<DisplayResolveInfo>();
+            mFilterLastUsed = filterLastUsed;
+            rebuildList();
+        }
+
+        public void handlePackagesChanged() {
+            rebuildList();
+            notifyDataSetChanged();
+        }
+
+        public DisplayResolveInfo getFilteredItem() {
+            if (mFilterLastUsed && mLastChosenPosition >= 0) {
+                // Not using getItem since it offsets to dodge this position for the list
+                return mList.get(mLastChosenPosition);
+            }
+            return null;
+        }
+
+        public DisplayResolveInfo getOtherProfile() {
+            return mOtherProfile;
+        }
+
+        public int getFilteredPosition() {
+            if (mFilterLastUsed && mLastChosenPosition >= 0) {
+                return mLastChosenPosition;
+            }
+            return AbsListView.INVALID_POSITION;
+        }
+
+        public boolean hasFilteredItem() {
+            return mFilterLastUsed && mLastChosenPosition >= 0;
+        }
+
+        private void rebuildList() {
+            List<ResolveInfo> currentResolveList;
+
+            try {
+                mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity(
+                        mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()),
+                        PackageManager.MATCH_DEFAULT_ONLY);
+            } catch (RemoteException re) {
+                Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
+            }
+
+            mList.clear();
+            if (mBaseResolveList != null) {
+                currentResolveList = mOrigResolveList = mBaseResolveList;
+            } else {
+                currentResolveList = mOrigResolveList = mPm.queryIntentActivities(
+                        mIntent, PackageManager.MATCH_DEFAULT_ONLY
+                        | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0));
+                // Filter out any activities that the launched uid does not
+                // have permission for.  We don't do this when we have an explicit
+                // list of resolved activities, because that only happens when
+                // we are being subclassed, so we can safely launch whatever
+                // they gave us.
+                if (currentResolveList != null) {
+                    for (int i=currentResolveList.size()-1; i >= 0; i--) {
+                        String thisName = ResolverActivity.class.getCanonicalName();
+                        if (!currentResolveList.get(i).activityInfo.name.equals(thisName)) {
+                            ActivityInfo ai = currentResolveList.get(i).activityInfo;
+                            int granted = ActivityManager.checkComponentPermission(
+                                    ai.permission, mLaunchedFromUid,
+                                    ai.applicationInfo.uid, ai.exported);
+                            if (granted != PackageManager.PERMISSION_GRANTED) {
+                                // Access not allowed!
+                                if (mOrigResolveList == currentResolveList) {
+                                    mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
+                                }
+                                currentResolveList.remove(i);
+                            }
+                        } else {
+                            currentResolveList.remove(i);
+                        }
+                    }
+                }
+            }
+            int N;
+            if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
+                // Only display the first matches that are either of equal
+                // priority or have asked to be default options.
+                ResolveInfo r0 = currentResolveList.get(0);
+                for (int i=1; i<N; i++) {
+                    ResolveInfo ri = currentResolveList.get(i);
+                    if (DEBUG) Log.v(
+                        TAG,
+                        r0.activityInfo.name + "=" +
+                        r0.priority + "/" + r0.isDefault + " vs " +
+                        ri.activityInfo.name + "=" +
+                        ri.priority + "/" + ri.isDefault);
+                    if (r0.priority != ri.priority ||
+                        r0.isDefault != ri.isDefault) {
+                        while (i < N) {
+                            if (mOrigResolveList == currentResolveList) {
+                                mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
+                            }
+                            currentResolveList.remove(i);
+                            N--;
+                        }
+                    }
+                }
+                if (N > 1) {
+                    Comparator<ResolveInfo> rComparator =
+                            new ResolverComparator(ResolverActivity.this, mIntent);
+                    Collections.sort(currentResolveList, rComparator);
+                }
+                // First put the initial items at the top.
+                if (mInitialIntents != null) {
+                    for (int i=0; i<mInitialIntents.length; i++) {
+                        Intent ii = mInitialIntents[i];
+                        if (ii == null) {
+                            continue;
+                        }
+                        ActivityInfo ai = ii.resolveActivityInfo(
+                                getPackageManager(), 0);
+                        if (ai == null) {
+                            Log.w(TAG, "No activity found for " + ii);
+                            continue;
+                        }
+                        ResolveInfo ri = new ResolveInfo();
+                        ri.activityInfo = ai;
+                        UserManager userManager =
+                                (UserManager) getSystemService(Context.USER_SERVICE);
+                        if (userManager.isManagedProfile()) {
+                            ri.noResourceId = true;
+                        }
+                        if (ii instanceof LabeledIntent) {
+                            LabeledIntent li = (LabeledIntent)ii;
+                            ri.resolvePackageName = li.getSourcePackage();
+                            ri.labelRes = li.getLabelResource();
+                            ri.nonLocalizedLabel = li.getNonLocalizedLabel();
+                            ri.icon = li.getIconResource();
+                        }
+                        addResolveInfo(new DisplayResolveInfo(ri,
+                                ri.loadLabel(getPackageManager()), null, ii));
+                    }
+                }
+
+                // Check for applications with same name and use application name or
+                // package name if necessary
+                r0 = currentResolveList.get(0);
+                int start = 0;
+                CharSequence r0Label =  r0.loadLabel(mPm);
+                mShowExtended = false;
+                for (int i = 1; i < N; i++) {
+                    if (r0Label == null) {
+                        r0Label = r0.activityInfo.packageName;
+                    }
+                    ResolveInfo ri = currentResolveList.get(i);
+                    CharSequence riLabel = ri.loadLabel(mPm);
+                    if (riLabel == null) {
+                        riLabel = ri.activityInfo.packageName;
+                    }
+                    if (riLabel.equals(r0Label)) {
+                        continue;
+                    }
+                    processGroup(currentResolveList, start, (i-1), r0, r0Label);
+                    r0 = ri;
+                    r0Label = riLabel;
+                    start = i;
+                }
+                // Process last group
+                processGroup(currentResolveList, start, (N-1), r0, r0Label);
+            }
+
+            // Layout doesn't handle both profile button and last chosen
+            // so disable last chosen if profile button is present.
+            if (mOtherProfile != null && mLastChosenPosition >= 0) {
+                mLastChosenPosition = -1;
+                mFilterLastUsed = false;
+            }
+        }
+
+        private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
+                CharSequence roLabel) {
+            // Process labels from start to i
+            int num = end - start+1;
+            if (num == 1) {
+                // No duplicate labels. Use label for entry at start
+                addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null));
+                updateLastChosenPosition(ro);
+            } else {
+                mShowExtended = true;
+                boolean usePkg = false;
+                CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm);
+                if (startApp == null) {
+                    usePkg = true;
+                }
+                if (!usePkg) {
+                    // Use HashSet to track duplicates
+                    HashSet<CharSequence> duplicates =
+                        new HashSet<CharSequence>();
+                    duplicates.add(startApp);
+                    for (int j = start+1; j <= end ; j++) {
+                        ResolveInfo jRi = rList.get(j);
+                        CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
+                        if ( (jApp == null) || (duplicates.contains(jApp))) {
+                            usePkg = true;
+                            break;
+                        } else {
+                            duplicates.add(jApp);
+                        }
+                    }
+                    // Clear HashSet for later use
+                    duplicates.clear();
+                }
+                for (int k = start; k <= end; k++) {
+                    ResolveInfo add = rList.get(k);
+                    if (usePkg) {
+                        // Use application name for all entries from start to end-1
+                        addResolveInfo(new DisplayResolveInfo(add, roLabel,
+                                add.activityInfo.packageName, null));
+                    } else {
+                        // Use package name for all entries from start to end-1
+                        addResolveInfo(new DisplayResolveInfo(add, roLabel,
+                                add.activityInfo.applicationInfo.loadLabel(mPm), null));
+                    }
+                    updateLastChosenPosition(add);
+                }
+            }
+        }
+
+        private void updateLastChosenPosition(ResolveInfo info) {
+            if (mLastChosen != null
+                    && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
+                    && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
+                mLastChosenPosition = mList.size() - 1;
+            }
+        }
+
+        private void addResolveInfo(DisplayResolveInfo dri) {
+            if (dri.ri.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) {
+                // So far we only support a single other profile at a time.
+                // The first one we see gets special treatment.
+                mOtherProfile = dri;
+            } else {
+                mList.add(dri);
+            }
+        }
+
+        public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
+            return (filtered ? getItem(position) : mList.get(position)).ri;
+        }
+
+        public Intent intentForPosition(int position, boolean filtered) {
+            DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position);
+            return intentForDisplayResolveInfo(dri);
+        }
+
+        public int getCount() {
+            int result = mList.size();
+            if (mFilterLastUsed && mLastChosenPosition >= 0) {
+                result--;
+            }
+            return result;
+        }
+
+        public DisplayResolveInfo getItem(int position) {
+            if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
+                position++;
+            }
+            return mList.get(position);
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+            if (view == null) {
+                view = mInflater.inflate(
+                        R.layout.resolve_list_item, parent, false);
+
+                final ViewHolder holder = new ViewHolder(view);
+                view.setTag(holder);
+            }
+            bindView(view, getItem(position));
+            return view;
+        }
+
+        private final void bindView(View view, DisplayResolveInfo info) {
+            final ViewHolder holder = (ViewHolder) view.getTag();
+            holder.text.setText(info.displayLabel);
+            if (mShowExtended) {
+                holder.text2.setVisibility(View.VISIBLE);
+                holder.text2.setText(info.extendedInfo);
+            } else {
+                holder.text2.setVisibility(View.GONE);
+            }
+            if (info.displayIcon == null) {
+                new LoadIconTask().execute(info);
+            }
+            holder.icon.setImageDrawable(info.displayIcon);
+        }
+    }
+
+    static class ViewHolder {
+        public TextView text;
+        public TextView text2;
+        public ImageView icon;
+
+        public ViewHolder(View view) {
+            text = (TextView) view.findViewById(R.id.text1);
+            text2 = (TextView) view.findViewById(R.id.text2);
+            icon = (ImageView) view.findViewById(R.id.icon);
+        }
+    }
+
+    class ItemLongClickListener implements AdapterView.OnItemLongClickListener {
+
+        @Override
+        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+            position -= mListView.getHeaderViewsCount();
+            if (position < 0) {
+                // Header views don't count.
+                return false;
+            }
+            ListAdapter d = mListView.getAdapter();
+            if (d == mAdapter) {
+                ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
+                showAppDetails(ri);
+            } else {
+                // Suggestions don't support long click, so skip
+            }
+            return true;
+        }
+
+    }
+
+    class LoadIconTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
+        @Override
+        protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
+            final DisplayResolveInfo info = params[0];
+            if (info.displayIcon == null) {
+                info.displayIcon = loadIconForResolveInfo(info.ri);
+            }
+            return info;
+        }
+
+        @Override
+        protected void onPostExecute(DisplayResolveInfo info) {
+            if (mProfileView != null && mAdapter.getOtherProfile() == info) {
+                bindProfileView();
+            }
+            mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    class LoadIconIntoViewTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
+        final ImageView mTargetView;
+
+        public LoadIconIntoViewTask(ImageView target) {
+            mTargetView = target;
+        }
+
+        @Override
+        protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
+            final DisplayResolveInfo info = params[0];
+            if (info.displayIcon == null) {
+                info.displayIcon = loadIconForResolveInfo(info.ri);
+            }
+            return info;
+        }
+
+        @Override
+        protected void onPostExecute(DisplayResolveInfo info) {
+            mTargetView.setImageDrawable(info.displayIcon);
+        }
+    }
+
+    private final class DisplayApplicationSuggestion {
+        ApplicationSuggestion suggestion;
+        Drawable displayIcon;
+
+        public DisplayApplicationSuggestion(ApplicationSuggestion suggestion, Drawable icon) {
+            this.suggestion = suggestion;
+            this.displayIcon = icon;
+        }
+    }
+
+    private final class ApplicationSuggestionAdapter extends BaseAdapter {
+        private LayoutInflater mInflater;
+
+        public List<DisplayApplicationSuggestion> mList;
+
+        public ApplicationSuggestionAdapter(Context context) {
+            mInflater = LayoutInflater.from(context);
+            mList = new ArrayList<>();
+            handlePackagesChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mList.size();
+        }
+
+        public DisplayApplicationSuggestion getItem(int position) {
+            return mList.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+            if (view == null) {
+                view = mInflater.inflate(
+                        org.cyanogenmod.resolver.R.layout.suggest_list_item, parent, false);
+
+                final SuggestViewHolder holder = new SuggestViewHolder(view);
+                view.setTag(holder);
+            }
+            bindView(view, getItem(position));
+            return view;
+        }
+
+        public void bindView(View view, DisplayApplicationSuggestion item) {
+            SuggestViewHolder holder = (SuggestViewHolder)view.getTag();
+            holder.name.setText(item.suggestion.getName());
+            if (item.displayIcon == null) {
+                new LoadSuggestIconTask().execute(item);
+            } else {
+                holder.icon.setImageDrawable(item.displayIcon);
+            }
+
+            holder.icon2.setVisibility(
+                    "com.android.vending".equals(item.suggestion.getPackageName()) ?
+                            View.GONE : View.VISIBLE
+            );
+        }
+
+        public void handlePackagesChanged() {
+            new AsyncTask<Void, Void, List<ApplicationSuggestion>>() {
+                @Override
+                public void onPreExecute() {
+
+                }
+
+                @Override
+                public List<ApplicationSuggestion> doInBackground(Void ... args) {
+                    return mSuggest.getSuggestions(mIntent);
+                }
+
+                @Override
+                public void onPostExecute(List<ApplicationSuggestion> result) {
+                    mList.clear();
+                    for (ApplicationSuggestion s : result) {
+                        mList.add(new DisplayApplicationSuggestion(s, null));
+                    }
+                    notifyDataSetChanged();
+                }
+            }.execute();
+
+
+        }
+
+        public DisplayApplicationSuggestion getRecommended() {
+            return !mList.isEmpty() ? mList.get(0) : null;
+        }
+    }
+
+    static class SuggestViewHolder {
+        TextView name;
+        ImageView icon;
+        ImageView icon2;
+
+        public SuggestViewHolder(View view) {
+            name = (TextView)view.findViewById(R.id.text1);
+            icon = (ImageView)view.findViewById(R.id.icon);
+            icon2 = (ImageView)view.findViewById(R.id.icon2);
+        }
+    }
+
+    class LoadSuggestIconTask extends AsyncTask<DisplayApplicationSuggestion, Void,
+            DisplayApplicationSuggestion> {
+        @Override
+        protected DisplayApplicationSuggestion doInBackground(DisplayApplicationSuggestion... params) {
+            params[0].displayIcon = mSuggest.loadIcon(params[0].suggestion);
+            return params[0];
+        }
+
+        @Override
+        protected void onPostExecute(DisplayApplicationSuggestion result) {
+            if (result.displayIcon != null) {
+                mSuggestAdapter.notifyDataSetChanged();
+            }
+        }
+    }
+
+    static final boolean isSpecificUriMatch(int match) {
+        match = match&IntentFilter.MATCH_CATEGORY_MASK;
+        return match >= IntentFilter.MATCH_CATEGORY_HOST
+                && match <= IntentFilter.MATCH_CATEGORY_PATH;
+    }
+
+    class ResolverComparator implements Comparator<ResolveInfo> {
+        private final Collator mCollator;
+        private final boolean mHttp;
+
+        public ResolverComparator(Context context, Intent intent) {
+            mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
+            String scheme = intent.getScheme();
+            mHttp = "http".equals(scheme) || "https".equals(scheme);
+        }
+
+        @Override
+        public int compare(ResolveInfo lhs, ResolveInfo rhs) {
+            // We want to put the one targeted to another user at the end of the dialog.
+            if (lhs.targetUserId != UserHandle.USER_CURRENT) {
+                return 1;
+            }
+
+            if (mHttp) {
+                // Special case: we want filters that match URI paths/schemes to be
+                // ordered before others.  This is for the case when opening URIs,
+                // to make native apps go above browsers.
+                final boolean lhsSpecific = isSpecificUriMatch(lhs.match);
+                final boolean rhsSpecific = isSpecificUriMatch(rhs.match);
+                if (lhsSpecific != rhsSpecific) {
+                    return lhsSpecific ? -1 : 1;
+                }
+            }
+
+            if (mStats != null) {
+                final long timeDiff =
+                        getPackageTimeSpent(rhs.activityInfo.packageName) -
+                        getPackageTimeSpent(lhs.activityInfo.packageName);
+
+                if (timeDiff != 0) {
+                    return timeDiff > 0 ? 1 : -1;
+                }
+            }
+
+            CharSequence  sa = lhs.loadLabel(mPm);
+            if (sa == null) sa = lhs.activityInfo.name;
+            CharSequence  sb = rhs.loadLabel(mPm);
+            if (sb == null) sb = rhs.activityInfo.name;
+
+            return mCollator.compare(sa.toString(), sb.toString());
+        }
+
+        private long getPackageTimeSpent(String packageName) {
+            if (mStats != null) {
+                final UsageStats stats = mStats.get(packageName);
+                if (stats != null) {
+                    return stats.getTotalTimeInForeground();
+                }
+
+            }
+            return 0;
+        }
+    }
+}
diff --git a/src/java/cyanogenmod/app/CMContextConstants.java b/src/java/cyanogenmod/app/CMContextConstants.java
index ab80b4f..b2278b1 100644
--- a/src/java/cyanogenmod/app/CMContextConstants.java
+++ b/src/java/cyanogenmod/app/CMContextConstants.java
@@ -85,4 +85,9 @@
      * @hide
      */
     public static final String CM_HARDWARE_SERVICE = "cmhardware";
+
+    /**
+     * @hide
+     */
+    public static final String CM_APP_SUGGEST_SERVICE = "cmappsuggest";
 }
diff --git a/src/java/cyanogenmod/app/suggest/AppSuggestManager.java b/src/java/cyanogenmod/app/suggest/AppSuggestManager.java
new file mode 100644
index 0000000..7bc034c
--- /dev/null
+++ b/src/java/cyanogenmod/app/suggest/AppSuggestManager.java
@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 cyanogenmod.app.suggest;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * Provides an interface to get information about suggested apps for an intent which may include
+ * applications not installed on the device. This is used by the CMResolver in order to provide
+ * suggestions when an intent is fired but no application exists for the given intent.
+ *
+ * @hide
+ */
+public class AppSuggestManager {
+    private static final String TAG = AppSuggestManager.class.getSimpleName();
+    private static final boolean DEBUG = true;
+
+    private static IAppSuggestManager sImpl;
+
+    private static AppSuggestManager sInstance;
+
+    private Context mContext;
+
+    /**
+     * Gets an instance of the AppSuggestManager.
+     *
+     * @param context
+     *
+     * @return An instance of the AppSuggestManager
+     */
+    public static synchronized AppSuggestManager getInstance(Context context) {
+        if (sInstance != null) {
+            return sInstance;
+        }
+
+        context = context.getApplicationContext() != null ? context.getApplicationContext() : context;
+
+        sInstance = new AppSuggestManager(context);
+
+        return sInstance;
+    }
+
+    private AppSuggestManager(Context context) {
+        mContext = context.getApplicationContext();
+    }
+
+    private static synchronized IAppSuggestManager getService() {
+        if (sImpl == null) {
+            IBinder b = ServiceManager.getService(CMContextConstants.CM_APP_SUGGEST_SERVICE);
+            if (b != null) {
+                sImpl = IAppSuggestManager.Stub.asInterface(b);
+            } else {
+                Log.e(TAG, "Unable to find implementation for app suggest service");
+            }
+        }
+
+        return sImpl;
+    }
+
+    /**
+     * Checks to see if an intent is handled by the App Suggestions Service. This should be
+     * implemented in such a way that it is safe to call inline on the UI Thread.
+     *
+     * @param intent The intent
+     * @return true if the App Suggestions Service has suggestions for this intent, false otherwise
+     */
+    public boolean handles(Intent intent) {
+        IAppSuggestManager mgr = getService();
+        if (mgr == null) return false;
+        try {
+            return mgr.handles(intent);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     *
+     * Gets a list of the suggestions for the given intent.
+     *
+     * @param intent The intent
+     * @return A list of application suggestions or an empty list if none.
+     */
+    public List<ApplicationSuggestion> getSuggestions(Intent intent) {
+        IAppSuggestManager mgr = getService();
+        if (mgr == null) return new ArrayList<>(0);
+        try {
+            return mgr.getSuggestions(intent);
+        } catch (RemoteException e) {
+            return new ArrayList<>(0);
+        }
+    }
+
+    /**
+     * Loads the icon for the given suggestion.
+     *
+     * @param suggestion The suggestion to load the icon for
+     *
+     * @return A {@link Drawable} or null if one cannot be found
+     */
+    public Drawable loadIcon(ApplicationSuggestion suggestion) {
+        try {
+            InputStream is = mContext.getContentResolver()
+                    .openInputStream(suggestion.getThumbailUri());
+            return Drawable.createFromStream(is, null);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+    }
+}
diff --git a/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl b/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl
new file mode 100644
index 0000000..7ab8584
--- /dev/null
+++ b/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 cyanogenmod.app.suggest;
+
+/**
+ * @hide
+ */
+parcelable ApplicationSuggestion;
diff --git a/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java b/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java
new file mode 100644
index 0000000..c10afe3
--- /dev/null
+++ b/src/java/cyanogenmod/app/suggest/ApplicationSuggestion.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 cyanogenmod.app.suggest;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import cyanogenmod.os.Build;
+
+/**
+ * @hide
+ */
+public class ApplicationSuggestion implements Parcelable {
+
+    public static final Creator<ApplicationSuggestion> CREATOR =
+            new Creator<ApplicationSuggestion>() {
+                public ApplicationSuggestion createFromParcel(Parcel in) {
+                    return new ApplicationSuggestion(in);
+                }
+
+                public ApplicationSuggestion[] newArray(int size) {
+                    return new ApplicationSuggestion[size];
+                }
+            };
+
+    private String mName;
+
+    private String mPackage;
+
+    private Uri mDownloadUri;
+
+    private Uri mThumbnailUri;
+
+    public ApplicationSuggestion(@NonNull String name, @NonNull String pkg,
+            @NonNull Uri downloadUri, @NonNull Uri thumbnailUri) {
+        mName = name;
+        mPackage = pkg;
+        mDownloadUri = downloadUri;
+        mThumbnailUri = thumbnailUri;
+    }
+
+    private ApplicationSuggestion(Parcel in) {
+        // Read parcelable version, make sure to define explicit changes
+        // within {@link Build.PARCELABLE_VERSION);
+        int parcelableVersion = in.readInt();
+        int parcelableSize = in.readInt();
+        int startPosition = in.dataPosition();
+
+        if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+            mName = in.readString();
+            mPackage = in.readString();
+            mDownloadUri = in.readParcelable(Uri.class.getClassLoader());
+            mThumbnailUri = in.readParcelable(Uri.class.getClassLoader());
+        }
+
+        in.setDataPosition(startPosition + parcelableSize);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        // Write parcelable version, make sure to define explicit changes
+        // within {@link Build.PARCELABLE_VERSION);
+        out.writeInt(Build.PARCELABLE_VERSION);
+
+        // Inject a placeholder that will store the parcel size from this point on
+        // (not including the size itself).
+        int sizePosition = out.dataPosition();
+        out.writeInt(0);
+        int startPosition = out.dataPosition();
+
+        out.writeString(mName);
+        out.writeString(mPackage);
+        out.writeParcelable(mDownloadUri, flags);
+        out.writeParcelable(mThumbnailUri, flags);
+
+        // Go back and write size
+        int parcelableSize = out.dataPosition() - startPosition;
+        out.setDataPosition(sizePosition);
+        out.writeInt(parcelableSize);
+        out.setDataPosition(startPosition + parcelableSize);
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getPackageName() {
+        return mPackage;
+    }
+
+    public Uri getDownloadUri() {
+        return mDownloadUri;
+    }
+
+    public Uri getThumbailUri() {
+        return mThumbnailUri;
+    }
+}
diff --git a/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl b/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl
new file mode 100644
index 0000000..68ab87f
--- /dev/null
+++ b/src/java/cyanogenmod/app/suggest/IAppSuggestManager.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 cyanogenmod.app.suggest;
+
+import android.content.Intent;
+
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * @hide
+ */
+interface IAppSuggestManager {
+    boolean handles(in Intent intent);
+
+    List<ApplicationSuggestion> getSuggestions(in Intent intent);
+}
\ No newline at end of file
diff --git a/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl b/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl
new file mode 100644
index 0000000..759880d
--- /dev/null
+++ b/src/java/cyanogenmod/app/suggest/IAppSuggestProvider.aidl
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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 cyanogenmod.app.suggest;
+
+import android.content.Intent;
+
+import cyanogenmod.app.suggest.ApplicationSuggestion;
+
+/**
+ * @hide
+ */
+interface IAppSuggestProvider {
+    boolean handles(in Intent intent);
+
+    List<ApplicationSuggestion> getSuggestions(in Intent intent);
+}
\ No newline at end of file
diff --git a/system-api/cm_system-current.txt b/system-api/cm_system-current.txt
index 1d0f4f7..daf5fea 100644
--- a/system-api/cm_system-current.txt
+++ b/system-api/cm_system-current.txt
@@ -463,6 +463,7 @@
 
   public static final class Manifest.permission {
     ctor public Manifest.permission();
+    field public static final java.lang.String ACCESS_APP_SUGGESTIONS = "cyanogenmod.permission.ACCESS_APP_SUGGESTIONS";
     field public static final java.lang.String HARDWARE_ABSTRACTION_ACCESS = "cyanogenmod.permission.HARDWARE_ABSTRACTION_ACCESS";
     field public static final java.lang.String MANAGE_ALARMS = "cyanogenmod.permission.MANAGE_ALARMS";
     field public static final java.lang.String MANAGE_PERSISTENT_STORAGE = "cyanogenmod.permission.MANAGE_PERSISTENT_STORAGE";
@@ -482,10 +483,18 @@
     ctor public R();
   }
 
+  public static final class R.array {
+    ctor public R.array();
+  }
+
   public static final class R.attr {
     ctor public R.attr();
   }
 
+  public static final class R.bool {
+    ctor public R.bool();
+  }
+
   public static final class R.drawable {
     ctor public R.drawable();
   }