summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml6
-rw-r--r--res/drawable-hdpi/search_bg.9.pngbin0 -> 352 bytes
-rw-r--r--res/drawable-mdpi/search_bg.9.pngbin0 -> 279 bytes
-rw-r--r--res/drawable-xhdpi/search_bg.9.pngbin0 -> 423 bytes
-rw-r--r--res/drawable-xxhdpi/search_bg.9.pngbin0 -> 654 bytes
-rw-r--r--res/layout-port/qsb.xml2
-rw-r--r--res/layout-sw720dp/qsb.xml2
-rw-r--r--res/layout/list_item_checkable.xml26
-rw-r--r--res/values/cm_integers.xml21
-rw-r--r--res/values/preferences_defaults.xml2
-rw-r--r--src/com/android/launcher/home/Home.java201
-rw-r--r--src/com/android/launcher3/AppsCustomizePagedView.java1
-rw-r--r--src/com/android/launcher3/Folder.java1
-rw-r--r--src/com/android/launcher3/FolderIcon.java1
-rw-r--r--src/com/android/launcher3/GelIntegrationHelper.java4
-rw-r--r--src/com/android/launcher3/Launcher.java45
-rw-r--r--src/com/android/launcher3/LauncherAppState.java14
-rw-r--r--src/com/android/launcher3/Utilities.java34
-rw-r--r--src/com/android/launcher3/Workspace.java21
-rw-r--r--src/com/android/launcher3/settings/FontStylePreference.java142
-rw-r--r--src/com/android/launcher3/settings/SettingsProvider.java4
-rw-r--r--src/org/cyanogenmod/trebuchet/TrebuchetLauncher.java332
-rw-r--r--src/org/cyanogenmod/trebuchet/home/HomeUtils.java112
-rw-r--r--src/org/cyanogenmod/trebuchet/home/HomeWrapper.java236
24 files changed, 981 insertions, 226 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 34e25b8b4..b26dfcf91 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -32,6 +32,10 @@
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="signatureOrSystem" />
<permission
+ android:name="com.android.launcher.home.permissions.HOME_APP"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous" />
+ <permission
android:name="com.android.launcher3.permission.READ_SETTINGS"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="dangerous"
@@ -73,7 +77,7 @@
android:largeHeap="@bool/config_largeHeap"
android:supportsRtl="true">
<activity
- android:name="com.android.launcher3.Launcher"
+ android:name="org.cyanogenmod.trebuchet.TrebuchetLauncher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
diff --git a/res/drawable-hdpi/search_bg.9.png b/res/drawable-hdpi/search_bg.9.png
new file mode 100644
index 000000000..798127b50
--- /dev/null
+++ b/res/drawable-hdpi/search_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/search_bg.9.png b/res/drawable-mdpi/search_bg.9.png
new file mode 100644
index 000000000..3b259d4fb
--- /dev/null
+++ b/res/drawable-mdpi/search_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/search_bg.9.png b/res/drawable-xhdpi/search_bg.9.png
new file mode 100644
index 000000000..947fd4de6
--- /dev/null
+++ b/res/drawable-xhdpi/search_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/search_bg.9.png b/res/drawable-xxhdpi/search_bg.9.png
new file mode 100644
index 000000000..6626cd99b
--- /dev/null
+++ b/res/drawable-xxhdpi/search_bg.9.png
Binary files differ
diff --git a/res/layout-port/qsb.xml b/res/layout-port/qsb.xml
index 4c9963dfb..f7a1c8773 100644
--- a/res/layout-port/qsb.xml
+++ b/res/layout-port/qsb.xml
@@ -18,7 +18,7 @@
xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@drawable/search_frame">
+ android:background="@drawable/search_bg">
<!-- Global search icon -->
<com.android.launcher3.HolographicLinearLayout
style="@style/SearchButton.WithPaddingStart"
diff --git a/res/layout-sw720dp/qsb.xml b/res/layout-sw720dp/qsb.xml
index e993f7847..399666ab0 100644
--- a/res/layout-sw720dp/qsb.xml
+++ b/res/layout-sw720dp/qsb.xml
@@ -19,7 +19,7 @@
style="@style/SearchDropTargetBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@drawable/search_frame">
+ android:background="@drawable/search_bg">
<!-- Global search icon -->
<com.android.launcher3.HolographicLinearLayout
style="@style/SearchButton.WithPaddingStart"
diff --git a/res/layout/list_item_checkable.xml b/res/layout/list_item_checkable.xml
deleted file mode 100644
index 86d6b1a4c..000000000
--- a/res/layout/list_item_checkable.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorAlertDialogListItem"
- android:gravity="center_vertical"
- android:paddingStart="16dip"
- android:paddingEnd="16dip"
- android:checkMark="?android:attr/listChoiceIndicatorSingle"
- android:ellipsize="marquee" />
diff --git a/res/values/cm_integers.xml b/res/values/cm_integers.xml
new file mode 100644
index 000000000..f3b0464ef
--- /dev/null
+++ b/res/values/cm_integers.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<resources>
+ <integer name="qsb_initial_alpha_state">55</integer>
+ <integer name="qsb_end_alpha_state">220</integer>
+ <integer name="qsb_buttons_end_colorfilter">120</integer>
+</resources>
diff --git a/res/values/preferences_defaults.xml b/res/values/preferences_defaults.xml
index b6ae9a6e6..64d904a0f 100644
--- a/res/values/preferences_defaults.xml
+++ b/res/values/preferences_defaults.xml
@@ -13,6 +13,4 @@
<bool name="preferences_interface_drawer_remove_hidden_apps_widgets_default">true</bool>
<bool name="preferences_interface_drawer_hide_icon_labels_default">false</bool>
<bool name="preferences_interface_general_icons_large_default">@bool/config_largeIcons</bool>
- <string name="preferences_interface_general_icons_text_font_family_default">sans-serif-condensed</string>
- <integer name="preferences_interface_general_icons_text_font_style_default">0</integer>
</resources>
diff --git a/src/com/android/launcher/home/Home.java b/src/com/android/launcher/home/Home.java
new file mode 100644
index 000000000..5dce71e86
--- /dev/null
+++ b/src/com/android/launcher/home/Home.java
@@ -0,0 +1,201 @@
+/*
+ * 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 com.android.launcher.home;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+
+/**
+ * The generic contract that should supports a <code>Home</code> app to could
+ * be invoked and registered by an Android launcher.<br/>
+ * <br/>
+ * This interface contains the version 1 of the <code>Home Host App</code> protocol.<br/>
+ * <br/>
+ * <br/>
+ * A <code>Home</code> app should:
+ * <ul>
+ * <li>
+ * should have at least a constructor with no arguments
+ * </li>
+ * <li>
+ * declares inside its manifest a <code>com.android.launcher.home</code> metadata with the
+ * full qualified that contains this interface<br/>
+ * <pre>
+ * &lt;meta-data android:name="com.android.launcher.home" value="org.cyanogenmod.launcher.home.HomeStub"/&gt;
+ * </pre>
+ * </li>
+ * <li>
+ * define the "com.android.launcher.home.permissions.HOME_APP" permission<br/>
+ * <pre>
+ * &lt;uses-permission android:name="com.android.launcher.home.permissions.HOME_APP"/&gt;
+ * </pre>
+ * </li>
+ * <li>
+ * implements the contract defined by this protocol.
+ * </li>
+ * </ul>
+ * <br/>
+ * Implementors classes of this protocol should be aware that all the {@link Context} references
+ * passed to this class owns to the host launcher app. This means that you cannot access
+ * to settings defined by the <code>Home</code> app inside its shared context.
+ */
+public interface Home {
+
+ /**
+ * A SHA-1 hash of all declared method of this interface. Home apps should compute as:<br/>
+ * <br/>
+ * <pre>
+ * for method in Home.class.getDeclaredMehod
+ * sha1.update method.toString.bytes
+ * </pre><br/>
+ * DO NOT MODIFY!
+ */
+ public static final String SIGNATURE = "5/A6Mxkz8gHHzzVf4qZR+hiSOAw=";
+
+ /**
+ * Defines the name of the metadata used to declared the full qualified Home stub class
+ * that implements this protocol.
+ */
+ public static final String METADATA_HOME_STUB = "com.android.launcher.home";
+
+ /**
+ * Defines the name of the permission that the Home app should explicitly declare.
+ */
+ public static final String PERMISSION_HOME_APP = "com.android.launcher.home.permissions.HOME_APP";
+
+ // Notification flags
+ public static final int FLAG_NOTIFY_MASK = 0x0000;
+ public static final int FLAG_NOTIFY_ON_RESUME = FLAG_NOTIFY_MASK + 0x0002;
+ public static final int FLAG_NOTIFY_ON_PAUSE = FLAG_NOTIFY_MASK + 0x0004;
+ public static final int FLAG_NOTIFY_ON_SHOW = FLAG_NOTIFY_MASK + 0x0008;
+ public static final int FLAG_NOTIFY_ON_SCROLL_PROGRESS_CHANGED = FLAG_NOTIFY_MASK + 0x0010;
+ public static final int FLAG_NOTIFY_ON_HIDE = FLAG_NOTIFY_MASK + 0x0020;
+ public static final int FLAG_NOTIFY_ALL = FLAG_NOTIFY_ON_RESUME | FLAG_NOTIFY_ON_PAUSE |
+ FLAG_NOTIFY_ON_SHOW | FLAG_NOTIFY_ON_SCROLL_PROGRESS_CHANGED | FLAG_NOTIFY_ON_HIDE;
+
+ // Operation support flags
+ public static final int FLAG_OP_MASK = 0x1000;
+ public static final int FLAG_OP_CUSTOM_SEARCH = FLAG_OP_MASK + 0x0002;
+ public static final int FLAG_OP_ALL = FLAG_OP_CUSTOM_SEARCH;
+
+ // Search modes
+ public static final int MODE_SEARCH_TEXT = 0x0000;
+ public static final int MODE_SEARCH_VOICE = 0x0001;
+
+ /**
+ * Invoked the first time the <code>Home</code> app is created.<br/>
+ * This method should be used by implementors classes of this protocol to load the needed
+ * resources.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onStart(Context context);
+
+ /**
+ * Invoked when the <code>Home</code> app should be destroy.<br/>
+ * This method should be used by implementors classes of this protocol to unload all unneeded
+ * resources.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onDestroy(Context context);
+
+ /**
+ * Invoked when the host launcher enters in resume mode.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onResume(Context context);
+
+ /**
+ * Invoked when the host launcher enters in pause mode.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onPause(Context context);
+
+ /**
+ * Invoked when the custom content page is totally displayed.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onShow(Context context);
+
+ /**
+ * Invoked when the custom content page is scrolled.
+ * @param context the current {@link Context} of the host launcher.
+ * @param progress the current scroll progress.
+ */
+ void onScrollProgressChanged(Context context, float progress);
+
+ /**
+ * Invoked when the custom content page is totally hidden.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onHide(Context context);
+
+ /**
+ * Invoked by the host launcher to request an invalidation of the ui elements and data used by
+ * the <code>Home</code> implementation class.
+ * @param context the current {@link Context} of the host launcher.
+ */
+ void onInvalidate(Context context);
+
+ /**
+ * Invoked when the host launcher request enter in search mode.
+ * @param context the current {@link Context} of the host launcher.
+ * @param mode the requested search mode. Must be one of:
+ * <ul>
+ * <li>{@link #MODE_SEARCH_TEXT}: Textual mode</li>
+ * <li>{@link #MODE_SEARCH_VOICE}: Voice mode</li>
+ * </ul>
+ */
+ void onRequestSearch(Context context, int mode);
+
+ /**
+ * Returns an instance of a {@link View} that holds the custom content to be displayed
+ * by this <code>Home</code> app.
+ * @param context the current {@link Context} of the host launcher.
+ * @return View The custom content view that will be enclosed inside a
+ * <code>com.android.launcher3.Launcher.QSBScroller</code>.<br/>
+ * Be aware the the height layout of the returned should be defined as
+ * {link {@link LayoutParams#WRAP_CONTENT}, so the view could be scrolled inside the
+ * custom content page.
+ */
+ View createCustomView(Context context);
+
+ /**
+ * Returns the name of the Home app (LIMIT: 30 characters).
+ * @param context the current {@link Context} of the host launcher.
+ */
+ String getName(Context context);
+
+ /**
+ * Implementations should return the combination of notification flags that want to listen to.
+ * @see #FLAG_NOTIFY_ON_RESUME
+ * @see #FLAG_NOTIFY_ON_PAUSE
+ * @see #FLAG_NOTIFY_ON_SHOW
+ * @see #FLAG_NOTIFY_ON_SCROLL_PROGRESS_CHANGED
+ * @see #FLAG_NOTIFY_ON_HIDE
+ * @see #FLAG_NOTIFY_ALL
+ */
+ int getNotificationFlags();
+
+ /**
+ * Implementations should return the combination of operation flags that want they want
+ * to support to.
+ * @see #FLAG_OP_CUSTOM_SEARCH
+ * @see #FLAG_OP_ALL
+ */
+ int getOperationFlags();
+}
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index 6015de624..f2c08a3e1 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -1176,7 +1176,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen
icon.setOnLongClickListener(this);
icon.setOnTouchListener(this);
icon.setOnKeyListener(this);
- Utilities.applyTypeface(icon);
int index = i - startIndex;
int x = index % mCellCountX;
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 779d26955..9d7907c43 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -664,7 +664,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
textView.setTag(item);
textView.setTextColor(getResources().getColor(R.color.folder_items_text_color));
textView.setShadowsEnabled(false);
- Utilities.applyTypeface(textView);
textView.setGlowColor(getResources().getColor(R.color.folder_items_glow_color));
textView.setOnClickListener(this);
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 51517548c..464b35da9 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -149,7 +149,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
icon.setClipToPadding(false);
icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
icon.mFolderName.setText(folderInfo.title);
- Utilities.applyTypeface(icon.mFolderName);
icon.mFolderName.setCompoundDrawablePadding(0);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
diff --git a/src/com/android/launcher3/GelIntegrationHelper.java b/src/com/android/launcher3/GelIntegrationHelper.java
index abda537a2..6dd9c0e32 100644
--- a/src/com/android/launcher3/GelIntegrationHelper.java
+++ b/src/com/android/launcher3/GelIntegrationHelper.java
@@ -63,10 +63,10 @@ public class GelIntegrationHelper {
if(GEL_ACTIVITY.equals(topActivityClassName)
&& GEL_PACKAGE_NAME.equals(topActivityPackageName)) {
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
- homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
homeIntent.addCategory(Intent.CATEGORY_HOME);
launcherActivity.startActivity(homeIntent);
- launcherActivity.overridePendingTransition(0, R.anim.exit_out_left);
+ launcherActivity.overridePendingTransition(0, 0);
dropEventsUntilLift();
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8fa5cfdd6..2947eda0f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -56,6 +56,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -99,6 +100,7 @@ import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.launcher.home.Home;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.PagedView.TransitionEffect;
import com.android.launcher3.settings.SettingsProvider;
@@ -289,6 +291,9 @@ public class Launcher extends Activity
private AppsCustomizePagedView mAppsCustomizeContent;
private boolean mAutoAdvanceRunning = false;
private View mQsb;
+ private View mQsbBar;
+ private ImageView mQsbBarSearch;
+ private ImageView mQsbBarVoice;
private Bundle mSavedState;
// We set the state in both onCreate and then onNewIntent in some cases, which causes both
@@ -578,6 +583,10 @@ public class Launcher extends Activity
}
/** To be overridden by subclasses to hint to Launcher that we have custom content */
+ protected boolean hasCustomSearchSupport() {
+ return false;
+ }
+
protected boolean hasCustomContentToLeft() {
return isGelIntegrationSupported() && isGelIntegrationEnabled();
}
@@ -652,6 +661,15 @@ public class Launcher extends Activity
}
}
+ /** To be overriden by subclasses to hint to Launcher that we have custom content and
+ * support {@link #hasCustomSearchSupport()}
+ * @see com.android.launcher.home.Home#MODE_SEARCH_TEXT
+ * @see com.android.launcher.home.Home#MODE_SEARCH_VOICE
+ * */
+ protected void requestSearch(int mode) {
+ // To be implemented
+ }
+
private void updateGlobalIcons() {
boolean searchVisible = false;
boolean voiceVisible = false;
@@ -1619,7 +1637,6 @@ public class Launcher extends Activity
favorite.setCompoundDrawables(null, d, null, null);
favorite.setOnTouchListener(getHapticFeedbackTouchListener());
}
- Utilities.applyTypeface(favorite);
return favorite;
}
@@ -2740,8 +2757,12 @@ public class Launcher extends Activity
* @param v The view that was clicked.
*/
public void onClickSearchButton(View v) {
- v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ if (hasCustomSearchSupport()) {
+ requestSearch(Home.MODE_SEARCH_TEXT);
+ return;
+ }
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
onSearchRequested();
}
@@ -2751,8 +2772,12 @@ public class Launcher extends Activity
* @param v The view that was clicked.
*/
public void onClickVoiceButton(View v) {
- v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ if (hasCustomSearchSupport()) {
+ requestSearch(Home.MODE_SEARCH_VOICE);
+ return;
+ }
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
startVoice();
}
@@ -3826,6 +3851,20 @@ public class Launcher extends Activity
return mQsb;
}
+ public ImageView getQsbBarSearchButton() {
+ if (mQsbBarSearch == null && getQsbBar() != null) {
+ return (ImageView) getQsbBar().findViewById(R.id.search_button);
+ }
+ return mQsbBarSearch;
+ }
+
+ public ImageView getQsbBarVoiceButton() {
+ if (mQsbBarVoice == null && getQsbBar() != null) {
+ return (ImageView) getQsbBar().findViewById(R.id.voice_button);
+ }
+ return mQsbBarVoice;
+ }
+
protected boolean updateGlobalSearchIcon() {
final View searchButtonContainer = findViewById(R.id.search_button_container);
final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 50ff612e9..11e18b186 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -24,8 +24,6 @@ import android.database.ContentObserver;
import android.os.Handler;
import android.util.Log;
-import com.android.launcher3.settings.SettingsProvider;
-
import java.lang.ref.WeakReference;
public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
@@ -116,18 +114,6 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
ContentResolver resolver = sContext.getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
mFavoritesObserver);
-
- // Generate default typeface
- String fontFamily = SettingsProvider.getString(sContext,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_FAMILY,
- R.string.preferences_interface_general_icons_text_font_family_default);
-
- // TODO: Implement font styles
- int fontStyle = SettingsProvider.getInt(sContext,
- SettingsProvider.SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_STYLE,
- R.integer.preferences_interface_general_icons_text_font_style_default);
-
- Utilities.generateTypeface(fontFamily, fontStyle);
}
public void recreateWidgetPreviewDb() {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index fc6496d00..cbc978585 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -21,14 +21,21 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
-import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
@@ -51,8 +58,6 @@ public final class Utilities {
private static final Rect sOldBounds = new Rect();
private static final Canvas sCanvas = new Canvas();
- private static Typeface sTypeface;
-
static {
sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
Paint.FILTER_BITMAP_FLAG));
@@ -213,27 +218,6 @@ public final class Utilities {
}
/**
- * Generates the default icon typeface for use in icons.
- *
- * @param familyName May be null. The name of the font family.
- * @param style The style (normal, bold, italic) of the typeface. e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
- */
- static void generateTypeface(String familyName, int style) {
- sTypeface = Typeface.create(familyName, style);
- }
-
- /**
- * Applies the default icon typeface to a textview.
- *
- * @param textView View to apply typeface to.
- */
- static void applyTypeface(TextView textView) {
- if (sTypeface != null) {
- textView.setTypeface(sTypeface);
- }
- }
-
- /**
* Given a coordinate relative to the descendant, find the coordinate in a parent view's
* coordinates.
*
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 008dc1527..c67ca9395 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1255,7 +1255,8 @@ public class Workspace extends SmoothPagedView
super.notifyPageSwitchListener();
Launcher.setScreen(getNextPage());
- if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
+ int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
+ if (hasCustomContent() && getNextPage() == ccIndex && !mCustomContentShowing) {
mCustomContentShowing = true;
if (mCustomContentCallbacks != null) {
@@ -1263,7 +1264,7 @@ public class Workspace extends SmoothPagedView
mCustomContentShowTime = System.currentTimeMillis();
mLauncher.updateVoiceButtonProxyVisible(false);
}
- } else if (hasCustomContent() && mCustomContentShowing) {
+ } else if (hasCustomContent() && getNextPage() != ccIndex && mCustomContentShowing) {
mCustomContentShowing = false;
if (mCustomContentCallbacks != null) {
mCustomContentCallbacks.onHide();
@@ -1826,12 +1827,17 @@ public class Workspace extends SmoothPagedView
// them
mLastSetWallpaperOffsetSteps = 0f;
+ moveAwayFromCustomContentIfRequired();
+ }
+
+ public void moveAwayFromCustomContentIfRequired() {
// Never resume to the custom page if GEL integration is enabled.
int customPageIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
// mCustomContentShowing can be lost if the Activity is recreated,
// So make sure it is set to the right value.
mCustomContentShowing = mCustomContentShowing
- || (customPageIndex == getCurrentPage() && hasCustomContent());
+ || (customPageIndex == getCurrentPage()
+ && hasCustomContent());
if (mCustomContentShowing && mLauncher.isGelIntegrationEnabled()) {
moveToScreen((customPageIndex + 1), true);
}
@@ -1844,6 +1850,8 @@ public class Workspace extends SmoothPagedView
mWallpaperOffset.jumpToFinal();
}
super.onLayout(changed, left, top, right, bottom);
+
+ moveAwayFromCustomContentIfRequired();
}
@Override
@@ -4187,7 +4195,12 @@ public class Workspace extends SmoothPagedView
}
public int getCurrentPageOffsetFromCustomContent() {
- return getNextPage() - numCustomPages();
+ int numCustomPages = numCustomPages();
+ // Special case where the Gel Integration page must be counted below
+ if(mLauncher.isGelIntegrationEnabled() && mLauncher.isGelIntegrationSupported()) {
+ numCustomPages += 1;
+ }
+ return getNextPage() - numCustomPages;
}
/**
diff --git a/src/com/android/launcher3/settings/FontStylePreference.java b/src/com/android/launcher3/settings/FontStylePreference.java
deleted file mode 100644
index 50df4b280..000000000
--- a/src/com/android/launcher3/settings/FontStylePreference.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package com.android.launcher3.settings;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.graphics.Typeface;
-import android.preference.ListPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.*;
-import com.android.launcher3.R;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class FontStylePreference extends ListPreference {
- private String mValue;
- public int mClickedDialogEntryIndex;
- private boolean mValueSet;
-
- public FontStylePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
- if (getEntries() == null || getEntryValues() == null) {
- throw new IllegalStateException(
- "ListPreference requires an entries array and an entryValues array.");
- }
-
- mClickedDialogEntryIndex = getValueIndex();
- builder.setSingleChoiceItems(new FontStyleAdapter(getContext()), mClickedDialogEntryIndex,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- mClickedDialogEntryIndex = which;
-
- /*
- * Clicking on an item simulates the positive button
- * click, and dismisses the dialog.
- */
- FontStylePreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
- dialog.dismiss();
- }
- });
-
- /*
- * The typical interaction for list-based dialogs is to have
- * click-on-an-item dismiss the dialog instead of the user having to
- * press 'Ok'.
- */
- builder.setPositiveButton(null, null);
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
-
- if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) {
- String value = getEntryValues()[mClickedDialogEntryIndex].toString();
- if (callChangeListener(value)) {
- setValue(value);
- }
- }
- }
-
- @Override
- protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
- setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue);
- }
-
- /**
- * Sets the value of the key. This should be one of the entries in
- * {@link #getEntryValues()}.
- *
- * @param value The value to set for the key.
- */
- @Override
- public void setValue(String value) {
- // Always persist/notify the first time.
- final boolean changed = !TextUtils.equals(mValue, value);
- if (changed || !mValueSet) {
- mValue = value;
- mValueSet = true;
- persistString(value);
- if (changed) {
- notifyChanged();
- }
- }
- }
-
- /**
- * Returns the value of the key. This should be one of the entries in
- * {@link #getEntryValues()}.
- *
- * @return The value of the key.
- */
- @Override
- public String getValue() {
- return mValue;
- }
-
- private int getValueIndex() {
- return findIndexOfValue(mValue);
- }
-
- private class FontStyleAdapter extends ArrayAdapter<CharSequence> {
- private LayoutInflater mInflater;
- private List<CharSequence> mEntries;
- private List<CharSequence> mValues;
-
- public FontStyleAdapter(Context context) {
- super(context, -1, getEntryValues());
-
- mInflater = LayoutInflater.from(context);
- mEntries = Arrays.asList(getEntries());
- mValues = Arrays.asList(getEntryValues());
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- CheckedTextView textView;
-
- if (convertView == null) {
- textView = (CheckedTextView) mInflater.inflate(R.layout.list_item_checkable, parent, false);
- } else {
- textView = (CheckedTextView) convertView;
- }
-
- if (textView != null) {
- textView.setText(mEntries.get(position));
- textView.setTag(mValues.get(position));
- textView.setTypeface(Typeface.create((String) mValues.get(position), Typeface.NORMAL));
- }
-
- return textView;
- }
- }
-}
diff --git a/src/com/android/launcher3/settings/SettingsProvider.java b/src/com/android/launcher3/settings/SettingsProvider.java
index c827ae478..d7782b2e6 100644
--- a/src/com/android/launcher3/settings/SettingsProvider.java
+++ b/src/com/android/launcher3/settings/SettingsProvider.java
@@ -38,10 +38,10 @@ public final class SettingsProvider {
public static final String SETTINGS_UI_DRAWER_REMOVE_HIDDEN_APPS_WIDGETS = "ui_drawer_remove_hidden_apps_widgets";
public static final String SETTINGS_UI_DRAWER_HIDE_ICON_LABELS = "ui_drawer_hide_icon_labels";
public static final String SETTINGS_UI_GENERAL_ICONS_LARGE = "ui_general_icons_large";
- public static final String SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_FAMILY = "ui_general_icons_text_font";
- public static final String SETTINGS_UI_GENERAL_ICONS_TEXT_FONT_STYLE = "ui_general_icons_text_font_style";
public static final String SETTINGS_UI_DRAWER_SORT_MODE = "ui_drawer_sort_mode";
+ public static final String SETTINGS_HOME_LAST_APP = "home_last_app";
+
public static SharedPreferences get(Context context) {
return context.getSharedPreferences(SETTINGS_KEY, Context.MODE_MULTI_PROCESS);
}
diff --git a/src/org/cyanogenmod/trebuchet/TrebuchetLauncher.java b/src/org/cyanogenmod/trebuchet/TrebuchetLauncher.java
new file mode 100644
index 000000000..ec91e10d7
--- /dev/null
+++ b/src/org/cyanogenmod/trebuchet/TrebuchetLauncher.java
@@ -0,0 +1,332 @@
+/*
+ * 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;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.ImageView;
+
+import com.android.launcher.home.Home;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+import org.cyanogenmod.trebuchet.home.HomeUtils;
+import org.cyanogenmod.trebuchet.home.HomeWrapper;
+
+import java.lang.Override;
+
+public class TrebuchetLauncher extends Launcher {
+
+ private static final String TAG = "TrebuchetLauncher";
+
+ private static final boolean DEBUG = false;
+ private static final float MIN_PROGRESS = 0;
+ private static final float MAX_PROGRESS = 1;
+
+
+ private static class HomeAppStub {
+ private final int mUid;
+ private final ComponentName mComponentName;
+ private final HomeWrapper mInstance;
+
+ private HomeAppStub(int uid, ComponentName componentName, Context context)
+ throws SecurityException, ReflectiveOperationException {
+ super();
+ mUid = uid;
+ mComponentName = componentName;
+
+ // Load a new instance of the Home app
+ ClassLoader classloader = context.getClassLoader();
+ Class<?> homeInterface = classloader.loadClass(Home.class.getName());
+ Class<?> homeClazz = classloader.loadClass(mComponentName.getClassName());
+ mInstance = new HomeWrapper(context, homeInterface, homeClazz.newInstance());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mComponentName == null) ? 0 : mComponentName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ HomeAppStub other = (HomeAppStub) obj;
+ if (mComponentName == null) {
+ if (other.mComponentName != null)
+ return false;
+ } else if (!mComponentName.equals(other.mComponentName))
+ return false;
+ return true;
+ }
+ }
+
+ private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Obtain the current instance or a new one if the current instance not exists
+ boolean invalidate = false;
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_PACKAGE_CHANGED) ||
+ action.equals(Intent.ACTION_PACKAGE_REPLACED) ||
+ action.equals(Intent.ACTION_PACKAGE_RESTARTED)) {
+ if (mCurrentHomeApp != null && intent.getIntExtra(Intent.EXTRA_UID, -1)
+ == mCurrentHomeApp.mUid) {
+ // The current Home app has changed or restarted. Invalidate the current
+ // one to be sure we will get all the new changes (if any)
+ if (DEBUG) Log.d(TAG, "Home package has changed. Invalidate layout.");
+ invalidate = true;
+ }
+ }
+ obtainCurrentHomeAppStubLocked(invalidate);
+ }
+ };
+
+ private CustomContentCallbacks mCustomContentCallbacks = new CustomContentCallbacks() {
+ @Override
+ public void onShow() {
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onShow();
+ }
+ }
+
+ @Override
+ public void onScrollProgressChanged(float progress) {
+ updateQsbBarColorState(progress);
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onScrollProgressChanged(progress);
+ }
+ }
+
+ @Override
+ public void onHide() {
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onHide();
+ }
+ }
+ };
+
+ private HomeAppStub mCurrentHomeApp;
+ private AccelerateInterpolator mQSBAlphaInterpolator;
+
+ private QSBScroller mQsbScroller;
+ private int mQsbInitialAlphaState;
+ private int mQsbEndAlphaState;
+ private int mQsbButtonsEndColorFilter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mQSBAlphaInterpolator = new AccelerateInterpolator();
+
+ // Set QsbBar color state
+ final Resources res = getResources();
+ mQsbInitialAlphaState = res.getInteger(R.integer.qsb_initial_alpha_state);
+ mQsbEndAlphaState = res.getInteger(R.integer.qsb_end_alpha_state);
+ mQsbButtonsEndColorFilter = res.getInteger(R.integer.qsb_buttons_end_colorfilter);
+ updateQsbBarColorState(MIN_PROGRESS);
+
+ // Obtain the user-defined Home app or a valid one
+ obtainCurrentHomeAppStubLocked(true);
+
+ // Register this class to listen for new/deleted packages
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addDataScheme("package");
+ registerReceiver(mPackageReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // Unregister services
+ unregisterReceiver(mPackageReceiver);
+ }
+
+ @Override
+ protected void onResume() {
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onResume();
+ }
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onPause();
+ }
+ super.onPause();
+ }
+
+ @Override
+ protected boolean hasCustomContentToLeft() {
+ return mCurrentHomeApp != null && super.hasCustomContentToLeft();
+ }
+
+ @Override
+ protected void invalidateHasCustomContentToLeft() {
+ invalidateHomeStub();
+ super.invalidateHasCustomContentToLeft();
+ }
+
+ @Override
+ protected void populateCustomContentContainer() {
+ if (mCurrentHomeApp != null) {
+ mQsbScroller = addToCustomContentPage(mCurrentHomeApp.mInstance.createCustomView(),
+ mCustomContentCallbacks, mCurrentHomeApp.mInstance.getName());
+ mQsbScroller.setScrollY(200);
+ }
+ }
+
+ @Override
+ protected boolean hasCustomSearchSupport() {
+ return hasCustomContentToLeft() && mCurrentHomeApp.mInstance.isOperationSupported(
+ Home.FLAG_OP_CUSTOM_SEARCH);
+ }
+
+ @Override
+ protected void requestSearch(int mode) {
+ if (!hasCustomSearchSupport()) {
+ return;
+ }
+ mCurrentHomeApp.mInstance.onRequestSearch(mode);
+ }
+
+ private synchronized void obtainCurrentHomeAppStubLocked(boolean invalidate) {
+ if (DEBUG) Log.d(TAG, "obtainCurrentHomeAppStubLocked called (" + invalidate + ")");
+
+ SparseArray<ComponentName> packages = HomeUtils.getInstalledHomePackages(this);
+ if (!invalidate && mCurrentHomeApp != null &&
+ packages.get(mCurrentHomeApp.mUid) != null) {
+ // We still have a valid Home app
+ return;
+ }
+
+ // We don't have a valid Home app, so we need to destroy the current the custom content
+ destroyHomeStub();
+
+ // Return the default valid home app
+ int size = packages.size();
+ for (int i = 0; i < size; i++) {
+ int key = packages.keyAt(i);
+ ComponentName pkg = packages.get(key);
+ String qualifiedPkg = pkg.toShortString();
+ Context ctx = HomeUtils.createNewHomePackageContext(this, pkg);
+ if (ctx == null) {
+ // We failed to create a valid context. Will try with the next package
+ continue;
+ }
+ try {
+ mCurrentHomeApp = new HomeAppStub(key, pkg, ctx);
+ } catch (ReflectiveOperationException ex) {
+ if (!DEBUG) {
+ Log.w(TAG, "Cannot instantiate home package: " + qualifiedPkg + ". Ignored.");
+ } else {
+ Log.w(TAG, "Cannot instantiate home package: " + qualifiedPkg +
+ ". Ignored.", ex);
+ }
+ } catch (SecurityException ex) {
+ if (!DEBUG) {
+ Log.w(TAG, "Home package is insecure: " + qualifiedPkg + ". Ignored.");
+ } else {
+ Log.w(TAG, "Home package is insecure: " + qualifiedPkg + ". Ignored.", ex);
+ }
+ }
+
+ // Notify home app that is going to be used
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onStart();
+ }
+ }
+
+ // Don't have a valid package. Anyway notify the launcher that custom content has changed
+ invalidateHasCustomContentToLeft();
+ }
+
+ private void invalidateHomeStub() {
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onInvalidate();
+ if (DEBUG) Log.d(TAG, "Home package " + mCurrentHomeApp.mComponentName.toShortString()
+ + " was invalidated.");
+ }
+ }
+
+ private void destroyHomeStub() {
+ if (mCurrentHomeApp != null) {
+ mCurrentHomeApp.mInstance.onInvalidate();
+ mCurrentHomeApp.mInstance.onDestroy();
+ if (DEBUG) Log.d(TAG, "Home package " + mCurrentHomeApp.mComponentName.toShortString()
+ + " was destroyed.");
+ }
+ mQsbScroller = null;
+ mCurrentHomeApp = null;
+ }
+
+ private void updateQsbBarColorState(float progress) {
+ if (getQsbBar() != null) {
+ float interpolation = mQSBAlphaInterpolator.getInterpolation(progress);
+
+ // Background alpha
+ int alphaInterpolation = (int)(mQsbInitialAlphaState +
+ (interpolation * (mQsbEndAlphaState - mQsbInitialAlphaState)));
+ Drawable background = getQsbBar().getBackground();
+ if (background != null) {
+ background.setAlpha(alphaInterpolation);
+ }
+
+ // Buttons color filter
+ int colorInterpolation = (int)(255 - (interpolation * mQsbButtonsEndColorFilter));
+ int color = Color.rgb(colorInterpolation, colorInterpolation,colorInterpolation);
+ ImageView voiceButton = getQsbBarVoiceButton();
+ if (voiceButton != null) {
+ if (progress > 0) {
+ voiceButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ }
+ }
+ ImageView searchButton = getQsbBarSearchButton();
+ if (searchButton != null) {
+ if (progress > 0) {
+ searchButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ }
+ }
+ }
+ }
+}
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;
+ }
+}