summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod/trebuchet/home
diff options
context:
space:
mode:
authorJorge Ruesga <jorge@ruesga.com>2014-02-24 01:04:33 +0100
committerMatt Garnes <matt@cyngn.com>2014-06-17 17:38:02 -0700
commit56db21e5af4c17f9308ceb07b0f3ab651743b5cd (patch)
tree03d9c1b1a761c78a277d888b3698ece86826df07 /src/org/cyanogenmod/trebuchet/home
parentcf43a17640a7eede4106670f97e0834b5ea0f976 (diff)
downloadandroid_packages_apps_Trebuchet-56db21e5af4c17f9308ceb07b0f3ab651743b5cd.tar.gz
android_packages_apps_Trebuchet-56db21e5af4c17f9308ceb07b0f3ab651743b5cd.tar.bz2
android_packages_apps_Trebuchet-56db21e5af4c17f9308ceb07b0f3ab651743b5cd.zip
trebuchet: custom home
Change-Id: I4faee66580ab0e41ee8e8bcbd79ce680d45bce97 Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
Diffstat (limited to 'src/org/cyanogenmod/trebuchet/home')
-rw-r--r--src/org/cyanogenmod/trebuchet/home/HomeUtils.java112
-rw-r--r--src/org/cyanogenmod/trebuchet/home/HomeWrapper.java236
2 files changed, 348 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/trebuchet/home/HomeUtils.java b/src/org/cyanogenmod/trebuchet/home/HomeUtils.java
new file mode 100644
index 000000000..63bf5ff60
--- /dev/null
+++ b/src/org/cyanogenmod/trebuchet/home/HomeUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 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.trebuchet.home;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.launcher.home.Home;
+
+import java.util.List;
+
+public class HomeUtils {
+
+ private static final String TAG = "HomeUtils";
+
+ // FIXME For now for security reason we will only support known Home apps
+ private static final String[] WELL_KNOWN_HOME_APP_PKGS =
+ {
+ "org.cyanogenmod.launcher.home"
+ };
+
+
+ public static final SparseArray<ComponentName> getInstalledHomePackages(Context context) {
+ // A Home app should:
+ // - declare the use of Home.PERMISSION_HOME_APP permission.
+ // - define the home stub class through the Home.METADATA_HOME_STUB metadata
+ SparseArray<ComponentName> installedHomePackages = new SparseArray<ComponentName>();
+
+ PackageManager packageManager = context.getPackageManager();
+
+ List<PackageInfo> installedPackages = packageManager.getInstalledPackages(
+ PackageManager.GET_PERMISSIONS);
+ for (PackageInfo pkg : installedPackages) {
+ boolean hasHomeAppPermission = false;
+ if (pkg.requestedPermissions != null) {
+ for (String perm : pkg.requestedPermissions) {
+ if (perm.equals(Home.PERMISSION_HOME_APP)) {
+ hasHomeAppPermission = true;
+ break;
+ }
+ }
+ }
+ if (hasHomeAppPermission) {
+ try {
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(pkg.packageName,
+ PackageManager.GET_META_DATA);
+ Bundle metadata = appInfo.metaData;
+ if (metadata != null && metadata.containsKey(Home.METADATA_HOME_STUB)) {
+ String homeStub = metadata.getString(Home.METADATA_HOME_STUB);
+ installedHomePackages.put(appInfo.uid,
+ new ComponentName(pkg.packageName, homeStub));
+ }
+ } catch (NameNotFoundException ex) {
+ // Ignored. The package doesn't exists ¿?
+ }
+ }
+ }
+
+ // FIXME For now we only support known Home apps. Remove this checks when
+ // Trebuchet allows Home apps through the full Home Host Protocol
+ if (installedHomePackages.size() > 0) {
+ for (String pkg : WELL_KNOWN_HOME_APP_PKGS) {
+ int i = installedHomePackages.size() - 1;
+ boolean isWellKnownPkg = false;
+ for (; i >= 0; i--) {
+ int key = installedHomePackages.keyAt(i);
+ if (installedHomePackages.get(key).getPackageName().equals(pkg)) {
+ isWellKnownPkg = true;
+ break;
+ }
+ }
+ if (!isWellKnownPkg) {
+ installedHomePackages.removeAt(i);
+ }
+ }
+ }
+
+ return installedHomePackages;
+ }
+
+ public static Context createNewHomePackageContext(Context ctx, ComponentName pkg) {
+ // Create a new context package for the current user
+ try {
+ return ctx.createPackageContext(pkg.getPackageName(),
+ Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
+ } catch (NameNotFoundException ex) {
+ Log.e(TAG, "Failed to load a home package context. Package not found.", ex);
+ }
+ return null;
+ }
+}
diff --git a/src/org/cyanogenmod/trebuchet/home/HomeWrapper.java b/src/org/cyanogenmod/trebuchet/home/HomeWrapper.java
new file mode 100644
index 000000000..df8b6cae6
--- /dev/null
+++ b/src/org/cyanogenmod/trebuchet/home/HomeWrapper.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2014 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.trebuchet.home;
+
+import android.content.Context;
+import android.util.Base64;
+import android.util.SparseArray;
+import android.view.View;
+
+import com.android.launcher.home.Home;
+
+import java.lang.reflect.Method;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class HomeWrapper {
+
+ private static final int M_ID_ONSTART = 0;
+ private static final int M_ID_ONDESTROY = 1;
+ private static final int M_ID_ONRESUME = 2;
+ private static final int M_ID_ONPAUSE = 3;
+ private static final int M_ID_ONSHOW = 4;
+ private static final int M_ID_ONSCROLLPROGRESSCHANGED = 5;
+ private static final int M_ID_ONHIDE = 6;
+ private static final int M_ID_ONINVALIDATE = 7;
+ private static final int M_ID_ONREQUESTSEARCH = 8;
+ private static final int M_ID_CREATECUSTOMVIEW = 9;
+ private static final int M_ID_GETNAME = 10;
+ private static final int M_ID_GETNOTIFICATIONFLAGS = 11;
+ private static final int M_ID_GETOPERATIONFLAGS = 12;
+ private static final int M_LAST_ID = M_ID_GETOPERATIONFLAGS + 1;
+
+ private final Context mContext;
+ private final Class<?> mClass;
+ private final Object mInstance;
+
+ private final SparseArray<Method> cachedMethods;
+
+ private final int mNotificationFlags;
+ private final int mOperationFlags;
+
+ public HomeWrapper(Context context, Class<?> cls, Object instance) throws SecurityException {
+ super();
+ mContext = context;
+ mClass = cls;
+ mInstance = instance;
+ cachedMethods = new SparseArray<Method>(M_LAST_ID);
+
+ final String sha1 = createDigest(cls);
+ if (!sha1.equals(Home.SIGNATURE)) {
+ throw new SecurityException("The remote Home app doesn't implement " +
+ "the current Home Host Protocol. Signature: " + sha1);
+ }
+
+ // Obtain the app flags
+ mNotificationFlags = getNotificationFlags();
+ mOperationFlags = getOperationFlags();
+ }
+
+ /** @see Home#onStart(Context) **/
+ public void onStart() {
+ invokeVoidContextMethod(M_ID_ONSTART, "onStart");
+ }
+
+ /** @see Home#onDestroy(Context) **/
+ public void onDestroy() {
+ invokeVoidContextMethod(M_ID_ONDESTROY, "onDestroy");
+ }
+
+ /** @see Home#onResume(Context) **/
+ public void onResume() {
+ if (!isNotificationSupported(Home.FLAG_NOTIFY_ON_RESUME)) {
+ return;
+ }
+ invokeVoidContextMethod(M_ID_ONRESUME, "onResume");
+ }
+
+ /** @see Home#onPause(Context) **/
+ public void onPause() {
+ if (!isNotificationSupported(Home.FLAG_NOTIFY_ON_PAUSE)) {
+ return;
+ }
+ invokeVoidContextMethod(M_ID_ONPAUSE, "onPause");
+ }
+
+ /** @see Home#onShow(Context) **/
+ public void onShow() {
+ if (!isNotificationSupported(Home.FLAG_NOTIFY_ON_SHOW)) {
+ return;
+ }
+ invokeVoidContextMethod(M_ID_ONSHOW, "onShow");
+ }
+
+ /** @see Home#onScrollProgressChanged(Context, float) **/
+ public void onScrollProgressChanged(float progress) {
+ if (!isNotificationSupported(Home.FLAG_NOTIFY_ON_SCROLL_PROGRESS_CHANGED)) {
+ return;
+ }
+ try {
+ Method method = cachedMethods.get(M_ID_ONSCROLLPROGRESSCHANGED);
+ if (method == null) {
+ method = mClass.getMethod("onScrollProgressChanged", Context.class, float.class);
+ }
+ method.invoke(mInstance, mContext, progress);
+ } catch (ReflectiveOperationException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /** @see Home#onHide(Context) **/
+ public void onHide() {
+ if (!isNotificationSupported(Home.FLAG_NOTIFY_ON_HIDE)) {
+ return;
+ }
+ invokeVoidContextMethod(M_ID_ONHIDE, "onHide");
+ }
+
+ /** @see Home#onInvalidate(Context) **/
+ public void onInvalidate() {
+ invokeVoidContextMethod(M_ID_ONINVALIDATE, "onInvalidate");
+ }
+
+ /**
+ * @see Home#onRequestSearch(Context, int)
+ */
+ public void onRequestSearch(int mode) {
+ try {
+ Method method = cachedMethods.get(M_ID_ONREQUESTSEARCH);
+ if (method == null) {
+ method = mClass.getMethod("onRequestSearch", Context.class, int.class);
+ }
+ method.invoke(mInstance, mContext, mode);
+ } catch (ReflectiveOperationException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /** @see Home#createCustomView(Context) **/
+ public View createCustomView() {
+ try {
+ Method method = cachedMethods.get(M_ID_CREATECUSTOMVIEW);
+ if (method == null) {
+ method = mClass.getMethod("createCustomView", Context.class);
+ }
+ return (View) method.invoke(mInstance, mContext);
+ } catch (ReflectiveOperationException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /** @see Home#getName(Context) **/
+ public String getName() {
+ try {
+ Method method = cachedMethods.get(M_ID_GETNAME);
+ if (method == null) {
+ method = mClass.getMethod("getName", Context.class);
+ }
+ return (String) method.invoke(mInstance, mContext);
+ } catch (ReflectiveOperationException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /** @see Home#getNotificationFlags() **/
+ private int getNotificationFlags() {
+ try {
+ Method method = cachedMethods.get(M_ID_GETNOTIFICATIONFLAGS);
+ if (method == null) {
+ method = mClass.getMethod("getNotificationFlags");
+ }
+ return (Integer) method.invoke(mInstance);
+ } catch (ReflectiveOperationException ex) {
+ return 0;
+ }
+ }
+
+ /** @see Home#getOperationFlags() **/
+ private int getOperationFlags() {
+ try {
+ Method method = cachedMethods.get(M_ID_GETOPERATIONFLAGS);
+ if (method == null) {
+ method = mClass.getMethod("getOperationFlags");
+ }
+ return (Integer) method.invoke(mInstance);
+ } catch (ReflectiveOperationException ex) {
+ return 0;
+ }
+ }
+
+ private void invokeVoidContextMethod(int methodId, String methodName) {
+ try {
+ Method method = cachedMethods.get(methodId);
+ if (method == null) {
+ method = mClass.getMethod(methodName, Context.class);
+ }
+ method.invoke(mInstance, mContext);
+ } catch (ReflectiveOperationException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ private final String createDigest(Class<?> cls) throws SecurityException {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA1");
+ Method[] methods = cls.getDeclaredMethods();
+ for (Method method : methods) {
+ digest.update(method.toString().getBytes());
+ }
+ return new String(Base64.encode(digest.digest(), Base64.NO_WRAP));
+ } catch (NoSuchAlgorithmException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ public boolean isNotificationSupported(int flag) {
+ return (mNotificationFlags & flag) == flag;
+ }
+
+ public boolean isOperationSupported(int flag) {
+ return (mOperationFlags & flag) == flag;
+ }
+}