summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk3
-rw-r--r--res/drawable-nodpi/letter_indicator.9.pngbin0 -> 3614 bytes
-rw-r--r--res/drawable/scrubber_back.xml20
-rw-r--r--res/drawable/seek_back.xml21
-rw-r--r--res/layout-land/launcher.xml6
-rw-r--r--res/layout-port/launcher.xml5
-rw-r--r--res/layout-sw720dp/launcher.xml6
-rw-r--r--res/layout/app_drawer_container.xml34
-rw-r--r--res/layout/app_drawer_item.xml49
-rw-r--r--res/layout/drawer_icon.xml36
-rw-r--r--res/layout/scrub_layout.xml100
-rw-r--r--res/values/attrs.xml5
-rw-r--r--res/values/dimens.xml5
-rw-r--r--src/com/android/launcher3/AppDrawerIconView.java74
-rw-r--r--src/com/android/launcher3/AppDrawerListAdapter.java480
-rw-r--r--src/com/android/launcher3/AppDrawerScrubber.java141
-rw-r--r--src/com/android/launcher3/AutoFitTextView.java381
-rw-r--r--src/com/android/launcher3/BubbleTextView.java10
-rw-r--r--src/com/android/launcher3/Launcher.java148
-rw-r--r--src/com/android/launcher3/PagedViewIcon.java134
-rw-r--r--src/com/android/launcher3/Utilities.java8
21 files changed, 1625 insertions, 41 deletions
diff --git a/Android.mk b/Android.mk
index 1f98b76..c7e2dfd 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,7 +23,8 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13 \
+ android-support-v7-recyclerview
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-java-files-under, WallpaperPicker/src) \
diff --git a/res/drawable-nodpi/letter_indicator.9.png b/res/drawable-nodpi/letter_indicator.9.png
new file mode 100644
index 0000000..af3578e
--- /dev/null
+++ b/res/drawable-nodpi/letter_indicator.9.png
Binary files differ
diff --git a/res/drawable/scrubber_back.xml b/res/drawable/scrubber_back.xml
new file mode 100644
index 0000000..56cf5af
--- /dev/null
+++ b/res/drawable/scrubber_back.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#99333333"/>
+ <corners android:radius="5dip"/>
+</shape>
diff --git a/res/drawable/seek_back.xml b/res/drawable/seek_back.xml
new file mode 100644
index 0000000..a49796c
--- /dev/null
+++ b/res/drawable/seek_back.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="line">
+ <stroke android:width="4dp" android:color="#FFF"/>
+ <size android:height="2dp" />
+</shape>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 8cd8673..4c097ba 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -62,5 +62,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
+
+ <include layout="@layout/app_drawer_container"
+ android:id="@+id/app_drawer_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
</com.android.launcher3.DragLayer>
</FrameLayout>
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index ad10278..e193e48 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -83,5 +83,10 @@
android:id="@+id/overview_panel"
android:visibility="gone" />
+ <include layout="@layout/app_drawer_container"
+ android:id="@+id/app_drawer_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
</com.android.launcher3.DragLayer>
</FrameLayout>
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 6261541..7721105 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -81,5 +81,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
+
+ <include layout="@layout/app_drawer_container"
+ android:id="@+id/app_drawer_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
</com.android.launcher3.DragLayer>
</FrameLayout>
diff --git a/res/layout/app_drawer_container.xml b/res/layout/app_drawer_container.xml
new file mode 100644
index 0000000..c0eb97a
--- /dev/null
+++ b/res/layout/app_drawer_container.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:id="@+id/fake_page_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+ <FrameLayout
+ android:id="@+id/fake_page"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible"
+ android:clipToPadding="false" />
+ </FrameLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/app_drawer_item.xml b/res/layout/app_drawer_item.xml
new file mode 100644
index 0000000..37bdeea
--- /dev/null
+++ b/res/layout/app_drawer_item.xml
@@ -0,0 +1,49 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:autofit="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:splitMotionEvents="false"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <LinearLayout
+ android:layout_marginTop="10dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <com.android.launcher3.AutoFitTextView
+ android:id="@+id/drawer_item_title"
+ android:layout_width="30dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp"
+ android:includeFontPadding="false"
+ android:gravity="start|top"
+ android:singleLine="true"
+ autofit:minTextSize="8sp"
+ android:textSize="35sp"
+ android:textColor="@android:color/white"/>
+ <LinearLayout
+ android:id="@+id/drawer_item_flow"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" />
+ </LinearLayout>
+ <Space android:id="@+id/spacer"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:visibility="gone" />
+</LinearLayout>
diff --git a/res/layout/drawer_icon.xml b/res/layout/drawer_icon.xml
new file mode 100644
index 0000000..1cdea78
--- /dev/null
+++ b/res/layout/drawer_icon.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<com.android.launcher3.AppDrawerIconView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+
+ <TextView
+ android:id="@+id/label"
+ style="@style/WorkspaceIcon"
+ android:shadowRadius="0"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+</com.android.launcher3.AppDrawerIconView>
diff --git a/res/layout/scrub_layout.xml b/res/layout/scrub_layout.xml
new file mode 100644
index 0000000..e8761e3
--- /dev/null
+++ b/res/layout/scrub_layout.xml
@@ -0,0 +1,100 @@
+<?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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_marginRight="20dp"
+ android:layout_marginLeft="20dp"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:clickable="true"
+ android:layout_marginTop="-5dp"
+ android:layout_below="@+id/scrubberIndicator"
+ android:orientation="horizontal"
+ android:background="@drawable/scrubber_back"
+ android:layout_width="match_parent"
+ android:layout_height="50dp">
+
+ <Space
+ android:layout_weight="0.1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent" />
+
+ <TextView
+ android:id="@+id/firstSection"
+ android:gravity="center"
+ android:textColor="@android:color/white"
+ android:paddingRight="10dp"
+ android:textStyle="bold"
+ style="?android:attr/textAppearanceLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" />
+
+ <LinearLayout
+ android:layout_weight="0.9"
+ android:layout_width="0dp"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ android:layout_height="match_parent">
+
+ <SeekBar
+ android:id="@+id/scrubber"
+ android:paddingLeft="0dp"
+ android:paddingRight="0dp"
+ android:layout_marginRight="-10dp"
+ android:layout_marginLeft="-10dp"
+ android:thumb="@android:color/transparent"
+ android:thumbOffset="-10dp"
+ android:progressDrawable="@drawable/seek_back"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:layout_height="match_parent" />
+
+ </LinearLayout>
+
+
+ <TextView
+ android:gravity="center"
+ android:id="@+id/lastSection"
+ android:paddingLeft="10dp"
+ android:textColor="@android:color/white"
+ android:textStyle="bold"
+ style="?android:attr/textAppearanceLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" />
+
+ <Space
+ android:layout_weight="0.1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/scrubberIndicator"
+ android:background="@drawable/letter_indicator"
+ android:layout_width="80dp"
+ android:textSize="30sp"
+ android:gravity="center"
+ android:textColor="@android:color/white"
+ android:clickable="false"
+ android:layout_marginBottom="-20dp"
+ android:visibility="invisible"
+ android:layout_height="100dp" />
+
+</RelativeLayout>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 240f311..b32c5a2 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -154,4 +154,9 @@
<attr name="overlay" format="boolean"/>
</declare-styleable>
+ <declare-styleable name="AutofitTextView">
+ <attr name="minTextSize" format="dimension" />
+ <attr name="precision" format="float" />
+ <attr name="sizeToFit" format="boolean" />
+ </declare-styleable>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d591535..7ceaf8b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -118,4 +118,9 @@
<dimen name="grid_padding">15dp</dimen>
<dimen name="grid_custom_text">50dp</dimen>
+
+ <dimen name="scrubber_bottom_padding">30dp</dimen>
+
+ <!-- Vertical app drawer padding -->
+ <dimen name="vertical_app_drawer_icon_padding">5px</dimen>
</resources>
diff --git a/src/com/android/launcher3/AppDrawerIconView.java b/src/com/android/launcher3/AppDrawerIconView.java
new file mode 100644
index 0000000..d8564b6
--- /dev/null
+++ b/src/com/android/launcher3/AppDrawerIconView.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 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.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * AppDrawerIconView - represents icons in the vertical app drawer.
+ * Found to be more performant than the BubbleTextView used in the
+ * legacy app drawer.
+ */
+public class AppDrawerIconView extends LinearLayout {
+
+ TextView mLabel;
+ ImageView mIcon;
+
+ public AppDrawerIconView(Context context) {
+ super(context);
+ }
+
+ public AppDrawerIconView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AppDrawerIconView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mLabel = (TextView) findViewById(R.id.label);
+ mIcon = (ImageView) findViewById(R.id.image);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
+ mLabel.setShadowLayer(BubbleTextView.SHADOW_LARGE_RADIUS, 0.0f,
+ BubbleTextView.SHADOW_Y_OFFSET, BubbleTextView.SHADOW_LARGE_COLOUR);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ setAlpha(PagedViewIcon.PRESS_ALPHA);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ setAlpha(1f);
+ break;
+ }
+ return super.onTouchEvent(event);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/AppDrawerListAdapter.java b/src/com/android/launcher3/AppDrawerListAdapter.java
new file mode 100644
index 0000000..891c323
--- /dev/null
+++ b/src/com/android/launcher3/AppDrawerListAdapter.java
@@ -0,0 +1,480 @@
+/*
+ * 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 com.android.launcher3;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.support.v7.widget.RecyclerView;
+import android.widget.LinearLayout;
+import android.widget.SectionIndexer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * AppDrawerListAdapter - list adapter for the vertical app drawer
+ */
+public class AppDrawerListAdapter extends RecyclerView.Adapter<AppDrawerListAdapter.ViewHolder>
+ implements View.OnLongClickListener, DragSource, SectionIndexer {
+
+ private static final int SCRUBBER_MARGIN_FROM_BOTTOM_DP = 80;
+ private static final char NUMERIC_OR_SPECIAL_CHAR = '#';
+ private static final String NUMERIC_OR_SPECIAL_HEADER = "#";
+
+ private ArrayList<AppItemIndexedInfo> mHeaderList;
+ private LayoutInflater mLayoutInflater;
+
+ private Launcher mLauncher;
+ private DeviceProfile mDeviceProfile;
+ private LinkedHashMap<String, Integer> mSectionHeaders;
+ private LinearLayout.LayoutParams mIconParams, mSpacerParams;
+ private Rect mIconRect;
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public AutoFitTextView mTextView;
+ public ViewGroup mLayout;
+ public View mSpacer;
+ public ViewHolder(View itemView) {
+ super(itemView);
+ mTextView = (AutoFitTextView) itemView.findViewById(R.id.drawer_item_title);
+ mLayout = (ViewGroup) itemView.findViewById(R.id.drawer_item_flow);
+ mSpacer = itemView.findViewById(R.id.spacer);
+ }
+ }
+
+ public AppDrawerListAdapter(Launcher launcher) {
+ mLauncher = launcher;
+ mHeaderList = new ArrayList<AppItemIndexedInfo>();
+ mDeviceProfile = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+ mLayoutInflater = LayoutInflater.from(launcher);
+ initParams();
+ }
+
+ private void initParams() {
+ mIconParams = new
+ LinearLayout.LayoutParams(mDeviceProfile.folderCellWidthPx,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ mSpacerParams = new
+ LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
+ (int) Utilities.convertDpToPixel(SCRUBBER_MARGIN_FROM_BOTTOM_DP, mLauncher));
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mIconRect = new Rect(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx);
+ }
+
+ /**
+ * Create and populate mHeaderList (buckets for app sorting)
+ * @param info
+ */
+ public void populateByCharacter(ArrayList<AppInfo> info) {
+ if (info == null || info.size() <= 0) {
+ return;
+ }
+
+ // Create a clone of AppInfo ArrayList to preserve data
+ ArrayList<AppInfo> tempInfo = new ArrayList<AppInfo>(info.size());
+ for (AppInfo i : info) {
+ tempInfo.add(i);
+ }
+
+ ListIterator<AppInfo> it = tempInfo.listIterator();
+ ArrayList<AppInfo> appInfos = new ArrayList<AppInfo>();
+ appInfos.clear();
+
+ // get next app
+ AppInfo app = it.next();
+
+ // get starting character
+ boolean isSpecial = false;
+ char startChar = app.title.toString().toUpperCase().charAt(0);
+ if (!Character.isLetter(startChar)) {
+ isSpecial = true;
+ }
+
+ // now iterate through
+ for (AppInfo info1 : tempInfo) {
+ char newChar = info1.title.toString().toUpperCase().charAt(0);
+ // if same character
+ if (newChar == startChar) {
+ // add it
+ appInfos.add(info1);
+ } else if (isSpecial && !Character.isLetter(newChar)) {
+ appInfos.add(info1);
+ }
+ }
+
+ for (int i = 0; i < appInfos.size(); i += mDeviceProfile.numColumnsBase) {
+ int endIndex = (int) Math.min(i + mDeviceProfile.numColumnsBase, appInfos.size());
+ ArrayList<AppInfo> subList = new ArrayList<AppInfo>(appInfos.subList(i, endIndex));
+ AppItemIndexedInfo indexInfo;
+ if (isSpecial) {
+ indexInfo = new AppItemIndexedInfo('#', subList, i != 0);
+ } else {
+ indexInfo = new AppItemIndexedInfo(startChar, subList, i != 0);
+ }
+ mHeaderList.add(indexInfo);
+ }
+
+ for (AppInfo remove : appInfos) {
+ // remove from mApps
+ tempInfo.remove(remove);
+ }
+ populateByCharacter(tempInfo);
+ }
+
+ public void setApps(ArrayList<AppInfo> list) {
+ if (!LauncherAppState.isDisableAllApps()) {
+ mHeaderList.clear();
+ Collections.sort(list, LauncherModel.getAppNameComparator());
+ populateByCharacter(list);
+ populateSectionHeaders();
+ mLauncher.updateScrubber();
+ this.notifyDataSetChanged();
+ }
+ }
+
+ private void populateSectionHeaders() {
+ if (mSectionHeaders == null || mSectionHeaders.size() != mHeaderList.size()) {
+ mSectionHeaders = new LinkedHashMap<String, Integer>();
+ }
+ int count = 0;
+ for (int i = 0; i < mHeaderList.size(); i++) {
+ AppItemIndexedInfo info = mHeaderList.get(i);
+ if (!mHeaderList.get(i).isChild) {
+ mSectionHeaders.put(String.valueOf(mHeaderList.get(i).mChar), count);
+ }
+ if (info.mInfo.size() < mDeviceProfile.numColumnsBase) {
+ count++;
+ } else {
+ count += info.mInfo.size() / mDeviceProfile.numColumnsBase;
+ }
+ }
+ }
+
+ private void reset() {
+ ArrayList<AppInfo> infos = getAllApps();
+ setApps(infos);
+ }
+
+ private ArrayList<AppInfo> getAllApps() {
+ ArrayList<AppInfo> indexedInfos = new ArrayList<AppInfo>();
+
+ for (int j = 0; j < mHeaderList.size(); ++j) {
+ AppItemIndexedInfo indexedInfo = mHeaderList.get(j);
+ for (AppInfo info : indexedInfo.mInfo) {
+ indexedInfos.add(info);
+ }
+ }
+ return indexedInfos;
+ }
+
+ public void updateApps(ArrayList<AppInfo> list) {
+ // We remove and re-add the updated applications list because it's properties may have
+ // changed (ie. the title), and this will ensure that the items will be in their proper
+ // place in the list.
+ if (!LauncherAppState.isDisableAllApps()) {
+ removeAppsWithoutInvalidate(list);
+ addAppsWithoutInvalidate(list);
+ reset();
+ }
+ }
+
+
+ public void addApps(ArrayList<AppInfo> list) {
+ if (!LauncherAppState.isDisableAllApps()) {
+ addAppsWithoutInvalidate(list);
+ reset();
+ }
+ }
+
+ private void addAppsWithoutInvalidate(ArrayList<AppInfo> list) {
+ // We add it in place, in alphabetical order
+ int count = list.size();
+ for (int i = 0; i < count; ++i) {
+ AppInfo info = list.get(i);
+ boolean found = false;
+ AppItemIndexedInfo lastInfoForSection = null;
+ for (int j = 0; j < mHeaderList.size(); ++j) {
+ AppItemIndexedInfo indexedInfo = mHeaderList.get(j);
+ if (info.title.charAt(0) == indexedInfo.mChar) {
+ Collections.sort(indexedInfo.mInfo, LauncherModel.getAppNameComparator());
+ int index =
+ Collections.binarySearch(indexedInfo.mInfo,
+ info, LauncherModel.getAppNameComparator());
+ if (index >= 0) {
+ found = true;
+ break;
+ } else {
+ lastInfoForSection = indexedInfo;
+ }
+ }
+ }
+ if (!found) {
+ if (lastInfoForSection != null) {
+ lastInfoForSection.mInfo.add(info);
+ } else {
+ // we need to create a new section
+ ArrayList<AppInfo> newInfos = new ArrayList<AppInfo>();
+ newInfos.add(info);
+ AppItemIndexedInfo newInfo =
+ new AppItemIndexedInfo(info.title.charAt(0), newInfos, false);
+ mHeaderList.add(newInfo);
+ }
+ }
+ }
+ }
+
+ public void removeApps(ArrayList<AppInfo> appInfos) {
+ if (!LauncherAppState.isDisableAllApps()) {
+ removeAppsWithoutInvalidate(appInfos);
+ //recreate everything
+ reset();
+ }
+ }
+
+ private void removeAppsWithoutInvalidate(ArrayList<AppInfo> list) {
+ // loop through all the apps and remove apps that have the same component
+ int length = list.size();
+ for (int i = 0; i < length; ++i) {
+ AppInfo info = list.get(i);
+ for (int j = 0; j < mHeaderList.size(); ++j) {
+ AppItemIndexedInfo indexedInfo = mHeaderList.get(j);
+ ArrayList<AppInfo> clonedIndexedInfoApps =
+ (ArrayList<AppInfo>) indexedInfo.mInfo.clone();
+ int index =
+ findAppByComponent(clonedIndexedInfoApps, info);
+ if (index > -1) {
+ indexedInfo.mInfo.remove(info);
+ }
+ }
+ }
+ }
+
+ private int findAppByComponent(List<AppInfo> list, AppInfo item) {
+ ComponentName removeComponent = item.intent.getComponent();
+ int length = list.size();
+ for (int i = 0; i < length; ++i) {
+ AppInfo info = list.get(i);
+ if (info.intent.getComponent().equals(removeComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /*
+ * AllAppsView implementation
+ */
+ public void setup(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).
+ inflate(R.layout.app_drawer_item, parent, false);
+ ViewHolder holder = new ViewHolder(v);
+ holder.mSpacer.setLayoutParams(mSpacerParams);
+ for (int i = 0; i < mDeviceProfile.numColumnsBase; i++) {
+ AppDrawerIconView icon = (AppDrawerIconView) mLayoutInflater.inflate(
+ R.layout.drawer_icon, holder.mLayout, false);
+ icon.setLayoutParams(mIconParams);
+ icon.setOnClickListener(mLauncher);
+ icon.setOnLongClickListener(this);
+ int padding = (int) mLauncher.getResources()
+ .getDimension(R.dimen.vertical_app_drawer_icon_padding);
+ icon.setPadding(padding, padding, padding, padding);
+ holder.mLayout.addView(icon);
+ }
+ return holder;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mHeaderList.size();
+ }
+
+ public AppItemIndexedInfo getItemAt(int position) {
+ if (position < mHeaderList.size())
+ return mHeaderList.get(position);
+ return null;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ AppItemIndexedInfo indexedInfo = mHeaderList.get(position);
+ holder.mTextView.setVisibility(indexedInfo.isChild ? View.INVISIBLE : View.VISIBLE);
+ if (!indexedInfo.isChild) {
+ if (indexedInfo.mChar == NUMERIC_OR_SPECIAL_CHAR) {
+ holder.mTextView.setText(NUMERIC_OR_SPECIAL_HEADER);
+ } else {
+ holder.mTextView.setText(String.valueOf(indexedInfo.mChar));
+ }
+ }
+ final int size = indexedInfo.mInfo.size();
+ for (int i = 0; i < holder.mLayout.getChildCount(); i++) {
+ AppDrawerIconView icon = (AppDrawerIconView) holder.mLayout.getChildAt(i);
+ if (i >= size) {
+ icon.setVisibility(View.INVISIBLE);
+ } else {
+ icon.setVisibility(View.VISIBLE);
+ AppInfo info = indexedInfo.mInfo.get(i);
+ icon.setTag(info);
+ Drawable d = Utilities.createIconDrawable(info.iconBitmap);
+ d.setBounds(mIconRect);
+ icon.mIcon.setImageDrawable(d);
+ icon.mLabel.setText(info.title);
+ }
+ }
+ if (position == getItemCount() - 1) {
+ holder.mSpacer.setVisibility(View.VISIBLE);
+ } else {
+ holder.mSpacer.setVisibility(View.GONE);
+ }
+ holder.itemView.setTag(indexedInfo);
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (v instanceof AppDrawerIconView) {
+ beginDraggingApplication(v);
+ mLauncher.showWorkspace();
+ }
+ return false;
+ }
+
+ @Override
+ public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
+ boolean success) {
+ // Return early and wait for onFlingToDeleteCompleted if this was the result of a fling
+ if (isFlingToDelete) return;
+
+ endDragging(target, false, success);
+
+ // Display an error message if the drag failed due to there not being enough space on the
+ // target layout we were dropping on.
+ if (!success) {
+ boolean showOutOfSpaceMessage = false;
+ if (target instanceof Workspace) {
+ int currentScreen = mLauncher.getCurrentWorkspaceScreen();
+ Workspace workspace = (Workspace) target;
+ CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
+ ItemInfo itemInfo = (ItemInfo) d.dragInfo;
+ if (layout != null) {
+ layout.calculateSpans(itemInfo);
+ showOutOfSpaceMessage =
+ !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
+ }
+ }
+ if (showOutOfSpaceMessage) {
+ mLauncher.showOutOfSpaceMessage(false);
+ }
+
+ d.deferDragViewCleanupPostAnimation = false;
+ }
+ }
+
+ /**
+ * Clean up after dragging.
+ *
+ * @param target where the item was dragged to (can be null if the item was flung)
+ */
+ private void endDragging(View target, boolean isFlingToDelete, boolean success) {
+ if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+ !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
+ // Exit spring loaded mode if we have not successfully dropped or have not handled the
+ // drop in Workspace
+ mLauncher.getWorkspace().removeExtraEmptyScreenDelayed(true, new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.exitSpringLoadedDragMode();
+ mLauncher.unlockScreenOrientation(false);
+ }
+ }, 0, true);
+ } else {
+ mLauncher.unlockScreenOrientation(false);
+ }
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsAppInfoDropTarget() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsDeleteDropTarget() {
+ return false;
+ }
+
+ @Override
+ public float getIntrinsicIconScaleFactor() {
+ return (float) mDeviceProfile.allAppsIconSizePx / mDeviceProfile.iconSizePx;
+ }
+
+ private void beginDraggingApplication(View v) {
+ // mLauncher.getWorkspace().onDragStartedWithItem(v);
+ mLauncher.getWorkspace().beginDragShared(v, this);
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // We just dismiss the drag when we fling, so cleanup here
+ }
+
+ public class AppItemIndexedInfo {
+ private boolean isChild;
+ private char mChar;
+ private ArrayList<AppInfo> mInfo;
+
+ private AppItemIndexedInfo(char startChar, ArrayList<AppInfo> info, boolean isChild) {
+ this.mChar = startChar;
+ this.mInfo = info;
+ this.isChild = isChild;
+ }
+
+ public char getChar() {
+ return mChar;
+ }
+ }
+
+ @Override
+ public Object[] getSections() {
+ return mSectionHeaders.keySet().toArray(new String[mSectionHeaders.size()]);
+ }
+
+ @Override
+ public int getPositionForSection(int sectionIndex) {
+ return mSectionHeaders.get(getSections()[sectionIndex]);
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ return mSectionHeaders.get(mHeaderList.get(position).mChar);
+ }
+}
diff --git a/src/com/android/launcher3/AppDrawerScrubber.java b/src/com/android/launcher3/AppDrawerScrubber.java
new file mode 100644
index 0000000..41c3199
--- /dev/null
+++ b/src/com/android/launcher3/AppDrawerScrubber.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2013 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.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.LinearLayoutManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class AppDrawerScrubber extends LinearLayout implements OnClickListener {
+
+ private final int SCRUBBER_INDICATOR_DISPLAY_DURATION = 200;
+ private final float SCRUBBER_INDICATOR_DISPLAY_TRANSLATIONY = 20f;
+
+ private AppDrawerListAdapter mAdapter;
+ private RecyclerView mListView;
+ private TextView mFirstIndicator, mLastIndicator;
+ private TextView mScrubberIndicator;
+ private SeekBar mSeekBar;
+ private String[] mSections;
+ private LinearLayoutManager mLayoutManager;
+
+ public AppDrawerScrubber(Context context) {
+ super(context);
+ LayoutInflater.from(context).inflate(R.layout.scrub_layout, this);
+ mFirstIndicator = ((TextView) findViewById(R.id.firstSection));
+ mFirstIndicator.setOnClickListener(this);
+ mLastIndicator = ((TextView) findViewById(R.id.lastSection));
+ mLastIndicator.setOnClickListener(this);
+ mScrubberIndicator = (TextView) findViewById(R.id.scrubberIndicator);
+ mSeekBar = (SeekBar) findViewById(R.id.scrubber);
+ init();
+ }
+
+ public void updateSections() {
+ mSections = (String[]) mAdapter.getSections();
+ mSeekBar.setMax(mSections.length - 1);
+ mFirstIndicator.setText(mSections[0]);
+ mLastIndicator.setText(mSections[mSections.length - 1]);
+ }
+
+ public void setSource(RecyclerView listView) {
+ mListView = listView;
+ mAdapter = (AppDrawerListAdapter) listView.getAdapter();
+ mLayoutManager = (LinearLayoutManager) listView.getLayoutManager();
+ }
+
+ private boolean isReady() {
+ return mListView != null &&
+ mAdapter != null &&
+ mSections != null;
+ }
+
+ private void init() {
+ mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, final int progress, boolean fromUser) {
+ if (!isReady()) {
+ return;
+ }
+ resetScrubber();
+ mScrubberIndicator.setTranslationX((progress * seekBar.getWidth()) /
+ mSections.length);
+ String section = String.valueOf(mSections[progress]);
+ mLayoutManager.scrollToPositionWithOffset(
+ mAdapter.getPositionForSection(progress), 0);
+ mScrubberIndicator.setText(section);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (!isReady()) {
+ return;
+ }
+ resetScrubber();
+ mScrubberIndicator.setAlpha(1f);
+ mScrubberIndicator.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (!isReady()) {
+ return;
+ }
+ resetScrubber();
+ mScrubberIndicator.animate()
+ .alpha(0f)
+ .translationYBy(SCRUBBER_INDICATOR_DISPLAY_TRANSLATIONY)
+ .setDuration(SCRUBBER_INDICATOR_DISPLAY_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mScrubberIndicator.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+
+ private void resetScrubber() {
+ mScrubberIndicator.animate().cancel();
+ mScrubberIndicator.setTranslationY(0f);
+ }
+ });
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mFirstIndicator) {
+ int positionForFirstSection = mAdapter.getPositionForSection(0);
+ mLayoutManager.scrollToPositionWithOffset(positionForFirstSection, 0);
+ } else if (v == mLastIndicator) {
+ int positionForLastSection = mAdapter.getPositionForSection(mSections.length - 1);
+ mLayoutManager.scrollToPositionWithOffset(positionForLastSection, 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/AutoFitTextView.java b/src/com/android/launcher3/AutoFitTextView.java
new file mode 100644
index 0000000..208dd40
--- /dev/null
+++ b/src/com/android/launcher3/AutoFitTextView.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2014 Grantland Chew
+ *
+ * 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.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.method.TransformationMethod;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+/**
+ * A TextView that resizes it's text to be no larger than the width of the view.
+ *
+ * @author Grantland Chew <grantlandchew@gmail.com>
+ */
+public class AutoFitTextView extends TextView {
+
+ private static final String TAG = "AutoFitTextView";
+ private static final boolean SPEW = false;
+
+ // Minimum size of the text in pixels
+ private static final int DEFAULT_MIN_TEXT_SIZE = 8; //sp
+ // How precise we want to be when reaching the target textWidth size
+ private static final float PRECISION = 0.5f;
+
+ // Attributes
+ private boolean mSizeToFit;
+ private int mMaxLines;
+ private float mMinTextSize;
+ private float mMaxTextSize;
+ private float mPrecision;
+ private TextPaint mPaint;
+
+ public AutoFitTextView(Context context) {
+ super(context);
+ init(context, null, 0);
+ }
+
+ public AutoFitTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs, 0);
+ }
+
+ public AutoFitTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context, attrs, defStyle);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyle) {
+ float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
+ boolean sizeToFit = true;
+ int minTextSize = (int) scaledDensity * DEFAULT_MIN_TEXT_SIZE;
+ float precision = PRECISION;
+
+ if (attrs != null) {
+ TypedArray ta = context.obtainStyledAttributes(
+ attrs,
+ R.styleable.AutofitTextView,
+ defStyle,
+ 0);
+ sizeToFit = ta.getBoolean(R.styleable.AutofitTextView_sizeToFit, sizeToFit);
+ minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitTextView_minTextSize,
+ minTextSize);
+ precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision);
+ ta.recycle();
+ }
+
+ mPaint = new TextPaint();
+ setSizeToFit(sizeToFit);
+ setRawTextSize(super.getTextSize());
+ setRawMinTextSize(minTextSize);
+ setPrecision(precision);
+ }
+
+ // Getters and Setters
+
+ /**
+ * @return whether or not the text will be automatically resized to fit its constraints.
+ */
+ public boolean isSizeToFit() {
+ return mSizeToFit;
+ }
+
+ /**
+ * Sets the property of this field (singleLine, to automatically resize the text to fit its constraints.
+ */
+ public void setSizeToFit() {
+ setSizeToFit(true);
+ }
+
+ /**
+ * If true, the text will automatically be resized to fit its constraints; if false, it will
+ * act like a normal TextView.
+ *
+ * @param sizeToFit
+ */
+ public void setSizeToFit(boolean sizeToFit) {
+ mSizeToFit = sizeToFit;
+ refitText();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public float getTextSize() {
+ return mMaxTextSize;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setTextSize(int unit, float size) {
+ Context context = getContext();
+ Resources r = Resources.getSystem();
+
+ if (context != null) {
+ r = context.getResources();
+ }
+
+ setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
+ }
+
+ private void setRawTextSize(float size) {
+ if (size != mMaxTextSize) {
+ mMaxTextSize = size;
+ refitText();
+ }
+ }
+
+ /**
+ * @return the minimum size (in pixels) of the text size in this AutofitTextView
+ */
+ public float getMinTextSize() {
+ return mMinTextSize;
+ }
+
+ /**
+ * Set the minimum text size to a given unit and value. See TypedValue for the possible
+ * dimension units.
+ *
+ * @param unit The desired dimension unit.
+ * @param minSize The desired size in the given units.
+ *
+ * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
+ */
+ public void setMinTextSize(int unit, float minSize) {
+ Context context = getContext();
+ Resources r = Resources.getSystem();
+
+ if (context != null) {
+ r = context.getResources();
+ }
+
+ setRawMinTextSize(TypedValue.applyDimension(unit, minSize, r.getDisplayMetrics()));
+ }
+
+ /**
+ * Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size
+ * is adjusted based on the current density and user font size preference.
+ *
+ * @param minSize The scaled pixel size.
+ *
+ * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
+ */
+ public void setMinTextSize(int minSize) {
+ setMinTextSize(TypedValue.COMPLEX_UNIT_SP, minSize);
+ }
+
+ private void setRawMinTextSize(float minSize) {
+ if (minSize != mMinTextSize) {
+ mMinTextSize = minSize;
+ refitText();
+ }
+ }
+
+ /**
+ * @return the amount of precision used to calculate the correct text size to fit within it's
+ * bounds.
+ */
+ public float getPrecision() {
+ return mPrecision;
+ }
+
+ /**
+ * Set the amount of precision used to calculate the correct text size to fit within it's
+ * bounds. Lower precision is more precise and takes more time.
+ *
+ * @param precision The amount of precision.
+ */
+ public void setPrecision(float precision) {
+ if (precision != mPrecision) {
+ mPrecision = precision;
+ refitText();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setLines(int lines) {
+ super.setLines(lines);
+ mMaxLines = lines;
+ refitText();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getMaxLines() {
+ return mMaxLines;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setMaxLines(int maxLines) {
+ super.setMaxLines(maxLines);
+ if (maxLines != mMaxLines) {
+ mMaxLines = maxLines;
+ refitText();
+ }
+ }
+
+ /**
+ * Re size the font so the specified text fits in the text box assuming the text box is the
+ * specified width.
+ */
+ private void refitText() {
+ if (!mSizeToFit) {
+ return;
+ }
+
+ if (mMaxLines <= 0) {
+ // Don't auto-size since there's no limit on lines.
+ return;
+ }
+
+ CharSequence text = getText();
+ TransformationMethod method = getTransformationMethod();
+ if (method != null) {
+ text = method.getTransformation(text, this);
+ }
+ int targetWidth = getWidth() - getPaddingLeft() - getPaddingRight();
+ if (targetWidth > 0) {
+ Context context = getContext();
+ Resources r = Resources.getSystem();
+ DisplayMetrics displayMetrics;
+
+ float size = mMaxTextSize;
+ float high = size;
+ float low = 0;
+
+ if (context != null) {
+ r = context.getResources();
+ }
+ displayMetrics = r.getDisplayMetrics();
+
+ mPaint.set(getPaint());
+ mPaint.setTextSize(size);
+
+ if ((mMaxLines == 1 && mPaint.measureText(text, 0, text.length()) > targetWidth)
+ || getLineCount(text, mPaint, size, targetWidth, displayMetrics) > mMaxLines) {
+ size = getTextSize(text, mPaint, targetWidth, mMaxLines, low, high, mPrecision,
+ displayMetrics);
+ }
+
+ if (size < mMinTextSize) {
+ size = mMinTextSize;
+ }
+
+ super.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+ }
+ }
+
+ /**
+ * Recursive binary search to find the best size for the text
+ */
+ private static float getTextSize(CharSequence text, TextPaint paint,
+ float targetWidth, int maxLines,
+ float low, float high, float precision,
+ DisplayMetrics displayMetrics) {
+ float mid = (low + high) / 2.0f;
+ int lineCount = 1;
+ StaticLayout layout = null;
+
+ paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid,
+ displayMetrics));
+
+ if (maxLines != 1) {
+ layout = new StaticLayout(text, paint, (int)targetWidth, Layout.Alignment.ALIGN_NORMAL,
+ 1.0f, 0.0f, true);
+ lineCount = layout.getLineCount();
+ }
+
+ if (SPEW) Log.d(TAG, "low=" + low + " high=" + high + " mid=" + mid +
+ " target=" + targetWidth + " maxLines=" + maxLines + " lineCount=" + lineCount);
+
+ if (lineCount > maxLines) {
+ return getTextSize(text, paint, targetWidth, maxLines, low, mid, precision,
+ displayMetrics);
+ }
+ else if (lineCount < maxLines) {
+ return getTextSize(text, paint, targetWidth, maxLines, mid, high, precision,
+ displayMetrics);
+ }
+ else {
+ float maxLineWidth = 0;
+ if (maxLines == 1) {
+ maxLineWidth = paint.measureText(text, 0, text.length());
+ } else {
+ for (int i = 0; i < lineCount; i++) {
+ if (layout.getLineWidth(i) > maxLineWidth) {
+ maxLineWidth = layout.getLineWidth(i);
+ }
+ }
+ }
+
+ if ((high - low) < precision) {
+ return low;
+ } else if (maxLineWidth > targetWidth) {
+ return getTextSize(text, paint, targetWidth, maxLines, low, mid, precision,
+ displayMetrics);
+ } else if (maxLineWidth < targetWidth) {
+ return getTextSize(text, paint, targetWidth, maxLines, mid, high, precision,
+ displayMetrics);
+ } else {
+ return mid;
+ }
+ }
+ }
+
+ private static int getLineCount(CharSequence text, TextPaint paint, float size, float width,
+ DisplayMetrics displayMetrics) {
+ paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size,
+ displayMetrics));
+ StaticLayout layout = new StaticLayout(text, paint, (int)width,
+ Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
+ return layout.getLineCount();
+ }
+
+ @Override
+ protected void onTextChanged(final CharSequence text, final int start,
+ final int lengthBefore, final int lengthAfter) {
+ super.onTextChanged(text, start, lengthBefore, lengthAfter);
+ refitText();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (w != oldw) {
+ refitText();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a368796..d0fb5c4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -42,11 +42,11 @@ public class BubbleTextView extends TextView {
private static SparseArray<Theme> sPreloaderThemes = new SparseArray<>(2);
- private static final float SHADOW_LARGE_RADIUS = 4.0f;
- private static final float SHADOW_SMALL_RADIUS = 1.75f;
- private static final float SHADOW_Y_OFFSET = 2.0f;
- private static final int SHADOW_LARGE_COLOUR = 0xDD000000;
- private static final int SHADOW_SMALL_COLOUR = 0xCC000000;
+ public static final float SHADOW_LARGE_RADIUS = 4.0f;
+ public static final float SHADOW_SMALL_RADIUS = 1.75f;
+ public static final float SHADOW_Y_OFFSET = 2.0f;
+ public static final int SHADOW_LARGE_COLOUR = 0xDD000000;
+ public static final int SHADOW_SMALL_COLOUR = 0xCC000000;
static final float PADDING_V = 3.0f;
private HolographicOutlineHelper mOutlineHelper;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8de95e9..d2ba9d2 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -70,6 +70,8 @@ import android.os.Message;
import android.os.StrictMode;
import android.os.SystemClock;
import android.speech.RecognizerIntent;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -297,6 +299,9 @@ public class Launcher extends Activity
OverviewSettingsPanel mOverviewSettingsPanel;
private View mAllAppsButton;
+ protected RecyclerView mAppDrawer;
+ private AppDrawerListAdapter mAppDrawerAdapter;
+ private AppDrawerScrubber mScrubber;
protected SearchDropTargetBar mSearchDropTargetBar;
private AppsCustomizeTabHost mAppsCustomizeTabHost;
@@ -596,6 +601,26 @@ public class Launcher extends Activity
protected void populateCustomContentContainer() {
}
+ private void initializeScrubber() {
+ if (mScrubber == null) {
+ mScrubber = new AppDrawerScrubber(this);
+ int scrubberPadding = getResources()
+ .getDimensionPixelSize(R.dimen.scrubber_bottom_padding);
+ mScrubber.setPadding(0, 0, 0, scrubberPadding);
+ mScrubber.setGravity(Gravity.BOTTOM);
+ mScrubber.setSource(mAppDrawer);
+ }
+ }
+
+ public void updateScrubber() {
+ mScrubber.updateSections();
+ }
+
+ public void initializeAdapter() {
+ mAppDrawerAdapter = new AppDrawerListAdapter(this);
+ mAppDrawerAdapter.notifyDataSetChanged();
+ }
+
/**
* Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
* ensure the custom content page is added or removed if necessary.
@@ -1644,6 +1669,9 @@ public class Launcher extends Activity
mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
mAppsCustomizeContent.setup(this, dragController);
+ // Setup AppDrawer
+ setupAppDrawer();
+
// Setup the drag controller (drop targets have to be added in reverse order in priority)
dragController.setDragScoller(mWorkspace);
dragController.setScrollView(mDragLayer);
@@ -1669,6 +1697,22 @@ public class Launcher extends Activity
}
}
+ private void setupAppDrawer() {
+ if (mAppDrawer == null) {
+ FrameLayout view = (FrameLayout) findViewById(R.id.app_drawer_container);
+ mAppDrawer = new RecyclerView(this);
+ mAppDrawer.setLayoutManager(new LinearLayoutManager(this));
+ if (mAppDrawerAdapter == null) {
+ initializeAdapter();
+ }
+ mAppDrawer.setHasFixedSize(true);
+ mAppDrawer.setAdapter(mAppDrawerAdapter);
+ view.addView(mAppDrawer);
+ initializeScrubber();
+ view.addView(mScrubber);
+ }
+ }
+
/**
* Sets the all apps button. This method is called from {@link Hotseat}.
*/
@@ -3205,7 +3249,7 @@ public class Launcher extends Activity
return false;
}
- boolean startActivitySafely(View v, Intent intent, Object tag) {
+ public boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
@@ -3594,7 +3638,13 @@ public class Launcher extends Activity
final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
final View fromView = mWorkspace;
- final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
+ final View toView;
+
+ if (contentType == AppsCustomizePagedView.ContentType.Applications) {
+ toView = (FrameLayout) findViewById(R.id.app_drawer_container);
+ } else {
+ toView = mAppsCustomizeTabHost;
+ }
final ArrayList<View> layerViews = new ArrayList<View>();
@@ -3616,20 +3666,18 @@ public class Launcher extends Activity
final AppsCustomizePagedView content = (AppsCustomizePagedView)
toView.findViewById(R.id.apps_customize_pane_content);
- final View page = content.getPageAt(content.getCurrentPage());
+ final View page = content != null ? content.getPageAt(content.getCurrentPage()) : null;
final View revealView = toView.findViewById(R.id.fake_page);
final float initialPanelAlpha = 1f;
final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets;
- if (isWidgetTray) {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
- } else {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
- }
+ revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
// Hide the real page background, and swap in the fake one
- content.setPageBackgroundsVisible(false);
+ if (content != null) {
+ content.setPageBackgroundsVisible(false);
+ }
revealView.setVisibility(View.VISIBLE);
// We need to hide this view as the animation start will be posted.
revealView.setAlpha(0);
@@ -3695,11 +3743,13 @@ public class Launcher extends Activity
}
View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator);
- pageIndicators.setAlpha(0.01f);
- ObjectAnimator indicatorsAlpha =
- ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f);
- indicatorsAlpha.setDuration(revealDuration);
- mStateAnimation.play(indicatorsAlpha);
+ if (pageIndicators != null) {
+ pageIndicators.setAlpha(0.01f);
+ ObjectAnimator indicatorsAlpha =
+ ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f);
+ indicatorsAlpha.setDuration(revealDuration);
+ mStateAnimation.play(indicatorsAlpha);
+ }
if (material) {
final View allApps = getAllAppsButton();
@@ -3737,7 +3787,9 @@ public class Launcher extends Activity
if (page != null) {
page.setLayerType(View.LAYER_TYPE_NONE, null);
}
- content.setPageBackgroundsVisible(true);
+ if (content != null) {
+ content.setPageBackgroundsVisible(true);
+ }
// Hide the search bar
if (mSearchDropTargetBar != null) {
@@ -3830,7 +3882,14 @@ public class Launcher extends Activity
final float scaleFactor = (float)
res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
- final View fromView = mAppsCustomizeTabHost;
+ final View fromView;
+
+ if (mAppsCustomizeContent.getContentType() != AppsCustomizePagedView.ContentType.Widgets) {
+ fromView = (FrameLayout) findViewById(R.id.app_drawer_container);
+ } else {
+ fromView = mAppsCustomizeTabHost;
+ }
+
final View toView = mWorkspace;
Animator workspaceAnim = null;
final ArrayList<View> layerViews = new ArrayList<View>();
@@ -3856,10 +3915,10 @@ public class Launcher extends Activity
final AppsCustomizePagedView content = (AppsCustomizePagedView)
fromView.findViewById(R.id.apps_customize_pane_content);
- final View page = content.getPageAt(content.getNextPage());
+ final View page = content != null ? content.getPageAt(content.getNextPage()) : null;
// We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases
- int count = content.getChildCount();
+ int count = content != null ? content.getChildCount() : 0;
for (int i = 0; i < count; i++) {
View child = content.getChildAt(i);
if (child != page) {
@@ -3872,15 +3931,12 @@ public class Launcher extends Activity
// don't perform all these no-op animations. In particularly, this was causing
// the all-apps button to pop in and out.
if (fromView.getVisibility() == View.VISIBLE) {
- AppsCustomizePagedView.ContentType contentType = content.getContentType();
+ AppsCustomizePagedView.ContentType contentType =
+ mAppsCustomizeContent.getContentType();
final boolean isWidgetTray =
contentType == AppsCustomizePagedView.ContentType.Widgets;
- if (isWidgetTray) {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
- } else {
- revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
- }
+ revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
int width = revealView.getMeasuredWidth();
int height = revealView.getMeasuredHeight();
@@ -3888,7 +3944,9 @@ public class Launcher extends Activity
// Hide the real page background, and swap in the fake one
revealView.setVisibility(View.VISIBLE);
- content.setPageBackgroundsVisible(false);
+ if (content != null) {
+ content.setPageBackgroundsVisible(false);
+ }
final View allAppsButton = getAllAppsButton();
revealView.setTranslationY(0);
@@ -3957,12 +4015,14 @@ public class Launcher extends Activity
}
View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator);
- pageIndicators.setAlpha(1f);
- ObjectAnimator indicatorsAlpha =
- LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f);
- indicatorsAlpha.setDuration(revealDuration);
- indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f));
- mStateAnimation.play(indicatorsAlpha);
+ if (pageIndicators != null) {
+ pageIndicators.setAlpha(1f);
+ ObjectAnimator indicatorsAlpha =
+ LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f);
+ indicatorsAlpha.setDuration(revealDuration);
+ indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f));
+ mStateAnimation.play(indicatorsAlpha);
+ }
width = revealView.getMeasuredWidth();
@@ -4011,9 +4071,11 @@ public class Launcher extends Activity
if (page != null) {
page.setLayerType(View.LAYER_TYPE_NONE, null);
}
- content.setPageBackgroundsVisible(true);
+ if (content != null) {
+ content.setPageBackgroundsVisible(true);
+ }
// Unhide side pages
- int count = content.getChildCount();
+ int count = content != null ? content.getChildCount() : 0;
for (int i = 0; i < count; i++) {
View child = content.getChildAt(i);
child.setVisibility(View.VISIBLE);
@@ -4025,7 +4087,9 @@ public class Launcher extends Activity
page.setTranslationY(0);
page.setAlpha(1);
}
- content.setCurrentPage(content.getNextPage());
+ if (content != null) {
+ content.setCurrentPage(content.getNextPage());
+ }
mAppsCustomizeContent.updateCurrentPageScroll();
}
@@ -4676,6 +4740,7 @@ public class Launcher extends Activity
if (!LauncherAppState.isDisableAllApps() &&
addedApps != null && mAppsCustomizeContent != null) {
mAppsCustomizeContent.addApps(addedApps);
+ mAppDrawerAdapter.addApps(addedApps);
}
}
@@ -5059,6 +5124,9 @@ public class Launcher extends Activity
LauncherModel.getSortedWidgetsAndShortcuts(this));
}
} else {
+ if (mAppDrawerAdapter != null) {
+ mAppDrawerAdapter.setApps(apps);
+ }
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.setApps(apps);
mAppsCustomizeContent.onPackagesUpdated(
@@ -5089,6 +5157,7 @@ public class Launcher extends Activity
if (!LauncherAppState.isDisableAllApps() &&
mAppsCustomizeContent != null) {
mAppsCustomizeContent.updateApps(apps);
+ mAppDrawerAdapter.updateApps(apps);
}
}
@@ -5168,6 +5237,7 @@ public class Launcher extends Activity
if (!LauncherAppState.isDisableAllApps() &&
mAppsCustomizeContent != null) {
mAppsCustomizeContent.removeApps(appInfos);
+ mAppDrawerAdapter.removeApps(appInfos);
}
}
@@ -5425,16 +5495,24 @@ public class Launcher extends Activity
if (mWorkspace != null) mWorkspace.setAlpha(1f);
if (mHotseat != null) mHotseat.setAlpha(1f);
if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
- if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
+ showSearch();
}
void hideWorkspaceSearchAndHotseat() {
if (mWorkspace != null) mWorkspace.setAlpha(0f);
if (mHotseat != null) mHotseat.setAlpha(0f);
if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
+ hideSearch();
+ }
+
+ void hideSearch() {
if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
}
+ void showSearch() {
+ if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
+ }
+
public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
// Called from search suggestion, not supported in other profiles.
final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
new file mode 100644
index 0000000..660251e
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+/**
+ * An icon on a PagedView, specifically for items in the launcher's paged view (with compound
+ * drawables on the top).
+ */
+public class PagedViewIcon extends TextView {
+ /** A simple callback interface to allow a PagedViewIcon to notify when it has been pressed */
+ public static interface PressedCallback {
+ void iconPressed(PagedViewIcon icon);
+ }
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "PagedViewIcon";
+ static final float PRESS_ALPHA = 0.4f;
+
+ private PagedViewIcon.PressedCallback mPressedCallback;
+ private boolean mLockDrawableState = false;
+
+ private Bitmap mIcon;
+
+ public PagedViewIcon(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewIcon(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void onFinishInflate() {
+ super.onFinishInflate();
+
+ // Ensure we are using the right text size
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
+ }
+
+ public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
+ PagedViewIcon.PressedCallback cb) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ mIcon = info.iconBitmap;
+ mPressedCallback = cb;
+ Drawable icon = Utilities.createIconDrawable(mIcon);
+ icon.setBounds(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx);
+ setCompoundDrawables(null, icon, null, null);
+ setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
+ setText(info.title);
+ setTag(info);
+ }
+
+ public void lockDrawableState() {
+ mLockDrawableState = true;
+ }
+
+ public void resetDrawableState() {
+ mLockDrawableState = false;
+ post(new Runnable() {
+ @Override
+ public void run() {
+ refreshDrawableState();
+ }
+ });
+ }
+
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ // We keep in the pressed state until resetDrawableState() is called to reset the press
+ // feedback
+ if (isPressed()) {
+ setAlpha(PRESS_ALPHA);
+ if (mPressedCallback != null) {
+ mPressedCallback.iconPressed(this);
+ }
+ } else if (!mLockDrawableState) {
+ setAlpha(1f);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // If text is transparent, don't draw any shadow
+ if (getCurrentTextColor() == getResources().getColor(android.R.color.transparent)) {
+ getPaint().clearShadowLayer();
+ super.draw(canvas);
+ return;
+ }
+
+ // We enhance the shadow by drawing the shadow twice
+ getPaint().setShadowLayer(BubbleTextView.SHADOW_LARGE_RADIUS, 0.0f,
+ BubbleTextView.SHADOW_Y_OFFSET, BubbleTextView.SHADOW_LARGE_COLOUR);
+ super.draw(canvas);
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
+ getScrollX() + getWidth(),
+ getScrollY() + getHeight(), Region.Op.INTERSECT);
+ getPaint().setShadowLayer(BubbleTextView.SHADOW_SMALL_RADIUS, 0.0f, 0.0f,
+ BubbleTextView.SHADOW_SMALL_COLOUR);
+ super.draw(canvas);
+ canvas.restore();
+ }
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index addd74c..fcd4b85 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -41,6 +41,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.os.Build;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -558,4 +559,11 @@ public final class Utilities {
}
return null;
}
+
+ public static float convertDpToPixel(float dp, Context context){
+ Resources resources = context.getResources();
+ DisplayMetrics metrics = resources.getDisplayMetrics();
+ float px = dp * (metrics.densityDpi / (float) DisplayMetrics.DENSITY_DEFAULT);
+ return px;
+ }
}