Merge remote-tracking branch 'origin/kitkat-dev'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f830c66
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+db_files
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..3bd20a7
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2013 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+#
+# Build app code.
+#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-renderscript-files-under, src) \
+ $(call all-proto-files-under, protos)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+
+LOCAL_SDK_VERSION := 19
+
+LOCAL_PACKAGE_NAME := Launcher3
+#LOCAL_CERTIFICATE := shared
+
+LOCAL_OVERRIDES_PACKAGES := Launcher2
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+#
+# Protocol Buffer Debug Utility in Java
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, util) \
+ $(call all-proto-files-under, protos)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := protoutil
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..901b638
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3">
+
+ <permission
+ android:name="com.android.launcher3.permission.PRELOAD_WORKSPACE"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="system|signature" />
+ <permission
+ android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_install_shortcut"
+ android:description="@string/permdesc_install_shortcut" />
+ <permission
+ android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_uninstall_shortcut"
+ android:description="@string/permdesc_uninstall_shortcut"/>
+ <permission
+ android:name="com.android.launcher3.permission.READ_SETTINGS"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_read_settings"
+ android:description="@string/permdesc_read_settings"/>
+ <permission
+ android:name="com.android.launcher3.permission.WRITE_SETTINGS"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_write_settings"
+ android:description="@string/permdesc_write_settings"/>
+
+ <permission
+ android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS"
+ android:protectionLevel="signature"
+ />
+
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
+ <uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.BIND_APPWIDGET" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
+ <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="com.android.launcher3.permission.READ_SETTINGS" />
+ <uses-permission android:name="com.android.launcher3.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS" />
+
+ <application
+ android:name="com.android.launcher3.LauncherApplication"
+ android:label="@string/application_name"
+ android:icon="@mipmap/ic_launcher_home"
+ android:hardwareAccelerated="true"
+ android:largeHeap="@bool/config_largeHeap"
+ android:supportsRtl="true">
+ <activity
+ android:name="com.android.launcher3.Launcher"
+ android:launchMode="singleTask"
+ android:clearTaskOnLaunch="true"
+ android:stateNotNeeded="true"
+ android:theme="@style/Theme"
+ android:windowSoftInputMode="adjustPan"
+ android:screenOrientation="nosensor">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.HOME" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.MONKEY"/>
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="com.android.launcher3.ToggleWeightWatcher"
+ android:label="@string/toggle_weight_watcher"
+ android:enabled="@bool/debug_memory_enabled"
+ android:icon="@mipmap/ic_launcher_home">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="com.android.launcher3.WallpaperPickerActivity"
+ android:theme="@style/Theme.WallpaperCropper"
+ android:label="@string/pick_wallpaper"
+ android:icon="@mipmap/ic_launcher_wallpaper"
+ android:finishOnCloseSystemDialogs="true"
+ android:process=":wallpaper_chooser">
+ <intent-filter>
+ <action android:name="android.intent.action.SET_WALLPAPER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="com.android.launcher3.WallpaperCropActivity"
+ android:theme="@style/Theme.WallpaperCropper"
+ android:label="@string/crop_wallpaper"
+ android:icon="@mipmap/ic_launcher_wallpaper"
+ android:finishOnCloseSystemDialogs="true"
+ android:process=":wallpaper_chooser">
+ <intent-filter>
+ <action android:name="android.service.wallpaper.CROP_AND_SET_WALLPAPER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ </intent-filter>
+ </activity>
+
+ <!-- Debugging tools -->
+ <activity
+ android:name="com.android.launcher3.MemoryDumpActivity"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:label="@string/debug_memory_activity"
+ android:enabled="@bool/debug_memory_enabled"
+ android:excludeFromRecents="true"
+ android:icon="@mipmap/ic_launcher_home"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <service android:name="com.android.launcher3.MemoryTracker"
+ android:enabled="@bool/debug_memory_enabled"
+ >
+ </service>
+
+ <!-- Intent received used to prepopulate the default workspace. -->
+ <receiver
+ android:name="com.android.launcher3.PreloadReceiver"
+ android:permission="com.android.launcher3.permission.PRELOAD_WORKSPACE">
+ <intent-filter>
+ <action android:name="com.android.launcher3.action.PRELOAD_WORKSPACE" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Intent received used to install shortcuts from other applications -->
+ <receiver
+ android:name="com.android.launcher3.InstallShortcutReceiver"
+ android:permission="com.android.launcher3.permission.INSTALL_SHORTCUT">
+ <intent-filter>
+ <action android:name="com.android.launcher3.action.INSTALL_SHORTCUT" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Intent received used to uninstall shortcuts from other applications -->
+ <receiver
+ android:name="com.android.launcher3.UninstallShortcutReceiver"
+ android:permission="com.android.launcher3.permission.UNINSTALL_SHORTCUT">
+ <intent-filter>
+ <action android:name="com.android.launcher3.action.UNINSTALL_SHORTCUT" />
+ </intent-filter>
+ </receiver>
+
+ <!-- New user initialization; set up initial wallpaper -->
+ <receiver
+ android:name="com.android.launcher3.UserInitializeReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.USER_INITIALIZE" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.launcher3.PackageChangedReceiver" >
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_CHANGED"/>
+ <action android:name="android.intent.action.PACKAGE_REPLACED"/>
+ <action android:name="android.intent.action.PACKAGE_REMOVED"/>
+ <data android:scheme="package"></data>
+ </intent-filter>
+ </receiver>
+
+ <!-- The settings provider contains Home's data, like the workspace favorites -->
+ <provider
+ android:name="com.android.launcher3.LauncherProvider"
+ android:authorities="com.android.launcher3.settings"
+ android:exported="true"
+ android:writePermission="com.android.launcher3.permission.WRITE_SETTINGS"
+ android:readPermission="com.android.launcher3.permission.READ_SETTINGS" />
+
+ <meta-data android:name="android.nfc.disable_beam_default"
+ android:value="true" />
+ </application>
+</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..c132395
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,56 @@
+# Copyright (C) 2007 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/fill_screens.py b/fill_screens.py
new file mode 100755
index 0000000..a887792
--- /dev/null
+++ b/fill_screens.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python2.5
+
+import cgi
+import os
+import shutil
+import sys
+import sqlite3
+
+SCREENS = 5
+COLUMNS = 4
+ROWS = 4
+CELL_SIZE = 110
+
+DIR = "db_files"
+AUTO_FILE = "launcher.db"
+
+APPLICATION_COMPONENTS = [
+ "com.android.calculator2/com.android.calculator2.Calculator",
+ "com.android.providers.downloads.ui/com.android.providers.downloads.ui.DownloadList",
+ "com.android.settings/com.android.settings.Settings",
+ "com.android.mms/com.android.mms.ui.ConversationList",
+ "com.android.contacts/com.android.contacts.activities.PeopleActivity",
+ "com.android.dialer/com.android.dialer.DialtactsActivity"
+]
+
+def usage():
+ print "usage: fill_screens.py -- fills up the launcher db"
+
+
+def make_dir():
+ shutil.rmtree(DIR, True)
+ os.makedirs(DIR)
+
+def pull_file(fn):
+ print "pull_file: " + fn
+ rv = os.system("adb pull"
+ + " /data/data/com.android.launcher/databases/launcher.db"
+ + " " + fn);
+ if rv != 0:
+ print "adb pull failed"
+ sys.exit(1)
+
+def push_file(fn):
+ print "push_file: " + fn
+ rv = os.system("adb push"
+ + " " + fn
+ + " /data/data/com.android.launcher/databases/launcher.db")
+ if rv != 0:
+ print "adb push failed"
+ sys.exit(1)
+
+def process_file(fn):
+ print "process_file: " + fn
+ conn = sqlite3.connect(fn)
+ c = conn.cursor()
+ c.execute("DELETE FROM favorites")
+
+ intentFormat = "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=%s;end"
+
+ id = 0;
+ for s in range(SCREENS):
+ for x in range(ROWS):
+ for y in range(COLUMNS):
+ id += 1
+ insert = "INSERT into favorites (_id, title, intent, container, screen, cellX, cellY, spanX, spanY, itemType, appWidgetId, iconType) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, %d, %d, %d, %d)"
+ insert = insert % (id, "title", "", -100, s, x, y, 1, 1, 2, -1, 0)
+ c.execute(insert)
+ folder_id = id
+
+ for z in range(15):
+ id += 1
+ intent = intentFormat % (APPLICATION_COMPONENTS[id % len(APPLICATION_COMPONENTS)])
+ insert = "INSERT into favorites (_id, title, intent, container, screen, cellX, cellY, spanX, spanY, itemType, appWidgetId, iconType) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, %d, %d, %d, %d)"
+ insert = insert % (id, "title", intent, folder_id, 0, 0, 0, 1, 1, 0, -1, 0)
+ c.execute(insert)
+
+ conn.commit()
+ c.close()
+
+def main(argv):
+ if len(argv) == 1:
+ make_dir()
+ pull_file(AUTO_FILE)
+ process_file(AUTO_FILE)
+ push_file(AUTO_FILE)
+ else:
+ usage()
+
+if __name__=="__main__":
+ main(sys.argv)
diff --git a/print_db.py b/print_db.py
new file mode 100755
index 0000000..05237d0
--- /dev/null
+++ b/print_db.py
@@ -0,0 +1,316 @@
+#!/usr/bin/env python2.5
+
+import cgi
+import codecs
+import os
+import pprint
+import shutil
+import sys
+import sqlite3
+
+SCREENS = 0
+COLUMNS = 4
+ROWS = 4
+HOTSEAT_SIZE = 4
+CELL_SIZE = 110
+
+CONTAINER_DESKTOP = -100
+CONTAINER_HOTSEAT = -101
+
+DIR = "db_files"
+AUTO_FILE = DIR + "/launcher.db"
+INDEX_FILE = DIR + "/index.html"
+
+def usage():
+ print "usage: print_db.py launcher.db <sw600|sw720> -- prints a launcher.db"
+ print "usage: print_db.py <sw600|sw720> -- adb pulls a launcher.db from a device"
+ print " and prints it"
+ print
+ print "The dump will be created in a directory called db_files in cwd."
+ print "This script will delete any db_files directory you have now"
+
+
+def make_dir():
+ shutil.rmtree(DIR, True)
+ os.makedirs(DIR)
+
+def adb_root_remount():
+ os.system("adb root")
+ os.system("adb remount")
+
+def pull_file(fn):
+ print "pull_file: " + fn
+ rv = os.system("adb pull"
+ + " /data/data/com.google.android.googlequicksearchbox/databases/launcher.db"
+ + " " + fn);
+ if rv != 0:
+ print "adb pull failed"
+ sys.exit(1)
+
+def get_favorites(conn):
+ c = conn.cursor()
+ c.execute("SELECT * FROM favorites")
+ columns = [d[0] for d in c.description]
+ rows = []
+ for row in c:
+ rows.append(row)
+ return columns,rows
+
+def get_screens(conn):
+ c = conn.cursor()
+ c.execute("SELECT * FROM workspaceScreens")
+ columns = [d[0] for d in c.description]
+ rows = []
+ for row in c:
+ rows.append(row)
+ return columns,rows
+
+def print_intent(out, id, i, cell):
+ if cell:
+ out.write("""<span class="intent" title="%s">shortcut</span>""" % (
+ cgi.escape(cell, True)
+ ))
+
+
+def print_icon(out, id, i, cell):
+ if cell:
+ icon_fn = "icon_%d.png" % id
+ out.write("""<img style="width: 3em; height: 3em;" src="%s">""" % ( icon_fn ))
+ f = file(DIR + "/" + icon_fn, "w")
+ f.write(cell)
+ f.close()
+
+def print_icon_type(out, id, i, cell):
+ if cell == 0:
+ out.write("Application (%d)" % cell)
+ elif cell == 1:
+ out.write("Shortcut (%d)" % cell)
+ elif cell == 2:
+ out.write("Folder (%d)" % cell)
+ elif cell == 4:
+ out.write("Widget (%d)" % cell)
+ elif cell:
+ out.write("%d" % cell)
+
+def print_cell(out, id, i, cell):
+ if not cell is None:
+ out.write(cgi.escape(unicode(cell)))
+
+FUNCTIONS = {
+ "intent": print_intent,
+ "icon": print_icon,
+ "iconType": print_icon_type
+}
+
+def render_cell_info(out, cell, occupied):
+ if cell is None:
+ out.write(" <td width=%d height=%d></td>\n" %
+ (CELL_SIZE, CELL_SIZE))
+ elif cell == occupied:
+ pass
+ else:
+ cellX = cell["cellX"]
+ cellY = cell["cellY"]
+ spanX = cell["spanX"]
+ spanY = cell["spanY"]
+ intent = cell["intent"]
+ if intent:
+ title = "title=\"%s\"" % cgi.escape(cell["intent"], True)
+ else:
+ title = ""
+ out.write((" <td colspan=%d rowspan=%d width=%d height=%d"
+ + " bgcolor=#dddddd align=center valign=middle %s>") % (
+ spanX, spanY,
+ (CELL_SIZE*spanX), (CELL_SIZE*spanY),
+ title))
+ itemType = cell["itemType"]
+ if itemType == 0:
+ out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] ))
+ out.write("<br/>\n")
+ out.write(cgi.escape(cell["title"]) + " <br/><i>(app)</i>")
+ elif itemType == 1:
+ out.write("""<img style="width: 4em; height: 4em;" src="icon_%d.png">\n""" % ( cell["_id"] ))
+ out.write("<br/>\n")
+ out.write(cgi.escape(cell["title"]) + " <br/><i>(shortcut)</i>")
+ elif itemType == 2:
+ out.write("""<i>folder</i>""")
+ elif itemType == 4:
+ out.write("<i>widget %d</i><br/>\n" % cell["appWidgetId"])
+ else:
+ out.write("<b>unknown type: %d</b>" % itemType)
+ out.write("</td>\n")
+
+def render_screen_info(out, screen):
+ out.write("<tr>")
+ out.write("<td>%s</td>" % (screen["_id"]))
+ out.write("<td>%s</td>" % (screen["screenRank"]))
+ out.write("</tr>")
+
+def process_file(fn):
+ global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE
+ print "process_file: " + fn
+ conn = sqlite3.connect(fn)
+ columns,rows = get_favorites(conn)
+ screenCols, screenRows = get_screens(conn)
+
+ data = [dict(zip(columns,row)) for row in rows]
+ screenData = [dict(zip(screenCols, screenRow)) for screenRow in screenRows]
+
+ # Calculate the proper number of screens, columns, and rows in this db
+ screensIdMap = []
+ hotseatIdMap = []
+ HOTSEAT_SIZE = 0
+ for d in data:
+ if d["spanX"] is None:
+ d["spanX"] = 1
+ if d["spanY"] is None:
+ d["spanY"] = 1
+ if d["container"] == CONTAINER_DESKTOP:
+ if d["screen"] not in screensIdMap:
+ screensIdMap.append(d["screen"])
+ COLUMNS = max(COLUMNS, d["cellX"] + d["spanX"])
+ ROWS = max(ROWS, d["cellX"] + d["spanX"])
+ elif d["container"] == CONTAINER_HOTSEAT:
+ hotseatIdMap.append(d["screen"])
+ HOTSEAT_SIZE = max(HOTSEAT_SIZE, d["screen"] + 1)
+ SCREENS = len(screensIdMap)
+
+ out = codecs.open(INDEX_FILE, encoding="utf-8", mode="w")
+ out.write("""<html>
+<head>
+<style type="text/css">
+.intent {
+ font-style: italic;
+}
+</style>
+</head>
+<body>
+""")
+
+ # Data table
+ out.write("<b>Favorites table</b><br/>\n")
+ out.write("""<html>
+<table border=1 cellspacing=0 cellpadding=4>
+<tr>
+""")
+ print_functions = []
+ for col in columns:
+ print_functions.append(FUNCTIONS.get(col, print_cell))
+ for i in range(0,len(columns)):
+ col = columns[i]
+ out.write(""" <th>%s</th>
+""" % ( col ))
+ out.write("""
+</tr>
+""")
+
+ for row in rows:
+ out.write("""<tr>
+""")
+ for i in range(0,len(row)):
+ cell = row[i]
+ # row[0] is always _id
+ out.write(""" <td>""")
+ print_functions[i](out, row[0], row, cell)
+ out.write("""</td>
+""")
+ out.write("""</tr>
+""")
+ out.write("""</table>
+""")
+
+ # Screens
+ out.write("<br/><b>Screens</b><br/>\n")
+ out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
+ out.write("<tr><td>Screen ID</td><td>Rank</td></tr>\n")
+ for screen in screenData:
+ render_screen_info(out, screen)
+ out.write("</table>\n")
+
+ # Hotseat
+ hotseat = []
+ for i in range(0, HOTSEAT_SIZE):
+ hotseat.append(None)
+ for row in data:
+ if row["container"] != CONTAINER_HOTSEAT:
+ continue
+ screen = row["screen"]
+ hotseat[screen] = row
+ out.write("<br/><b>Hotseat</b><br/>\n")
+ out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
+ for cell in hotseat:
+ render_cell_info(out, cell, None)
+ out.write("</table>\n")
+
+ # Pages
+ screens = []
+ for i in range(0,SCREENS):
+ screen = []
+ for j in range(0,ROWS):
+ m = []
+ for k in range(0,COLUMNS):
+ m.append(None)
+ screen.append(m)
+ screens.append(screen)
+ occupied = "occupied"
+ for row in data:
+ # desktop
+ if row["container"] != CONTAINER_DESKTOP:
+ continue
+ screen = screens[screensIdMap.index(row["screen"])]
+ cellX = row["cellX"]
+ cellY = row["cellY"]
+ spanX = row["spanX"]
+ spanY = row["spanY"]
+ for j in range(cellY, cellY+spanY):
+ for k in range(cellX, cellX+spanX):
+ screen[j][k] = occupied
+ screen[cellY][cellX] = row
+ i=0
+ for screen in screens:
+ out.write("<br/><b>Screen %d</b><br/>\n" % i)
+ out.write("<table class=layout border=1 cellspacing=0 cellpadding=4>\n")
+ for m in screen:
+ out.write(" <tr>\n")
+ for cell in m:
+ render_cell_info(out, cell, occupied)
+ out.write("</tr>\n")
+ out.write("</table>\n")
+ i=i+1
+
+ out.write("""
+</body>
+</html>
+""")
+
+ out.close()
+
+def updateDeviceClassConstants(str):
+ global SCREENS, COLUMNS, ROWS, HOTSEAT_SIZE
+ devClass = str.lower()
+ if devClass == "sw600":
+ COLUMNS = 6
+ ROWS = 6
+ HOTSEAT_SIZE = 6
+ return True
+ elif devClass == "sw720":
+ COLUMNS = 8
+ ROWS = 6
+ HOTSEAT_SIZE = 8
+ return True
+ return False
+
+def main(argv):
+ if len(argv) == 1 or (len(argv) == 2 and updateDeviceClassConstants(argv[1])):
+ make_dir()
+ adb_root_remount()
+ pull_file(AUTO_FILE)
+ process_file(AUTO_FILE)
+ elif len(argv) == 2 or (len(argv) == 3 and updateDeviceClassConstants(argv[2])):
+ make_dir()
+ process_file(argv[1])
+ else:
+ usage()
+
+if __name__=="__main__":
+ main(sys.argv)
diff --git a/proguard.flags b/proguard.flags
new file mode 100644
index 0000000..9b59b21
--- /dev/null
+++ b/proguard.flags
@@ -0,0 +1,51 @@
+-keep class com.android.launcher3.Launcher {
+ public void previousScreen(android.view.View);
+ public void nextScreen(android.view.View);
+ public void launchHotSeat(android.view.View);
+ public void onClickSearchButton(android.view.View);
+ public void onClickVoiceButton(android.view.View);
+ public void onClickConfigureButton(android.view.View);
+ public void onClickAllAppsButton(android.view.View);
+ public void onClickAppMarketButton(android.view.View);
+ public void dismissFirstRunCling(android.view.View);
+ public void dismissWorkspaceCling(android.view.View);
+ public void dismissAllAppsCling(android.view.View);
+}
+
+-keep class com.android.launcher3.CellLayout {
+ public float getBackgroundAlpha();
+ public void setBackgroundAlpha(float);
+}
+
+-keep class com.android.launcher3.DragLayer$LayoutParams {
+ public void setWidth(int);
+ public int getWidth();
+ public void setHeight(int);
+ public int getHeight();
+ public void setX(int);
+ public int getX();
+ public void setY(int);
+ public int getY();
+}
+
+-keep class com.android.launcher3.CellLayout$LayoutParams {
+ public void setWidth(int);
+ public int getWidth();
+ public void setHeight(int);
+ public int getHeight();
+ public void setX(int);
+ public int getX();
+ public void setY(int);
+ public int getY();
+}
+
+-keep class com.android.launcher3.Workspace {
+ public float getBackgroundAlpha();
+ public void setBackgroundAlpha(float);
+ public float getChildrenOutlineAlpha();
+ public void setChildrenOutlineAlpha(float);
+}
+
+-keep class com.android.launcher3.MemoryDumpActivity {
+ *;
+}
diff --git a/protos/backup.proto b/protos/backup.proto
new file mode 100644
index 0000000..7ba2937
--- /dev/null
+++ b/protos/backup.proto
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 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 launcher_backup;
+
+option java_package = "com.android.launcher3.backup";
+option java_outer_classname = "BackupProtos";
+
+message Key {
+ enum Type {
+ FAVORITE = 1;
+ SCREEN = 2;
+ ICON = 3;
+ WIDGET = 4;
+ }
+ required Type type = 1;
+ optional string name = 2; // keep this short
+ optional int64 id = 3;
+ optional int64 checksum = 4;
+}
+
+message CheckedMessage {
+ required bytes payload = 1;
+ required int64 checksum = 2;
+}
+
+message Journal {
+ required int32 app_version = 1;
+ required int64 t = 2;
+ optional int64 bytes = 3;
+ optional int32 rows = 4;
+ repeated Key key = 5;
+}
+
+message Favorite {
+ required int64 id = 1;
+ required int32 itemType = 2;
+ optional string title = 3;
+ optional int32 container = 4;
+ optional int32 screen = 5;
+ optional int32 cellX = 6;
+ optional int32 cellY = 7;
+ optional int32 spanX = 8;
+ optional int32 spanY = 9;
+ optional int32 displayMode = 10;
+ optional int32 appWidgetId = 11;
+ optional string appWidgetProvider = 12;
+ optional string intent = 13;
+ optional string uri = 14;
+ optional int32 iconType = 15;
+ optional string iconPackage = 16;
+ optional string iconResource = 17;
+ optional bytes icon = 18;
+ }
+
+message Screen {
+ required int64 id = 1;
+ optional int32 rank = 2;
+ }
+
+message Resource {
+ required int32 dpi = 1;
+ required bytes data = 2;
+ }
+
+message Widget {
+ required string provider = 1;
+ optional string label = 2;
+ optional bool configure = 3;
+ optional Resource icon = 4;
+ optional Resource preview = 5;
+ }
diff --git a/res/anim/fade_in_fast.xml b/res/anim/fade_in_fast.xml
new file mode 100644
index 0000000..4fa9847
--- /dev/null
+++ b/res/anim/fade_in_fast.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator"
+
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+
+ android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml
new file mode 100644
index 0000000..a061a6c
--- /dev/null
+++ b/res/anim/fade_out_fast.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/accelerate_interpolator"
+
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+
+ android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/drawable-hdpi/apps_customize_bg.png b/res/drawable-hdpi/apps_customize_bg.png
new file mode 100644
index 0000000..2847467
--- /dev/null
+++ b/res/drawable-hdpi/apps_customize_bg.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_appwidget_error.9.png b/res/drawable-hdpi/bg_appwidget_error.9.png
new file mode 100644
index 0000000..4da3195
--- /dev/null
+++ b/res/drawable-hdpi/bg_appwidget_error.9.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling1.png b/res/drawable-hdpi/bg_cling1.png
new file mode 100644
index 0000000..0e15532
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling2.png b/res/drawable-hdpi/bg_cling2.png
new file mode 100644
index 0000000..e65d9a2
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling3.png b/res/drawable-hdpi/bg_cling3.png
new file mode 100644
index 0000000..ea71fbd
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-hdpi/bg_cling4.png b/res/drawable-hdpi/bg_cling4.png
new file mode 100644
index 0000000..9403667
--- /dev/null
+++ b/res/drawable-hdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-hdpi/cling.9.png b/res/drawable-hdpi/cling.9.png
new file mode 100644
index 0000000..36fbfc8
--- /dev/null
+++ b/res/drawable-hdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_down.png b/res/drawable-hdpi/cling_arrow_down.png
new file mode 100644
index 0000000..4f521ea
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_left.png b/res/drawable-hdpi/cling_arrow_left.png
new file mode 100644
index 0000000..13764c9
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_right.png b/res/drawable-hdpi/cling_arrow_right.png
new file mode 100644
index 0000000..be52244
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_arrow_up.png b/res/drawable-hdpi/cling_arrow_up.png
new file mode 100644
index 0000000..83b5b37
--- /dev/null
+++ b/res/drawable-hdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_button.9.png b/res/drawable-hdpi/cling_button.9.png
new file mode 100644
index 0000000..e308382
--- /dev/null
+++ b/res/drawable-hdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-hdpi/cling_button_pressed.9.png b/res/drawable-hdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..4f9ca6f
--- /dev/null
+++ b/res/drawable-hdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/custom_content_page.png b/res/drawable-hdpi/custom_content_page.png
new file mode 100644
index 0000000..9eef50c
--- /dev/null
+++ b/res/drawable-hdpi/custom_content_page.png
Binary files differ
diff --git a/res/drawable-hdpi/focused_bg.9.png b/res/drawable-hdpi/focused_bg.9.png
new file mode 100644
index 0000000..2925ae8
--- /dev/null
+++ b/res/drawable-hdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hand.png b/res/drawable-hdpi/hand.png
new file mode 100644
index 0000000..bd4f6df
--- /dev/null
+++ b/res/drawable-hdpi/hand.png
Binary files differ
diff --git a/res/drawable-hdpi/home_press.9.png b/res/drawable-hdpi/home_press.9.png
new file mode 100644
index 0000000..ef07011
--- /dev/null
+++ b/res/drawable-hdpi/home_press.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_actionbar_accept.png b/res/drawable-hdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000..53cf687
--- /dev/null
+++ b/res/drawable-hdpi/ic_actionbar_accept.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps.png b/res/drawable-hdpi/ic_allapps.png
new file mode 100644
index 0000000..e7677d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed.png b/res/drawable-hdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..863eeba
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_home_all_apps_holo_dark.png b/res/drawable-hdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..1dc02d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_home_search_normal_holo.png b/res/drawable-hdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..3f64d68
--- /dev/null
+++ b/res/drawable-hdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_home_voice_search_holo.png b/res/drawable-hdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..dae5446
--- /dev/null
+++ b/res/drawable-hdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_images.png b/res/drawable-hdpi/ic_images.png
new file mode 100644
index 0000000..0003c6c
--- /dev/null
+++ b/res/drawable-hdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_clear_active_holo.png b/res/drawable-hdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..cdd0052
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_clear_normal_holo.png b/res/drawable-hdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..84549ff
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_info_active_holo.png b/res/drawable-hdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..c534e56
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_info_normal_holo.png b/res/drawable-hdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..c9bcd7f
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_market_holo.png b/res/drawable-hdpi/ic_launcher_market_holo.png
new file mode 100644
index 0000000..dc78251
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_market_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-hdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..82b1b59
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-hdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..3fc2e83
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_add.png b/res/drawable-hdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..c37d622
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_current.png b/res/drawable-hdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..aac8d40
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default.png b/res/drawable-hdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..bafd94b
--- /dev/null
+++ b/res/drawable-hdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting.png b/res/drawable-hdpi/ic_setting.png
new file mode 100644
index 0000000..c617154
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting_pressed.png b/res/drawable-hdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..fb58a4b
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper.png b/res/drawable-hdpi/ic_wallpaper.png
new file mode 100644
index 0000000..5e5d118
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed.png b/res/drawable-hdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..d104e57
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget.png b/res/drawable-hdpi/ic_widget.png
new file mode 100644
index 0000000..8c57af0
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_pressed.png b/res/drawable-hdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..081f9f9
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/overscroll_glow_left.9.png b/res/drawable-hdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..aaf43c7
--- /dev/null
+++ b/res/drawable-hdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-hdpi/overscroll_glow_right.9.png b/res/drawable-hdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..d034864
--- /dev/null
+++ b/res/drawable-hdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left_holo.9.png b/res/drawable-hdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..8a1aa5f
--- /dev/null
+++ b/res/drawable-hdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right_holo.9.png b/res/drawable-hdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..abf8f51
--- /dev/null
+++ b/res/drawable-hdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/paged_view_indicator.9.png b/res/drawable-hdpi/paged_view_indicator.9.png
new file mode 100644
index 0000000..5b47f44
--- /dev/null
+++ b/res/drawable-hdpi/paged_view_indicator.9.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_container_holo.9.png b/res/drawable-hdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..ba8a4a8
--- /dev/null
+++ b/res/drawable-hdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_inner_holo.png b/res/drawable-hdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..857a01e
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_inner_nolip_holo.png b/res/drawable-hdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..53df36a
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_outer_holo.png b/res/drawable-hdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..b711cf3
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/portal_ring_rest.png b/res/drawable-hdpi/portal_ring_rest.png
new file mode 100644
index 0000000..2979b73
--- /dev/null
+++ b/res/drawable-hdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel.9.png b/res/drawable-hdpi/screenpanel.9.png
new file mode 100644
index 0000000..36e7dfd
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_hover.9.png b/res/drawable-hdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..3321fc9
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-hdpi/search_frame.9.png b/res/drawable-hdpi/search_frame.9.png
new file mode 100644
index 0000000..15ca1f4
--- /dev/null
+++ b/res/drawable-hdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_focused_holo.9.png b/res/drawable-hdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..673e3bf
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_holo.9.png b/res/drawable-hdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..d57df98
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..4b312d9
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_pressed_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..6278eef
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..294991d
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_holo.9.png b/res/drawable-hdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..19532ab
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..5140b35
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_pressed_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..aadc6f8
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_picker_focused.9.png b/res/drawable-hdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..c72d6a2
--- /dev/null
+++ b/res/drawable-hdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_picker_pressed.9.png b/res/drawable-hdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..44c65ac
--- /dev/null
+++ b/res/drawable-hdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_picker_selected.9.png b/res/drawable-hdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..461bacb
--- /dev/null
+++ b/res/drawable-hdpi/tile_picker_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_shadow_bottom.9.png b/res/drawable-hdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..e80558b
--- /dev/null
+++ b/res/drawable-hdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tile_shadow_top.9.png b/res/drawable-hdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..7e93865
--- /dev/null
+++ b/res/drawable-hdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_container_holo.9.png b/res/drawable-hdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..8c15a7c
--- /dev/null
+++ b/res/drawable-hdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_frame_holo.9.png b/res/drawable-hdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..2d6fcf5
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_bottom.png b/res/drawable-hdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..f0afd61
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_left.png b/res/drawable-hdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..47613b2
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_right.png b/res/drawable-hdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..acc28be
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_resize_handle_top.png b/res/drawable-hdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..2c60be0
--- /dev/null
+++ b/res/drawable-hdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_tile.png b/res/drawable-hdpi/widget_tile.png
new file mode 100644
index 0000000..310ff8b
--- /dev/null
+++ b/res/drawable-hdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-hdpi/workspace_bg.9.png b/res/drawable-hdpi/workspace_bg.9.png
new file mode 100644
index 0000000..5bbfa4f
--- /dev/null
+++ b/res/drawable-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-hdpi/bg_cling1.png b/res/drawable-land-hdpi/bg_cling1.png
new file mode 100644
index 0000000..7123c5c
--- /dev/null
+++ b/res/drawable-land-hdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-hdpi/bg_cling2.png b/res/drawable-land-hdpi/bg_cling2.png
new file mode 100644
index 0000000..889b627
--- /dev/null
+++ b/res/drawable-land-hdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-hdpi/bg_cling3.png b/res/drawable-land-hdpi/bg_cling3.png
new file mode 100644
index 0000000..4ff338c
--- /dev/null
+++ b/res/drawable-land-hdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-hdpi/ic_home_voice_search_holo.png b/res/drawable-land-hdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..5a7fc99
--- /dev/null
+++ b/res/drawable-land-hdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-hdpi/workspace_bg.9.png b/res/drawable-land-hdpi/workspace_bg.9.png
new file mode 100644
index 0000000..1a58144
--- /dev/null
+++ b/res/drawable-land-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/bg_cling1.png b/res/drawable-land-mdpi/bg_cling1.png
new file mode 100644
index 0000000..f5faeb4
--- /dev/null
+++ b/res/drawable-land-mdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-mdpi/bg_cling2.png b/res/drawable-land-mdpi/bg_cling2.png
new file mode 100644
index 0000000..963967d
--- /dev/null
+++ b/res/drawable-land-mdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-mdpi/bg_cling3.png b/res/drawable-land-mdpi/bg_cling3.png
new file mode 100644
index 0000000..921831a
--- /dev/null
+++ b/res/drawable-land-mdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-mdpi/ic_home_voice_search_holo.png b/res/drawable-land-mdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..ee7dde5
--- /dev/null
+++ b/res/drawable-land-mdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-mdpi/workspace_bg.9.png b/res/drawable-land-mdpi/workspace_bg.9.png
new file mode 100644
index 0000000..a12519e
--- /dev/null
+++ b/res/drawable-land-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/bg_cling1.png b/res/drawable-land-xhdpi/bg_cling1.png
new file mode 100644
index 0000000..2282117
--- /dev/null
+++ b/res/drawable-land-xhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/bg_cling2.png b/res/drawable-land-xhdpi/bg_cling2.png
new file mode 100644
index 0000000..5243889
--- /dev/null
+++ b/res/drawable-land-xhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/bg_cling3.png b/res/drawable-land-xhdpi/bg_cling3.png
new file mode 100644
index 0000000..08475f7
--- /dev/null
+++ b/res/drawable-land-xhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/ic_home_voice_search_holo.png b/res/drawable-land-xhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..56bbbbb
--- /dev/null
+++ b/res/drawable-land-xhdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/workspace_bg.9.png b/res/drawable-land-xhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..ce41454
--- /dev/null
+++ b/res/drawable-land-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/bg_cling1.png b/res/drawable-land-xxhdpi/bg_cling1.png
new file mode 100644
index 0000000..78943f0
--- /dev/null
+++ b/res/drawable-land-xxhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/bg_cling2.png b/res/drawable-land-xxhdpi/bg_cling2.png
new file mode 100644
index 0000000..98b6568
--- /dev/null
+++ b/res/drawable-land-xxhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/bg_cling3.png b/res/drawable-land-xxhdpi/bg_cling3.png
new file mode 100644
index 0000000..e249fe5
--- /dev/null
+++ b/res/drawable-land-xxhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/ic_home_voice_search_holo.png b/res/drawable-land-xxhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..6ea7368
--- /dev/null
+++ b/res/drawable-land-xxhdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-land-xxhdpi/workspace_bg.9.png b/res/drawable-land-xxhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..b0b4561
--- /dev/null
+++ b/res/drawable-land-xxhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/apps_customize_bg.png b/res/drawable-mdpi/apps_customize_bg.png
new file mode 100644
index 0000000..2847467
--- /dev/null
+++ b/res/drawable-mdpi/apps_customize_bg.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_appwidget_error.9.png b/res/drawable-mdpi/bg_appwidget_error.9.png
new file mode 100644
index 0000000..493c0d4
--- /dev/null
+++ b/res/drawable-mdpi/bg_appwidget_error.9.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling1.png b/res/drawable-mdpi/bg_cling1.png
new file mode 100644
index 0000000..f284412
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling2.png b/res/drawable-mdpi/bg_cling2.png
new file mode 100644
index 0000000..0052dc2
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling3.png b/res/drawable-mdpi/bg_cling3.png
new file mode 100644
index 0000000..fabdf7a
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling4.png b/res/drawable-mdpi/bg_cling4.png
new file mode 100644
index 0000000..2f152f4d
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-mdpi/bg_cling5.png b/res/drawable-mdpi/bg_cling5.png
new file mode 100644
index 0000000..e094809
--- /dev/null
+++ b/res/drawable-mdpi/bg_cling5.png
Binary files differ
diff --git a/res/drawable-mdpi/cling.9.png b/res/drawable-mdpi/cling.9.png
new file mode 100644
index 0000000..4c0f139
--- /dev/null
+++ b/res/drawable-mdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_down.png b/res/drawable-mdpi/cling_arrow_down.png
new file mode 100644
index 0000000..58e66fb
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_left.png b/res/drawable-mdpi/cling_arrow_left.png
new file mode 100644
index 0000000..023c717
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_right.png b/res/drawable-mdpi/cling_arrow_right.png
new file mode 100644
index 0000000..cf0eb10
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_arrow_up.png b/res/drawable-mdpi/cling_arrow_up.png
new file mode 100644
index 0000000..9b0e6b7
--- /dev/null
+++ b/res/drawable-mdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_button.9.png b/res/drawable-mdpi/cling_button.9.png
new file mode 100644
index 0000000..a0b6f97
--- /dev/null
+++ b/res/drawable-mdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-mdpi/cling_button_pressed.9.png b/res/drawable-mdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..986e669
--- /dev/null
+++ b/res/drawable-mdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/custom_content_page.png b/res/drawable-mdpi/custom_content_page.png
new file mode 100644
index 0000000..cc4005d
--- /dev/null
+++ b/res/drawable-mdpi/custom_content_page.png
Binary files differ
diff --git a/res/drawable-mdpi/focused_bg.9.png b/res/drawable-mdpi/focused_bg.9.png
new file mode 100644
index 0000000..89c29ac
--- /dev/null
+++ b/res/drawable-mdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/hand.png b/res/drawable-mdpi/hand.png
new file mode 100644
index 0000000..fe5a035
--- /dev/null
+++ b/res/drawable-mdpi/hand.png
Binary files differ
diff --git a/res/drawable-mdpi/home_press.9.png b/res/drawable-mdpi/home_press.9.png
new file mode 100644
index 0000000..679a1f6
--- /dev/null
+++ b/res/drawable-mdpi/home_press.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_actionbar_accept.png b/res/drawable-mdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000..35cda8e
--- /dev/null
+++ b/res/drawable-mdpi/ic_actionbar_accept.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps.png b/res/drawable-mdpi/ic_allapps.png
new file mode 100644
index 0000000..e0fd9c0
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed.png b/res/drawable-mdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..3bd87b1
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_home_all_apps_holo_dark.png b/res/drawable-mdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..84fa594
--- /dev/null
+++ b/res/drawable-mdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_home_search_normal_holo.png b/res/drawable-mdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..7367c38
--- /dev/null
+++ b/res/drawable-mdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_home_voice_search_holo.png b/res/drawable-mdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..f211a7b
--- /dev/null
+++ b/res/drawable-mdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_images.png b/res/drawable-mdpi/ic_images.png
new file mode 100644
index 0000000..aabc123
--- /dev/null
+++ b/res/drawable-mdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_clear_active_holo.png b/res/drawable-mdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..2683bea
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_clear_normal_holo.png b/res/drawable-mdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..219f3e5
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_info_active_holo.png b/res/drawable-mdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..f84b4a6
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_info_normal_holo.png b/res/drawable-mdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..eac578f
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_market_holo.png b/res/drawable-mdpi/ic_launcher_market_holo.png
new file mode 100644
index 0000000..cacb374
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_market_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-mdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..0350e55
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-mdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..799b62f
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_add.png b/res/drawable-mdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..8e05e64
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current.png b/res/drawable-mdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..ab5f4c8
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default.png b/res/drawable-mdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..c919ee8
--- /dev/null
+++ b/res/drawable-mdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting.png b/res/drawable-mdpi/ic_setting.png
new file mode 100644
index 0000000..0c8ae9d
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_icn.png b/res/drawable-mdpi/ic_setting_icn.png
new file mode 100644
index 0000000..5c32c54
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_icn.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_pressed.png b/res/drawable-mdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..846091f
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper.png b/res/drawable-mdpi/ic_wallpaper.png
new file mode 100644
index 0000000..333a206
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed.png b/res/drawable-mdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..273c48b
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget.png b/res/drawable-mdpi/ic_widget.png
new file mode 100644
index 0000000..5f974c2
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_pressed.png b/res/drawable-mdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..0a3e883
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/overscroll_glow_left.9.png b/res/drawable-mdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..b79cdcd
--- /dev/null
+++ b/res/drawable-mdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-mdpi/overscroll_glow_right.9.png b/res/drawable-mdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..1321303
--- /dev/null
+++ b/res/drawable-mdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left_holo.9.png b/res/drawable-mdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..561d3cd
--- /dev/null
+++ b/res/drawable-mdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right_holo.9.png b/res/drawable-mdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..2681f23
--- /dev/null
+++ b/res/drawable-mdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/paged_view_indicator.9.png b/res/drawable-mdpi/paged_view_indicator.9.png
new file mode 100644
index 0000000..647b60f
--- /dev/null
+++ b/res/drawable-mdpi/paged_view_indicator.9.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_container_holo.9.png b/res/drawable-mdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..1e4afae
--- /dev/null
+++ b/res/drawable-mdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_inner_holo.png b/res/drawable-mdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..72e0af8
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_inner_nolip_holo.png b/res/drawable-mdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..483f0eb
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_outer_holo.png b/res/drawable-mdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..e9b35f3
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/portal_ring_rest.png b/res/drawable-mdpi/portal_ring_rest.png
new file mode 100644
index 0000000..d0a976e
--- /dev/null
+++ b/res/drawable-mdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel.9.png b/res/drawable-mdpi/screenpanel.9.png
new file mode 100644
index 0000000..4de3017
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_hover.9.png b/res/drawable-mdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..dd77406
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-mdpi/search_frame.9.png b/res/drawable-mdpi/search_frame.9.png
new file mode 100644
index 0000000..058905b
--- /dev/null
+++ b/res/drawable-mdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_focused_holo.9.png b/res/drawable-mdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..c9972e7
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_holo.9.png b/res/drawable-mdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..587337c
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..284f534
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_pressed_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..155c4fc
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..f0cecd1
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_holo.9.png b/res/drawable-mdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..a2dbf42
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..f1a2819
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_pressed_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..b1223fe
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_picker_focused.9.png b/res/drawable-mdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..13b325b
--- /dev/null
+++ b/res/drawable-mdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_picker_pressed.9.png b/res/drawable-mdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..4e8196d
--- /dev/null
+++ b/res/drawable-mdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_picker_selected.9.png b/res/drawable-mdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..eee69ec
--- /dev/null
+++ b/res/drawable-mdpi/tile_picker_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_shadow_bottom.9.png b/res/drawable-mdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..d95787b
--- /dev/null
+++ b/res/drawable-mdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tile_shadow_top.9.png b/res/drawable-mdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..8da913c
--- /dev/null
+++ b/res/drawable-mdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_container_holo.9.png b/res/drawable-mdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..db24457
--- /dev/null
+++ b/res/drawable-mdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_frame_holo.9.png b/res/drawable-mdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..028bd62
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_bottom.png b/res/drawable-mdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..c838bf4
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_left.png b/res/drawable-mdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..ff0b0d3
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_right.png b/res/drawable-mdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..fc4808e
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_resize_handle_top.png b/res/drawable-mdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..3b1df01
--- /dev/null
+++ b/res/drawable-mdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_tile.png b/res/drawable-mdpi/widget_tile.png
new file mode 100644
index 0000000..1ba559d
--- /dev/null
+++ b/res/drawable-mdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-mdpi/workspace_bg.9.png b/res/drawable-mdpi/workspace_bg.9.png
new file mode 100644
index 0000000..2856e09
--- /dev/null
+++ b/res/drawable-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..98f7ac8
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..0a511f9
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_allapps.png b/res/drawable-sw600dp-hdpi/ic_allapps.png
new file mode 100644
index 0000000..8bda435
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..07ff331
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..3928e2c
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..e34de34
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..b3be472
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..96b981c
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..bc13a26
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..3ae4aff
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..31148dd
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_allapps.png b/res/drawable-sw600dp-mdpi/ic_allapps.png
new file mode 100644
index 0000000..e2afea5
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..d409c7e
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..58ec10c
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..a986fd3
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..319c074
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..8537714
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..365dcfc
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..c25b590
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..24ac880
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_allapps.png b/res/drawable-sw600dp-xhdpi/ic_allapps.png
new file mode 100644
index 0000000..8fed290
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..786b676
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..b66dd2f
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..3ccce33
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..d4ce45f
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..9aa13c9
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..0106cd6
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png b/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png
new file mode 100644
index 0000000..c661f68
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png b/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png
new file mode 100644
index 0000000..bf6ab97
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_allapps.png b/res/drawable-sw600dp-xxhdpi/ic_allapps.png
new file mode 100644
index 0000000..2429656
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png b/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..b93a51b
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png b/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..472c3f9
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png b/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..e30cc97
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..65b2541
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..5068646
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png b/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..6628425
--- /dev/null
+++ b/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-sw720dp-hdpi/workspace_bg.9.png b/res/drawable-sw720dp-hdpi/workspace_bg.9.png
new file mode 100644
index 0000000..5bbfa4f
--- /dev/null
+++ b/res/drawable-sw720dp-hdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw720dp-mdpi/workspace_bg.9.png b/res/drawable-sw720dp-mdpi/workspace_bg.9.png
new file mode 100644
index 0000000..2856e09
--- /dev/null
+++ b/res/drawable-sw720dp-mdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-sw720dp-xhdpi/workspace_bg.9.png b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..72269f2
--- /dev/null
+++ b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/apps_customize_bg.png b/res/drawable-xhdpi/apps_customize_bg.png
new file mode 100644
index 0000000..a51cc11
--- /dev/null
+++ b/res/drawable-xhdpi/apps_customize_bg.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_appwidget_error.9.png b/res/drawable-xhdpi/bg_appwidget_error.9.png
new file mode 100644
index 0000000..b792cc8
--- /dev/null
+++ b/res/drawable-xhdpi/bg_appwidget_error.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling1.png b/res/drawable-xhdpi/bg_cling1.png
new file mode 100644
index 0000000..b71351a
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling2.png b/res/drawable-xhdpi/bg_cling2.png
new file mode 100644
index 0000000..ad78dfe
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling3.png b/res/drawable-xhdpi/bg_cling3.png
new file mode 100644
index 0000000..ae04195
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-xhdpi/bg_cling4.png b/res/drawable-xhdpi/bg_cling4.png
new file mode 100644
index 0000000..f4bb83e
--- /dev/null
+++ b/res/drawable-xhdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling.9.png b/res/drawable-xhdpi/cling.9.png
new file mode 100644
index 0000000..1cb4681
--- /dev/null
+++ b/res/drawable-xhdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_down.png b/res/drawable-xhdpi/cling_arrow_down.png
new file mode 100644
index 0000000..ee10933
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_left.png b/res/drawable-xhdpi/cling_arrow_left.png
new file mode 100644
index 0000000..cffbcf3
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_right.png b/res/drawable-xhdpi/cling_arrow_right.png
new file mode 100644
index 0000000..d880d67
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_arrow_up.png b/res/drawable-xhdpi/cling_arrow_up.png
new file mode 100644
index 0000000..fd2c60c
--- /dev/null
+++ b/res/drawable-xhdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_button.9.png b/res/drawable-xhdpi/cling_button.9.png
new file mode 100644
index 0000000..4192563
--- /dev/null
+++ b/res/drawable-xhdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/cling_button_pressed.9.png b/res/drawable-xhdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..d3ce469
--- /dev/null
+++ b/res/drawable-xhdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/custom_content_page.png b/res/drawable-xhdpi/custom_content_page.png
new file mode 100644
index 0000000..e1da91c
--- /dev/null
+++ b/res/drawable-xhdpi/custom_content_page.png
Binary files differ
diff --git a/res/drawable-xhdpi/focused_bg.9.png b/res/drawable-xhdpi/focused_bg.9.png
new file mode 100644
index 0000000..197a269
--- /dev/null
+++ b/res/drawable-xhdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/hand.png b/res/drawable-xhdpi/hand.png
new file mode 100644
index 0000000..35b678c
--- /dev/null
+++ b/res/drawable-xhdpi/hand.png
Binary files differ
diff --git a/res/drawable-xhdpi/home_press.9.png b/res/drawable-xhdpi/home_press.9.png
new file mode 100644
index 0000000..d9abfd3
--- /dev/null
+++ b/res/drawable-xhdpi/home_press.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_actionbar_accept.png b/res/drawable-xhdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000..b52dc37
--- /dev/null
+++ b/res/drawable-xhdpi/ic_actionbar_accept.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps.png b/res/drawable-xhdpi/ic_allapps.png
new file mode 100644
index 0000000..f71964c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed.png b/res/drawable-xhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..d678f02
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_home_all_apps_holo_dark.png b/res/drawable-xhdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..81228d4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_home_search_normal_holo.png b/res/drawable-xhdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..0fe1cd1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_home_voice_search_holo.png b/res/drawable-xhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..1fc5cc8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_images.png b/res/drawable-xhdpi/ic_images.png
new file mode 100644
index 0000000..3f21faf
--- /dev/null
+++ b/res/drawable-xhdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_clear_active_holo.png b/res/drawable-xhdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..1a7e53d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_clear_normal_holo.png b/res/drawable-xhdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..d4965d9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_info_active_holo.png b/res/drawable-xhdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..b8cdbc4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_info_normal_holo.png b/res/drawable-xhdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..f503fb8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_market_holo.png b/res/drawable-xhdpi/ic_launcher_market_holo.png
new file mode 100644
index 0000000..958f0de
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_market_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-xhdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..c155274
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-xhdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..2ec7ad9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_add.png b/res/drawable-xhdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..28e164b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current.png b/res/drawable-xhdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..aed3d71
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default.png b/res/drawable-xhdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..0887416
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting.png b/res/drawable-xhdpi/ic_setting.png
new file mode 100644
index 0000000..91ba98c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed.png b/res/drawable-xhdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..08aafc4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper.png b/res/drawable-xhdpi/ic_wallpaper.png
new file mode 100644
index 0000000..41dc000
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed.png b/res/drawable-xhdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..ffff053
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget.png b/res/drawable-xhdpi/ic_widget.png
new file mode 100644
index 0000000..47dcdd1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed.png b/res/drawable-xhdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..8bb387b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/overscroll_glow_left.9.png b/res/drawable-xhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..4f248f7
--- /dev/null
+++ b/res/drawable-xhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/overscroll_glow_right.9.png b/res/drawable-xhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..818a70d
--- /dev/null
+++ b/res/drawable-xhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left_holo.9.png b/res/drawable-xhdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..4972a2e
--- /dev/null
+++ b/res/drawable-xhdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right_holo.9.png b/res/drawable-xhdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..b99461f
--- /dev/null
+++ b/res/drawable-xhdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/paged_view_indicator.9.png b/res/drawable-xhdpi/paged_view_indicator.9.png
new file mode 100644
index 0000000..fb8a228
--- /dev/null
+++ b/res/drawable-xhdpi/paged_view_indicator.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_container_holo.9.png b/res/drawable-xhdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..cedbdc4
--- /dev/null
+++ b/res/drawable-xhdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_inner_holo.png b/res/drawable-xhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..f9acfa2
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-xhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..eb2cf5f
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_outer_holo.png b/res/drawable-xhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..f32051d
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/portal_ring_rest.png b/res/drawable-xhdpi/portal_ring_rest.png
new file mode 100644
index 0000000..ff0369a
--- /dev/null
+++ b/res/drawable-xhdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel.9.png b/res/drawable-xhdpi/screenpanel.9.png
new file mode 100644
index 0000000..b4b828d
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_hover.9.png b/res/drawable-xhdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..a44dc11
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/search_frame.9.png b/res/drawable-xhdpi/search_frame.9.png
new file mode 100644
index 0000000..32a0714
--- /dev/null
+++ b/res/drawable-xhdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..03cfb09
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_holo.9.png b/res/drawable-xhdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..e4229f2
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..2412711
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..e862cb1
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..f3a5cbd
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_holo.9.png b/res/drawable-xhdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..9465173
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..1653600
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..f1eb673
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_picker_focused.9.png b/res/drawable-xhdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..279e859
--- /dev/null
+++ b/res/drawable-xhdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_picker_pressed.9.png b/res/drawable-xhdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..abe0e00
--- /dev/null
+++ b/res/drawable-xhdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_picker_selected.9.png b/res/drawable-xhdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..b047591
--- /dev/null
+++ b/res/drawable-xhdpi/tile_picker_selected.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_shadow_bottom.9.png b/res/drawable-xhdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..81571f3
--- /dev/null
+++ b/res/drawable-xhdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tile_shadow_top.9.png b/res/drawable-xhdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..8503a59
--- /dev/null
+++ b/res/drawable-xhdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_container_holo.9.png b/res/drawable-xhdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..1313fe7
--- /dev/null
+++ b/res/drawable-xhdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_frame_holo.9.png b/res/drawable-xhdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..76cec60
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_bottom.png b/res/drawable-xhdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..19437d7
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_left.png b/res/drawable-xhdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..28c5487
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_right.png b/res/drawable-xhdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..4f672a6
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_resize_handle_top.png b/res/drawable-xhdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..e866c00
--- /dev/null
+++ b/res/drawable-xhdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_tile.png b/res/drawable-xhdpi/widget_tile.png
new file mode 100644
index 0000000..9730f35
--- /dev/null
+++ b/res/drawable-xhdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-xhdpi/workspace_bg.9.png b/res/drawable-xhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..72269f2
--- /dev/null
+++ b/res/drawable-xhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/arrow_dashed.png b/res/drawable-xxhdpi/arrow_dashed.png
new file mode 100644
index 0000000..b64f4d0
--- /dev/null
+++ b/res/drawable-xxhdpi/arrow_dashed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling1.png b/res/drawable-xxhdpi/bg_cling1.png
new file mode 100644
index 0000000..0777856
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling1.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling2.png b/res/drawable-xxhdpi/bg_cling2.png
new file mode 100644
index 0000000..1797a1b
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling2.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling3.png b/res/drawable-xxhdpi/bg_cling3.png
new file mode 100644
index 0000000..a87be63
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling3.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling4.png b/res/drawable-xxhdpi/bg_cling4.png
new file mode 100644
index 0000000..cabe919
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling4.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling_home.png b/res/drawable-xxhdpi/bg_cling_home.png
new file mode 100644
index 0000000..1ae93e7
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling_home.png
Binary files differ
diff --git a/res/drawable-xxhdpi/bg_cling_nakasi3.png b/res/drawable-xxhdpi/bg_cling_nakasi3.png
new file mode 100644
index 0000000..f47236c
--- /dev/null
+++ b/res/drawable-xxhdpi/bg_cling_nakasi3.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling.9.png b/res/drawable-xxhdpi/cling.9.png
new file mode 100644
index 0000000..7beae03
--- /dev/null
+++ b/res/drawable-xxhdpi/cling.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_down.png b/res/drawable-xxhdpi/cling_arrow_down.png
new file mode 100644
index 0000000..48c4f06
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_down.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_left.png b/res/drawable-xxhdpi/cling_arrow_left.png
new file mode 100644
index 0000000..8760d05
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_left.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_right.png b/res/drawable-xxhdpi/cling_arrow_right.png
new file mode 100644
index 0000000..356ba17
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_right.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_arrow_up.png b/res/drawable-xxhdpi/cling_arrow_up.png
new file mode 100644
index 0000000..4cb805f
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_arrow_up.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_button.9.png b/res/drawable-xxhdpi/cling_button.9.png
new file mode 100644
index 0000000..e412876
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_button.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cling_button_pressed.9.png b/res/drawable-xxhdpi/cling_button_pressed.9.png
new file mode 100644
index 0000000..55e89da
--- /dev/null
+++ b/res/drawable-xxhdpi/cling_button_pressed.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/default_widget_preview_holo.9.png b/res/drawable-xxhdpi/default_widget_preview_holo.9.png
new file mode 100644
index 0000000..0f62097
--- /dev/null
+++ b/res/drawable-xxhdpi/default_widget_preview_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/focused_bg.9.png b/res/drawable-xxhdpi/focused_bg.9.png
new file mode 100644
index 0000000..84d3062
--- /dev/null
+++ b/res/drawable-xxhdpi/focused_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hand.png b/res/drawable-xxhdpi/hand.png
new file mode 100644
index 0000000..88c2a88
--- /dev/null
+++ b/res/drawable-xxhdpi/hand.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hotseat_bg_panel.9.png b/res/drawable-xxhdpi/hotseat_bg_panel.9.png
new file mode 100644
index 0000000..40fc076
--- /dev/null
+++ b/res/drawable-xxhdpi/hotseat_bg_panel.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hotseat_scrubber_holo.9.png b/res/drawable-xxhdpi/hotseat_scrubber_holo.9.png
new file mode 100644
index 0000000..8a77536
--- /dev/null
+++ b/res/drawable-xxhdpi/hotseat_scrubber_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/hotseat_track_holo.9.png b/res/drawable-xxhdpi/hotseat_track_holo.9.png
new file mode 100644
index 0000000..dd2216f
--- /dev/null
+++ b/res/drawable-xxhdpi/hotseat_track_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps.png b/res/drawable-xxhdpi/ic_allapps.png
new file mode 100644
index 0000000..624e0ef
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed.png b/res/drawable-xxhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000..77b45ae
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_all_apps_holo_dark.png b/res/drawable-xxhdpi/ic_home_all_apps_holo_dark.png
new file mode 100644
index 0000000..0b8e88c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_all_apps_holo_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_google_logo_normal_holo.png b/res/drawable-xxhdpi/ic_home_google_logo_normal_holo.png
new file mode 100644
index 0000000..a47b2ba
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_google_logo_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_google_logo_pressed_holo.png b/res/drawable-xxhdpi/ic_home_google_logo_pressed_holo.png
new file mode 100644
index 0000000..75625d1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_google_logo_pressed_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_search_normal_holo.png b/res/drawable-xxhdpi/ic_home_search_normal_holo.png
new file mode 100644
index 0000000..a9523d3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_search_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_search_pressed_holo.png b/res/drawable-xxhdpi/ic_home_search_pressed_holo.png
new file mode 100644
index 0000000..800d994
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_search_pressed_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_voice_search_holo.png b/res/drawable-xxhdpi/ic_home_voice_search_holo.png
new file mode 100644
index 0000000..c9c0b50
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_voice_search_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_home_voice_search_pressed_holo.png b/res/drawable-xxhdpi/ic_home_voice_search_pressed_holo.png
new file mode 100644
index 0000000..27a5897
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_home_voice_search_pressed_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_images.png b/res/drawable-xxhdpi/ic_images.png
new file mode 100644
index 0000000..d00db9a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_images.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_clear_active_holo.png b/res/drawable-xxhdpi/ic_launcher_clear_active_holo.png
new file mode 100644
index 0000000..95cf841
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_clear_active_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_clear_normal_holo.png b/res/drawable-xxhdpi/ic_launcher_clear_normal_holo.png
new file mode 100644
index 0000000..b0f5a27
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_clear_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_info_active_holo.png b/res/drawable-xxhdpi/ic_launcher_info_active_holo.png
new file mode 100644
index 0000000..57f332a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_info_active_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_info_normal_holo.png b/res/drawable-xxhdpi/ic_launcher_info_normal_holo.png
new file mode 100644
index 0000000..94f0955
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_info_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_trashcan_active_holo.png b/res/drawable-xxhdpi/ic_launcher_trashcan_active_holo.png
new file mode 100644
index 0000000..3bb098c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_trashcan_active_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_trashcan_normal_holo.png b/res/drawable-xxhdpi/ic_launcher_trashcan_normal_holo.png
new file mode 100644
index 0000000..550cc5b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher_trashcan_normal_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_add.png b/res/drawable-xxhdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000..fd8a662
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_add.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current.png b/res/drawable-xxhdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000..08615f3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_current.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default.png b/res/drawable-xxhdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000..9d4fbf8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_default.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_qs_remote_display.png b/res/drawable-xxhdpi/ic_qs_remote_display.png
new file mode 100644
index 0000000..25ea9fa
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_qs_remote_display.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_qs_remote_display_connected.png b/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
new file mode 100644
index 0000000..33a8d2d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting.png b/res/drawable-xxhdpi/ic_setting.png
new file mode 100644
index 0000000..6e1e662
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed.png b/res/drawable-xxhdpi/ic_setting_pressed.png
new file mode 100644
index 0000000..a202a40
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper.png b/res/drawable-xxhdpi/ic_wallpaper.png
new file mode 100644
index 0000000..c718444
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed.png b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000..03324aa
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget.png b/res/drawable-xxhdpi/ic_widget.png
new file mode 100644
index 0000000..fddfeca
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed.png b/res/drawable-xxhdpi/ic_widget_pressed.png
new file mode 100644
index 0000000..3d3670e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_pressed.png
Binary files differ
diff --git a/res/drawable-xxhdpi/overscroll_glow_left.9.png b/res/drawable-xxhdpi/overscroll_glow_left.9.png
new file mode 100644
index 0000000..1a895cd
--- /dev/null
+++ b/res/drawable-xxhdpi/overscroll_glow_left.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/overscroll_glow_right.9.png b/res/drawable-xxhdpi/overscroll_glow_right.9.png
new file mode 100644
index 0000000..5766761
--- /dev/null
+++ b/res/drawable-xxhdpi/overscroll_glow_right.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left_holo.9.png b/res/drawable-xxhdpi/page_hover_left_holo.9.png
new file mode 100644
index 0000000..626aafb
--- /dev/null
+++ b/res/drawable-xxhdpi/page_hover_left_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right_holo.9.png b/res/drawable-xxhdpi/page_hover_right_holo.9.png
new file mode 100644
index 0000000..66257dc
--- /dev/null
+++ b/res/drawable-xxhdpi/page_hover_right_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_container_holo.9.png b/res/drawable-xxhdpi/portal_container_holo.9.png
new file mode 100644
index 0000000..599a076
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_inner_holo.png b/res/drawable-xxhdpi/portal_ring_inner_holo.png
new file mode 100644
index 0000000..8cd6a59
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_inner_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_inner_nolip_holo.png b/res/drawable-xxhdpi/portal_ring_inner_nolip_holo.png
new file mode 100644
index 0000000..0fad656
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_inner_nolip_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_outer_holo.png b/res/drawable-xxhdpi/portal_ring_outer_holo.png
new file mode 100644
index 0000000..0aee4f0
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_outer_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/portal_ring_rest.png b/res/drawable-xxhdpi/portal_ring_rest.png
new file mode 100644
index 0000000..6fa6a53
--- /dev/null
+++ b/res/drawable-xxhdpi/portal_ring_rest.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel.9.png b/res/drawable-xxhdpi/screenpanel.9.png
new file mode 100644
index 0000000..c44f3b8
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover.9.png b/res/drawable-xxhdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000..1ab18da
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel_hover.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/search_bg_panel.9.png b/res/drawable-xxhdpi/search_bg_panel.9.png
new file mode 100644
index 0000000..85cae17
--- /dev/null
+++ b/res/drawable-xxhdpi/search_bg_panel.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/search_frame.9.png b/res/drawable-xxhdpi/search_frame.9.png
new file mode 100644
index 0000000..f297bf1
--- /dev/null
+++ b/res/drawable-xxhdpi/search_frame.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_focused_holo.9.png b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..2400c65
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_holo.9.png b/res/drawable-xxhdpi/tab_selected_holo.9.png
new file mode 100644
index 0000000..5067cbb
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..84c246d
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..939e0c3
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_holo.9.png b/res/drawable-xxhdpi/tab_unselected_holo.9.png
new file mode 100644
index 0000000..62ca6cf
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..58ac0d6
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_picker_focused.9.png b/res/drawable-xxhdpi/tile_picker_focused.9.png
new file mode 100644
index 0000000..1004c14
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_picker_focused.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_picker_pressed.9.png b/res/drawable-xxhdpi/tile_picker_pressed.9.png
new file mode 100644
index 0000000..9658444
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_picker_pressed.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_picker_selected.9.png b/res/drawable-xxhdpi/tile_picker_selected.9.png
new file mode 100644
index 0000000..a3cd303
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_picker_selected.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_shadow_bottom.9.png b/res/drawable-xxhdpi/tile_shadow_bottom.9.png
new file mode 100644
index 0000000..55250f0
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_shadow_bottom.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tile_shadow_top.9.png b/res/drawable-xxhdpi/tile_shadow_top.9.png
new file mode 100644
index 0000000..3f22633
--- /dev/null
+++ b/res/drawable-xxhdpi/tile_shadow_top.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_container_holo.9.png b/res/drawable-xxhdpi/widget_container_holo.9.png
new file mode 100644
index 0000000..8f79920
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_container_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_frame_holo.9.png b/res/drawable-xxhdpi/widget_resize_frame_holo.9.png
new file mode 100644
index 0000000..1681387
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_bottom.png b/res/drawable-xxhdpi/widget_resize_handle_bottom.png
new file mode 100644
index 0000000..d549fcd
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_bottom.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_left.png b/res/drawable-xxhdpi/widget_resize_handle_left.png
new file mode 100644
index 0000000..dd56dad
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_left.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_right.png b/res/drawable-xxhdpi/widget_resize_handle_right.png
new file mode 100644
index 0000000..296a1c1
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_right.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_resize_handle_top.png b/res/drawable-xxhdpi/widget_resize_handle_top.png
new file mode 100644
index 0000000..e86270a
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_resize_handle_top.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_tile.png b/res/drawable-xxhdpi/widget_tile.png
new file mode 100644
index 0000000..3a3790d
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_tile.png
Binary files differ
diff --git a/res/drawable-xxhdpi/workspace_bg.9.png b/res/drawable-xxhdpi/workspace_bg.9.png
new file mode 100644
index 0000000..efc9b04
--- /dev/null
+++ b/res/drawable-xxhdpi/workspace_bg.9.png
Binary files differ
diff --git a/res/drawable/all_apps_button_icon.xml b/res/drawable/all_apps_button_icon.xml
new file mode 100644
index 0000000..7c69cad
--- /dev/null
+++ b/res/drawable/all_apps_button_icon.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_allapps_pressed" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_allapps_pressed" />
+ <item android:drawable="@drawable/ic_allapps" />
+</selector>
diff --git a/res/drawable/cling_arrow_end.xml b/res/drawable/cling_arrow_end.xml
new file mode 100644
index 0000000..3f63c7d
--- /dev/null
+++ b/res/drawable/cling_arrow_end.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/cling_arrow_right"
+ android:autoMirrored="true">
+</bitmap>
diff --git a/res/drawable/cling_arrow_start.xml b/res/drawable/cling_arrow_start.xml
new file mode 100644
index 0000000..ebe9183
--- /dev/null
+++ b/res/drawable/cling_arrow_start.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/cling_arrow_left"
+ android:autoMirrored="true">
+</bitmap>
diff --git a/res/drawable/cling_button_bg.xml b/res/drawable/cling_button_bg.xml
new file mode 100644
index 0000000..7bf6ce7
--- /dev/null
+++ b/res/drawable/cling_button_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:drawable="@drawable/cling_button_pressed" />
+ <item android:drawable="@drawable/cling_button" />
+</selector>
diff --git a/res/drawable/focusable_view_bg.xml b/res/drawable/focusable_view_bg.xml
new file mode 100644
index 0000000..66661e2
--- /dev/null
+++ b/res/drawable/focusable_view_bg.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/focused_bg" />
+</selector>
diff --git a/res/drawable/info_target_selector.xml b/res/drawable/info_target_selector.xml
new file mode 100644
index 0000000..f3a7016
--- /dev/null
+++ b/res/drawable/info_target_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, 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.
+*/
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_launcher_info_normal_holo" />
+ <item android:drawable="@drawable/ic_launcher_info_active_holo" />
+</transition>
diff --git a/res/drawable/remove_target_selector.xml b/res/drawable/remove_target_selector.xml
new file mode 100644
index 0000000..5e071fb
--- /dev/null
+++ b/res/drawable/remove_target_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_launcher_clear_normal_holo" />
+ <item android:drawable="@drawable/ic_launcher_clear_active_holo" />
+</transition>
diff --git a/res/drawable/setting_button.xml b/res/drawable/setting_button.xml
new file mode 100644
index 0000000..4d66a1a
--- /dev/null
+++ b/res/drawable/setting_button.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_setting_pressed" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_setting_pressed" />
+ <item android:drawable="@drawable/ic_setting" />
+</selector>
diff --git a/res/drawable/tab_widget_indicator_selector.xml b/res/drawable/tab_widget_indicator_selector.xml
new file mode 100644
index 0000000..d06f757
--- /dev/null
+++ b/res/drawable/tab_widget_indicator_selector.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Non focused states -->
+ <item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_selected_holo" />
+
+ <!-- Focused states -->
+ <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected_focused_holo" />
+ <item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_selected_focused_holo" />
+
+ <!-- Pressed -->
+ <!-- Non focused states -->
+ <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_holo" />
+ <item android:state_focused="false" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_holo" />
+
+ <!-- Focused states -->
+ <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_focused_holo" />
+ <item android:state_focused="true" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_focused_holo" />
+</selector>
diff --git a/res/drawable/uninstall_target_selector.xml b/res/drawable/uninstall_target_selector.xml
new file mode 100644
index 0000000..229942e
--- /dev/null
+++ b/res/drawable/uninstall_target_selector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_launcher_trashcan_normal_holo" />
+ <item android:drawable="@drawable/ic_launcher_trashcan_active_holo" />
+</transition>
diff --git a/res/drawable/wallpaper_button.xml b/res/drawable/wallpaper_button.xml
new file mode 100644
index 0000000..72da99d
--- /dev/null
+++ b/res/drawable/wallpaper_button.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_wallpaper_pressed" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_wallpaper_pressed" />
+ <item android:drawable="@drawable/ic_wallpaper" />
+</selector>
diff --git a/res/drawable/wallpaper_tile_fg.xml b/res/drawable/wallpaper_tile_fg.xml
new file mode 100644
index 0000000..c299f32
--- /dev/null
+++ b/res/drawable/wallpaper_tile_fg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_checked="true" android:drawable="@drawable/tile_picker_selected" />
+ <item android:state_focused="true" android:drawable="@drawable/tile_picker_focused" />
+ <item android:state_pressed="true" android:drawable="@drawable/tile_picker_pressed" />
+ <item android:state_selected="true" android:drawable="@drawable/tile_picker_selected" />
+ <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/widget_button.xml b/res/drawable/widget_button.xml
new file mode 100644
index 0000000..6936c87
--- /dev/null
+++ b/res/drawable/widget_button.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:drawable="@drawable/ic_widget_pressed" />
+ <item android:state_pressed="true" android:drawable="@drawable/ic_widget_pressed" />
+ <item android:drawable="@drawable/ic_widget" />
+</selector>
diff --git a/res/layout-land/all_apps_cling.xml b/res/layout-land/all_apps_cling.xml
new file mode 100644
index 0000000..820f00a
--- /dev/null
+++ b/res/layout-land/all_apps_cling.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="all_apps_landscape">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="40dp"
+ android:layout_marginTop="40dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/all_apps_cling_title"
+ android:text="@string/all_apps_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/all_apps_cling_add_item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/all_apps_cling_add_item" />
+ </LinearLayout>
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:layout_marginBottom="15dp"
+ android:layout_marginEnd="10dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissAllAppsCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-land/first_run_cling.xml b/res/layout-land/first_run_cling.xml
new file mode 100644
index 0000000..9baee64
--- /dev/null
+++ b/res/layout-land/first_run_cling.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:drawIdentifier="first_run_portrait">
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:id="@+id/bubble_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginLeft="100dp"
+ android:layout_marginRight="100dp"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingAltTitleText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="10dp"
+ android:text="@string/first_run_cling_title"
+ android:textColor="#FFFFFFFF"
+ android:textSize="30sp"
+ android:gravity="center" />
+ <TextView
+ style="@style/ClingAltTitleText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/first_run_cling_description"
+ android:textColor="#80000000"
+ android:textSize="16sp"
+ android:gravity="center" />
+ </LinearLayout>
+ <TextView
+ style="@style/ClingHintText"
+ android:id="@+id/search_bar_hint"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="10dp"
+ android:layout_marginTop="65dp"
+ android:visibility="gone"
+ android:drawableTop="@drawable/cling_arrow_up"
+ android:drawablePadding="5dp"
+ android:text="@string/first_run_cling_search_bar_hint" />
+ <TextView
+ style="@style/ClingHintText"
+ android:id="@+id/custom_content_hint"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="100dp"
+ android:visibility="gone"
+ android:drawableStart="@drawable/cling_arrow_left"
+ android:drawablePadding="10dp"
+ android:text="@string/first_run_cling_custom_content_hint" />
+ <TextView
+ style="@style/ClingHintText"
+ android:layout_width="160dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="85dp"
+ android:drawableEnd="@drawable/cling_arrow_right"
+ android:drawablePadding="5dp"
+ android:text="@string/first_run_cling_create_screens_hint" />
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginEnd="20dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissFirstRunCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-land/folder_cling.xml b/res/layout-land/folder_cling.xml
new file mode 100644
index 0000000..86286d7
--- /dev/null
+++ b/res/layout-land/folder_cling.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="folder_portrait">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="15dp"
+ android:layout_marginEnd="15dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
+ android:orientation="vertical"
+ android:background="@drawable/cling">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/folder_cling_title"
+ android:text="@string/folder_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/folder_cling_create_folder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/folder_cling_create_folder" />
+ </LinearLayout>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/cling_arrow_down" />
+ </LinearLayout>
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:layout_marginBottom="15dp"
+ android:layout_marginEnd="20dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissFolderCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
new file mode 100644
index 0000000..77ea2e9
--- /dev/null
+++ b/res/layout-land/launcher.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!-- Full screen view projects under the status bar and contains the background -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+ android:id="@+id/launcher"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/workspace_bg">
+
+ <com.android.launcher3.DragLayer
+ android:id="@+id/drag_layer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <!-- The workspace contains 5 screens of cells -->
+ <com.android.launcher3.Workspace
+ android:id="@+id/workspace"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
+ launcher:pageSpacing="@dimen/workspace_page_spacing"
+ launcher:pageIndicator="@id/page_indicator" />
+
+ <include layout="@layout/hotseat"
+ android:id="@+id/hotseat"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end" />
+
+ <include
+ android:id="@+id/qsb_bar"
+ layout="@layout/qsb_bar" />
+
+ <include layout="@layout/overview_panel"
+ android:id="@+id/overview_panel"
+ android:visibility="gone" />
+
+ <!-- The Workspace cling must appear under the AppsCustomizePagedView below to ensure
+ that it is still visible during the transition to AllApps and doesn't overlay on
+ top of that view. -->
+ <com.android.launcher3.ScrimView
+ android:id="@+id/cling_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ <include layout="@layout/first_run_cling"
+ android:id="@+id/first_run_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ <include layout="@layout/workspace_cling"
+ android:id="@+id/workspace_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <include layout="@layout/folder_cling"
+ android:id="@+id/folder_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <!-- TODO: Fix
+ <com.android.launcher3.DrawableStateProxyView
+ android:id="@+id/voice_button_proxy"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_gravity="top|start"
+ android:layout_marginTop="64dp"
+ android:clickable="true"
+ android:onClick="onClickVoiceButton"
+ android:importantForAccessibility="no"
+ launcher:sourceViewId="@+id/voice_button" />
+ -->
+
+ <include layout="@layout/apps_customize_pane"
+ android:id="@+id/apps_customize_pane"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+ </com.android.launcher3.DragLayer>
+</FrameLayout>
diff --git a/res/layout-land/search_bar.xml b/res/layout-land/search_bar.xml
new file mode 100644
index 0000000..d56e380
--- /dev/null
+++ b/res/layout-land/search_bar.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <!-- Global search icon -->
+ <com.android.launcher3.HolographicImageView
+ style="@style/SearchButton"
+ android:id="@+id/search_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:scaleType="center"
+ android:src="@drawable/ic_home_search_normal_holo"
+ android:adjustViewBounds="true"
+ android:onClick="onClickSearchButton"
+ android:focusable="true"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_search_button" />
+
+ <!-- Voice search icon -->
+ <com.android.launcher3.HolographicImageView
+ style="@style/SearchButton"
+ android:id="@+id/voice_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:scaleType="center"
+ android:src="@drawable/ic_home_voice_search_holo"
+ android:adjustViewBounds="true"
+ android:onClick="onClickVoiceButton"
+ android:focusable="true"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_voice_search_button" />
+</LinearLayout>
diff --git a/res/layout-land/workspace_cling.xml b/res/layout-land/workspace_cling.xml
new file mode 100644
index 0000000..db33db0
--- /dev/null
+++ b/res/layout-land/workspace_cling.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:drawIdentifier="workspace_portrait">
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="25dp"
+ android:layout_marginEnd="25dp"
+ android:layout_marginTop="310dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:orientation="vertical">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:src="@drawable/cling_arrow_up" />
+ <LinearLayout
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@drawable/cling">
+ <TextView
+ style="@style/ClingTitleText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/workspace_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/workspace_cling_move_item" />
+ </LinearLayout>
+ </LinearLayout>
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginEnd="20dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissWorkspaceCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/all_apps_cling.xml b/res/layout-port/all_apps_cling.xml
new file mode 100644
index 0000000..62284ec
--- /dev/null
+++ b/res/layout-port/all_apps_cling.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="all_apps_portrait">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="20dp"
+ android:layout_marginTop="20dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/all_apps_cling_title"
+ android:text="@string/all_apps_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/all_apps_cling_add_item"
+ android:layout_width="285dp"
+ android:layout_height="wrap_content"
+ android:text="@string/all_apps_cling_add_item" />
+ </LinearLayout>
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:layout_marginBottom="15dp"
+ android:layout_marginEnd="10dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissAllAppsCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/first_run_cling.xml b/res/layout-port/first_run_cling.xml
new file mode 100644
index 0000000..4830e5d
--- /dev/null
+++ b/res/layout-port/first_run_cling.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:drawIdentifier="first_run_portrait">
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:id="@+id/bubble_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginLeft="100dp"
+ android:layout_marginRight="100dp"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingAltTitleText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="10dp"
+ android:text="@string/first_run_cling_title"
+ android:textColor="#FFFFFFFF"
+ android:textSize="30sp"
+ android:gravity="center" />
+ <TextView
+ style="@style/ClingAltTitleText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/first_run_cling_description"
+ android:textColor="#80000000"
+ android:textSize="16sp"
+ android:gravity="center" />
+ </LinearLayout>
+ <TextView
+ style="@style/ClingHintText"
+ android:id="@+id/search_bar_hint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="10dp"
+ android:layout_marginTop="65dp"
+ android:gravity="center_horizontal"
+ android:maxWidth="160dp"
+ android:visibility="gone"
+ android:drawableTop="@drawable/cling_arrow_up"
+ android:drawablePadding="5dp"
+ android:text="@string/first_run_cling_search_bar_hint" />
+ <TextView
+ style="@style/ClingHintText"
+ android:id="@+id/custom_content_hint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|start"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginTop="100dp"
+ android:maxWidth="160dp"
+ android:visibility="gone"
+ android:drawableStart="@drawable/cling_arrow_start"
+ android:drawablePadding="10dp"
+ android:text="@string/first_run_cling_custom_content_hint" />
+ <TextView
+ style="@style/ClingHintText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="85dp"
+ android:maxWidth="180dp"
+ android:drawableEnd="@drawable/cling_arrow_end"
+ android:drawablePadding="5dp"
+ android:text="@string/first_run_cling_create_screens_hint" />
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginEnd="20dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissFirstRunCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/folder_cling.xml b/res/layout-port/folder_cling.xml
new file mode 100644
index 0000000..86286d7
--- /dev/null
+++ b/res/layout-port/folder_cling.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="folder_portrait">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="15dp"
+ android:layout_marginEnd="15dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
+ android:orientation="vertical"
+ android:background="@drawable/cling">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/folder_cling_title"
+ android:text="@string/folder_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/folder_cling_create_folder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/folder_cling_create_folder" />
+ </LinearLayout>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/cling_arrow_down" />
+ </LinearLayout>
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:layout_marginBottom="15dp"
+ android:layout_marginEnd="20dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissFolderCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
new file mode 100644
index 0000000..6fbf7c7
--- /dev/null
+++ b/res/layout-port/launcher.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!-- Full screen view projects under the status bar and contains the background -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+ android:id="@+id/launcher"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/workspace_bg">
+
+ <com.android.launcher3.DragLayer
+ android:id="@+id/drag_layer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- The workspace contains 5 screens of cells -->
+ <com.android.launcher3.Workspace
+ android:id="@+id/workspace"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
+ launcher:pageSpacing="@dimen/workspace_page_spacing"
+ launcher:pageIndicator="@id/page_indicator">
+ </com.android.launcher3.Workspace>
+
+ <include layout="@layout/hotseat"
+ android:id="@+id/hotseat"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <include layout="@layout/overview_panel"
+ android:id="@+id/overview_panel"
+ android:visibility="gone" />
+
+ <!-- Keep these behind the workspace so that they are not visible when
+ we go into AllApps -->
+ <include
+ android:id="@+id/page_indicator"
+ layout="@layout/page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal" />
+
+ <include
+ android:id="@+id/qsb_bar"
+ layout="@layout/qsb_bar" />
+
+ <!-- The Workspace cling must appear under the AppsCustomizePagedView below to ensure
+ that it is still visible during the transition to AllApps and doesn't overlay on
+ top of that view. -->
+ <com.android.launcher3.ScrimView
+ android:id="@+id/cling_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ <include layout="@layout/first_run_cling"
+ android:id="@+id/first_run_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ <include layout="@layout/workspace_cling"
+ android:id="@+id/workspace_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ <include layout="@layout/folder_cling"
+ android:id="@+id/folder_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <!-- This is the search bar voice button proxy view. It allows us to have a larger
+ touch target than the microphone constrained by the search bar bounds. -->
+ <com.android.launcher3.DrawableStateProxyView
+ android:id="@+id/voice_button_proxy"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_gravity="top|end"
+ android:clickable="true"
+ android:onClick="onClickVoiceButton"
+ android:importantForAccessibility="no"
+ launcher:sourceViewId="@+id/voice_button" />
+
+ <include layout="@layout/apps_customize_pane"
+ android:id="@+id/apps_customize_pane"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+ </com.android.launcher3.DragLayer>
+</FrameLayout>
diff --git a/res/layout-port/search_bar.xml b/res/layout-port/search_bar.xml
new file mode 100644
index 0000000..1c96ca3
--- /dev/null
+++ b/res/layout-port/search_bar.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ style="@style/SearchDropTargetBar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/search_frame">
+ <!-- Global search icon -->
+ <com.android.launcher3.HolographicLinearLayout
+ style="@style/SearchButton"
+ launcher:sourceImageViewId="@+id/search_button"
+ android:id="@+id/search_button_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@+id/voice_button_container"
+ android:paddingStart="8dp"
+ android:onClick="onClickSearchButton"
+ android:focusable="true"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_search_button">
+ <ImageView
+ android:id="@+id/search_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_home_search_normal_holo"
+ android:adjustViewBounds="true" />
+ </com.android.launcher3.HolographicLinearLayout>
+
+ <!-- Voice search icon -->
+ <com.android.launcher3.HolographicLinearLayout
+ style="@style/SearchButton"
+ launcher:sourceImageViewId="@+id/voice_button"
+ android:id="@+id/voice_button_container"
+ android:layout_width="@dimen/app_icon_size"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true"
+ android:paddingEnd="8dp"
+ android:onClick="onClickVoiceButton"
+ android:focusable="true"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_voice_search_button">
+ <ImageView
+ android:id="@+id/voice_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_home_voice_search_holo"
+ android:adjustViewBounds="true" />
+ </com.android.launcher3.HolographicLinearLayout>
+</RelativeLayout>
diff --git a/res/layout-port/workspace_cling.xml b/res/layout-port/workspace_cling.xml
new file mode 100644
index 0000000..9c000cb
--- /dev/null
+++ b/res/layout-port/workspace_cling.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:drawIdentifier="workspace_portrait">
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:layout_marginStart="25dp"
+ android:layout_marginEnd="25dp"
+ android:layout_marginTop="30dp"
+ android:orientation="vertical">
+ <LinearLayout
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@drawable/cling">
+ <TextView
+ style="@style/ClingTitleText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/workspace_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/workspace_cling_move_item" />
+ </LinearLayout>
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:src="@drawable/cling_arrow_down" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/focused_hotseat_app_bubble"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|right"
+ android:layout_marginRight="25dp"
+ android:layout_marginBottom="90dp"
+ android:orientation="vertical"
+ android:visibility="gone">
+ <LinearLayout
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
+ android:layout_width="240dp"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@drawable/cling">
+ <TextView
+ android:id="@+id/focused_hotseat_app_title"
+ style="@style/ClingTitleText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/focused_hotseat_app_description"
+ style="@style/ClingText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:layout_marginRight="80dp"
+ android:src="@drawable/cling_arrow_down" />
+ </LinearLayout>
+ </FrameLayout>
+
+ <Button
+ style="@style/ClingButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="20dp"
+ android:layout_gravity="bottom|left"
+ android:onClick="dismissWorkspaceCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw600dp-port/all_apps_cling.xml b/res/layout-sw600dp-port/all_apps_cling.xml
new file mode 100644
index 0000000..cf65e41
--- /dev/null
+++ b/res/layout-sw600dp-port/all_apps_cling.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="all_apps_portrait">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="20dp"
+ android:layout_marginTop="20dp">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/all_apps_cling_title"
+ android:text="@string/all_apps_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/all_apps_cling_add_item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/all_apps_cling_add_item" />
+ </LinearLayout>
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:minWidth="168dp"
+ android:textSize="24sp"
+ android:layout_marginTop="235dp"
+ android:layout_marginEnd="36dp"
+ android:layout_gravity="top|end"
+ android:onClick="dismissAllAppsCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw600dp-port/folder_cling.xml b/res/layout-sw600dp-port/folder_cling.xml
new file mode 100644
index 0000000..87086cb
--- /dev/null
+++ b/res/layout-sw600dp-port/folder_cling.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="folder_portrait">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginTop="@dimen/folderClingMarginTop">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/folder_cling_title"
+ android:text="@string/folder_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/folder_cling_create_folder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/folder_cling_create_folder" />
+ </LinearLayout>
+ </FrameLayout>
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:minWidth="168dp"
+ android:textSize="24sp"
+ android:layout_marginBottom="27dp"
+ android:layout_marginEnd="36dp"
+ android:layout_gravity="bottom|end"
+ android:onClick="dismissFolderCling" />
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw720dp-port/folder_cling.xml b/res/layout-sw720dp-port/folder_cling.xml
new file mode 100644
index 0000000..40d4e20
--- /dev/null
+++ b/res/layout-sw720dp-port/folder_cling.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="folder_large">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/cling_text_block_offset_x"
+ android:layout_marginTop="@dimen/cling_text_block_offset_y">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/folder_cling_title"
+ android:text="@string/folder_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/folder_cling_create_folder"
+ android:layout_width="480dp"
+ android:layout_height="wrap_content"
+ android:text="@string/folder_cling_create_folder" />
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:layout_marginTop="15dp"
+ android:onClick="dismissFolderCling" />
+ </LinearLayout>
+ </FrameLayout>
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw720dp/all_apps_cling.xml b/res/layout-sw720dp/all_apps_cling.xml
new file mode 100644
index 0000000..824d84f
--- /dev/null
+++ b/res/layout-sw720dp/all_apps_cling.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ launcher:drawIdentifier="all_apps_large">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/cling_text_block_offset_x"
+ android:layout_marginTop="@dimen/cling_text_block_offset_y">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ style="@style/ClingTitleText"
+ android:id="@+id/all_apps_cling_title"
+ android:text="@string/all_apps_cling_title" />
+ <TextView
+ style="@style/ClingText"
+ android:id="@+id/all_apps_cling_add_item"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/all_apps_cling_add_item" />
+ <Button
+ style="@style/ClingButton"
+ android:id="@+id/cling_dismiss"
+ android:layout_marginTop="15dp"
+ android:onClick="dismissAllAppsCling" />
+ </LinearLayout>
+ </FrameLayout>
+</com.android.launcher3.Cling>
diff --git a/res/layout-sw720dp/external_widget_drop_list_item.xml b/res/layout-sw720dp/external_widget_drop_list_item.xml
new file mode 100644
index 0000000..48e333b
--- /dev/null
+++ b/res/layout-sw720dp/external_widget_drop_list_item.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="64dp">
+ <ImageView
+ android:id="@+id/provider_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="20dp"
+ android:maxWidth="32dp"
+ android:maxHeight="32dp"
+ android:scaleType="fitCenter"
+ android:src="@mipmap/ic_launcher_application" />
+ <TextView
+ android:id="@+id/provider"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="5dp"
+ android:gravity="center_vertical"
+ android:textSize="18sp" />
+</LinearLayout>
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
new file mode 100644
index 0000000..951e63a
--- /dev/null
+++ b/res/layout-sw720dp/launcher.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<!-- Full screen view projects under the status bar and contains the background -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+ android:id="@+id/launcher"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/workspace_bg">
+
+ <com.android.launcher3.DragLayer
+ android:id="@+id/drag_layer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <!-- The workspace contains 5 screens of cells -->
+ <com.android.launcher3.Workspace
+ android:id="@+id/workspace"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
+ launcher:pageSpacing="@dimen/workspace_page_spacing"
+ launcher:pageIndicator="@id/page_indicator">
+ </com.android.launcher3.Workspace>
+
+ <include layout="@layout/hotseat"
+ android:id="@+id/hotseat"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <include
+ android:id="@+id/qsb_bar"
+ layout="@layout/qsb_bar" />
+
+ <include layout="@layout/overview_panel"
+ android:id="@+id/overview_panel"
+ android:visibility="gone" />
+
+ <!-- Keep these behind the workspace so that they are not visible when
+ we go into AllApps -->
+ <include
+ android:id="@+id/page_indicator"
+ layout="@layout/page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal" />
+
+ <!-- The Workspace cling must appear under the AppsCustomizePagedView below to ensure
+ that it is still visible during the transition to AllApps and doesn't overlay on
+ top of that view. -->
+ <include layout="@layout/first_run_cling"
+ android:id="@+id/first_run_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ <include layout="@layout/workspace_cling"
+ android:id="@+id/workspace_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <include layout="@layout/folder_cling"
+ android:id="@+id/folder_cling"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <com.android.launcher3.DrawableStateProxyView
+ android:id="@+id/voice_button_proxy"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_gravity="top|end"
+ android:clickable="true"
+ android:onClick="onClickVoiceButton"
+ android:importantForAccessibility="no"
+ launcher:sourceViewId="@+id/voice_button" />
+
+ <include layout="@layout/apps_customize_pane"
+ android:id="@+id/apps_customize_pane"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
+ </com.android.launcher3.DragLayer>
+</FrameLayout>
diff --git a/res/layout-sw720dp/market_button.xml b/res/layout-sw720dp/market_button.xml
new file mode 100644
index 0000000..f1e9959
--- /dev/null
+++ b/res/layout-sw720dp/market_button.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/MarketButton"
+ android:onClick="onClickAppMarketButton"
+ android:gravity="center"
+ android:paddingStart="32dp"
+ android:paddingEnd="32dp"
+ android:drawablePadding="10dp"
+ android:background="@drawable/tab_widget_indicator_selector"
+ android:text="@string/market"
+ android:contentDescription="@string/market"
+ android:textColor="@color/workspace_all_apps_and_delete_zone_text_color"
+ android:textStyle="bold"
+ android:textSize="14sp"
+ android:textAllCaps="true"
+ android:shadowColor="@color/workspace_all_apps_and_delete_zone_text_shadow_color"
+ android:shadowDx="0.0"
+ android:shadowDy="0.0"
+ android:shadowRadius="2.0"
+ android:focusable="true"
+ android:clickable="true" />
diff --git a/res/layout-sw720dp/search_bar.xml b/res/layout-sw720dp/search_bar.xml
new file mode 100644
index 0000000..69dd61a
--- /dev/null
+++ b/res/layout-sw720dp/search_bar.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ style="@style/SearchDropTargetBar"
+ android:background="@drawable/search_frame">
+ <!-- Global search icon -->
+ <com.android.launcher3.HolographicLinearLayout
+ style="@style/SearchButton"
+ launcher:sourceImageViewId="@+id/search_button"
+ android:id="@+id/search_button_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@+id/voice_button_container"
+ android:paddingStart="8dp"
+ android:onClick="onClickSearchButton"
+ android:focusable="true"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_search_button">
+ <ImageView
+ android:id="@+id/search_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_home_search_normal_holo"
+ android:adjustViewBounds="true" />
+ </com.android.launcher3.HolographicLinearLayout>
+
+ <!-- Voice search icon -->
+ <com.android.launcher3.HolographicLinearLayout
+ style="@style/SearchButton"
+ launcher:sourceImageViewId="@+id/voice_button"
+ android:id="@+id/voice_button_container"
+ android:layout_width="@dimen/app_icon_size"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true"
+ android:paddingEnd="8dp"
+ android:onClick="onClickVoiceButton"
+ android:focusable="true"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_voice_search_button">
+ <ImageView
+ android:id="@+id/voice_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_home_voice_search_holo"
+ android:adjustViewBounds="true" />
+ </com.android.launcher3.HolographicLinearLayout>
+</RelativeLayout>
diff --git a/res/layout/actionbar_set_wallpaper.xml b/res/layout/actionbar_set_wallpaper.xml
new file mode 100644
index 0000000..1622742
--- /dev/null
+++ b/res/layout/actionbar_set_wallpaper.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="?android:actionButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+ <TextView style="?android:actionBarTabTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:paddingRight="20dp"
+ android:drawableLeft="@drawable/ic_actionbar_accept"
+ android:drawablePadding="8dp"
+ android:gravity="center_vertical"
+ android:text="@string/wallpaper_instructions" />
+</FrameLayout>
diff --git a/res/layout/add_list_item.xml b/res/layout/add_list_item.xml
new file mode 100644
index 0000000..0ae0113
--- /dev/null
+++ b/res/layout/add_list_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:gravity="center_vertical"
+ android:drawablePadding="14dip"
+ android:paddingStart="15dip"
+ android:paddingEnd="15dip" />
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
new file mode 100644
index 0000000..1b9ea08
--- /dev/null
+++ b/res/layout/all_apps_button.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/WorkspaceIcon"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg" />
diff --git a/res/layout/application.xml b/res/layout/application.xml
new file mode 100644
index 0000000..e4909dd
--- /dev/null
+++ b/res/layout/application.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/WorkspaceIcon"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg" />
diff --git a/res/layout/apps_customize_application.xml b/res/layout/apps_customize_application.xml
new file mode 100644
index 0000000..3b0fa6f
--- /dev/null
+++ b/res/layout/apps_customize_application.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<com.android.launcher3.PagedViewIcon
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+ style="@style/WorkspaceIcon.AppsCustomize"
+
+ android:id="@+id/application_icon"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg" />
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
new file mode 100644
index 0000000..11a938f
--- /dev/null
+++ b/res/layout/apps_customize_pane.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.AppsCustomizeTabHost
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:background="#80FFFFFF">
+ <LinearLayout
+ android:id="@+id/apps_customize_content"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone">
+ <!-- The layout_width of the tab bar gets overriden to align the content
+ with the text in the tabs in AppsCustomizeTabHost. -->
+ <FrameLayout
+ android:id="@+id/tabs_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/apps_customize_tab_bar_height"
+ android:layout_marginTop="@dimen/apps_customize_tab_bar_margin_top"
+ android:layout_gravity="center_horizontal"
+ android:visibility="gone">
+ <com.android.launcher3.FocusOnlyTabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:gravity="start"
+ android:background="@drawable/tab_unselected_holo"
+ android:tabStripEnabled="false"
+ android:divider="@null" />
+ <include
+ android:id="@+id/market_button"
+ layout="@layout/market_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end" />
+ </FrameLayout>
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.launcher3.AppsCustomizePagedView
+ android:id="@+id/apps_customize_pane_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="@dimen/apps_customize_page_indicator_offset"
+ launcher:widgetCountX="@integer/apps_customize_widget_cell_count_x"
+ launcher:widgetCountY="@integer/apps_customize_widget_cell_count_y"
+ launcher:clingFocusedX="@integer/apps_customize_cling_focused_x"
+ launcher:clingFocusedY="@integer/apps_customize_cling_focused_y"
+ launcher:maxGap="@dimen/workspace_max_gap"
+ launcher:pageIndicator="@+id/page_indicator" />
+ <FrameLayout
+ android:id="@+id/animation_buffer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#FF000000"
+ android:visibility="gone" />
+ <include
+ android:id="@+id/page_indicator"
+ layout="@layout/page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/apps_customize_page_indicator_margin" />
+ </FrameLayout>
+ </LinearLayout>
+</com.android.launcher3.AppsCustomizeTabHost>
diff --git a/res/layout/apps_customize_progressbar.xml b/res/layout/apps_customize_progressbar.xml
new file mode 100644
index 0000000..6aa9099
--- /dev/null
+++ b/res/layout/apps_customize_progressbar.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<ProgressBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="?android:attr/progressBarStyleLarge"
+ android:id="@+id/apps_customize_progress_bar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
diff --git a/res/layout/apps_customize_widget.xml b/res/layout/apps_customize_widget.xml
new file mode 100644
index 0000000..f2d2342
--- /dev/null
+++ b/res/layout/apps_customize_widget.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.PagedViewWidget
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical"
+
+ android:background="@drawable/focusable_view_bg"
+ android:focusable="true">
+
+ <!-- The preview of the widget or shortcut. -->
+ <com.android.launcher3.PagedViewWidgetImageView
+ android:id="@+id/widget_preview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="@dimen/app_widget_preview_padding_top"
+ android:paddingStart="@dimen/app_widget_preview_padding_left"
+ android:paddingEnd="@dimen/app_widget_preview_padding_right"
+ android:scaleType="matrix"
+ android:background="@drawable/screenpanel" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/app_widget_preview_label_margin_top"
+ android:layout_marginStart="@dimen/app_widget_preview_label_margin_left"
+ android:layout_marginEnd="@dimen/app_widget_preview_label_margin_right"
+ android:orientation="horizontal">
+ <!-- The name of the widget. -->
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="start"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+
+ android:textColor="#FFFFFFFF"
+ android:textSize="13sp"
+ android:textAlignment="viewStart"
+ android:fontFamily="sans-serif-condensed"
+ android:shadowRadius="2.0"
+ android:shadowColor="#B0000000" />
+
+ <!-- The original dimensions of the widget (can't be the same text as above due to different
+ style. -->
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_dims"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="5dp"
+ android:layout_weight="0"
+ android:gravity="start"
+
+ android:textColor="#FFAAAAAA"
+ android:textSize="12sp"
+ android:fontFamily="sans-serif-condensed"
+ android:shadowRadius="2.0"
+ android:shadowColor="#B0000000" />
+ </LinearLayout>
+
+
+</com.android.launcher3.PagedViewWidget>
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
new file mode 100644
index 0000000..f5a9148
--- /dev/null
+++ b/res/layout/appwidget_error.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="10dip"
+ android:paddingBottom="10dip"
+ android:paddingStart="20dip"
+ android:paddingEnd="20dip"
+ android:gravity="center"
+ android:background="@drawable/bg_appwidget_error"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
+ android:textColor="@color/appwidget_error_color"
+ android:text="@string/gadget_error_text"
+ />
diff --git a/res/layout/custom_content_page_indicator_marker.xml b/res/layout/custom_content_page_indicator_marker.xml
new file mode 100644
index 0000000..8fe3f8f
--- /dev/null
+++ b/res/layout/custom_content_page_indicator_marker.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<com.android.launcher3.PageIndicatorMarker
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_gravity="center_vertical">
+ <ImageView
+ android:id="@+id/inactive"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerInside"
+ android:src="@drawable/custom_content_page"
+ />
+ <ImageView
+ android:id="@+id/active"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerInside"
+ android:src="@drawable/custom_content_page"
+ android:alpha="0"
+ android:scaleX="0.5"
+ android:scaleY="0.5"
+ />
+</com.android.launcher3.PageIndicatorMarker>
diff --git a/res/layout/custom_workspace_cling.xml b/res/layout/custom_workspace_cling.xml
new file mode 100644
index 0000000..3744f2e
--- /dev/null
+++ b/res/layout/custom_workspace_cling.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<!-- dummy layout, to be replaced in overlays -->
+<com.android.launcher3.Cling
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:drawIdentifier="workspace_custom">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="20dp"
+ android:textColor="@android:color/holo_blue_light"
+ android:text="@string/dummy_custom_cling_error_message"
+ android:gravity="start"
+ android:layout_gravity="bottom|start"
+ />
+</com.android.launcher3.Cling>
diff --git a/res/layout/drop_target_bar.xml b/res/layout/drop_target_bar.xml
new file mode 100644
index 0000000..f38a500
--- /dev/null
+++ b/res/layout/drop_target_bar.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/DropTargetButtonContainer"
+ android:layout_weight="1">
+ <!-- Delete target -->
+ <com.android.launcher3.DeleteDropTarget
+ style="@style/DropTargetButton"
+ android:id="@+id/delete_target_text"
+ android:text="@string/delete_zone_label_workspace"
+ android:drawableStart="@drawable/remove_target_selector" />
+ </FrameLayout>
+ <FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/DropTargetButtonContainer"
+ android:layout_weight="1">
+ <!-- Info target -->
+ <com.android.launcher3.InfoDropTarget
+ style="@style/DropTargetButton"
+ android:id="@+id/info_target_text"
+ android:text="@string/info_target_label"
+ android:drawableStart="@drawable/info_target_selector" />
+ </FrameLayout>
+</merge>
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
new file mode 100644
index 0000000..4405682
--- /dev/null
+++ b/res/layout/folder_icon.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.FolderIcon
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:background="@drawable/focusable_view_bg">
+ <ImageView
+ android:id="@+id/preview_background"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:antialias="true"
+ android:src="@drawable/portal_ring_inner_holo"/>
+ <com.android.launcher3.BubbleTextView
+ style="@style/WorkspaceIcon"
+ android:id="@+id/folder_icon_name" />
+</com.android.launcher3.FolderIcon>
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
new file mode 100644
index 0000000..dc9ed2a
--- /dev/null
+++ b/res/layout/hotseat.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.Hotseat
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <com.android.launcher3.CellLayout
+ android:id="@+id/layout"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+</com.android.launcher3.Hotseat>
diff --git a/res/layout/market_button.xml b/res/layout/market_button.xml
new file mode 100644
index 0000000..4a718c3
--- /dev/null
+++ b/res/layout/market_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/MarketButton"
+ android:onClick="onClickAppMarketButton"
+ android:gravity="center"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:background="@drawable/tab_widget_indicator_selector"
+ android:contentDescription="@string/market"
+ android:shadowColor="@color/workspace_all_apps_and_delete_zone_text_shadow_color"
+ android:shadowDx="0.0"
+ android:shadowDy="0.0"
+ android:shadowRadius="2.0"
+ android:focusable="true"
+ android:clickable="true" />
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
new file mode 100644
index 0000000..e36004c
--- /dev/null
+++ b/res/layout/overview_panel.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center_horizontal|bottom"
+ android:paddingBottom="@dimen/overview_panel_bottom_padding">
+
+ <TextView
+ android:id="@+id/wallpaper_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/wallpaper_button_text"
+ android:drawablePadding="4dp"
+ android:drawableTop="@drawable/wallpaper_button"
+ android:gravity="center_horizontal"
+ android:fontFamily="sans-serif-condensed"
+ android:textAllCaps="true"
+ android:textSize="12sp" />
+ <Space
+ android:layout_width="@dimen/overview_panel_buttonSpacing"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/widget_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/widget_button_text"
+ android:drawablePadding="4dp"
+ android:gravity="center_horizontal"
+ android:drawableTop="@drawable/widget_button"
+ android:fontFamily="sans-serif-condensed"
+ android:textAllCaps="true"
+ android:textSize="12sp"/>
+ <Space
+ android:layout_width="@dimen/overview_panel_buttonSpacing"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/settings_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/settings_button_text"
+ android:drawablePadding="4dp"
+ android:gravity="center_horizontal"
+ android:drawableTop="@drawable/setting_button"
+ android:fontFamily="sans-serif-condensed"
+ android:textAllCaps="true"
+ android:textSize="12sp" />
+</LinearLayout>
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
new file mode 100644
index 0000000..14eff75
--- /dev/null
+++ b/res/layout/page_indicator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<com.android.launcher3.PageIndicator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:animateLayoutChanges="true"
+ launcher:windowSize="@integer/config_maxNumberOfPageIndicatorsToShow">
+</com.android.launcher3.PageIndicator>
diff --git a/res/layout/page_indicator_marker.xml b/res/layout/page_indicator_marker.xml
new file mode 100644
index 0000000..7c0c389
--- /dev/null
+++ b/res/layout/page_indicator_marker.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<com.android.launcher3.PageIndicatorMarker
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_gravity="center_vertical">
+ <ImageView
+ android:id="@+id/inactive"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_pageindicator_default"
+ />
+ <ImageView
+ android:id="@+id/active"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_pageindicator_current"
+ android:alpha="0"
+ android:scaleX="0.5"
+ android:scaleY="0.5"
+ />
+</com.android.launcher3.PageIndicatorMarker>
diff --git a/res/layout/qsb_bar.xml b/res/layout/qsb_bar.xml
new file mode 100644
index 0000000..030acf6
--- /dev/null
+++ b/res/layout/qsb_bar.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.launcher3.SearchDropTargetBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/QSBBar"
+ android:focusable="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- Drag specific targets container -->
+ <LinearLayout
+ style="@style/SearchDropTargetBar"
+ android:id="@+id/drag_target_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center">
+
+ <include
+ layout="@layout/drop_target_bar" />
+ </LinearLayout>
+</com.android.launcher3.SearchDropTargetBar>
diff --git a/res/layout/rename_folder.xml b/res/layout/rename_folder.xml
new file mode 100644
index 0000000..21a335c
--- /dev/null
+++ b/res/layout/rename_folder.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="20dip"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/label"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/rename_folder_label"
+ android:gravity="start"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/folder_name"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:scrollHorizontally="true"
+ android:autoText="false"
+ android:capitalize="none"
+ android:gravity="fill_horizontal"
+ android:maxLength="30"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</LinearLayout>
diff --git a/res/layout/tab_widget_indicator.xml b/res/layout/tab_widget_indicator.xml
new file mode 100644
index 0000000..de7c50e
--- /dev/null
+++ b/res/layout/tab_widget_indicator.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<com.android.launcher3.AccessibleTabView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/TabIndicator.AppsCustomize" />
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
new file mode 100644
index 0000000..5d5f33b
--- /dev/null
+++ b/res/layout/user_folder.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.Folder
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:background="@drawable/portal_container_holo">
+
+ <ScrollView
+ android:id="@+id/scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.launcher3.CellLayout
+ android:id="@+id/folder_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:cacheColorHint="#ff333333"
+ android:hapticFeedbackEnabled="false" />
+ </ScrollView>
+
+ <com.android.launcher3.FolderEditText
+ android:id="@+id/folder_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:paddingTop="@dimen/folder_name_padding"
+ android:paddingBottom="@dimen/folder_name_padding"
+ android:background="#00000000"
+ android:hint="@string/folder_hint_text"
+ android:textSize="14sp"
+ android:textColor="#ff777777"
+ android:textColorHighlight="#ffCCCCCC"
+ android:textCursorDrawable="@null"
+ android:gravity="center_horizontal"
+ android:singleLine="true"
+ android:imeOptions="flagNoExtractUi"
+ android:fontFamily="sans-serif-condensed"/>
+</com.android.launcher3.Folder>
diff --git a/res/layout/wallpaper_cropper.xml b/res/layout/wallpaper_cropper.xml
new file mode 100644
index 0000000..3a3d98a
--- /dev/null
+++ b/res/layout/wallpaper_cropper.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/wallpaper_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.launcher3.CropView
+ android:id="@+id/cropView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ <ProgressBar
+ android:id="@+id/loading"
+ style="@android:style/Widget.Holo.ProgressBar.Large"
+ android:visibility="invisible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:background="@android:color/transparent" />
+</RelativeLayout>
diff --git a/res/layout/wallpaper_picker.xml b/res/layout/wallpaper_picker.xml
new file mode 100644
index 0000000..c91cc7e
--- /dev/null
+++ b/res/layout/wallpaper_picker.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, 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.
+*/
+-->
+
+<com.android.launcher3.WallpaperRootView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/wallpaper_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.android.launcher3.CropView
+ android:id="@+id/cropView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ <ProgressBar
+ android:id="@+id/loading"
+ style="@android:style/Widget.Holo.ProgressBar.Large"
+ android:visibility="invisible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:background="@android:color/transparent" />
+ <LinearLayout
+ android:id="@+id/wallpaper_strip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:orientation="vertical" >
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="2dp"
+ android:background="@drawable/tile_shadow_top" />
+ <HorizontalScrollView
+ android:id="@+id/wallpaper_scroll_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+ <LinearLayout android:id="@+id/master_wallpaper_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <LinearLayout android:id="@+id/wallpaper_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" />
+ <LinearLayout android:id="@+id/live_wallpaper_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" />
+ <LinearLayout android:id="@+id/third_party_wallpaper_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" />
+ </LinearLayout>
+ </HorizontalScrollView>
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="2dp"
+ android:background="@drawable/tile_shadow_bottom" />
+ </LinearLayout>
+</com.android.launcher3.WallpaperRootView>
diff --git a/res/layout/wallpaper_picker_image_picker_item.xml b/res/layout/wallpaper_picker_image_picker_item.xml
new file mode 100644
index 0000000..ae3c43d
--- /dev/null
+++ b/res/layout/wallpaper_picker_image_picker_item.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.CheckableFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/wallpaperThumbnailWidth"
+ android:layout_height="@dimen/wallpaperThumbnailHeight"
+ android:focusable="true"
+ android:clickable="true"
+ android:background="@drawable/wallpaper_tile_fg"
+ android:foreground="@drawable/wallpaper_tile_fg">
+ <ImageView
+ android:id="@+id/wallpaper_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/wallpaper_picker_translucent_gray"
+ android:scaleType="centerCrop" />
+ <TextView
+ android:id="@+id/wallpaper_item_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:text="@string/pick_image"
+ android:drawableTop="@drawable/ic_images"
+ android:drawablePadding="4dp"
+ android:textColor="@android:color/white"/>
+</com.android.launcher3.CheckableFrameLayout>
diff --git a/res/layout/wallpaper_picker_item.xml b/res/layout/wallpaper_picker_item.xml
new file mode 100644
index 0000000..0ac8f97
--- /dev/null
+++ b/res/layout/wallpaper_picker_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.CheckableFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/wallpaperThumbnailWidth"
+ android:layout_height="@dimen/wallpaperThumbnailHeight"
+ android:focusable="true"
+ android:clickable="true"
+ android:background="@drawable/wallpaper_tile_fg"
+ android:foreground="@drawable/wallpaper_tile_fg">
+ <ImageView
+ android:id="@+id/wallpaper_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop" />
+</com.android.launcher3.CheckableFrameLayout>
diff --git a/res/layout/wallpaper_picker_live_wallpaper_item.xml b/res/layout/wallpaper_picker_live_wallpaper_item.xml
new file mode 100644
index 0000000..29fdb1b
--- /dev/null
+++ b/res/layout/wallpaper_picker_live_wallpaper_item.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.CheckableFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/wallpaperThumbnailWidth"
+ android:layout_height="@dimen/wallpaperThumbnailHeight"
+ android:focusable="true"
+ android:clickable="true"
+ android:background="@drawable/wallpaper_tile_fg"
+ android:foreground="@drawable/wallpaper_tile_fg">
+ <ImageView
+ android:id="@+id/wallpaper_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:background="@android:color/black"
+ android:scaleType="centerCrop" />
+ <ImageView
+ android:id="@+id/wallpaper_icon"
+ android:layout_width="@dimen/wallpaperItemIconSize"
+ android:layout_height="@dimen/wallpaperItemIconSize"
+ android:layout_gravity="center"
+ android:visibility="gone" />
+ <TextView
+ android:id="@+id/wallpaper_item_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:gravity="center"
+ android:padding="4dp"
+ android:layout_gravity="bottom"
+ android:background="@color/wallpaper_picker_translucent_gray"
+ android:textColor="@android:color/white"/>
+</com.android.launcher3.CheckableFrameLayout>
diff --git a/res/layout/wallpaper_picker_third_party_item.xml b/res/layout/wallpaper_picker_third_party_item.xml
new file mode 100644
index 0000000..68661bc
--- /dev/null
+++ b/res/layout/wallpaper_picker_third_party_item.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.CheckableFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/wallpaperThumbnailWidth"
+ android:layout_height="@dimen/wallpaperThumbnailHeight"
+ android:focusable="true"
+ android:clickable="true"
+ android:background="@drawable/wallpaper_tile_fg"
+ android:foreground="@drawable/wallpaper_tile_fg">
+ <ImageView
+ android:id="@+id/wallpaper_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/wallpaper_picker_translucent_gray"
+ android:scaleType="centerCrop" />
+ <TextView
+ android:id="@+id/wallpaper_item_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:drawablePadding="4dp"
+ android:textColor="@android:color/white"/>
+</com.android.launcher3.CheckableFrameLayout>
diff --git a/res/layout/workspace_screen.xml b/res/layout/workspace_screen.xml
new file mode 100644
index 0000000..855cf39
--- /dev/null
+++ b/res/layout/workspace_screen.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.launcher3.CellLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
+
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:hapticFeedbackEnabled="false"
+
+ launcher:maxGap="@dimen/workspace_max_gap" />
diff --git a/res/menu/cab_delete_wallpapers.xml b/res/menu/cab_delete_wallpapers.xml
new file mode 100644
index 0000000..38ac5c4
--- /dev/null
+++ b/res/menu/cab_delete_wallpapers.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, 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.
+*/
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item
+ android:id="@+id/menu_delete"
+ android:title="@string/wallpaper_delete"
+ android:showAsAction="always"
+ android:icon="@android:drawable/ic_menu_delete" />
+</menu>
diff --git a/res/mipmap-hdpi/ic_launcher_application.png b/res/mipmap-hdpi/ic_launcher_application.png
new file mode 100644
index 0000000..b9aa101
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-hdpi/ic_launcher_home.png b/res/mipmap-hdpi/ic_launcher_home.png
new file mode 100644
index 0000000..b556d7a
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/mipmap-hdpi/ic_launcher_wallpaper.png b/res/mipmap-hdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..affee85
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_application.png b/res/mipmap-mdpi/ic_launcher_application.png
new file mode 100644
index 0000000..4771b85
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_home.png b/res/mipmap-mdpi/ic_launcher_home.png
new file mode 100644
index 0000000..961bb7d
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_wallpaper.png b/res/mipmap-mdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..cb4443b
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_application.png b/res/mipmap-xhdpi/ic_launcher_application.png
new file mode 100644
index 0000000..932f0f7
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home.png b/res/mipmap-xhdpi/ic_launcher_home.png
new file mode 100644
index 0000000..46ec2b7
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_wallpaper.png b/res/mipmap-xhdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..60f8dce
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_application.png b/res/mipmap-xxhdpi/ic_launcher_application.png
new file mode 100644
index 0000000..7fc739a
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_application.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home.png b/res/mipmap-xxhdpi/ic_launcher_home.png
new file mode 100644
index 0000000..d2975a3
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_home.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_wallpaper.png b/res/mipmap-xxhdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000..023fb58
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_wallpaper.png
Binary files differ
diff --git a/res/values-af-land/strings.xml b/res/values-af-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-af-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000..e167189
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Tuis"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android-kernprogramme"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Stel muurpapier"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d gekies"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d gekies"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d gekies"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Muurpapier %1$d van %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Het <xliff:g id="LABEL">%1$s</xliff:g> gekies"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Vee uit"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Kies prent"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Muurpapiere"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Snoei muurpapier"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Program is nie geïnstalleer nie."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Legstukke"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Legstukke"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Wys Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Raak en hou om \'n legstuk op te tel."</string>
+ <string name="market" msgid="2619650989819296998">"Winkel"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Kan nie item op hierdie Tuisskerm laat los nie."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Kies legstuk om te skep"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Vouernaam"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Hernoem vouer"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Kanselleer"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Voeg by Tuisskerm"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Programme"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Kortpaaie"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Legstukke"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Niks meer spasie op jou Tuisskerms nie."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Niks meer plek op die warmlaai nie."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Hierdie legstuk is te groot vir die warmlaai."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Kortpad \"<xliff:g id="NAME">%s</xliff:g>\" is geskep."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Kortpad \"<xliff:g id="NAME">%s</xliff:g>\" is verwyder."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Kortpad \"<xliff:g id="NAME">%s</xliff:g>\" bestaan reeds."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Kies kortpad"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Kies program"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Programme"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Tuis"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Verwyder"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Deïnstalleer"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Verwyder"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Deïnstalleer"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Programinligting"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Soek"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Stemsoektog"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Programme"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Verwyder"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Deïnstalleer opdatering"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Deïnstalleer program"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Programbesonderhede"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 program gekies"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 legstuk gekies"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 vouer gekies"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 kortpad gekies"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"installeer kortpaaie"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Laat \'n program toe om kortpaaie by te voeg sonder gebruikerinmenging."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"deïnstalleer kortpaaie"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Laat die program toe om kortpaaie te verwyder sonder gebruikerinmenging."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"lees Tuis-instellings en -kortpaaie"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Laat die program toe om die instellings en kortpaaie in Tuis te lees."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"skryf Tuis-instellings en -kortpaaie"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Laat die program toe om die instellings en kortpaaie in Tuis te verander."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Kon nie legstuk laai nie"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dit is \'n stelselprogram en kan nie gedeïnstalleer word nie."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Vuurpyllanseerder"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Naamlose vouer"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Tuisskerm %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Bladsy %1$d van %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Tuisskerm %1$d van %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Programme-bladsy %1$d van %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Legstukke-bladsy %1$d van %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Welkom!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Maak jouself tuis."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Skep meer skerms vir programme en vouers"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organiseer jou spasie"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Raak en hou agtergrond om muurpapier, legstukke en instellings te bestuur."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Kies \'n paar programme"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Om \'n program by jou Tuisskerm te voeg, raak en hou dit."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Hier\'s \'n vouer"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Om een soos dié te skep, raak en hou \'n program en skuif dit dan oor \'n ander een."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Vouer oopgemaak, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Raak om vouer toe te maak"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Raak om hernoem te stoor"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Vouer is gesluit"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Vouer hernoem na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Vouer: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Legstukke"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Muurpapiere"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Instellings"</string>
+</resources>
diff --git a/res/values-am-land/strings.xml b/res/values-am-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-am-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000..6e8aaa4
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"ማስጀመሪያ3"</string>
+ <string name="home" msgid="7658288663002113681">"መነሻ"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android ዋና መተግበሪያዎች"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"የግድግዳ ወረቀት አዘጋጅ"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d ተመርጧል"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d ተመርጧል"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d ተመርጧል"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"የግድግዳ ወረቀት %1$d ከ%2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> ተመርጧል"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"ሰርዝ"</string>
+ <string name="pick_image" msgid="1272073934062909527">"ምስል ምረጥ"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"የግድግዳ ወረቀቶች"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"የግድግዳ ወረቀት ከርክም"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"መተግበሪያ አልተጫነም።"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"ፍርግሞች"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"ፍርግሞች"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"ማህደረ ማስታወሻ አሳይ"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"ፍርግም ለማንሳት ይንኩ እና ይያዙት"</string>
+ <string name="market" msgid="2619650989819296998">"ግዛ"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"ንጥሉን እዚህ የመነሻ ማያ ገጽ ላይ ማኖር አልተቻለም።"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"ለመፍጠር መግብር ይምረጡ"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"አቃፊ ስም"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"አቃፊ ዳግም ሰይም"</string>
+ <string name="rename_action" msgid="5559600076028658757">"እሺ"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"ተው"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"ወደ መነሻ ማያ ገጽ ያክሉ"</string>
+ <string name="group_applications" msgid="3797214114206693605">"መተግበሪያዎች"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"አቋራጮች"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"ፍርግሞች"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"የመነሻ ማያ ገጾችዎ ላይ ተጨማሪ ቦታ የለም።"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"በመትከያ ቦታው ላይ ተጨማሪ ቦታ የለም።"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"ይህ ፍርግም ለመትከያ ቦታው በጣም ትልቅ ነው።"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"አቋራጭ «<xliff:g id="NAME">%s</xliff:g>» ተፈጥሯል።"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"አቋራጭ «<xliff:g id="NAME">%s</xliff:g>» ተወግዶ ነበር።"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"አቋራጭ «<xliff:g id="NAME">%s</xliff:g>» አስቀድሞ አለ።"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"አቋራጭ ይምረጡ"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"መተግበሪያ ይምረጡ"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"መተግበሪያዎች"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"መነሻ"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"አስወግድ"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"አራግፍ"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"አስወግድ"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"አራግፍ"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"የመተግበሪያ መረጃ"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"ፍለጋ"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"የድምፅ ፍለጋ"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"መተግበሪያዎች"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"አስወግድ"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"ዝማኔ አራግፍ"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"መተግበሪያ አራግፍ"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"የመተግበሪያ ዝርዝሮች"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 መተግበሪያ ተመርጧል"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 ፍርግም ተመርጧል"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 አቃፊ ተመርጧል"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 አቋራጭ ተመርጧል"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"አቋራጮችን ይጭናል"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"መተግበሪያው ያለተጠቃሚ ጣልቃ ገብነት አቋራጭ እንዲያክል ያስችለዋል።"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"አቋራጮችን ያራግፋል"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"መተግበሪያው አቋራጮችን ያለተጠቃሚ ጣልቃ ገብነት እንዲያስወግድ ያስችለዋል።"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"የመነሻ ቅንብሮች እና አቋራጮችን ያነባል"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"መተግበሪያው በመነሻ ውስጥ ያሉ ቅንብሮችን እና አቋራጮችን እንዲያነብ ያስችለዋል።"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"የመነሻ ቅንብሮችን እና አቋራጮችን ይጽፋል"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"መተግብሪያው ቅንብሮችን እና አቋራጮችን በመነሻ ውስጥ እንዲቀይራቸው ያስችለዋል።"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"ፍርግም የመጫን ችግር"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"ይህ የስርዓት መተግበሪያ ነው እና ማራገፍ አይቻልም።"</string>
+ <string name="dream_name" msgid="1530253749244328964">"የሮኬት ማስጀመሪያ"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"ስም-አልባ አቃፊ"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"መነሻ ማያ ገጽ %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"ገጽ %1$d ከ%2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"መነሻ ማያ ገጽ %1$d ከ%2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"የመተግበሪያዎች ገጽ %1$d ከ%2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"የመግብሮች ገጽ %1$d ከ%2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"እንኳን ደህና መጡ!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"ልክ እቤትዎ እንዳሉ ሆነው ዘና ይበሉ።"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"ለመተግበሪያዎች እና አቃፊዎች ተጨማሪ ማያ ገጾችን ይፍጠሩ"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"ቦታዎን ያደራጁ"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"የግድግዳ ወረቀት፣ ምግብሮችን እና ቅንብሮችን ለማቀናበር ጀርባውን ይንኩ እና ይያዙት።"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"አንዳንድ መተግበሪያዎችን ይምረጡ"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"አንድ መተግበሪያ ወደ መነሻ ማያ ገጽዎ ለማከል ይንኩት እና ይያዙት።"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"አንድ አቃፊ እነሆ"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"አንድ እንደዚህ አይነት ለመፍጠር መተግበሪያውን ነክተው ይያዙት እና ወደ ሌላ ያንቀሳቅሱት።"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"እሺ"</string>
+ <string name="folder_opened" msgid="94695026776264709">"አቃፊ ተከፍቷል፣ <xliff:g id="WIDTH">%1$d</xliff:g> በ<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"አቃፊን ለመዝጋት ይንኩ"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"ዳግም የተሰየመውን ለማስቀመጥ ይንኩ"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"አቃፊ ተዘግቷል"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"አቃፊ <xliff:g id="NAME">%1$s</xliff:g> ተብሎ ዳግም ተሰይሟል"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"አቃፊ፦ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"ፍርግሞች"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"የግድግዳ ወረቀቶች"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"ቅንብሮች"</string>
+</resources>
diff --git a/res/values-ar-land/strings.xml b/res/values-ar-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ar-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000..73ee690
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"الرئيسية"</string>
+ <string name="uid_name" msgid="7820867637514617527">"تطبيقات Android الأساسية"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"تعيين خلفية"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"تم تحديد %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"تم تحديد %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"تم تحديد %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"الخلفية %1$d من %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"تم تحديد <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"حذف"</string>
+ <string name="pick_image" msgid="1272073934062909527">"اختيار صورة"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"الخلفيات"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"اقتصاص الخلفية"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"الأدوات"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"الأدوات"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"عرض الذاكرة"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"المس مع الاستمرار لاختيار إحدى الأدوات."</string>
+ <string name="market" msgid="2619650989819296998">"تسوق"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"تعذر إسقاط العنصر على هذه الشاشة الرئيسية."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"اختيار أداة لإنشائها"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"اسم المجلد"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"إعادة تسمية المجلد"</string>
+ <string name="rename_action" msgid="5559600076028658757">"موافق"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"إلغاء"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"إضافة إلى الشاشة الرئيسية"</string>
+ <string name="group_applications" msgid="3797214114206693605">"التطبيقات"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"الاختصارات"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"الأدوات"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"ليس هناك مساحة أخرى في الشاشات الرئيسية."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"ليست هناك مساحة أخرى في منطقة الإرساء القابلة للتخصيص."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"حجم هذه الأداة كبير للغاية بحيث لا تتسع له منطقة الإرساء القابلة للتخصيص."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"تم إنشاء الاختصار \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"تمت إزالة الاختصار \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"الاختصار \"<xliff:g id="NAME">%s</xliff:g>\" موجود من قبل."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"اختيار اختصار"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"اختيار تطبيق"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"التطبيقات"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"الرئيسية"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"إزالة"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"إزالة"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"إزالة"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إزالة"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"معلومات عن التطبيق"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"بحث"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"البحث الصوتي"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"التطبيقات"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"إزالة"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"إزالة التحديث"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"إزالة التطبيق"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"تفاصيل التطبيق"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"تم تحديد تطبيق واحد"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"تم تحديد أداة واحدة"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"تم تحديد مجلد واحد"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"تم تحديد اختصار واحد"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"تثبيت اختصارات"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"إزالة الاختصارات"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"للسماح للتطبيق بإزالة الاختصارات بدون تدخل المستخدم."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"قراءة إعدادات واختصارات الشاشة الرئيسية"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"للسماح للتطبيق بقراءة الإعدادات والاختصارات في الشاشة الرئيسية."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"كتابة إعدادات واختصارات الشاشة الرئيسية"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"للسماح للتطبيق بتغيير الإعدادات والاختصارات في الشاشة الرئيسية."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"حدثت مشكلة أثناء تحميل الأداة"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"هذا تطبيق نظام وتتعذر إزالته."</string>
+ <string name="dream_name" msgid="1530253749244328964">"قاذفة صواريخ"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"مجلد بدون اسم"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"الشاشة الرئيسية %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"الصفحة %1$d من %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"الشاشة الرئيسية %1$d من %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"صفحة التطبيقات %1$d من %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"صفحة الأدوات %1$d من %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"مرحبًا!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"تصرف على راحتك."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"إنشاء المزيد من الشاشات للتطبيقات والمجلدات"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"تنظيم مساحتك"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"المس مع الاستمرار الجزء الخلفي من صورة الشاشة لإدارة الخلفية والأدوات والإعدادات."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"اختيار بعض التطبيقات"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"لإضافة تطبيق إلى الشاشة الرئيسية، المسه مع الاستمرار."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"إليك المجلد"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"لإنشاء مجلد مثل هذا، المس أحد التطبيقات مع استمرار اللمس، ثم حركه فوق آخر."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"موافق"</string>
+ <string name="folder_opened" msgid="94695026776264709">"تم فتح المجلد، بمقاس <xliff:g id="WIDTH">%1$d</xliff:g> في <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"المس لإغلاق المجلد"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"المس لحفظ إعادة التسمية"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"تم إغلاق المجلد"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"تمت إعادة تسمية المجلد إلى <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"المجلد: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"الأدوات"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"الخلفيات"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"الإعدادات"</string>
+</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
new file mode 100644
index 0000000..ddcf404
--- /dev/null
+++ b/res/values-be/strings.xml
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for application_name (5181331383435256801) -->
+ <skip />
+ <!-- no translation found for home (7658288663002113681) -->
+ <skip />
+ <!-- no translation found for uid_name (7820867637514617527) -->
+ <skip />
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <!-- no translation found for wallpaper_instructions (563973358787555519) -->
+ <skip />
+ <!-- no translation found for number_of_items_selected:zero (7464587177007785408) -->
+ <!-- no translation found for number_of_items_selected:one (142482526010824029) -->
+ <!-- no translation found for number_of_items_selected:other (1418352074806573570) -->
+ <!-- no translation found for wallpaper_accessibility_name (1655953108132967972) -->
+ <skip />
+ <!-- no translation found for announce_selection (8338254712932127413) -->
+ <skip />
+ <!-- no translation found for wallpaper_delete (8095005658756613921) -->
+ <skip />
+ <!-- no translation found for pick_image (1272073934062909527) -->
+ <skip />
+ <!-- no translation found for pick_wallpaper (8179698221502010609) -->
+ <skip />
+ <!-- no translation found for crop_wallpaper (8334345984491368009) -->
+ <skip />
+ <!-- no translation found for activity_not_found (8071924732094499514) -->
+ <skip />
+ <!-- no translation found for widgets_tab_label (2921133187116603919) -->
+ <skip />
+ <!-- no translation found for widget_adder (3201040140710381657) -->
+ <skip />
+ <!-- no translation found for toggle_weight_watcher (5645299835184636119) -->
+ <skip />
+ <!-- no translation found for long_press_widget_to_add (7699152356777458215) -->
+ <skip />
+ <!-- no translation found for market (2619650989819296998) -->
+ <skip />
+ <!-- no translation found for widget_dims_format (2370757736025621599) -->
+ <skip />
+ <!-- no translation found for external_drop_widget_error (3165821058322217155) -->
+ <skip />
+ <!-- no translation found for external_drop_widget_pick_title (3486317258037690630) -->
+ <skip />
+ <!-- no translation found for rename_folder_label (3727762225964550653) -->
+ <skip />
+ <!-- no translation found for rename_folder_title (3771389277707820891) -->
+ <skip />
+ <!-- no translation found for rename_action (5559600076028658757) -->
+ <skip />
+ <!-- no translation found for cancel_action (7009134900002915310) -->
+ <skip />
+ <!-- no translation found for menu_item_add_item (1264911265836810421) -->
+ <skip />
+ <!-- no translation found for group_applications (3797214114206693605) -->
+ <skip />
+ <!-- no translation found for group_shortcuts (6012256992764410535) -->
+ <skip />
+ <!-- no translation found for group_widgets (1569030723286851002) -->
+ <skip />
+ <!-- no translation found for completely_out_of_space (6106288382070760318) -->
+ <skip />
+ <!-- no translation found for out_of_space (4691004494942118364) -->
+ <skip />
+ <!-- no translation found for hotseat_out_of_space (9139760413395605841) -->
+ <skip />
+ <!-- no translation found for invalid_hotseat_item (1211534262129849507) -->
+ <skip />
+ <!-- no translation found for shortcut_installed (1701742129426969556) -->
+ <skip />
+ <!-- no translation found for shortcut_uninstalled (8176767991305701821) -->
+ <skip />
+ <!-- no translation found for shortcut_duplicate (9167217446062498127) -->
+ <skip />
+ <!-- no translation found for title_select_shortcut (6680642571148153868) -->
+ <skip />
+ <!-- no translation found for title_select_application (3280812711670683644) -->
+ <skip />
+ <!-- no translation found for all_apps_button_label (9110807029020582876) -->
+ <skip />
+ <!-- no translation found for all_apps_home_button_label (252062713717058851) -->
+ <skip />
+ <!-- no translation found for delete_zone_label_workspace (4009607676751398685) -->
+ <skip />
+ <!-- no translation found for delete_zone_label_all_apps (8083826390278958980) -->
+ <skip />
+ <!-- no translation found for delete_target_label (1822697352535677073) -->
+ <skip />
+ <!-- no translation found for delete_target_uninstall_label (5100785476250872595) -->
+ <skip />
+ <!-- no translation found for info_target_label (8053346143994679532) -->
+ <skip />
+ <!-- no translation found for accessibility_search_button (1628520399424565142) -->
+ <skip />
+ <!-- no translation found for accessibility_voice_search_button (4637324840434406584) -->
+ <skip />
+ <!-- no translation found for accessibility_all_apps_button (2603132375383800483) -->
+ <skip />
+ <!-- no translation found for accessibility_delete_button (6466114477993744621) -->
+ <skip />
+ <!-- no translation found for delete_zone_label_all_apps_system_app (449755632749610895) -->
+ <skip />
+ <!-- no translation found for cab_menu_delete_app (7435191475867183689) -->
+ <skip />
+ <!-- no translation found for cab_menu_app_info (8593722221450362342) -->
+ <skip />
+ <!-- no translation found for cab_app_selection_text (374688303047985416) -->
+ <skip />
+ <!-- no translation found for cab_widget_selection_text (1833458597831541241) -->
+ <skip />
+ <!-- no translation found for cab_folder_selection_text (7999992513806132118) -->
+ <skip />
+ <!-- no translation found for cab_shortcut_selection_text (2103811025667946450) -->
+ <skip />
+ <!-- no translation found for permlab_install_shortcut (5632423390354674437) -->
+ <skip />
+ <!-- no translation found for permdesc_install_shortcut (923466509822011139) -->
+ <skip />
+ <!-- no translation found for permlab_uninstall_shortcut (864595034498083837) -->
+ <skip />
+ <!-- no translation found for permdesc_uninstall_shortcut (5134129545001836849) -->
+ <skip />
+ <!-- no translation found for permlab_read_settings (1941457408239617576) -->
+ <skip />
+ <!-- no translation found for permdesc_read_settings (5833423719057558387) -->
+ <skip />
+ <!-- no translation found for permlab_write_settings (3574213698004620587) -->
+ <skip />
+ <!-- no translation found for permdesc_write_settings (5440712911516509985) -->
+ <skip />
+ <!-- no translation found for gadget_error_text (6081085226050792095) -->
+ <skip />
+ <!-- no translation found for uninstall_system_app_text (4172046090762920660) -->
+ <skip />
+ <!-- no translation found for dream_name (1530253749244328964) -->
+ <skip />
+ <!-- no translation found for folder_hint_text (6617836969016293992) -->
+ <skip />
+ <!-- no translation found for workspace_description_format (2950174241104043327) -->
+ <skip />
+ <!-- no translation found for default_scroll_format (7475544710230993317) -->
+ <skip />
+ <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
+ <skip />
+ <!-- no translation found for apps_customize_apps_scroll_format (370005296147130238) -->
+ <skip />
+ <!-- no translation found for apps_customize_widgets_scroll_format (3106209519974971521) -->
+ <skip />
+ <!-- no translation found for first_run_cling_title (7257389003637362144) -->
+ <skip />
+ <!-- no translation found for first_run_cling_description (6447072552696253358) -->
+ <skip />
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <!-- no translation found for first_run_cling_create_screens_hint (6950729526680114157) -->
+ <skip />
+ <!-- no translation found for workspace_cling_title (5626202359865825661) -->
+ <skip />
+ <!-- no translation found for workspace_cling_move_item (528201129978005352) -->
+ <skip />
+ <!-- no translation found for all_apps_cling_title (34929250753095858) -->
+ <skip />
+ <!-- no translation found for all_apps_cling_add_item (400866858451850784) -->
+ <skip />
+ <!-- no translation found for folder_cling_title (3894908818693254164) -->
+ <skip />
+ <!-- no translation found for folder_cling_create_folder (6158215559475836131) -->
+ <skip />
+ <!-- no translation found for cling_dismiss (8962359497601507581) -->
+ <skip />
+ <!-- no translation found for folder_opened (94695026776264709) -->
+ <skip />
+ <!-- no translation found for folder_tap_to_close (1884479294466410023) -->
+ <skip />
+ <!-- no translation found for folder_tap_to_rename (9191075570492871147) -->
+ <skip />
+ <!-- no translation found for folder_closed (4100806530910930934) -->
+ <skip />
+ <!-- no translation found for folder_renamed (1794088362165669656) -->
+ <skip />
+ <!-- no translation found for folder_name_format (6629239338071103179) -->
+ <skip />
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <!-- no translation found for widget_button_text (2880537293434387943) -->
+ <skip />
+ <!-- no translation found for wallpaper_button_text (8404103075899945851) -->
+ <skip />
+ <!-- no translation found for settings_button_text (8119458837558863227) -->
+ <skip />
+</resources>
diff --git a/res/values-bg-land/strings.xml b/res/values-bg-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-bg-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000..2f53fa3
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Начало"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Основни приложения на Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Задаване на тапета"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Избрахте %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Избрахте %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Избрахте %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Тапет %1$d от %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Избрахте <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Изтриване"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Избиране на изображение"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Тапети"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Подрязване на тапета"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Приложението не е инсталирано."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Приспособления"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Приспособления"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Показване на паметта"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Докоснете и задръжте за избор на приспособление."</string>
+ <string name="market" msgid="2619650989819296998">"Пазаруване"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Не можа да се премести на този начален екран."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Избор на приспособл. за създаване"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Име на папката"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Преименуване на папка"</string>
+ <string name="rename_action" msgid="5559600076028658757">"ОK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Отказ"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Добавяне към началния екран"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Приложения"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Преки пътища"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Приспособления"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"На началните ви екрани няма повече място."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"На този начален екран няма повече място."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"В трамплина няма повече място."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Това приспособление е твърде голямо за трамплина."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Прекият път към <xliff:g id="NAME">%s</xliff:g> е създаден."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Прекият път към <xliff:g id="NAME">%s</xliff:g> бе премахнат."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Прекият път към <xliff:g id="NAME">%s</xliff:g> вече съществува."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Избор на пряк път"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Избор на приложение"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Начало"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Премахване"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Деинсталиране"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Премахване"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Деинсталиране"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Информация за приложението"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Търсене"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Гласово търсене"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Приложения"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Премахване"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Деинст. на актуализацията"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Деинсталиране на приложението"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Подробности за приложението"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Избрано е 1 приложение"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Избрано е 1 приспособление"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Избрана е 1 папка"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Избран е 1 пряк път"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталиране на преки пътища"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Разрешава на приложението да добавя преки пътища без намеса на потребителя."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"деинсталиране на преки пътища"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Разрешава на приложението да премахва преки пътища без намеса на потребителя."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"четене на настройките и преките пътища в Начало"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Разрешава на приложението да чете настройките и преките пътища в Начало."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"запис на настройките и преките пътища в Начало"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Разрешава на приложението да променя настройките и преките пътища в Начало."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Проблем при зареждане на приспособлението"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Това е системно приложение и не може да се деинсталира."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Ракетна площадка"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Папка без име"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Начален екран %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Страница %1$d от %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Начален екран %1$d от %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Страница с приложения %1$d от %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Страница с приспособления %1$d от %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Добре дошли!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Персонализиране и приспособяване."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Създаване на още екрани за приложения и папки"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Организиране на мястото ви"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Докоснете и задръжте фона, за да управлявате тапета, приспособленията и настройките."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Изберете някои приложения"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"За да добавите приложение към началния си екран, го докоснете и задръжте."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Ето една папка"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"За да създадете подобна, докоснете и задръжте приложение, след което го преместете върху друго."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"ОK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Папката е отворена – <xliff:g id="WIDTH">%1$d</xliff:g> на <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Докоснете, за да затворите папката"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Докоснете, за да запазите новото име"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Папката бе затворена"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Папката е преименувана на „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Папка: „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Приспособления"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Настройки"</string>
+</resources>
diff --git a/res/values-ca-land/strings.xml b/res/values-ca-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ca-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000..3400482
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Inici"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Aplicacions principals d\'Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Estableix el fons de pantalla"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Seleccionats: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Seleccionats: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Seleccionats: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fons de pantalla %1$d de %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"S\'ha seleccionat <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Suprimeix"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Selecciona una imatge"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Fons de pantalla"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Retalla el fons de pantalla"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"L\'aplicació no s\'ha instal·lat."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostra la memòria"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén premut un widget per triar-lo."</string>
+ <string name="market" msgid="2619650989819296998">"Compra"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"No s\'ha pogut deixar anar l\'element a Inici."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Tria el widget que vulguis crear"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nom de la carpeta"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Canvi de nom de la carpeta"</string>
+ <string name="rename_action" msgid="5559600076028658757">"D\'acord"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Cancel·la"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Afegir a la pantalla d\'inici"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplicacions"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Dreceres"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"No queda espai a les pantalles d\'inici."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Ja no queda espai en aquesta pantalla d\'inici."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"No queda espai al hotseat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Aquest widget és massa gran per al hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"S\'ha creat la drecera \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"S\'ha suprimit la drecera \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"La drecera \"<xliff:g id="NAME">%s</xliff:g>\" ja existeix."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Tria d\'una drecera"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Tria d\'una aplicació"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacions"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Inici"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Suprimeix"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstal·la"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Suprimeix"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstal·la"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informació de l\'aplicació"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Cerca"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Cerca per veu"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicacions"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Suprimeix"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstal·la l\'actualització"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstal·la l\'aplicació"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalls de l\'aplicació"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicació seleccionada"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget seleccionat"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 carpeta seleccionada"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 drecera seleccionada"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instal·la dreceres"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet que una aplicació afegeixi dreceres sense la intervenció de l\'usuari."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstal·la dreceres"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permet que l\'aplicació suprimeixi dreceres sense la intervenció de l\'usuari."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"llegeix la configuració i les dreceres de la pantalla d\'inici"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permet que l\'aplicació llegeixi la configuració i les dreceres de la pantalla d\'inici."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"escriu la configuració i les dreceres de la pantalla d\'inici"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permet que l\'aplicació canviï la configuració i les dreceres de la pantalla d\'inici."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"S\'ha produït un problema en carregar el widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Aquesta aplicació és una aplicació del sistema i no es pot desinstal·lar."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Carpeta sense nom"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Pantalla d\'inici %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Pàgina %1$d de %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla d\'inici %1$d de %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pàgina d\'aplicacions %1$d de %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pàgina de widgets %1$d de %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Hola!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Personalitza la pantalla d\'inici"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crea més pantalles per a aplicacions i carpetes"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organitza el teu espai"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Toca i mantén premut el fons per gestionar el fons de pantalla, els widgets i la configuració."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Tria unes quantes aplicacions"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Per afegir una aplicació a la pantalla d\'inici, mantén-la premuda."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Aquí hi ha una carpeta"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Per crear-ne una com aquesta, mantén premuda una aplicació i, a continuació, mou-la sobre una altra."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"D\'acord"</string>
+ <string name="folder_opened" msgid="94695026776264709">"S\'ha obert la carpeta, <xliff:g id="WIDTH">%1$d</xliff:g> per <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Toca per tancar la carpeta"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toca per desar el canvi de nom"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Carpeta tancada"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"S\'ha canviat el nom de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Fons de pantalla"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Configuració"</string>
+</resources>
diff --git a/res/values-cs-land/strings.xml b/res/values-cs-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-cs-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..1061e79
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Plocha"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Nastavit jako tapetu"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Vybráno: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Vybráno: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Vybráno: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Tapeta %1$d z %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Vybrána položka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Smazat"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Vybrat obrázek"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Tapety"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Oříznutí tapety"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Aplikace není nainstalována."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgety"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgety"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Zobrazit Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget vyberete dotykem a podržením."</string>
+ <string name="market" msgid="2619650989819296998">"Obchod"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Na tuto plochu položku nelze přesunout."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Vyberte widget k vytvoření"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Název složky"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Přejmenovat složku"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Zrušit"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Přidat na plochu"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplikace"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Klávesové zkratky"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgety"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Na plochách již není místo."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Na této ploše již není místo."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"V sekci hotseat již není místo."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Widget je pro hotseat příliš velký."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Zástupce aplikace <xliff:g id="NAME">%s</xliff:g> byl vytvořen."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Zástupce aplikace <xliff:g id="NAME">%s</xliff:g> byl odebrán."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Zástupce aplikace <xliff:g id="NAME">%s</xliff:g> již existuje."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Výběr zástupce"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Výběr aplikace"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikace"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Plocha"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Odstranit"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odinstalovat"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Odstranit"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odinstalovat"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informace o aplikaci"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Hledat"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Hlasové vyhledávání"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikace"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Odstranit"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odinstalovat aktualizaci"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odinstalovat aplikaci"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Podrobnosti o aplikaci"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Vybrána 1 aplikace"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Vybrán 1 widget"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Vybrána 1 složka"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Vybrán 1 zástupce"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalace zástupce"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Umožňuje aplikaci přidat zástupce bez zásahu uživatele."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odinstalovat zástupce"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Umožňuje aplikaci odstranit zástupce bez zásahu uživatele."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"čtení nastavení a odkazů plochy"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Umožňuje aplikaci číst nastavení a odkazy na ploše."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"zápis nastavení a odkazů plochy"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Umožňuje aplikaci změnit nastavení a odkazy na ploše."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problém s načtením widgetu"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Toto je systémová aplikace a nelze ji odinstalovat."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Složka bez názvu"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Plocha %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Strana %1$d z %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Plocha %1$d z %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stránka aplikací %1$d z %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stránka widgetů %1$d z %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Vítejte!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Udělejte si pohodlí."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Vytvořte několik obrazovek pro aplikace a složky"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organizace prostoru"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Chcete-li spravovat tapetu, widgety a nastavení, dotkněte se pozadí a přidržte je."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Vyberte nějaké aplikace"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Chcete-li na plochu přidat aplikaci, dotkněte se jí a přidržte ji."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Toto je složka"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Chcete-li vytvořit složku, přetáhněte aplikaci na jinou aplikaci."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Složka otevřena, rozměry <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Dotykem složku zavřete"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dotykem uložíte změnu názvu"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Složka je uzavřena"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Složka přejmenována na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Složka: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgety"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Nastavení"</string>
+</resources>
diff --git a/res/values-da-land/strings.xml b/res/values-da-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-da-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000..5db246e
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Startskærm"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Kerneapps i Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Angiv baggrund"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d er valgt"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d er valgt"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d er valgt"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Baggrund %1$d af %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> blev valgt"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Slet"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Vælg billede"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Baggrunde"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Beskær baggrund"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installeret."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Vis Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryk på en widget, og hold den nede for at vælge."</string>
+ <string name="market" msgid="2619650989819296998">"Køb i Google Play Butik"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Elementet kunne ikke trækkes til startskærmen."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Vælg den widget, du vil oprette"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Mappenavn"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Omdøb mappe"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Annuller"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Føj til startskærm"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Genveje"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Der er ikke mere plads på dine startskærme."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Der er ikke mere plads i hotseat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Denne widget er for stor til hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Genvejen \"<xliff:g id="NAME">%s</xliff:g>\" blev oprettet."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Genvejen \"<xliff:g id="NAME">%s</xliff:g>\" blev fjernet."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Genvejen \"<xliff:g id="NAME">%s</xliff:g>\" findes allerede."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Vælg genvej"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Vælg app"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Startskærm"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Fjern"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Afinstaller"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Fjern"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Afinstaller"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Oplysninger om appen"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Søg"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Stemmesøgning"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Fjern"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Afinstaller opdatering"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Afinstaller appen"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Oplysninger om appen"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 app er valgt"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget er valgt"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 mappe er valgt"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 genvej er valgt"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer genveje"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Tillader, at en app tilføjer genveje uden brugerens indgriben."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"afinstaller genveje"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Tillader, at appen fjerner genveje uden brugerens indgriben."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"læs indstillinger og genveje for startskærmen"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Tillader, at appen læser indstillingerne og genvejene på startskærmen."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"skriv indstillinger og genveje for startskærmen"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Tillader, at appen ændrer indstillingerne og genvejene på startskærmen."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Der er problemer med indlæsning af widgetten"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dette er en systemapp, som ikke kan afinstalleres."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Unavngiven mappe"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Startskærm %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Side %1$d ud af %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskærm %1$d ud af %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps-side %1$d ud af %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets-side %1$d ud af %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Velkommen"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Føl dig hjemme."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Opret flere skærme til apps og mapper"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organiser din arbejdsplads"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Tryk på en baggrund, og hold fingeren nede for at administrere baggrunde, widgets og indstillinger."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Vælge nogle apps"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Tryk på en app, og hold fingeren nede for at føje appen til startskærmen."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Her kan du se en mappe"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Du kan oprette en mappe magen til denne ved at trykke på en app og holde fingeren nede, mens du flytter appen til en anden mappe."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Mappen er åben, <xliff:g id="WIDTH">%1$d</xliff:g> gange <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Tryk for at lukke mappen"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tryk for at gemme det nye navn"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Mappen er lukket"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Mappen er omdøbt til <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Baggrunde"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Indstillinger"</string>
+</resources>
diff --git a/res/values-de-land/strings.xml b/res/values-de-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-de-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..7a5e7e6
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Startseite"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Hintergrund auswählen"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d ausgewählt"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d ausgewählt"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d ausgewählt"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Hintergrund %1$d von %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> ausgewählt"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Entfernen"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Bild auswählen"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Hintergründe"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Hintergrund zuschneiden"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"App ist nicht installiert."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Speicher anzeigen"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Zum Hinzufügen Widget berühren und halten"</string>
+ <string name="market" msgid="2619650989819296998">"Einkaufen"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Element wurde nicht auf diesem Startbildschirm abgelegt."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Widget zum Erstellen auswählen"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Ordnername"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Ordner umbenennen"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Abbrechen"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Zum Startbildschirm hinzufügen"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Verknüpfungen"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Auf Ihren Startbildschirmen ist kein Platz mehr vorhanden."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Kein Platz mehr auf der App-Leiste"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Dieses Widget ist zu groß für die App-Leiste."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Verknüpfung \"<xliff:g id="NAME">%s</xliff:g>\" wurde erstellt."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Verknüpfung \"<xliff:g id="NAME">%s</xliff:g>\" wurde entfernt."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Verknüpfung \"<xliff:g id="NAME">%s</xliff:g>\" ist bereits vorhanden."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Verknüpfung auswählen"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"App auswählen"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Startseite"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Entfernen"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Deinstallieren"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Entfernen"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Deinstallieren"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"App-Info"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Suchen"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Sprachsuche"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Entfernen"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Update deinstallieren"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"App deinstallieren"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"App-Details"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 App ausgewählt"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 Widget ausgewählt"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 Ordner ausgewählt"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 Verknüpfung ausgewählt"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"Verknüpfungen installieren"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ermöglicht einer App das Hinzufügen von Verknüpfungen ohne Eingreifen des Nutzers"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"Verknüpfungen deinstallieren"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ermöglicht einer App das Entfernen von Verknüpfungen ohne Eingreifen des Nutzers"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"Einstellungen und Verknüpfungen auf dem Startbildschirm lesen"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Ermöglicht einer App, die Einstellungen und Verknüpfungen auf dem Startbildschirm zu lesen"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"Einstellungen und Verknüpfungen für den Startbildschirm schreiben"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Ermöglicht einer App, die Einstellungen und Verknüpfungen auf dem Startbildschirm zu ändern"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem beim Laden des Widgets"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dies ist eine Systemanwendung, die nicht deinstalliert werden kann."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Unbenannter Ordner"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Startbildschirm %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Seite %1$d von %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Startbildschirm %1$d von %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps-Seite %1$d von %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets-Seite %1$d von %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Willkommen!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Gerät personalisieren"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Weitere Bildschirme für Apps und Ordner erstellen"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Arbeitsbereich organisieren"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Hintergrund berühren und halten, um Hintergrund, Widgets und Einstellungen zu verwalten"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Apps auswählen"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Berühren und halten Sie eine App, um sie zum Startbildschirm hinzuzufügen."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Hier ist ein Ordner"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Um einen Ordner zu erstellen, berühren und halten Sie eine App und verschieben Sie sie auf eine andere."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Ordner geöffnet, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Ordner durch Berühren schließen"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Umbenennung durch Berühren speichern"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Ordner geschlossen"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Ordner umbenannt in <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Ordner: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Hintergründe"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Einstellungen"</string>
+</resources>
diff --git a/res/values-el-land/strings.xml b/res/values-el-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-el-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000..74322e0
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Αρχική σελίδα"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Βασικές εφαρμογές Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Ορισμός ταπετσαρίας"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d επιλεγμένα"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d επιλεγμένα"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d επιλεγμένα"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Ταπετσαρία %1$d από %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Επιλέχθηκε το <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Διαγραφή"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Επιλογή εικόνας"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Ταπετσαρίες"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Περικοπή ταπετσαρίας"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Η εφαρμογή δεν έχει εγκατασταθεί."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Γραφικά στοιχεία"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Γραφικά στοιχεία"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Εμφάνιση Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Αγγίξτε παρατεταμένα για να πάρετε ένα γραφ.στοιχ."</string>
+ <string name="market" msgid="2619650989819296998">"Αγορά"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Αδυναμία τοποθέτησης στοιχείου στην Αρχική οθόνη."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Επιλ. γραφ. στοιχείο για δημιουργία"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Όνομα φακέλου"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Μετονομασία φακέλου"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Ακύρωση"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Προσθήκη στην αρχική οθόνη"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Εφαρμογές"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Συντομεύσεις"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Γραφικά στοιχεία"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Δεν υπάρχει άλλος χώρος στις Αρχικές οθόνες σας."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Δεν υπάρχει άλλος χώρος στη γραμμή γρήγορης πρόσβασης."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Αυτό το γραφικό στοιχείο είναι πολύ μεγάλο για τη γραμμή γρήγορης πρόσβασης."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Δημιουργήθηκε η συντόμευση \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Η συντόμευση \"<xliff:g id="NAME">%s</xliff:g>\" καταργήθηκε."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Η συντόμευση \"<xliff:g id="NAME">%s</xliff:g>\" υπάρχει ήδη."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Επιλογή συντόμευσης"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Επιλογή εφαρμογής"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Εφαρμογές"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Αρχική σελίδα"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Κατάργηση"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Κατάργηση εγκατάστασης"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Κατάργηση"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Κατάργηση εγκατάστασης"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Πληροφορίες εφαρμογής"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Αναζήτηση"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Φωνητική αναζήτηση"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Εφαρμογές"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Κατάργηση"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Κατάργηση εγκατάστασης ενημέρωσης"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Κατάργηση εγκατάστασης εφαρμογής"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Λεπτομέρειες εφαρμογής"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Επιλέχτηκε 1 εφαρμογή"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Επιλέχτηκε 1 γραφικό στοιχείο"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Επιλέχτηκε 1 φάκελος"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Επιλέχτηκε 1 συντόμευση"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"εγκατάσταση συντομεύσεων"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"κατάργηση εγκατάστασης συντομεύσεων"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Επιτρέπει στην εφαρμογή την κατάργηση συντομεύσεων χωρίς την παρέμβαση του χρήστη."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"ανάγνωση ρυθμίσεων και συντομεύσεων αρχικής οθόνης"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Επιτρέπει στην εφαρμογή την ανάγνωση των ρυθμίσεων και των συντομεύσεων στην Αρχική οθόνη."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"εγγραφή ρυθμίσεων και συντομεύσεων αρχικής οθόνης"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Επιτρέπει στην εφαρμογή την αλλαγή των ρυθμίσεων και των συντομεύσεων στην Αρχική οθόνη."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Παρουσιάστηκε πρόβλημα στη φόρτωση του γραφικού στοιχείου"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Αυτή είναι μια εφαρμογή συστήματος και δεν είναι δυνατή η κατάργηση της εγκατάστασής της."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Φάκελος χωρίς όνομα"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Αρχική οθόνη %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Σελίδα %1$d από %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Αρχική οθόνη %1$d από %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Σελίδα εφαρμογών %1$d από %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Σελίδα γραφικών στοιχείων %1$d από %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Καλώς ορίσατε!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Νιώστε σαν στο σπίτι σας."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Δημιουργία περισσότερων οθονών για εφαρμογές και φακέλους"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Οργανώστε το χώρο σας"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Αγγίξτε παρατεταμένα το φόντο για να διαχειριστείτε την ταπετσαρία, τα γραφικά στοιχεία και τις ρυθμίσεις."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Επιλέξτε ορισμένες εφαρμογές"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Για να προσθέσετε μια εφαρμογή στην αρχική σας οθόνη, αγγίξτε την παρατεταμένα."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Ορίστε ένας φάκελος"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Για να δημιουργήσετε έναν φάκελο σαν κι αυτόν, πατήστε παρατεταμένα μια εφαρμογή και στη συνέχεια, μετακινήστε τη πάνω σε μια άλλη."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Άνοιγμα φακέλου, <xliff:g id="WIDTH">%1$d</xliff:g> επί <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Αγγίξτε για να κλείσετε τον φάκελο"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Αγγίξτε για να αποθηκεύσετε το νέο όνομα"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Ο φάκελος έκλεισε"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Ο φάκελος μετονομάστηκε σε <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Φάκελος: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Γραφικά στοιχεία"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Ταπετσαρίες"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Ρυθμίσεις"</string>
+</resources>
diff --git a/res/values-en-rGB-land/strings.xml b/res/values-en-rGB-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-en-rGB-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..316585e
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Home"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Set wallpaper"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d selected"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d selected"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d selected"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d of %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Selected <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Delete"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Pick image"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Wallpapers"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Crop wallpaper"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Show Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
+ <string name="market" msgid="2619650989819296998">"Shop"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Couldn\'t drop item on this Home screen."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Choose widget to create"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Folder name"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Rename folder"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Cancel"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Add to Home screen"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Shortcuts"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"No more room on your Home screens."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"No more room on the hot seat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"This widget is too large for the hot seat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" created."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" was removed."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" already exists."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Choose shortcut"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Choose app"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remove"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Uninstall"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Remove"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Uninstall"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"App info"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Search"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Voice Search"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Remove"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Uninstall update"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Uninstall app"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"App details"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 app selected"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selected"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder selected"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 shortcut selected"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"uninstall shortcuts"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Allows the app to remove shortcuts without user intervention."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Home screen %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps page %1$d of %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets page %1$d of %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Welcome!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Make yourself at home."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Create more screens for apps and folders"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organise your space"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Touch & hold background to manage wallpaper, widgets and settings."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Choose some apps"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"To add an app to your Home screen, touch & hold it."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Here\'s a folder"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"To create one like this, touch & hold an app, then move it over another."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Touch to close folder"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Touch to save rename"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Settings"</string>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..316585e
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Home"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Set wallpaper"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d selected"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d selected"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d selected"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d of %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Selected <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Delete"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Pick image"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Wallpapers"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Crop wallpaper"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Show Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch & hold to pick up a widget."</string>
+ <string name="market" msgid="2619650989819296998">"Shop"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Couldn\'t drop item on this Home screen."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Choose widget to create"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Folder name"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Rename folder"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Cancel"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Add to Home screen"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Shortcuts"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"No more room on your Home screens."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"No more room on the hot seat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"This widget is too large for the hot seat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" created."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" was removed."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Shortcut \"<xliff:g id="NAME">%s</xliff:g>\" already exists."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Choose shortcut"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Choose app"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remove"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Uninstall"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Remove"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Uninstall"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"App info"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Search"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Voice Search"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Remove"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Uninstall update"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Uninstall app"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"App details"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 app selected"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selected"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder selected"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 shortcut selected"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"uninstall shortcuts"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Allows the app to remove shortcuts without user intervention."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Home screen %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps page %1$d of %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgets page %1$d of %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Welcome!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Make yourself at home."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Create more screens for apps and folders"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organise your space"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Touch & hold background to manage wallpaper, widgets and settings."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Choose some apps"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"To add an app to your Home screen, touch & hold it."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Here\'s a folder"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"To create one like this, touch & hold an app, then move it over another."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Touch to close folder"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Touch to save rename"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Settings"</string>
+</resources>
diff --git a/res/values-es-land/strings.xml b/res/values-es-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-es-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-es-rUS-land/strings.xml b/res/values-es-rUS-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-es-rUS-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..c9a44a9
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Pantalla principal"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Aplicaciones básicas de Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Establecer como fondo de pantalla"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d seleccionados"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d seleccionado"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d seleccionados"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fondo de pantalla %1$d de %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> seleccionado"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Eliminar"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Elegir imagen"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Fondos de pantalla"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Recortar fondo de pantalla"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"No se instaló la aplicación."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar memoria"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén presionado el widget que desees elegir."</string>
+ <string name="market" msgid="2619650989819296998">"Comprar"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Error al soltar elemento en la pantalla principal"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Elegir los widgets para crear"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nombre de carpeta"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Cambiar nombre de carpeta"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Aceptar"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Agregar a la pantalla principal"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplicaciones"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Accesos directos"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"No hay más espacio en tus pantallas principales."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"No hay más espacio en esta pantalla principal."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"No hay más espacio en la barra de accesos directos."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget es demasiado grande para la barra de accesos directos."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Se creó el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Se eliminó el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"El acceso directo \"<xliff:g id="NAME">%s</xliff:g>\" ya existe."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Elegir acceso directo"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Elegir aplicación"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Pantalla principal"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eliminar"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Eliminar"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Información de la aplicación"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Buscar"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Búsqueda por voz"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicaciones"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Eliminar"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar actualización"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar aplicación"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalles de la aplicación"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Se seleccionó 1 aplicación."</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Se seleccionó 1 widget."</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Se seleccionó 1 carpeta."</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Se seleccionó 1 acceso directo."</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar accesos directos"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que una aplicación agregue accesos directos sin que el usuario intervenga."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar accesos directos"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite que la aplicación elimine accesos directos sin que el usuario intervenga."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"leer configuración y accesos directos de la pantalla principal"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite que la aplicación lea la configuración y los accesos directos de la pantalla principal."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"escribir configuración y accesos directos de la pantalla principal"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite que la aplicación cambie la configuración y los accesos directos de la pantalla principal."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problema al cargar el widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Esta es una aplicación del sistema y no se puede desinstalar."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Lanzacohetes"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Carpeta sin nombre"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Pantalla principal %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla principal %1$d de %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicaciones %1$d de %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets %1$d de %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"¡Bienvenido/a!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Siéntete como en casa."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crea más pantallas para aplicaciones y carpetas."</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organiza tu espacio"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Mantén presionado el fondo para administrar el fondo de pantalla, los widgets y la configuración."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Elige algunas aplicaciones"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para agregar una aplicación a tu pantalla principal, mantenla presionada."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Aquí tienes una carpeta"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para crear una carpeta como esta, mantén presionada una aplicación y luego muévela sobre otra."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Aceptar"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Carpeta abierta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Toca para cerrar la carpeta."</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toca para guardar el nuevo nombre."</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Carpeta cerrada"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"El nombre de la carpeta se cambió a <xliff:g id="NAME">%1$s</xliff:g>."</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Configuración"</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..193e096
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Home"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Aplicaciones básicas de Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Establecer fondo"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Seleccionados: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Seleccionados: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Seleccionados: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fondo de pantalla %1$d de %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> seleccionado"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Eliminar"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Seleccionar imagen"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Fondos de pantalla"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Recortar fondo de pantalla"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar memoria"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Mantén pulsado el widget que quieras seleccionar."</string>
+ <string name="market" msgid="2619650989819296998">"Tienda"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Error al arrastrar elemento a pantalla de inicio."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Selecciona widget a añadir"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nombre de carpeta"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Cambiar nombre de carpeta"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Aceptar"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Añadir a la pantalla de inicio"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplicaciones"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Accesos directos"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"No queda espacio en las pantallas de inicio."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"No queda espacio en la barra de accesos directos."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget es demasiado grande para la barra de accesos directos."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Se ha creado el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Se ha eliminado el acceso directo \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"El acceso directo \"<xliff:g id="NAME">%s</xliff:g>\" ya existe."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Selecciona un acceso directo"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Selecciona una aplicación"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Inicio"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eliminar"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Eliminar"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Información de la aplicación"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Buscar"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Búsqueda por voz"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicaciones"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Eliminar"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar actualización"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar aplicación"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Información de la aplicación"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicación seleccionada"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget seleccionado"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 carpeta seleccionada"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 acceso directo seleccionado"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar accesos directos"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que una aplicación añada accesos directos sin intervención del usuario."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar accesos directos"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite que la aplicación elimine accesos directos sin intervención del usuario."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"leer información de accesos directos y de ajustes de la pantalla de inicio"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite que la aplicación consulte los ajustes y los accesos directos de la pantalla de inicio."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"escribir información de accesos directos y de ajustes de la pantalla de inicio"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite que las aplicaciones cambien los ajustes y los accesos directos de la pantalla de inicio."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problema al cargar el widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Esta aplicación es del sistema y no se puede desinstalar."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Carpeta sin nombre"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Pantalla de inicio %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla de inicio %1$d de %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicaciones %1$d de %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets %1$d de %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Te damos la bienvenida"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Personaliza tu pantalla de inicio."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crea más pantallas para aplicaciones y carpetas"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organiza tu espacio"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Mantén pulsado el fondo para gestionar el fondo de pantalla, los widgets y los ajustes."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Selecciona algunas aplicaciones"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para añadir una aplicación a tu pantalla de inicio, solo tienes que mantenerla pulsada."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Esto es una carpeta"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para crear una carpeta como esta, mantén pulsada una aplicación y muévela sobre otra."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Aceptar"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Carpeta abierta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Toca para cerrar la carpeta"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toca para cambiar el nuevo nombre"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Carpeta cerrada"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Se ha cambiado el nombre de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Fondos de pantalla"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Ajustes"</string>
+</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..f026b08
--- /dev/null
+++ b/res/values-et-rEE/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Avaekraan"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Androidi tuumrakendused"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Määra taustapilt"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Valitud on %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Valitud on %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Valitud on %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d/%2$d taustapildist"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Valitud on <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Kustuta"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Vali kujutis"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Taustapildid"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Taustapildi kärpimine"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Rakendus pole installitud."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Vidinad"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Vidinad"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mälu kuvamine"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Vidina valimiseks vajutage ja hoidke seda all."</string>
+ <string name="market" msgid="2619650989819296998">"Pood"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Üksust ei saanud sellele avaekraanile tuua."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Valige loomiseks vidin"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Kausta nimi"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Kausta ümbernimetamine"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Tühista"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Avaekraanile lisamine"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Rakendused"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Otseteed"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Vidinad"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Teie avaekraanidel ei ole enam ruumi."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Sellel avaekraanil pole enam ruumi."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Kohandataval dokialal pole rohkem ruumi."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"See vidin on kohandatava dokiala jaoks liiga suur."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on loodud."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on eemaldatud."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on juba olemas."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Otsetee valimine"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Rakenduse valimine"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Rakendused"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Avaekraan"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eemalda"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalli"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Eemalda"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalli"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Rakenduse teave"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Otsing"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Häälotsing"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Rakendused"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Eemalda"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalli värskendus"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Rakenduse desinstallimine"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Rakenduse üksikasjad"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Valitud on 1 rakendus"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Valitud on 1 vidin"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Valitud on 1 kaust"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Valitud on 1 otsetee"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"installi otseteed"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalli otseteed"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Võimaldab rakendusel eemaldada otseteid kasutaja sekkumiseta."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"loe avaekraani seadeid ja otseteid"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Võimaldab rakendusel lugeda avaekraanil seadeid ja otseteid."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"kirjuta avaekraani seaded ja otseteed"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Võimaldab rakendusel muuta avaekraanil seadeid ja otseteid."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Probleem vidina laadimisel"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"See on süsteemirakendus ja seda ei saa desinstallida."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Nimetu kaust"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Avaekraan %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Leht %1$d/%2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Avaekraan %1$d/%2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Rakenduste leht %1$d/%2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Vidinate leht %1$d/%2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Tere tulemast!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Tundke end nagu kodus."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Looge rakenduste ja kaustade jaoks rohkem ekraanikuvasid"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Korraldage oma ruumi"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Taustapildi, vidinate ja seadete haldamiseks puudutage tausta ning hoidke seda all."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Valige mõned rakendused"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Rakenduse lisamiseks avaekraanile vajutage ja hoidke seda all."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Siin on kaust"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Sarnase loomiseks vajutage ja hoidke rakendust all, seejärel viige see teise peale."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Kaust on avatud, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Puudutage kausta sulgemiseks"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Puudutage uue nime salvestamiseks"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Kaust on suletud"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Kausta uus nimi: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Kaust: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Vidinad"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Taustapildid"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Seaded"</string>
+</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
new file mode 100644
index 0000000..acd0727
--- /dev/null
+++ b/res/values-et/strings.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="8424725141379931883">"Käivitaja"</string>
+ <string name="home" msgid="5921706419368316758">"Kodu"</string>
+ <string name="uid_name" msgid="3371120195364560632">"Androidi tuumrakendused"</string>
+ <string name="folder_name" msgid="8551881338202938211"></string>
+ <string name="chooser_wallpaper" msgid="6063168087625352235">"Taustapildi valimiskoht:"</string>
+ <string name="wallpaper_instructions" msgid="4215640646180727542">"Määra taustapilt"</string>
+ <string name="pick_wallpaper" msgid="5630222540525626723">"Taustapildid"</string>
+ <string name="activity_not_found" msgid="217823393239365967">"Rakendus pole installitud."</string>
+ <string name="widgets_tab_label" msgid="9145860100000983599">"Vidinad"</string>
+ <string name="long_press_widget_to_add" msgid="7395697462851217506">"Vidina valimiseks puudutage seda pikalt."</string>
+ <string name="market" msgid="2652226429823445833">"Pood"</string>
+ <string name="widget_dims_format" msgid="1386418557719032947">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="2285187188524172774">"Üksust ei saa sellele avaekraanile tuua."</string>
+ <string name="external_drop_widget_pick_title" msgid="7040647073452295370">"Valige loomiseks vidin"</string>
+ <string name="rename_folder_label" msgid="5646236631298452787">"Kausta nimi"</string>
+ <string name="rename_folder_title" msgid="4544573104191526550">"Nimeta kaust ümber"</string>
+ <string name="rename_action" msgid="6016003384693240896">"OK"</string>
+ <string name="cancel_action" msgid="3811860427489435048">"Tühista"</string>
+ <string name="menu_item_add_item" msgid="6233177331075781114">"Lisa avamenüüsse"</string>
+ <string name="group_applications" msgid="2103752818818161976">"Rakendused"</string>
+ <string name="group_shortcuts" msgid="9133529424900391877">"Otseteed"</string>
+ <string name="group_widgets" msgid="6704978494073105844">"Vidinad"</string>
+ <string name="group_wallpapers" msgid="1568191644272224858">"Taustapildid"</string>
+ <string name="completely_out_of_space" msgid="1759078539443491182">"Teie avakuvadel ei ole enam ruumi."</string>
+ <string name="out_of_space" msgid="8365249326091984698">"Sellel avalehel pole enam ruumi."</string>
+ <string name="hotseat_out_of_space" msgid="6304886797358479361">"Kohandataval dokialal pole rohkem ruumi."</string>
+ <string name="invalid_hotseat_item" msgid="6545340627805449250">"See vidin on tööpunkti jaoks liiga suur."</string>
+ <string name="shortcut_installed" msgid="7071557296331322355">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” loodud."</string>
+ <string name="shortcut_uninstalled" msgid="2129499669449749995">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” eemaldatud."</string>
+ <string name="shortcut_duplicate" msgid="4757756326465060694">"Otsetee „<xliff:g id="NAME">%s</xliff:g>” on juba olemas."</string>
+ <string name="title_select_shortcut" msgid="1873670208166882222">"Otsetee valimine"</string>
+ <string name="title_select_application" msgid="1793455815754848652">"Rakenduse valimine"</string>
+ <string name="all_apps_button_label" msgid="2578400570124163469">"Rakendused"</string>
+ <string name="all_apps_home_button_label" msgid="1022222300329398558">"Kodu"</string>
+ <string name="delete_zone_label_workspace" msgid="7153615831493049150">"Eemalda"</string>
+ <string name="delete_zone_label_all_apps" msgid="6664588234817475108">"Desinstalli"</string>
+ <string name="delete_target_label" msgid="665300185123139530">"Eemalda"</string>
+ <string name="delete_target_uninstall_label" msgid="748894921183769150">"Desinstalli"</string>
+ <string name="info_target_label" msgid="4019495079517426980">"Rakenduse teave"</string>
+ <string name="accessibility_search_button" msgid="816822994629942611">"Otsing"</string>
+ <string name="accessibility_voice_search_button" msgid="3938249215065842475">"Häälotsing"</string>
+ <string name="accessibility_all_apps_button" msgid="8803738611398979849">"Rakendused"</string>
+ <string name="accessibility_delete_button" msgid="3628162007991023603">"Eemalda"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="3683920959591819044">"Desinstalli värskendus"</string>
+ <string name="menu_add" msgid="3065046628354640854">"Lisa"</string>
+ <string name="menu_manage_apps" msgid="2308685199463588895">"Rakenduste haldamine"</string>
+ <string name="menu_wallpaper" msgid="5837429080911269832">"Taustapilt"</string>
+ <string name="menu_search" msgid="4826514464423239041">"Otsing"</string>
+ <string name="menu_notifications" msgid="6424587053194766192">"Teadistused"</string>
+ <string name="menu_settings" msgid="3946232973327980394">"Süsteemiseaded"</string>
+ <string name="menu_help" msgid="4901160661634590633">"Abi"</string>
+ <string name="cab_menu_delete_app" msgid="4089398025537640349">"Rakenduse desinstallimine"</string>
+ <string name="cab_menu_app_info" msgid="914548323652698884">"Rakenduse üksikasjad"</string>
+ <string name="cab_app_selection_text" msgid="6378522164293415735">"Valitud on 1 rakendus"</string>
+ <string name="cab_widget_selection_text" msgid="962527270506951955">"1 vidin on valitud"</string>
+ <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 kaust on valitud"</string>
+ <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 otsetee on valitud"</string>
+ <string name="permlab_install_shortcut" msgid="1201690825493376489">"otseteede installimine"</string>
+ <string name="permdesc_install_shortcut" msgid="8634424803272077038">"Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta."</string>
+ <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"otseteede desinstallimine"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="274355570620220977">"Võimaldab rakendusel eemaldada otseteid kasutaja sekkumiseta."</string>
+ <string name="permlab_read_settings" msgid="3452408290738106747">"avalehe seadete ja otseteede lugemine"</string>
+ <string name="permdesc_read_settings" msgid="5788109303585403679">"Võimaldab rakendusel lugeda avalehe seadeid ja otseteid."</string>
+ <string name="permlab_write_settings" msgid="1360567537236705628">"avalehe seadete ja otseteede kirjutamine"</string>
+ <string name="permdesc_write_settings" msgid="8530105489115785531">"Võimaldab rakendusel muuta avalehel seadeid ja otseteid."</string>
+ <string name="gadget_error_text" msgid="8359351016167075858">"Probleem vidina laadimisel"</string>
+ <string name="uninstall_system_app_text" msgid="6429814133777046491">"See on süsteemirakendus ja seda ei saa desinstallida."</string>
+ <string name="dream_name" msgid="2847171357608437154">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="8633351560105748141">"Nimeta kaust"</string>
+ <string name="workspace_description_format" msgid="2968608205939373034">"Avakuva %1$d"</string>
+ <string name="default_scroll_format" msgid="4057140866420001240">"Leht %1$d/%2$d"</string>
+ <string name="workspace_scroll_format" msgid="1704767047951143301">"Avakuva %1$d/%2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="5494241912377704885">"Rakenduste leht %1$d/%2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="5383009742241717437">"Vidinate leht %1$d/%2$d"</string>
+ <string name="workspace_cling_title" msgid="738396473989890567">"Tunne end nagu kodus"</string>
+ <string name="workspace_cling_move_item" msgid="791013895761065070">"Võite panna oma lemmikrakendused siia."</string>
+ <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"Kõikide oma rakenduste nägemiseks puudutage ringi."</string>
+ <string name="all_apps_cling_title" msgid="2559734712581447107">"Valige mõned rakendused"</string>
+ <string name="all_apps_cling_add_item" msgid="5665035103260318891">"Rakenduse lisamiseks avakuvale puudutage seda pikalt."</string>
+ <string name="folder_cling_title" msgid="4308949882377840953">"Korraldage oma rakendused kaustadesse"</string>
+ <string name="folder_cling_move_item" msgid="270598675060435169">"Rakenduse liigutamiseks pange sõrm rakendusele ja hoidke seda."</string>
+ <string name="folder_cling_create_folder" msgid="8352867485656129478">"Avakuval uue kausta tegemiseks virnastage üks rakendus teisele."</string>
+ <string name="cling_dismiss" msgid="2780907108735868381">"OK"</string>
+ <string name="folder_opened" msgid="1262064100943801533">"Kaust on avatud, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1335478160661137579">"Puudutage kausta sulgemiseks"</string>
+ <string name="folder_tap_to_rename" msgid="5201612989905472442">"Puudutage uue nime salvestamiseks"</string>
+ <string name="folder_closed" msgid="3130534551370511932">"Kaust suletud"</string>
+ <string name="folder_renamed" msgid="7951233572858053642">"Kausta uus nimi: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="3051680259794759037">"<xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="1433009175359948587"></string>
+ <string name="custom_workspace_cling_description_1" msgid="6875529190849858047"></string>
+ <string name="custom_workspace_cling_title_2" msgid="5516006164661020362"></string>
+ <string name="custom_workspace_cling_description_2" msgid="2758258454975288377"></string>
+</resources>
diff --git a/res/values-fa-land/strings.xml b/res/values-fa-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-fa-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000..3a0d0b8
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"صفحه اصلی"</string>
+ <string name="uid_name" msgid="7820867637514617527">"برنامههای Android Core"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"تنظیم کاغذدیواری"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d انتخاب شد"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d انتخاب شد"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d انتخاب شد"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"کاغذ دیواری %1$d از %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> انتخاب شده"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"حذف"</string>
+ <string name="pick_image" msgid="1272073934062909527">"انتخاب تصویر"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"کاغذدیواریها"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"برش کاغذ دیواری"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"برنامه نصب نشده است."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"ابزارکها"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"ابزارکها"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"نمایش Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"برای انتخاب ابزارک لمس کنید و نگه دارید."</string>
+ <string name="market" msgid="2619650989819296998">"فروشگاه"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"این مورد را نمیتوان در این صفحه اصلی رها کرد."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"انتخاب ابزارکی که باید ایجاد شود"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"نام پوشه"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"تغییر نام پوشه"</string>
+ <string name="rename_action" msgid="5559600076028658757">"تأیید"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"لغو"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"افزودن به صفحه اصلی"</string>
+ <string name="group_applications" msgid="3797214114206693605">"برنامهها"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"میانبرها"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"ابزارکها"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"فضای بیشتری در صفحات نمایش اصلی شما موجود نیست."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"فضای بیشتری در این صفحه اصلی موجود نیست."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"فضای بیشتری در جایگاه اتصال نیست."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"این ابزارک بیش از حد برای جایگاه اتصال بزرگ است."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"میانبر «<xliff:g id="NAME">%s</xliff:g>» ایجاد شد."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"میانبر «<xliff:g id="NAME">%s</xliff:g>» حذف شد."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"میانبر «<xliff:g id="NAME">%s</xliff:g>» در حال حاضر وجود دارد."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"انتخاب میانبر"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"انتخاب برنامه"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"برنامهها"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"صفحه اصلی"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"حذف"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"حذف نصب"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"حذف"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"حذف نصب"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"اطلاعات برنامه"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"جستجو"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"جستجوی شفاهی"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"برنامهها"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"حذف"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"حذف نصب بهروزرسانی"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"حذف نصب برنامه"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"جزئیات برنامه"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"۱ برنامه انتخاب شد"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"۱ ابزارک انتخاب شد"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"۱ پوشه انتخاب شد"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"۱ میانبر انتخاب شد"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"نصب میانبرها"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"به برنامه اجازه میدهد میانبرها را بدون دخالت کاربر اضافه کند."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"حذف نصب میانبرها"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"به برنامه اجازه میدهد میانبرها را بدون دخالت کاربر حذف کند."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"خواندن تنظیمات و میانبرهای صفحه اصلی"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"به برنامه اجازه میدهد تنظیمات و میانبرها را در صفحه اصلی بخواند."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"نوشتن تنظیمات و میانبرهای صفحه اصلی"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"به برنامه اجازه میدهد تنظیمات و میانبرها را در صفحه اصلی تغییر دهد."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"مشکل در بارگیری ابزارک"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"این برنامه سیستمی است و حذف نصب نمیشود."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"پوشه بینام"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"صفحه اصلی %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"صفحه %1$d از %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"صفحه اصلی %1$d از %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"صفحه برنامهها %1$d از %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"صفحه ابزارکها %1$d از %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"خوش آمدید!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"راحت باشید."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"صفحات بیشتری را برای برنامهها و پوشهها ایجاد کنید"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"فضای خود را سازماندهی کنید"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"برای مدیریت کاغذدیواری، ابزارکها و تنظیمات، پسزمینه را لمس کرده و نگهدارید."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"چند برنامه انتخاب کنید"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"اگر میخواهید برنامهای را به صفحه اصلی خود اضافه کنید، آن را لمس کرده، نگهدارید."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"اینجا یک پوشه است"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"برای ایجاد پوشهای مثل این، یک برنامه را لمس کرده و نگهدارید، سپس آن را روی برنامه دیگر بیاندازید."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"تأیید"</string>
+ <string name="folder_opened" msgid="94695026776264709">"پوشه باز شده، <xliff:g id="WIDTH">%1$d</xliff:g> در <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"برای بستن پوشه لمس کنید"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"برای ذخیره تغییر نام لمس کنید"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"پوشه بسته شد"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"نام پوشه به <xliff:g id="NAME">%1$s</xliff:g> تغییر کرد"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"ابزارکها"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"کاغذدیواریها"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"تنظیمات"</string>
+</resources>
diff --git a/res/values-fi-land/strings.xml b/res/values-fi-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-fi-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000..d327250
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Aloitusruutu"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Androidin ydinsovellukset"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Aseta taustakuva"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d valittu"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d valittu"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d valittu"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Taustakuva %1$d/%2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Valittu: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Poista"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Valitse kuva"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Taustakuvat"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Rajaa taustakuva"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Sovellusta ei ole asennettu."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgetit"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgetit"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Näytä muisti"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Valitse widget painamalla sitä pitkään."</string>
+ <string name="market" msgid="2619650989819296998">"Kauppa"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Kohteen lisääminen tähän aloitusruutuun epäonnistui."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Valitse luotava widget"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Kansion nimi"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Nimeä kansio uudelleen"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Peruuta"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Lisää aloitusruutuun"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Sovellukset"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Pikakuvakkeet"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgetit"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Aloitusruuduilla ei ole enää tilaa."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Tässä aloitusruudussa ei ole enää tilaa."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Hotseatissa ei ole enää tilaa."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Tämä widget on liian suuri tähän hotseat-paikkaan."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Pikakuvake <xliff:g id="NAME">%s</xliff:g> luotiin."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Pikakuvake <xliff:g id="NAME">%s</xliff:g> poistettiin."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Pikakuvake <xliff:g id="NAME">%s</xliff:g> on jo olemassa."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Valitse pikakuvake"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Valitse sovellus"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Sovellukset"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Aloitusruutu"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Poista"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Poista"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Poista"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Poista"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Sovelluksen tiedot"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Haku"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Puhehaku"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Sovellukset"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Poista"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Poista päivitys"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Poista sovellus"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Sovelluksen tiedot"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 sovellus valittu"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget valittu"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 kansio valittu"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 pikakuvake valittu"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"asenna pikakuvakkeita"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Antaa sovelluksen lisätä pikakuvakkeita itsenäisesti ilman käyttäjän valintaa."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"poista pikakuvakkeita"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Antaa sovelluksen poistaa pikakuvakkeita ilman käyttäjän valintaa."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"lue aloitusruudun asetuksia ja pikakuvakkeita"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Antaa sovelluksen lukea aloitusruudun asetukset ja pikakuvakkeet."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"kirjoita aloitusruudun asetuksia ja pikakuvakkeita"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Antaa sovelluksen muuttaa aloitusruudun asetuksia ja pikakuvakkeita."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Ongelma ladattaessa widgetiä"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Tämä on järjestelmäsovellus, eikä sitä voi poistaa."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Sinko"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Nimetön kansio"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Aloitusruutu %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Sivu %1$d / %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Aloitusruutu %1$d/%2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Apps-sivu %1$d / %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgetit-sivu %1$d / %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Tervetuloa!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Ole kuin kotonasi."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Luo lisää ruutuja sovelluksille ja kansioille"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Järjestä tilasi"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Hallitse taustakuvaa, widgetejä ja asetuksia koskettamalla taustaa pitkään."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Valitse joitakin sovelluksia"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Voit lisätä sovelluksen aloitusruutuun koskettamalla sitä pitkään."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Tässä on kansio"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Luo se seuraavasti: kosketa sovellusta pitkään ja siirrä se sitten toisen päälle."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Kansio avattu, koko <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Sulje kansio koskettamalla"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tallenna uudella nimellä koskettamalla"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Kansio on suljettu"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Kansion nimeksi vaihdettiin <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Kansio: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgetit"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Taustakuvat"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Asetukset"</string>
+</resources>
diff --git a/res/values-fr-land/strings.xml b/res/values-fr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-fr-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..9d8dbeb
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Lanceur3"</string>
+ <string name="home" msgid="7658288663002113681">"Accueil"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Applications de base Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Définir le fond d\'écran"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d sélectionné"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d sélectionné"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d sélectionné(s)"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fond d\'écran %1$d sur %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Sélection : <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Supprimer"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Sélectionner une image"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Fonds d\'écran"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Rogner le fond d\'écran"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Afficher la mémoire"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Maintenez un doigt sur le widget pour l\'ajouter."</string>
+ <string name="market" msgid="2619650989819296998">"Magasiner"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Imposs. de déposer l\'élément sur l\'écran d\'accueil"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Sélectionnez le widget à créer"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nom du dossier"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Renommer le dossier"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Annuler"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Ajouter à l\'écran d\'accueil"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Applications"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Raccourcis"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Vous n\'avez plus d\'espace libre sur vos écrans d\'accueil."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur l\'écran d\'accueil."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Vous n\'avez plus de place sur la barre d\'accès rapide."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ce widget est trop volumineux pour la barre d\'accès rapide."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Le raccourci « <xliff:g id="NAME">%s</xliff:g> » a été créé."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Le raccourci « <xliff:g id="NAME">%s</xliff:g> » a été supprimé."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Le raccourci « <xliff:g id="NAME">%s</xliff:g> » existe déjà."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Sélectionner un raccourci"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Sélectionner une application"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Accueil"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Supprimer"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Désinstaller"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Supprimer"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Désinstaller"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Détails de l\'application"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Rechercher"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Recherche vocale"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Applications"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Supprimer"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Désinstaller la mise à jour"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Désinstaller l\'application"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Détails de l\'application"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 application sélectionnée"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget sélectionné"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 dossier sélectionné"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 raccourci sélectionné"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"désinstaller des raccourcis"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permet à l\'application de supprimer des raccourcis sans l\'intervention de l\'utilisateur."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"lire les paramètres et les raccourcis de la page d\'accueil"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permet à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"enregistrer les paramètres de la page d\'accueil et des raccourcis"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permet à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problème lors du chargement du widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette application, car il s\'agit d\'une application système."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Lance-missile"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Dossier sans nom"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Écran d\'accueil %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Page des applications : %1$d sur %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Page des widgets : %1$d sur %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Bienvenue!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Faites comme chez vous."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Créer plus d\'écrans pour les applications et les dossiers"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organiser son espace personnel"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Maintenez votre doigt sur l\'arrière-plan pour gérer les fonds d\'écran, les widgets et les paramètres."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Sélectionner des applications"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Pour ajouter une application à votre écran d\'accueil, maintenez votre doigt dessus."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Voici un dossier"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Pour créer un dossier comme ça, maintenez votre doigt sur une application, puis déplacez-la sur une autre."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Dossier ouvert, <xliff:g id="WIDTH">%1$d</xliff:g> par <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Toucher pour fermer le dossier"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toucher pour enregistrer le nouveau nom"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Dossier fermé"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Fonds d\'écran"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Paramètres"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..b1b0a79
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Accueil"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Applications de base Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Définir comme fond d\'écran"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d fond d\'écran sélectionné"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d fond d\'écran sélectionné"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d fonds d\'écran sélectionnés"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Fond d\'écran %1$d sur %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> est sélectionné."</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Supprimer"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Sélectionner une image"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Fonds d\'écran"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Rogner le fond d\'écran"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Afficher la mémoire"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"App. de manière prolongée pour sélectionner widget."</string>
+ <string name="market" msgid="2619650989819296998">"Boutique"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Impossible de déposer élément sur écran d\'accueil."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Sélectionner le widget à créer"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nom du dossier"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Renommer le dossier"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Annuler"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Ajouter à l\'écran d\'accueil"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Applications"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Raccourcis"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Vous n\'avez plus d\'espace libre sur vos écrans d\'accueil."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur cet écran d\'accueil."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Vous n\'avez plus de place sur la barre d\'accès rapide."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ce widget est trop volumineux pour la barre d\'accès rapide."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Le raccourci \"<xliff:g id="NAME">%s</xliff:g>\" a été créé."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Le raccourci \"<xliff:g id="NAME">%s</xliff:g>\" a été supprimé."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Le raccourci \"<xliff:g id="NAME">%s</xliff:g>\" existe déjà."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Sélectionner un raccourci"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Sélectionner une application"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Accueil"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Supprimer"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Désinstaller"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Supprimer"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Désinstaller"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informations sur l\'application"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Rechercher"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Recherche vocale"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Applications"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Supprimer"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Désinstaller la mise à jour"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Désinstaller l\'application"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Informations sur l\'application"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 application sélectionnée"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget sélectionné"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 dossier sélectionné"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 raccourci sélectionné"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permettre à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"désinstaller des raccourcis"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permettre à l\'application de supprimer des raccourcis sans l\'intervention de l\'utilisateur"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"lire les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permettre à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"modifier les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permettre à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problème lors du chargement du widget."</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette application, car il s\'agit d\'une application système."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Dossier sans nom"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Écran d\'accueil %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Page des applications %1$d sur %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Page des widgets %1$d sur %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Bienvenue !"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Familiarisez-vous avec l\'écran d\'accueil."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Créez des écrans personnalisés pour vos applis et dossiers"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organisez votre espace"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Appuyez de manière prolongée sur l\'arrière-plan pour gérer les fonds d\'écran, les widgets et les paramètres."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Sélectionner des applications"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Pour ajouter une application à votre écran d\'accueil, appuyez dessus de manière prolongée."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Voici un dossier"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Pour en créer un, appuyez de manière prolongée sur une application, puis déplacez-la vers une autre."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Dossier ouvert, <xliff:g id="WIDTH">%1$d</xliff:g> par <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Appuyez pour fermer le dossier."</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Appuyez pour enregistrer le nouveau nom."</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Dossier fermé"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Dossier \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Fonds d\'écran"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Paramètres"</string>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000..eddd75c
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"होम"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android के मुख्य एप्लिकेशन"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"वॉलपेपर सेट करें"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d चयनित"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d चयनित"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d चयनित"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"वॉलपेपर %2$d में से %1$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"चयनित <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"हटाएं"</string>
+ <string name="pick_image" msgid="1272073934062909527">"चित्र चुनें"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"वॉलपेपर"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"वॉलपेपर काटें"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"एप्लिकेशन इंस्टॉल नहीं है."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"विजेट"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"विजेट"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"स्मृति दिखाएं"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"विजेट को चुनने के लिए स्पर्श करके रखें."</string>
+ <string name="market" msgid="2619650989819296998">"खरीदारी करें"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"आइटम को इस होम स्क्रीन पर नहीं छोड़ा जा सका."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"बनाने के लिए विजेट चुनें"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"फ़ोल्डर का नाम"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"फ़ोल्डर का नाम बदलें"</string>
+ <string name="rename_action" msgid="5559600076028658757">"ठीक"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"रद्द करें"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"होम स्क्रीन में जोड़ें"</string>
+ <string name="group_applications" msgid="3797214114206693605">"एप्लिकेशन"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"शॉर्टकट"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"विजेट"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"आपकी होम स्क्रीन पर स्थान शेष नहीं है."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"इस होम स्क्रीन पर स्थान शेष नहीं है."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"हॉटसीट पर स्थान शेष नहीं है."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"हॉटसीट के लिए यह विजेट बहुत बड़ा है."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"शॉर्टकट \"<xliff:g id="NAME">%s</xliff:g>\" बनाया गया."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"शॉर्टकट \"<xliff:g id="NAME">%s</xliff:g>\" निकाल दिया गया था."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"शॉर्टकट \"<xliff:g id="NAME">%s</xliff:g>\" पहले से मौजूद है."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"शॉर्टकट चुनें"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"एप्लिकेशन चुनें"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"एप्लिकेशन"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"होम"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"निकालें"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"अनइंस्टॉल करें"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"निकालें"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"अनइंस्टॉल करें"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"एप्लिकेशन की जानकारी"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"खोजें"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"बोलकर खोजें"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"एप्लिकेशन"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"निकालें"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"अपडेट अनइंस्टॉल करें"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"एप्लिकेशन अनइंस्टॉल करें"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"एप्लिकेशन का विवरण"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 एप्लिकेशन चयनित"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 विजेट चयनित"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 फ़ोल्डर चयनित"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 शॉर्टकट चयनित"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"शॉर्टकट इंस्टॉल करें"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"एप्लिकेशन को उपयोगकर्ता के हस्तक्षेप के बिना शॉर्टकट जोड़ने देती है."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"शॉर्टकट अनइंस्टॉल करें"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"एप्लिकेशन को उपयोगकर्ता के हस्तक्षेप के बिना शॉर्टकट निकालने देती है."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"होम सेटिंग और शॉर्टकट पढ़ें"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"एप्लिकेशन को होम में सेटिंग और शॉर्टकट पढ़ने देती है."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"होम सेटिंग और शॉर्टकट लिखें"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"एप्लिकेशन को होम में सेटिंग और शॉर्टकट बदलने देती है."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"विजेट लोड करने में समस्या"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"यह एक सिस्टम एप्लिकेशन है और इसे अनइंस्टॉल नहीं किया जा सकता."</string>
+ <string name="dream_name" msgid="1530253749244328964">"रॉकेट लॉन्चर"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"अनामित फ़ोल्डर"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"होम स्क्रीन %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"पृष्ठ %2$d में से %1$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रीन %2$d में से %1$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"एप्लिकेशन पृष्ठ %2$d में से %1$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"विजेट पृष्ठ %2$d में से %1$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"स्वागत है!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"जैसा चाहें वैसा उपयोग करें."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"एप्लिकेशन और फ़ोल्डर के लिए और अधिक स्क्रीन बनाएं"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"अपने स्थान को व्यवस्थित करें"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"वॉलपेपर, विजेट और सेटिंग प्रबंधित करने के लिए पृष्ठभूमि को स्पर्श करके रखें."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"कुछ एप्लिकेशन चुनें"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"किसी एप्लिकेशन को अपनी होम स्क्रीन से जोड़ने के लिए, उसे स्पर्श करके रखें."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"यहां एक फ़ोल्डर है"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"इसके जैसा कोई एक बनाने के लिए, किसी एप्लिकेशन को स्पर्श करके रखें, फिर इसे किसी दूसरे पर ले जाएं."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"ठीक"</string>
+ <string name="folder_opened" msgid="94695026776264709">"फ़ोल्डर खोला गया, <xliff:g id="WIDTH">%1$d</xliff:g> गुणा <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"फ़ोल्डर बंद करने के लिए स्पर्श करें"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"बदला गया नाम सहेजने के लिए स्पर्श करें"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"फ़ोल्डर बंद किया गया"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"फ़ोल्डर का नाम बदलकर <xliff:g id="NAME">%1$s</xliff:g> किया गया"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"फ़ोल्डर: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"विजेट"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"वॉलपेपर"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"सेटिंग"</string>
+</resources>
diff --git a/res/values-hr-land/strings.xml b/res/values-hr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-hr-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000..783946d
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Pokretač3"</string>
+ <string name="home" msgid="7658288663002113681">"Početna"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Matične aplikacije za Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Postavi pozadinsku sliku"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Odabrano: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Odabrano: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Odabrano: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d. pozadinska slika od %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Odabrana je stavka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Izbriši"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Odaberi sliku"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Pozadinske slike"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Obreži pozadinsku sliku"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgeti"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgeti"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Prikaži mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Dodirnite i držite kako biste podigli widget."</string>
+ <string name="market" msgid="2619650989819296998">"Kupi"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Stavka nije ispuštena na ovaj početni zaslon."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Odabir widgeta za stvaranje"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Naziv mape"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Preimenovanje mape"</string>
+ <string name="rename_action" msgid="5559600076028658757">"U redu"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Odustani"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Dodavanje na početni zaslon"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplikacije"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Prečaci"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgeti"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Na vašim početnim zaslonima više nema mjesta."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom zaslonu više nema mjesta."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Na hotseatu više nema mjesta."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ovaj je widget prevelik za hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Izrađen je prečac za \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Uklonjen je prečac za \"<xliff:g id="NAME">%s</xliff:g>\"."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Prečac za \"<xliff:g id="NAME">%s</xliff:g>\" već postoji."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Odabir prečaca"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Odabir aplikacije"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Početna"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Ukloni"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Deinstaliraj"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Ukloni"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Deinstaliraj"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informacije o aplikaciji"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Pretraži"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Glasovno pretraživanje"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikacije"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Ukloni"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Deinstalacija ažuriranja"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Deinstaliranje aplikacije"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Pojedinosti o aplikaciji"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Odabrana je 1 aplikacija"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Odabran je 1 widget"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Odabrana je 1 mapa"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Odabran je 1 prečac"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instaliranje prečaca"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Aplikaciji omogućuje dodavanje prečaca bez intervencije korisnika."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"deinstaliranje prečaca"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Aplikaciji omogućuje uklanjanje prečaca bez intervencije korisnika."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"čitanje postavki početnog zaslona i prečaca"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Aplikaciji omogućuje čitanje postavki i prečaca na početnom zaslonu."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"pisanje postavki početnog zaslona i prečaca"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Aplikaciji omogućuje promjenu postavki i prečaca na početnom zaslonu."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem pri učitavanju widgeta"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ovo je aplikacija sustava i ne može se ukloniti."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Lansirna rampa"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Neimenovana mapa"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Početni zaslon %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Stranica %1$d od %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Početni zaslon %1$d od %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stranica aplikacija %1$d od %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stranica widgeta %1$d od %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Dobro došli!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Osjećajte se kao kod kuće."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Izradite više zaslona za aplikacije i mape"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organizirajte svoj prostor"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Dodirnite i držite pozadinu da biste upravljali pozadinskom slikom, widgetima i postavkama."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Odaberite neke aplikacije"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Da biste dodali aplikaciju na početni zaslon, dodirnite je i zadržite."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Evo mape"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Da biste izradili ovakvu mapu, dodirnite i držite aplikaciju pa je pomaknite preko druge aplikacije."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"U redu"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Mapa je otvorena, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Dodirnite da biste zatvorili mapu"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dodirnite da biste spremili preimenovanje"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Mapa je zatvorena"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana u <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgeti"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Pozadinske slike"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Postavke"</string>
+</resources>
diff --git a/res/values-hu-land/strings.xml b/res/values-hu-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-hu-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000..8876652
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Főoldal"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Alap Android-alkalmazások"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Háttérkép beállítása"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d kiválasztva"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d kiválasztva"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d kiválasztva"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%2$d/%1$d. háttérkép"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> kiválasztva"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Törlés"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Kép választása"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Háttérképek"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Háttérkép körbevágása"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Az alkalmazás nincs telepítve."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Modulok"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Modulok"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mem. megjelenítése"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Modul felvételéhez érintse meg, és tartsa lenyomva"</string>
+ <string name="market" msgid="2619650989819296998">"Vásárlás"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Nem lehet elemeket dobni erre a kezdőképernyőre."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"A létrehozáshoz válasszon modult"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Mappa neve"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Mappa átnevezése"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Mégse"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Hozzáadás a kezdőképernyőhöz"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Alkalmazások"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Parancsikonok"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Modulok"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Nincs több hely a kezdőképernyőkön."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Nincs több hely ezen a kezdőképernyőn."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Nincs több hely az egyéni mezőben."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ez a modul túl nagy az egyéni mező számára."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"A(z) „<xliff:g id="NAME">%s</xliff:g>” parancsikon létrehozva."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"A(z) „<xliff:g id="NAME">%s</xliff:g>” parancsikon eltávolítva."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"A(z) „<xliff:g id="NAME">%s</xliff:g>” parancsikon már létezik."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Parancsikon választása"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Válasszon alkalmazást"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Alkalmazások"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Főoldal"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eltávolítás"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Eltávolítás"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Eltávolítás"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Eltávolítás"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Alkalmazásinformáció"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Keresés"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Hangalapú keresés"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Alkalmazások"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Eltávolítás"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Eltávolítja a frissítést"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Alkalmazás eltávolítása"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Az alkalmazás adatai"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 alkalmazás kiválasztva"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 modul kiválasztva"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 mappa kiválasztva"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 parancsikon kiválasztva"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"parancsikonok telepítése"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül adjon hozzá parancsikonokat."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"parancsikonok eltávolítása"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül távolítson el parancsikonokat."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"Főoldal beállításainak és parancsikonjainak beolvasása"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Lehetővé teszi az alkalmazás számára, hogy beolvassa a kezdőképernyő beállításait és parancsikonjait."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"Főoldal beállításainak és parancsikonjainak írása"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Lehetővé teszi az alkalmazás számára, hogy módosítsa a kezdőképernyő beállításait és parancsikonjait."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Probléma történt a modul betöltésekor"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ez egy rendszeralkalmazás, és nem lehet eltávolítani."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Aknavető"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Névtelen mappa"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"%1$d. kezdőképernyő"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"%2$d/%1$d. oldal"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d/%1$d. kezdőképernyő"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%2$d/%1$d. alkalmazásoldal"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%2$d/%1$d. moduloldal"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Üdvözöljük!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Varázsolja egyedivé készülékét."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Hozzon létre további képernyőket az alkalmazásoknak és mappáknak"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Munkaterület rendezése"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Érintse meg és tartsa lenyomva a hátteret a háttérkép, modulok és beállítások kezeléséhez."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Válasszon ki néhány alkalmazást"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ha egy alkalmazást szeretne elhelyezni a kezdőképernyőn, érintse meg, és tartsa lenyomva."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Itt egy mappa"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Mappa létrehozásához érintse meg és tartsa lenyomva az alkalmazást, majd húzza egy másik fölé."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Mappa megnyitva – szélesség: <xliff:g id="WIDTH">%1$d</xliff:g>; magasság: <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Érintse meg a mappa bezárásához"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Érintse meg az átnevezés mentéséhez"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Mappa lezárva"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"A mappa új neve: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Modulok"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Háttérképek"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Beállítások"</string>
+</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..ab7388a
--- /dev/null
+++ b/res/values-hy-rAM/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Հիմնական"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Սահմանել պաստառը"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d ընտրված"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d ընտրված"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d ընտրված"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d պաստառ՝ %2$d-ից"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Ընտրված է <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Ջնջել"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Ընտրել պատկեր"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Պաստառներ"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Եզրատել պաստառը"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Ծրագիրը տեղադրված չէ:"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Վիջեթներ"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Վիջեթներ"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Ցուցադրել մեմը"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Հպեք և պահեք՝ վիջեթն ընտրելու համար:"</string>
+ <string name="market" msgid="2619650989819296998">"Խանութ"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Հնարավոր չէ տեղադրել տարրն այս հիմնական էկրանին:"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Ստեղծելու համար ընտրեք վիջեթը"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Թղթապանակի անունը"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Վերանվանել թղթապանակը"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Լավ"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Չեղարկել"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Ավելացնել հիմնական էկրանին"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Ծրագրեր"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Դյուրանցումներ"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Վիջեթներ"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Այլևս տեղ չկա ձեր հիմնական էկրաններին:"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Այլևս տեղ չկա այս հիմնական էկրանին:"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Թեժ նստատեղերում այլևս տեղ չկա:"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Այս վիջեթը չափազանց մեծ է թեժ նստատեղերի համար:"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"«<xliff:g id="NAME">%s</xliff:g>» դյուրանցումը ստեղծված է:"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"«<xliff:g id="NAME">%s</xliff:g>» դյուրանցումը հեռացվեց:"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"«<xliff:g id="NAME">%s</xliff:g>» դյուրանցումն արդեն գոյություն ունի:"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Ընտրել դյուրանցումը"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Ընտրել ծրագիրը"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Ծրագրեր"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Հիմնական"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Հեռացնել"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Ապատեղադրել"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Հեռացնել"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Ապատեղադրել"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Ծրագրի տեղեկություններ"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Որոնել"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Ձայնային որոնում"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Ծրագրեր"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Հեռացնել"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Ապատեղադրել թարմացումը"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Ապատեղադրել ծրագիրը"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Ծրագրի մանրամասներ"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 ընտրված ծրագիր"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 ընտրված վիջեթ"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 ընտրված թղթապանակ"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 ընտրված դյուրանցում"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"տեղադրել դյուրանցումներ"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ծրագրին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ապատեղադրել դյուրանցումները"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ծրագրին թույլ է տալիս հեռացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Ծրագրին թույլ է տալիս կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները:"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"ստեղծել հիմնաէջի կարգավորումներ ու դյուրանցումներ"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Ծրագրին թույլ է տալիս փոփոխել հիմնաէջի կարգավորումներն ու դյուրանցումները:"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Վիջեթի բեռնման խնդիր կա"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Սա համակարգային ծրագիր է և չի կարող ապատեղադրվել:"</string>
+ <string name="dream_name" msgid="1530253749244328964">"Հրթիռային թողարկիչ"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Անանուն թղթապանակ"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Հիմնական էկրան %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Էջ %1$d՝ %2$d-ից"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Հիմնական էկրան %1$d` %2$d-ից"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Ծրագրերի էջերը՝ %1$d %2$d-ից"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Վիջեթների էջերը՝ %1$d %2$d-ից"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Բարի գալուստ:"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Զգացեք ձեզ ինչպես տանը:"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Ստեղծեք նոր էկրաններ ծրագրերի և թղթապանակների համար"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Կառավարեք ձեր տարածությունը"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Հպեք և պահեք հետնաշերտի վրա՝ պաստառները, վիջեթներն ու կարգավորումները կառավարելու համար:"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Ընտրեք ինչ-որ ծրագիր"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ձեր հիմնական էկրանին ծրագիր ավելացնելու համար հպեք և պահեք այն:"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Ահա մի թղթապանակ"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Նման թղթապանակ ստեղծելու համար հպեք և պահեք որևէ ծրագրի վրա, ապա տեղաշարժեք այն մեկ ուրիշ ծրագրի վրա:"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Լավ"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Թղթապանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Հպեք՝ թղթապանակը փակելու համար"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Հպեք՝ վերանվանումը պահելու համար"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Թղթապանակը փակ է"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Թղթապանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Թղթապանակ՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Վիջեթներ"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Պաստառներ"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Կարգավորումներ"</string>
+</resources>
diff --git a/res/values-in-land/strings.xml b/res/values-in-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-in-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000..e1e93f7
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Layar Utama"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Aplikasi Inti Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Setel wallpaper"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d dipilih"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d dipilih"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d dipilih"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d dari %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> terpilih"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Hapus"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Pilih gambar"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Wallpaper"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Pangkas wallpaper"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Aplikasi tidak dipasang."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widget"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widget"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Tampilkan Memori"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
+ <string name="market" msgid="2619650989819296998">"Belanja"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Tidak dapat melepas item ke layar Utama ini."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pilih widget untuk membuat"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nama folder"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Ganti nama folder"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Oke"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Batal"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Tambahkan ke layar Utama"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplikasi"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Pintasan"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widget"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Tidak ada ruang lagi di layar Utama Anda."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Tidak ada ruang lagi di hotseat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Widget ini terlalu besar untuk hotseat tersebut."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah dibuat."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dihapus."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah ada."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Pilih pintasan"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Pilih aplikasi"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikasi"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Layar Utama"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Hapus"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Copot pemasangan"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Hapus"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Copot pemasangan"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Info aplikasi"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Telusuri"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Penelusuran Suara"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikasi"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Hapus"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Copot pemasangan pembaruan"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Copot aplikasi"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Detail aplikasi"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplikasi dipilih"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget dipilih"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder dipilih"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 pintasan dipilih"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"memasang pintasan"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Mengizinkan aplikasi menambahkan pintasan tanpa campur tangan pengguna."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"mencopot pemasangan pintasan"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Mengizinkan aplikasi menghapus pintasan tanpa campur tangan pengguna."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"membaca setelan dan pintasan layar Utama"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Mengizinkan aplikasi membaca setelan dan pintasan di layar Utama."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"menulis setelan dan pintasan layar Utama"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Mengizinkan aplikasi mengubah setelan dan pintasan di layar Utama."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Masalah memuat widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ini adalah aplikasi sistem dan tidak dapat dicopot pemasangannya."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Folder Tanpa Nama"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Layar utama %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Laman %1$d dari %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Layar utama %1$d dari %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Laman aplikasi %1$d dari %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Laman widget %1$d dari %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Selamat datang!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Serasa di rumah sendiri."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Buat lebih banyak layar untuk aplikasi dan folder"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Kelola ruang Anda"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Sentuh lama latar belakang untuk mengelola wallpaper, widget, dan setelan."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Pilih beberapa aplikasi"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Untuk menambah aplikasi ke layar Utama Anda, sentuh lama aplikasi tersebut."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Ini adalah folder"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Untuk membuat seperti yang ini, sentuh lama aplikasi, lalu pindahkan ke atas aplikasi lain."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Oke"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Sentuh untuk menutup folder"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Sentuh untuk menyimpan ganti nama"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folder ditutup"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Folder diganti namanya menjadi <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widget"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpaper"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Setelan"</string>
+</resources>
diff --git a/res/values-it-land/strings.xml b/res/values-it-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-it-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..d5ada8f
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Home page"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Applicazioni di base Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Imposta sfondo"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d selezionati"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d selezionato"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d selezionati"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Sfondo %1$d di %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Elemento selezionato: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Elimina"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Scegli immagine"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Sfondi"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Ritaglia sfondo"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"App non installata."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widget"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widget"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostra Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tocca e tieni premuto per scegliere un widget."</string>
+ <string name="market" msgid="2619650989819296998">"Acquista"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Rilascio elemento in schermata Home non riuscito."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Scegli il widget da creare"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nome cartella"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Rinomina cartella"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Annulla"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Aggiungi a schermata Home"</string>
+ <string name="group_applications" msgid="3797214114206693605">"App"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Scorciatoie"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widget"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Spazio nelle schermate Home esaurito."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Spazio nell\'area hotseat esaurito."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Questo widget è troppo grande per l\'area hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Scorciatoia \"<xliff:g id="NAME">%s</xliff:g>\" creata."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"La scorciatoia \"<xliff:g id="NAME">%s</xliff:g>\" è stata rimossa."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Scorciatoia \"<xliff:g id="NAME">%s</xliff:g>\" già presente."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Scegli scorciatoia"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Scegli app"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"App"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Home page"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Rimuovi"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Disinstalla"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Rimuovi"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Disinstalla"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informazioni app"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Cerca"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Ricerca vocale"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"App"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Rimuovi"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Disinstalla aggiornamento"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Disinstalla app"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Dettagli sull\'app"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 app selezionata"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selezionato"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 cartella selezionata"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 scorciatoia selezionata"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"aggiunta di scorciatoie"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Consente a un\'app di aggiungere scorciatoie automaticamente."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"eliminazione di scorciatoie"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Consente all\'app di rimuovere scorciatoie automaticamente."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"lettura di impostazioni e scorciatoie in Home"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Consente all\'app di leggere le impostazioni e le scorciatoie in Home."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"creazione di impostazioni e scorciatoie in Home"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Consente all\'app di modificare le impostazioni e le scorciatoie in Home."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Errore durante il caricamento del widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Questa è un\'app di sistema e non può essere disinstallata."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Lanciamissili"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Cartella senza nome"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Schermata Home %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d di %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Schermata Home %1$d di %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pagina di applicazioni %1$d di %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pagina di widget %1$d di %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Benvenuto!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Personalizza la schermata Home."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Creare più schermate per app e cartelle"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organizza il tuo spazio"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Tocca e tieni premuto lo sfondo per gestire sfondi, widget e impostazioni."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Scegli alcune applicazioni"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Per aggiungere un\'app alla schermata Home, tocca e tieni premuto."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Ecco una cartella"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Per crearne una simile, tocca un\'app e tieni premuto, dopodiché spostala sopra un\'altra."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Cartella aperta, <xliff:g id="WIDTH">%1$d</xliff:g> per <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Tocca per chiudere la cartella"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tocca per salvare nuovo nome"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Cartella chiusa"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Nome della cartella sostituito con <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Cartella: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widget"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Sfondi"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Impostazioni"</string>
+</resources>
diff --git a/res/values-iw-land/strings.xml b/res/values-iw-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-iw-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000..603dbb5
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"דף הבית"</string>
+ <string name="uid_name" msgid="7820867637514617527">"אפליקציות הליבה של Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"הגדר טפט"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d נבחרו"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d נבחרו"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d נבחרו"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"טפט %1$d מתוך %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"בחרת <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"מחק"</string>
+ <string name="pick_image" msgid="1272073934062909527">"בחר תמונה"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"טפטים"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"חתוך את הטפט"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"האפליקציה לא מותקנת."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"רכיבי ווידג\'ט"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"רכיבי ווידג\'ט"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"הצג זכרון"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"גע נגיעה רציפה בווידג\'ט כדי לבחור בו."</string>
+ <string name="market" msgid="2619650989819296998">"קנה"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"לא ניתן היה לשחרר את הפריט במסך דף הבית הזה."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"בחר ווידג\'ט ליצירה"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"שם תיקיה"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"שנה את שם התיקיה"</string>
+ <string name="rename_action" msgid="5559600076028658757">"אישור"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"בטל"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"הוסף למסך דף הבית"</string>
+ <string name="group_applications" msgid="3797214114206693605">"אפליקציות"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"קיצורי דרך"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"רכיבי ווידג\'ט"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"אין יותר מקום במסכי דף הבית."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"אין עוד מקום במסך דף הבית הזה."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"אין יותר מקום בפס האפליקציות."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"הווידג\'ט הזה גדול מדי עבור פס האפליקציות."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"קיצור הדרך \'<xliff:g id="NAME">%s</xliff:g>\' נוצר."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"קיצור הדרך \'<xliff:g id="NAME">%s</xliff:g>\' הוסר."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"קיצור הדרך \'<xliff:g id="NAME">%s</xliff:g>\' כבר קיים."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"בחר קיצור דרך"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"בחר אפליקציה"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"אפליקציות"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"דף הבית"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"הסר"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"הסר התקנה"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"הסר"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"הסר התקנה"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"פרטי אפליקציה"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"חפש"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"חיפוש קולי"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"אפליקציות"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"הסר"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"הסר את התקנת העדכון"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"הסר את התקנת האפליקציה"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"פרטי האפליקציה"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"נבחרה אפליקציה אחת"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"נבחר ווידג\'ט אחד"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"נבחרה תיקיה אחת"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"נבחר קיצור דרך אחד"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"התקן קיצורי דרך"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"מאפשר לאפליקציה להוסיף קיצורי דרך ללא התערבות המשתמש."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"הסר התקנה של קיצורי דרך"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"מאפשר לאפליקציה להסיר קיצורי דרך ללא התערבות המשתמש."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"קרא הגדרות וקיצורי דרך של דף הבית"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"מאפשר לאפליקציה לקרוא את ההגדרות וקיצורי הדרך בדף הבית."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"כתוב הגדרות וקיצורי דרך של דף הבית"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"מאפשר לאפליקציה לשנות את ההגדרות וקיצורי הדרך בדף הבית."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"בעיה בטעינת ווידג\'ט"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"זוהי אפליקציית מערכת ולא ניתן להסיר את התקנתה."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"תיקיה ללא שם"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"מסך דף הבית %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"דף %1$d מתוך %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"מסך דף הבית %1$d מתוך %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"דף אפליקציות %1$d מתוך %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"דף רכיבי ווידג\'ט %1$d מתוך %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"ברוך הבא!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"להרגיש בבית."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"צור מסכים נוספים עבור אפליקציות ותיקיות"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"ארגן את אזור העבודה שלך"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"גע נגיעה רציפה ברקע כדי לנהל את הטפט, רכיבי הווידג\'ט וההגדרות."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"בחר כמה אפליקציות"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"כדי להוסיף אפליקציה למסך דף הבית, גע בה נגיעה רציפה."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"הנה תיקייה"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"כדי ליצור תיקייה כזו, גע נגיעה רציפה באפליקציה, ולאחר מכן גרור ושחרר אותו על-גבי אפליקציה אחרת."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"אישור"</string>
+ <string name="folder_opened" msgid="94695026776264709">"תיקיה פתוחה, <xliff:g id="WIDTH">%1$d</xliff:g> על <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"גע כדי לסגור את התיקיה"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"גע כדי לשמור את שינוי השם"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"התיקיה נסגרה"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"שם התיקיה שונה ל-<xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"תיקיה: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"רכיבי ווידג\'ט"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"טפטים"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"הגדרות"</string>
+</resources>
diff --git a/res/values-ja-land/strings.xml b/res/values-ja-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ja-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..6e39c54
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"ホーム"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"壁紙を設定"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d件選択済み"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d件選択済み"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d件選択済み"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"壁紙: %1$d/%2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"選択: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"削除"</string>
+ <string name="pick_image" msgid="1272073934062909527">"画像を選択"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"壁紙"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"壁紙をトリミング"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"このアプリはインストールされていません。"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"ウィジェット"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"ウィジェット"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"メモリーを表示"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"ウィジェットを追加するには押し続けます。"</string>
+ <string name="market" msgid="2619650989819296998">"ショップ"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$dx%2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"このホーム画面にアイテムをドロップできませんでした"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"作成するウィジェットの選択"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"フォルダ名"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"フォルダ名を変更"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"キャンセル"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"ホーム画面に追加"</string>
+ <string name="group_applications" msgid="3797214114206693605">"アプリ"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"ショートカット"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"ウィジェット"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"ホーム画面に空きスペースがありません。"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"このホーム画面に空きスペースがありません。"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"ホットシートに空きスペースがありません。"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"このウィジェットはホットシートには大きすぎます。"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"ショートカット「<xliff:g id="NAME">%s</xliff:g>」を作成しました。"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"ショートカット「<xliff:g id="NAME">%s</xliff:g>」を削除しました。"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"ショートカット「<xliff:g id="NAME">%s</xliff:g>」は既に存在します。"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"ショートカットを選択"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"アプリを選択"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"アプリ"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"ホーム"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"削除"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"アンインストール"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"削除"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"アンインストール"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"アプリ情報"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"検索"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"音声検索"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"アプリ"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"削除"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"更新をアンインストール"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"アプリをアンインストール"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"アプリの詳細"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1つのアプリが選択されています"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1つのウィジェットが選択されています"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1つのフォルダが選択されています"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1つのショートカットが選択されています"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"ショートカットのインストール"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"ユーザー操作なしでショートカットを追加することをアプリに許可します。"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ショートカットのアンインストール"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"ユーザー操作なしでショートカットを削除することをアプリに許可します。"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"ホームの設定とショートカットの読み取り"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"ホームの設定とショートカットの読み取りをアプリに許可します。"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"ホームの設定とショートカットの書き込み"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"ホームの設定とショートカットの変更をアプリに許可します。"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"ウィジェットを表示できません"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"このシステムアプリはアンインストールできません。"</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"名前のないフォルダ"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"ホーム画面: %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"%1$d/%2$dページ"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"ホーム画面: %1$d/%2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"アプリの%1$d/%2$dページ"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ウィジェットの%1$d/%2$dページ"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"ようこそ!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"ホームをカスタマイズします。"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"アプリとフォルダの画面をもっと作成します"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"スペースを整理"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"壁紙、ウィジェット、設定を管理するには、背景を押し続けます。"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"アプリの選択"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"アプリをホーム画面に追加するにはアプリを押し続けます。"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"これがフォルダです"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"これと同じフォルダを作成するには、アプリを押し続けてから別のアプリの上に移動します。"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"フォルダが開いています。<xliff:g id="WIDTH">%1$d</xliff:g>x<xliff:g id="HEIGHT">%2$d</xliff:g>の大きさです"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"タップしてフォルダを閉じます"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"タップして名前の変更を保存します"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"フォルダは閉じています"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"フォルダの名前を「<xliff:g id="NAME">%1$s</xliff:g>」に変更しました"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"フォルダ: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"ウィジェット"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"壁紙"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
+</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..a2e3374
--- /dev/null
+++ b/res/values-ka-rGE/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"მთავარი"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android-ის ბირთვის აპები"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"ფონის დაყენება"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"არჩეულია %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"არჩეულია %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"არჩეულია %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"ფონი %1$d %2$d-დან"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"არჩეულია <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"წაშლა"</string>
+ <string name="pick_image" msgid="1272073934062909527">"სურათის ამორჩევა"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"ფონები"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"ფონის ჩამოჭრა"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"აპი არ არის დაყენებული."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"ვიჯეტები"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"ვიჯეტები"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mem-ის ჩვენება"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"შეეხეთ და დააყოვნეთ ვიჯეტის ასარჩევად."</string>
+ <string name="market" msgid="2619650989819296998">"მაღაზია"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"ერთეულის მთავარ ეკრანზე ჩაგდება ვერ მოხერხდა."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"აირჩიეთ ვიჯეტი შესაქმნელად"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"საქაღალდის სახელი"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"საქაღალდის გადარქმევა"</string>
+ <string name="rename_action" msgid="5559600076028658757">"კარგი"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"გაუქმება"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"მთავარ ეკრანზე დამატება"</string>
+ <string name="group_applications" msgid="3797214114206693605">"აპები"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"მალსახმობები"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"ვიჯეტები"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"მთავარ ეკრანებზე ადგილი აღარ არის."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"ამ მთავარ ეკრანზე ადგილი აღარ არის."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"hotseat-ში მეტი ადგილი არ არის."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"ეს ვიჯეტი ძალიან დიდია hotseat-ისთვის."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"შეიქმნა მალსახმობი „<xliff:g id="NAME">%s</xliff:g>“."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"მასლახმობი „<xliff:g id="NAME">%s</xliff:g>“ წაშლილია."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"მალსახმობი „<xliff:g id="NAME">%s</xliff:g>“ უკვე არსებობს."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"აირჩიეთ მალსახმობი"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"აირჩიეთ აპი"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"აპები"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"მთავარი"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"წაშლა"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"დეინსტალაცია"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"წაშლა"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"დეინსტალაცია"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"აპის შესახებ"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"ძიება"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ხმოვანი ძიება"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"აპები"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"წაშლა"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"განახლების დეინსტალაცია"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"აპის დეინსტალაცია"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"აპის შესახებ"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"არჩეულია 1 აპი"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"არჩეულია 1 ვიჯეტი"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"არჩეულია 1 საქაღალდე"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"არჩეულია 1 მალსახმობი"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"მალსახმობების დაყენება"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"აპისთვის მალსახმობების დამოუკიდებლად დამატების უფლების მიცემა."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"მალსახმობების წაშლა"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"აპისთვის მალსახმობების დამოუკიდებლად წაშლის უფლების მიცემა."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვა"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"აპისთვის მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვის უფლების მიცემა."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"მთავარი ეკრანის პარამეტრებისა და მალსახმობების ჩაწერა"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"აპისთვის მთავარი ეკრანის პარამეტრებისა და მალსახმობების შეცვლის უფლების მიცემა."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"პრობლემა ვიჯეტის ჩატვირთვისას"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"ეს სისტემური აპია და მისი წაშლა შეუძლებელია."</string>
+ <string name="dream_name" msgid="1530253749244328964">"ფეიერვერკი"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"უსახელო საქაღალდე"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"მთავარი ეკრანი %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"გვერდი %1$d %2$d-დან"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"მთავარი ეკრანი %1$d, %2$d-დან"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"აპების გვერდი %1$d, %2$d-დან"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ვიჯეტების გვერდი %1$d, %2$d-დან"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"კეთილი იყოს თქვენი მობრძანება!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"იგრძენით თავი საკუთარ სახლში"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"აპებისა და საქაღალდეებისთვის კიდევ ერთი ეკრანის შექმნა"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"თქვენი სივრცის ორგანიზება"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"თუ გსურთ ფონების, ვიჯეტების და პარამეტრების მართვა, შეეხეთ და არ აუშვათ ფონს."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"აირჩიეთ რამდენიმე აპი"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"აპის მთავარ ეკრანზე დასამატებლად შეეხეთ მის ხატულას და არ აუშვათ."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"აი, საქაღალდე"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"ასეთის შესაქმნელად, შეეხეთ და დააყოვნეთ აპზე, ხოლო შემდეგ გადააჩოჩეთ შემდეგზე."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"კარგი"</string>
+ <string name="folder_opened" msgid="94695026776264709">"საქაღალდე გახსნილია, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"შეეხეთ საქაღალდის დასახურად"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"შეეხეთ ახალი სახელის შესანახად"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"საქაღალდე დაიხურა"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"საქაღალდეს შეეცვალა სახელი „<xliff:g id="NAME">%1$s</xliff:g>“-ად"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"საქაღალდე: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"ვიჯეტები"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"ფონები"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"პარამეტრები"</string>
+</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..977b2dd
--- /dev/null
+++ b/res/values-km-rKH/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"ដើម"</string>
+ <string name="uid_name" msgid="7820867637514617527">"កម្មវិធីសំខាន់ៗរបស់ Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"កំណត់ផ្ទាំងរូបភាព"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"បានជ្រើស %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"បានជ្រើស %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"បានជ្រើស %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"ផ្ទាំងរូបភាព %1$d នៃ %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"បានជ្រើស <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"លុប"</string>
+ <string name="pick_image" msgid="1272073934062909527">"ជ្រើសយករូបភាព"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"ផ្ទាំងរូបភាព"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"ច្រឹបផ្ទាំងរូបភាព"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"មិនបានដំឡើងកម្មវិធី។"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"ធាតុក្រាហ្វិក"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"ធាតុក្រាហ្វិក"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"បង្ហាញ Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"ប៉ះ & សង្កត់ ដើម្បីជ្រើសធាតុក្រាហ្វិក។"</string>
+ <string name="market" msgid="2619650989819296998">"ហាងទំនិញ"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"មិនអាចទម្លាក់ធាតុលើអេក្រង់ដើមនេះទេ"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"ជ្រើសធាតុក្រាហ្វិក ដើម្បីបង្កើត"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"ឈ្មោះថត"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"ប្ដូរឈ្មោះថត"</string>
+ <string name="rename_action" msgid="5559600076028658757">"យល់ព្រម"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"បោះបង់"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"បន្ថែមទៅអេក្រង់ដើម"</string>
+ <string name="group_applications" msgid="3797214114206693605">"កម្មវិធី"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"ផ្លូវកាត់"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"ធាតុក្រាហ្វិក"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"គ្មានបន្ទប់នៅលើអេក្រង់ដើមរស់អ្នកទៀតទេ។"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"គ្មានបន្ទប់នៅលើអេក្រង់ដើមនេះទៀតទេ។"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"គ្មានបន្ទប់នៅក្នុងមជ្ឈមណ្ឌលទៀតទេ។"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"ធាតុក្រាហ្វិកនេះធំពេកសម្រាប់មជ្ឈមណ្ឌល។"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"បានបង្កើតផ្លូវកាត់ \"<xliff:g id="NAME">%s</xliff:g>\" ។"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"បានលុបផ្លូវកាត់ \"<xliff:g id="NAME">%s</xliff:g>\" ។"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"មានផ្លូវកាត់ \"<xliff:g id="NAME">%s</xliff:g>\" រួចហើយ។"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"ជ្រើសផ្លូវកាត់"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"ជ្រើសកម្មវិធី"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"កម្មវិធី"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"ដើម"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"លុបចេញ"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"លុប"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"លុបចេញ"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"លុប"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"ព័ត៌មានកម្មវិធី"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"ស្វែងរក"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ស្វែងរកតាមសំឡេង"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"កម្មវិធី"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"លុបចេញ"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"លុបបច្ចុប្បន្នភាព"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"លុបកម្មវិធី"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"ព័ត៌មានលម្អិតកម្មវិធី"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"បានជ្រើសកម្មវិធី ១"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"បានជ្រើសធាតុ ១"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"បានជ្រើសថត ១"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"បានជ្រើសផ្លូវកាត់ ១"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"ដំឡើងផ្លូវកាត់"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"អនុញ្ញាតឲ្យកម្មវិធីបន្ថែមផ្លូវកាត់ ដោយមិនចាំបាច់អំពើពីអ្នកប្រើ។"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"លុបផ្លូវកាត់"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"អនុញ្ញាតឲ្យកម្មវិធីលុបផ្លូវកាត់ដោយមិនចាំបាច់អំពើពីអ្នកប្រើ។"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"អានការកំណត់ និងផ្លូវកាត់អេក្រង់ដើម"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"អនុញ្ញាតឲ្យកម្មវិធីអានការកំណត់ និងផ្លូវកាត់ក្នុងអេក្រង់ដើម។"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"សរសេរការកំណត់ និងផ្លូវកាត់លើអេក្រង់ដើម"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"អនុញ្ញាតឲ្យកម្មវិធីប្ដូរការកំណត់ និងផ្លូវកាត់ក្នុងអេក្រង់ដើម។"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"បញ្ហាក្នុងការផ្ទុកធាតុក្រាហ្វិក"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"នេះជាកម្មវិធីប្រព័ន្ធ មិនអាចលុបបានទេ។"</string>
+ <string name="dream_name" msgid="1530253749244328964">"កម្មវិធីចាប់ផ្ដើមរ៉ូកែត"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"ថតគ្មានឈ្មោះ"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"អេក្រង់ដើម %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"ទំព័រ %1$d នៃ %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"អេក្រង់ដើម %1$d នៃ %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"ទំព័រកម្មវិធី %1$d នៃ %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ទំព័រធាតុក្រាហ្វិក %1$d នៃ %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"សូមស្វាគមន៍!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"ធ្វើដោយខ្លួនឯងនៅលើអេក្រង់ដើម។"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"បង្កើតអេក្រង់ច្រើនសម្រាប់កម្មវិធី និងថតឯកសារ"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"រៀបចំចន្លោះរបស់អ្នក"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"ប៉ះ & សង្កត់លើផ្ទៃខាងក្រោម ដើម្បីគ្រប់គ្រងផ្ទាំងរូបភាព, ធាតុក្រាហ្វិក និងការកំណត់។"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"ជ្រើសកម្មវិធីមួយចំនួន"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"ប៉ះ & សង្កត់វា ដើម្បីបន្ថែមកម្មវិធីទៅអេក្រង់ដើមរបស់អ្នក"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"នេះជាថត"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"ដើម្បីបង្កើតមួយដូចនេះ ប៉ះ & សង្កត់លើកម្មវិធី បន្ទាប់មកផ្លាស់ទីវាទៅលើធាតុមួយផ្សេងទៀត។"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"យល់ព្រម"</string>
+ <string name="folder_opened" msgid="94695026776264709">"បានបើកថត <xliff:g id="WIDTH">%1$d</xliff:g> ដោយ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"ប៉ះ ដើម្បីបិទថត"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"ប៉ះ ដើម្បីរក្សាទុកការប្ដូរឈ្មោះ"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"បានបិទថត"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"បានប្ដូរឈ្មោះថតជា <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"ធាតុក្រាហ្វិក"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"ផ្ទាំងរូបភាព"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"ការកំណត់"</string>
+</resources>
diff --git a/res/values-ko-land/strings.xml b/res/values-ko-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ko-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..31c2d69
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"홈"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android 핵심 앱"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"배경화면 설정"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d개 선택됨"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d개 선택됨"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d개 선택됨"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"배경화면 %1$d/%2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> 선택함"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"삭제"</string>
+ <string name="pick_image" msgid="1272073934062909527">"이미지 선택"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"배경화면"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"배경화면 잘라내기"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"앱이 설치되지 않았습니다."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"위젯"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"위젯"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"메모리 표시"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"위젯을 선택하려면 길게 터치하세요."</string>
+ <string name="market" msgid="2619650989819296998">"쇼핑하기"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"홈 화면에 항목을 놓을 수 없습니다."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"만들 위젯 선택"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"폴더 이름"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"폴더 이름 바꾸기"</string>
+ <string name="rename_action" msgid="5559600076028658757">"확인"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"취소"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"홈 화면에 추가"</string>
+ <string name="group_applications" msgid="3797214114206693605">"앱"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"바로가기"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"위젯"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"홈 화면에 더 이상 공간이 없습니다."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"홈 화면에 더 이상 공간이 없습니다."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"즐겨찾는 앱 모음에 더 이상 빈 공간이 없습니다."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"이 위젯은 너무 커서 즐겨찾는 앱 모음에 들어갈 수 없습니다."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 생성되었습니다."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 삭제되었습니다."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 이미 있습니다."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"바로가기 선택"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"앱 선택"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"앱"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"홈"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"삭제"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"제거"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"삭제"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"제거"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"앱 정보"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"검색"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"음성 검색"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"앱"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"삭제"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"업데이트 제거"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"앱 제거"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"앱 세부정보"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"앱 1개 선택됨"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"위젯 1개 선택됨"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"폴더 1개 선택됨"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"바로가기 1개 선택됨"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"바로가기 설치"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"앱이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"바로가기 제거"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"앱이 사용자의 작업 없이 바로가기를 삭제할 수 있도록 합니다."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"홈 설정 및 바로가기 읽기"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"앱이 홈에 있는 설정 및 바로가기를 읽을 수 있도록 합니다."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"홈 설정 및 바로가기 쓰기"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"앱이 홈에 있는 설정 및 바로가기를 변경할 수 있도록 합니다."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"위젯을 로드하는 중 문제가 발생했습니다."</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"시스템 앱은 제거할 수 없습니다."</string>
+ <string name="dream_name" msgid="1530253749244328964">"로켓 실행기"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"이름이 없는 폴더"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"홈 화면 %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"페이지 %1$d/%2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"홈 화면 %1$d/%2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"앱 페이지 %1$d/%2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"위젯 페이지 %1$d/%2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"환영합니다!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"편리한 사용 환경을 만드세요."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"앱 및 폴더를 표시할 화면 더 만들기"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"공간 관리하기"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"배경화면, 위젯, 설정을 관리하려면 백그라운드를 길게 터치합니다."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"앱 선택하기"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"홈 화면에 앱을 추가하려면 길게 터치합니다."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"폴더"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"폴더를 만들려면 앱을 길게 터치한 다음 다른 앱 위에 올려 놓으세요."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"확인"</string>
+ <string name="folder_opened" msgid="94695026776264709">"폴더 열림(<xliff:g id="WIDTH">%1$d</xliff:g>X<xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"터치하여 폴더를 닫음"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"터치하여 바꾼 이름을 저장"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"폴더 닫음"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"폴더 이름 변경: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"폴더: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"위젯"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"배경화면"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"설정"</string>
+</resources>
diff --git a/res/values-land/config.xml b/res/values-land/config.xml
new file mode 100644
index 0000000..121bb0c
--- /dev/null
+++ b/res/values-land/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+<!-- Workspace -->
+ <!-- Whether or not the drop targets drop down as opposed to fade in -->
+ <bool name="config_useDropTargetDownTransition">false</bool>
+ <!-- Whether or not to fade the side pages -->
+ <bool name="config_workspaceFadeAdjacentScreens">false</bool>
+</resources>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
new file mode 100644
index 0000000..5961b19
--- /dev/null
+++ b/res/values-land/dimens.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+<!-- QSB -->
+ <dimen name="toolbar_button_vertical_padding">8dip</dimen>
+ <dimen name="toolbar_button_horizontal_padding">0dip</dimen>
+
+<!-- Workspace -->
+ <!-- We really want the page spacing to be the max of either the button bar
+ height or the qsb bar height -->
+ <dimen name="workspace_page_spacing">-1dp</dimen>
+
+<!-- AppsCustomize -->
+ <dimen name="apps_customize_tab_bar_height">42dp</dimen>
+ <integer name="apps_customize_widget_cell_count_x">3</integer>
+ <integer name="apps_customize_widget_cell_count_y">2</integer>
+ <integer name="apps_customize_cling_focused_x">2</integer>
+ <integer name="apps_customize_cling_focused_y">1</integer>
+</resources>
diff --git a/res/values-land/strings.xml b/res/values-land/strings.xml
new file mode 100644
index 0000000..ec4c7e7
--- /dev/null
+++ b/res/values-land/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Manifest configuration. -->
+ <skip />
+</resources>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
new file mode 100644
index 0000000..ccb5fcb
--- /dev/null
+++ b/res/values-land/styles.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources>
+<!-- Search Bar -->
+ <style name="QSBBar">
+ </style>
+ <style name="SearchDropTargetBar">
+ </style>
+ <style name="SearchButton">
+ </style>
+ <style name="DropTargetButtonContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">0dp</item>
+ </style>
+ <style name="DropTargetButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center</item>
+ <item name="android:paddingTop">@dimen/toolbar_button_vertical_padding</item>
+ <item name="android:paddingBottom">@dimen/toolbar_button_vertical_padding</item>
+ <item name="android:paddingStart">@dimen/toolbar_button_horizontal_padding</item>
+ <item name="android:paddingEnd">@dimen/toolbar_button_horizontal_padding</item>
+ <item name="android:shadowColor">#DD000000</item>
+ <item name="android:shadowDx">0.0</item>
+ <item name="android:shadowDy">1.0</item>
+ <item name="android:shadowRadius">4.0</item>
+ </style>
+
+<!-- AppsCustomize -->
+ <style name="TabIndicator.AppsCustomize">
+ <item name="android:maxWidth">200dp</item>
+ </style>
+</resources>
+
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..e3a95dd
--- /dev/null
+++ b/res/values-lo-rLA/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"ໜ້າຫຼັກ"</string>
+ <string name="uid_name" msgid="7820867637514617527">"ແອັບພລິເຄຊັນຫຼັກຂອງ Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"ຕັ້ງເປັນພາບພື້ນຫຼັງ"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"ເລືອກ %1$d ລາຍການແລ້ວ"</item>
+ <item quantity="one" msgid="142482526010824029">"ເລືອກ %1$d ລາຍການແລ້ວ"</item>
+ <item quantity="other" msgid="1418352074806573570">"ເລືອກ %1$d ລາຍການແລ້ວ"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"ຮູບພືື້ນຫຼັງ %1$d ໃນ %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"ເລືອກແລ້ວ <xliff:g id="LABEL">%1$s</xliff:g> ອັນ"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"ລຶບ"</string>
+ <string name="pick_image" msgid="1272073934062909527">"ເລືອກຮູບ"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"ພາບພື້ນຫຼັງ"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"ຕັດຮູບພື້ນຫຼັງ"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"ແອັບຯບໍ່ໄດ້ຖືກຕິດຕັ້ງ."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"ວິດເຈັດ"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"ວິດເຈັດ"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"ສະແດງຄວາມຈຳ"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"ສຳພັດຄ້າງໄວ້ ເພື່ອຈັບວິດເຈັດ."</string>
+ <string name="market" msgid="2619650989819296998">"ຮ້ານຄ້າ"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"ບໍ່ສາມາດວາງລາຍການໃສ່ໜ້າຈໍຫຼັກນີ້ໄດ້"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"ເລືອກວິດເຈັດເພື່ອສ້າງມັນ"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"ຊື່ໂຟນເດີ"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"ປ່ຽນຊື່ໂຟນເດີ"</string>
+ <string name="rename_action" msgid="5559600076028658757">"ຕົກລົງ"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"ຍົກເລີກ"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"ເພີ່ມໃສ່ໜ້າຈໍຫຼັກ"</string>
+ <string name="group_applications" msgid="3797214114206693605">"ແອັບຯ"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"ທາງລັດ"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"ວິດເຈັດ"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກຂອງທ່ານ."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"ບໍ່ມີຫ້ອງຫວ່າງໃນ hotseat ແລ້ວ."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"ວິດເຈັດ ມີຂະໜາດໃຫຍ່ເກີນໄປສຳລັບ hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"ທາງລັດ \"<xliff:g id="NAME">%s</xliff:g>\" ຖືກສ້າງແລ້ວ."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"ທາງລັດ \"<xliff:g id="NAME">%s</xliff:g>\" ຖືກລຶບແລ້ວ."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"ທາງລັດ \"<xliff:g id="NAME">%s</xliff:g>\" ມີຢູ່ແລ້ວ."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"ເລືອກທາງລັດ"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"ເລືອກແອັບຯ"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"ແອັບຯ"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"ໜ້າຫຼັກ"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"ລຶບ"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"ຖອນການຕິດຕັ້ງ"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"ລຶບ"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"ຖອນການຕິດຕັ້ງ"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"ຂໍ້ມູນແອັບຯ"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"ຊອກຫາ"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ຊອກຫາດ້ວຍສຽງ"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"ແອັບຯ"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"ລຶບ"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"ຖອນອັບເດດ"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"ຖອນແອັບຯ"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"ລາຍລະອຽດແອັບຯ"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 ແອັບຯຖືກເລືອກ"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 ວິດເຈັດຖືກເລືອກ"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 ໂຟນເດີຖືກເລືອກ"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 ທາງລັດຖືກເລືອກ"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"ຕິດຕັ້ງທາງລັດ"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"ອະນຸຍາດໃຫ້ແອັບຯ ເພີ່ມທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ຖອນທາງລັດ"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວ ລຶບທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"ອ່ານການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວອ່ານການຕັ້ງຄ່າ ແລະທາງລັດໃນໜ້າຫຼັກ."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"ຂຽນການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວ ປ່ຽນການຕັ້ງຄ່າ ແລະທາງລັດໃນໜ້າຫຼັກ."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"ມີບັນຫາໃນການໂຫລດວິດເຈັດ"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"ນີ້ແມ່ນແອັບຯຂອງລະບົບ ແລະບໍ່ສາມາດຖອນການຕິດຕັ້ງອອກໄດ້."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"ໂຟນເດີຍັງບໍ່ຖືກຕັ້ງຊື່"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"ໜ້າຈໍຫຼັກ %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"ໜ້າ %1$d ຈາກ %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"ໜ້າຈໍຫຼັກ %1$d ໃນ %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"ແອັບຯໜ້າ %1$d ໃນ %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"ວິດເຈັດໜ້າ %1$d ໃນ %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"ຍິນດີຕ້ອນຮັບ!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"ເຮັດໂຕໃຫ້ຄືຢູ່ໃນບ້ານ"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"ສ້າງຈໍເພີ່ມເຕີມສຳລັບແອັບຯ ແລະໂຟນເດີ"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"ຈັດການພື້ນທີ່ຂອງທ່ານ"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"ແຕະຄ້າງໄວ້ທີ່ພາບພື້ນຫຼັງເພື່ອຈັດການພາບພື້ນຫຼັງ, ວິດເຈັດແລະການຕັ້ງຄ່າ."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"ເລືອກແອັບຯ"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"ເພື່ອເພີ່ມແອັບຯໃສ່ໜ້າຈໍຫຼັກຂອງທ່ານ, ໃຫ້ແຕະຄ້າງໄວ້."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"ນີ້ແມ່ນໂຟນເດີ່"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"ເພື່ອສ້າງອັນໃໝ່ແບບນີ້, ແຕະຄ້າງໄວ້ທີ່ແອັບຯ ແລ້ວຍ້າຍມັນໄປຫາໂຕອື່ນ."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"ຕົກລົງ"</string>
+ <string name="folder_opened" msgid="94695026776264709">"ເປີດໂຟນເດີແລ້ວ, <xliff:g id="WIDTH">%1$d</xliff:g> ຄູນ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"ສຳພັດເພື່ອປິດໂຟນເດີ"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"ສຳພັດເພື່ອບັນທຶກການປ່ຽນຊື່"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"ປິດໂຟນເດີແລ້ວ"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"ປ່ຽນຊື່ໂຟນເດີເປັນ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"ໂຟນເດີ: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"ວິດເຈັດ"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"ພາບພື້ນຫຼັງ"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"ການຕັ້ງຄ່າ"</string>
+</resources>
diff --git a/res/values-lt-land/strings.xml b/res/values-lt-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-lt-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000..80d4412
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Pagrindinis"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Pagrindinės „Android“ programos"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Nustatyti ekrano foną"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Pasirinkta: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Pasirinkta: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Pasirinkta: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d iš %2$d ekrano fonų"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Pasirinkta: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Ištrinti"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Pasirinkti vaizdą"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Ekrano fonai"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Ekrano fono apkirpimas"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Programa neįdiegta."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Valdikliai"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Valdikliai"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Rodyti atmintinę"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Palieskite ir laikykite, kad pasirinkt. valdiklį."</string>
+ <string name="market" msgid="2619650989819296998">"Apsipirkti"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Nepavyko nuvilkti elemento į šį pagrindinį ekraną."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pasirinkite norimą kurti valdiklį"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Aplanko pavadinimas"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Pervardyti aplanką"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Gerai"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Atšaukti"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Pridėti prie pagrindinio ekrano"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Programos"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Spartieji klavišai"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Valdikliai"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Pagrindiniuose ekranuose vietos nebėra."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Šiame pagrindiniame ekrane vietos nebėra."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Įtvirtintojoje srityje nebėra vietos."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Šis valdiklis įtvirtintajai sričiai per didelis."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Spartusis klavišas „<xliff:g id="NAME">%s</xliff:g>“ sukurtas."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Spartusis klavišas „<xliff:g id="NAME">%s</xliff:g>“ pašalintas."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Spartusis klavišas „<xliff:g id="NAME">%s</xliff:g>“ jau yra."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Pasirinkite spartųjį klavišą"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Pasirinkite programą"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Programos"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Pagrindinis"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Pašalinti"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Pašalinti"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Pašalinti"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Pašalinti"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Programos informacija"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Ieškoti"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Paieška balsu"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Programos"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Pašalinti"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Pašalinti naujinį"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Pašalinti programą"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Išsami programos informacija"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Pasirinkta 1 programa"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Pasirinktas 1 valdiklis"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Pasirinktas 1 aplankas"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Pasirinktas 1 spartusis klavišas"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"įdiegti sparčiuosius klavišus"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Programai leidžiama pridėti sparčiuosius klavišus be naudotojo įsikišimo."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"pašalinti sparčiuosius klavišus"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Programai leidžiama pašalinti sparčiuosius klavišus be naudotojo įsikišimo."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Programai leidžiama skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"rašyti pagrindinio puslapio nustatymus ir sparčiuosius klavišus"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Programai leidžiama keisti pagrindinio puslapio nustatymus ir sparčiuosius klavišus."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problema įkeliant valdiklį"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Tai sistemos programa ir jos negalima pašalinti."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Aplankas be pavadinimo"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"%1$d pagrindinis ekranas"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"%1$d psl. iš %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d pagrindinis ekranas iš %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%1$d programų psl. iš %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%1$d valdiklių psl. iš %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Sveiki!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Jauskitės kaip namie."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Sukurkite daugiau programų ir aplankų ekrano kopijų"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Tvarkykite savo vietą"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Palieskite ir laikykite foną, jei norite tvarkyti ekrano foną, valdiklius ir nustatymus."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Pasirinkite kelias programas"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Jei norite prie pagrindinio ekrano pridėti programą, palieskite ją ir laikykite."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Štai aplankas"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Kad sukurtumėte tokį patį, palieskite ir laikykite programą, tada perkelkite ją virš kitos programos."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Gerai"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Atidarytas aplankas, <xliff:g id="WIDTH">%1$d</xliff:g> ir <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Palieskite, kad uždarytumėte aplanką"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Palieskite, kad išsaugotumėte naują pavadinimą"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Aplankas uždarytas"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Aplankas pervardytas kaip „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Aplankas: „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Valdikliai"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Ekrano fonai"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Nustatymai"</string>
+</resources>
diff --git a/res/values-lv-land/strings.xml b/res/values-lv-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-lv-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000..b70f5dc
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Sākums"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android pamatlietotnes"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Iestatīt fona tapeti"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Atlasīti: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Atlasīti: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Atlasīti: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%1$d. fona tapete no %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Atlasīts: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Dzēst"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Izvēlēties attēlu"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Fona tapetes"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Apgriezt fona tapeti"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Lietotne nav instalēta."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Logrīki"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Logrīki"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Rādīt atmiņu"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Lai izvēlētos logrīku, pieskarieties un turiet to."</string>
+ <string name="market" msgid="2619650989819296998">"Iepirkties"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Nevarēja nomest vienumu šajā sākuma ekrānā."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Izveidojamā logrīka izvēle"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Mapes nosaukums"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Mapes pārdēvēšana"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Labi"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Atcelt"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Pievienošana sākuma ekrānam"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Lietotnes"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Saīsnes"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Logrīki"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Sākuma ekrānos vairs nav vietas."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Šajā sākuma ekrānā vairs nav vietas."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Režīmā “hotseat” vairs nav vietas."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Šis logrīks ir pārāk liels režīmam “hotseat”."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Tika izveidota saīsne “<xliff:g id="NAME">%s</xliff:g>”."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Tika noņemta saīsne “<xliff:g id="NAME">%s</xliff:g>”."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Saīsne “<xliff:g id="NAME">%s</xliff:g>” jau pastāv."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Saīsnes izvēle"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Lietotnes izvēle"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Lietotnes"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Sākums"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Noņemt"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Atinstalēt"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Noņemt"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Atinstalēt"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Lietotnes informācija"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Meklēt"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Meklēšana ar balsi"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Lietotnes"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Noņemt"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Atinstalēt atjauninājumu"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Atinstalēt lietotni"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Lietotnes informācija"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Atlasīta 1 lietotne"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Atlasīts 1 logrīks"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Atlasīta 1 mape"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Atlasīta 1 saīsne"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalēt saīsnes"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ļauj lietotnei pievienot saīsnes, nejautājot lietotājam."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"atinstalēt saīsnes"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ļauj lietotnei noņemt saīsnes, nejautājot lietotājam."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"lasīt sākuma ekrāna iestatījumus un saīsnes"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Ļauj lietotnei lasīt iestatījumus un saīsnes sākuma ekrānā."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"rakstīt sākuma ekrāna iestatījumus un saīsnes"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Ļauj lietotnei mainīt iestatījumus un saīsnes sākuma ekrānā."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Ielādējot logrīku, radās problēma."</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Šī ir sistēmas lietotne, un to nevar atinstalēt."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Mape bez nosaukuma"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Sākuma ekrāns: %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"%1$d. lapa no %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Sākuma ekrāns: %1$d no %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%1$d. lietotņu lapa no %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%1$d. logrīku lapa no %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Laipni lūdzam!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Informācija par pamatfunkcijām"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Izveidojiet vairāk ekrānu lietotnēm un mapēm."</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Kārtojiet savu darbvietu"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Pieskarieties fonam un turiet to, lai pārvaldītu fona tapeti, logrīkus un iestatījumus."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Izvēlieties dažas lietotnes"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Lai sākuma ekrānam pievienotu lietotni, pieskarieties tai un turiet to."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Lūk, mape!"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Lai izveidotu tādu pašu, pieskarieties lietotnei un turiet to, pēc tam pārvietojiet to virs citas lietotnes."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Labi"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Atvērta mape: <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Pieskarieties, lai aizvērtu mapi."</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Pieskarieties, lai saglabātu pārdēvēto nosaukumu."</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Mape aizvērta"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Mape pārdēvēta par: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Mape: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Logrīki"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Fona tapetes"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Iestatījumi"</string>
+</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..7cf7a9c
--- /dev/null
+++ b/res/values-mn-rMN/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Нүүр"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Андройд үндсэн апп"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Ханын зургийг тохируулах"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d сонгогдсон"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d сонгогдсон"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d сонгогдсон"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"%2$d ханын цаасны %1$d нь"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> сонгогдсон"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Устгах"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Зураг сонгох"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Ханын зураг"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Ханын зургийг тайрах"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Апп суугаагүй байна."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Виджет"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Виджет"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Мем харуулах"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Виджетийг авах бол хүрээд барина уу."</string>
+ <string name="market" msgid="2619650989819296998">"Дэлгүүр"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Энэ Нүүр дэлгэцэнд буулгах боломжгүй."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Үүсгэх виджетээ сонгоно уу"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Фолдерын нэр"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Фолдерын нэр өөрчлөх"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Тийм"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Цуцлах"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Нүүр дэлгэцэнд нэмэх"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Апп"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Товчлол"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Виджет"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Таны Нүүр дэлгэц зайгүй."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Энэ Нүүр дэлгэц зайгүй."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Суурь зайгүй."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Энэ виджет сууринд хэт томдож байна."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"\"<xliff:g id="NAME">%s</xliff:g>\" товчлол үүсэв."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"\"<xliff:g id="NAME">%s</xliff:g>\" товчлол устгагдав."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"\"<xliff:g id="NAME">%s</xliff:g>\" товчлол өмнө үүссэн байна."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Товчлол сонгох"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Апп сонгох"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Апп"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Нүүр"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Устгах"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Устгах"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Устгах"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Устгах"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Апп мэдээлэл"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Хайх"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Дуун хайлт"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Апп"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Устгах"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Шинэчлэлийг устгах"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Апп устгах"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Апп дэлгэрэнгүй"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 апп сонгогдсон"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 виджет сонгогдсон"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 фолдер сонгогдсон"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 товчлол сонгогдсон"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"товчлол суулгах"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг нэмэж чадна"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"товчлолыг устгах"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг устгаж чадна"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"Нүүрний тохиргоо болон товчлолыг унших"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Апп нь Нүүрэндэх товчлол болон тохиргоог уншиж чадна."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"Нүүрний тохиргоо болон товчлолыг бичих"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Апп нь Нүүрэндэх товчлол болон тохиргоог өөрчилж чадна."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Виджет ачаалахад асуудал гарав"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Энэ апп нь системийн апп ба устгах боломжгүй."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Пуужин хөөргөгч"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Нэргүй фолдер"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Нүүр дэлгэц %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"%2$d-н %1$d хуудас"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d-н Нүүр дэлгэц %1$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%2$d-н %1$d апп хуудас"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%2$d-н %1$d виджет хуудас"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Тавтай морилно уу!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Гэртээ байгаа мэт тухлаарай."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Апп болон фолдеруудад зориулан өөр дэлгэцүүд үүсгээрэй"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Өөрийнхөө зайг тохируулаарай"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Арын дэвсгэр дээр хүрээд & дарснаар ханын зураг, виджет болон тохиргоог өөрчилж болно."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Апп сонгоно уу"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Нүүр дэлгэцэнд апп нэмэх бол хүрээд барина уу."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Фолдер энд байна"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Үүнтэй адилханыг үүсгэхийн тулд апп дээр хүрч & бариад нөгөөхийн дээр зөөнө үү."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Тийм"</string>
+ <string name="folder_opened" msgid="94695026776264709">"<xliff:g id="WIDTH">%1$d</xliff:g> <xliff:g id="HEIGHT">%2$d</xliff:g> фолдер нээгдэв"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Фолдер хаах бол хүрнэ үү"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Шинэ нэрийг хадгалах бол хүрнэ үү"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Фолдер хаагдав"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Фолдерын нэр <xliff:g id="NAME">%1$s</xliff:g> болов"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Виджет"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Ханын зураг"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Тохиргоо"</string>
+</resources>
diff --git a/res/values-ms-land/strings.xml b/res/values-ms-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ms-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..4ee3545
--- /dev/null
+++ b/res/values-ms-rMY/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Laman Utama"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Apl Teras Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Tetapkan kertas dinding"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d dipilih"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d dipilih"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d dipilih"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Kertas dinding %1$d daripada %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Memilih <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Padam"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Pilih imej"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Kertas dinding"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Pangkas kertas dinding"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Apl tidak dipasang."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widget"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widget"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Papar Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh & tahan untuk mengambil widget."</string>
+ <string name="market" msgid="2619650989819296998">"Beli-belah"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Tidak dapat melepaskan item pada Skrin Utama."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pilih widget yang hendak dibuat"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nama folder"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Namakan semula folder"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Batal"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Tambah ke skrin Laman Utama"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apl"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Pintasan"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widget"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Tiada lagi ruang pada skrin Laman Utama anda."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Tiada lagi ruang pada skrin Laman Utama ini."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Tiada lagi ruang pada kerusi panas."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Widget ini terlalu besar untuk kerusi panas."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dibuat."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dialih keluar."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah wujud."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Pilih jalan pintas"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Pilih apl"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apl"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Laman Utama"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Alih keluar"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Nyahpasang"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Alih keluar"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Nyahpasang"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Maklumat apl"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Cari"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Carian Suara"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apl"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Alih keluar"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Nyahpasang kemas kini"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Nyahpasang apl"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Butiran apl"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 apl dipilih"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget dipilih"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder dipilih"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 pintasan dipilih"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"pasang pintasan"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Membenarkan apl menambah pintasan tanpa campur tangan pengguna."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"nyahpasang pintasan"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Membenarkan apl mengalih keluar pintasan tanpa campur tangan pengguna."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"baca tetapan dan pintasan Laman Utama"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Membenarkan apl membaca tetapan dan pintasan di Laman Utama."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"tulis tetapan dan pintasan Laman Utama"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Membenarkan apl menukar tetapan dan pintasan di Laman Utama."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Masalah memuatkan widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ini ialah apl sistem dan tidak boleh dinyahpasang."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Pelancar Roket"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Folder Tanpa Nama"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Skrin Laman Utama %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Halaman %1$d daripada %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Skrin Laman Utama %1$d daripada %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Halaman apl %1$d daripada %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Halaman widget %1$d daripada %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Selamat datang!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Buat seperti berada di rumah sendiri."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Buat lebih banyak skrin untuk apl dan folder"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Susun ruang anda"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Sentuh & tahan latar belakang untuk mengurus kertas dinding, widget dan tetapan."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Pilih beberapa apl"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Untuk menambahkan apl pada skrin Laman Utama anda, sentuh & tahan apl."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Ini ada folder"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Untuk membuat satu folder seperti ini, sentuh & tahan apl, kemudian alihkan ke atas folder lain."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> kali <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Sentuh untuk menutup folder"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Sentuh untuk menyimpan penamaan semula"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folder ditutup"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Folder dinamakan semula kepada <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widget"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Kertas dinding"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Tetapan"</string>
+</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
new file mode 100644
index 0000000..d2f6bb1
--- /dev/null
+++ b/res/values-ms/strings.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="8424725141379931883">"Pelancar"</string>
+ <string name="home" msgid="5921706419368316758">"Laman Utama"</string>
+ <string name="uid_name" msgid="3371120195364560632">"Apl Teras Android"</string>
+ <string name="folder_name" msgid="8551881338202938211"></string>
+ <string name="chooser_wallpaper" msgid="6063168087625352235">"Pilih kertas dinding dari"</string>
+ <string name="wallpaper_instructions" msgid="4215640646180727542">"Tetapkan kertas dinding"</string>
+ <string name="pick_wallpaper" msgid="5630222540525626723">"Kertas dinding"</string>
+ <string name="activity_not_found" msgid="217823393239365967">"Aplikasi tidak dipasang."</string>
+ <string name="widgets_tab_label" msgid="9145860100000983599">"Widget"</string>
+ <string name="long_press_widget_to_add" msgid="7395697462851217506">"Sentuh & tahan untuk mengambil widget."</string>
+ <string name="market" msgid="2652226429823445833">"Kedai"</string>
+ <string name="widget_dims_format" msgid="1386418557719032947">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="2285187188524172774">"Tidak dapat melepaskan item pada skrin Utama ini."</string>
+ <string name="external_drop_widget_pick_title" msgid="7040647073452295370">"Pilih widget untuk dibuat"</string>
+ <string name="rename_folder_label" msgid="5646236631298452787">"Nama folder"</string>
+ <string name="rename_folder_title" msgid="4544573104191526550">"Namakan semula folder"</string>
+ <string name="rename_action" msgid="6016003384693240896">"OK"</string>
+ <string name="cancel_action" msgid="3811860427489435048">"Batal"</string>
+ <string name="menu_item_add_item" msgid="6233177331075781114">"Tambah ke Skrin utama"</string>
+ <string name="group_applications" msgid="2103752818818161976">"Aplikasi"</string>
+ <string name="group_shortcuts" msgid="9133529424900391877">"Pintasan"</string>
+ <string name="group_widgets" msgid="6704978494073105844">"Widget"</string>
+ <string name="group_wallpapers" msgid="1568191644272224858">"Kertas dinding"</string>
+ <string name="completely_out_of_space" msgid="1759078539443491182">"Tiada lagi ruang pada skrin Utama anda."</string>
+ <string name="out_of_space" msgid="8365249326091984698">"Tiada lagi ruang pada skrin Utama ini"</string>
+ <string name="hotseat_out_of_space" msgid="6304886797358479361">"Tiada lagi ruang pada kerusi panas."</string>
+ <string name="invalid_hotseat_item" msgid="6545340627805449250">"Widget ini terlalu besar untuk kerusi panas."</string>
+ <string name="shortcut_installed" msgid="7071557296331322355">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" diwujudkan."</string>
+ <string name="shortcut_uninstalled" msgid="2129499669449749995">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" telah dialih keluar."</string>
+ <string name="shortcut_duplicate" msgid="4757756326465060694">"Pintasan \"<xliff:g id="NAME">%s</xliff:g>\" sudah pun wujud."</string>
+ <string name="title_select_shortcut" msgid="1873670208166882222">"Pilih pintasan"</string>
+ <string name="title_select_application" msgid="1793455815754848652">"Pilih aplikasi"</string>
+ <string name="all_apps_button_label" msgid="2578400570124163469">"Apl"</string>
+ <string name="all_apps_home_button_label" msgid="1022222300329398558">"Laman Utama"</string>
+ <string name="delete_zone_label_workspace" msgid="7153615831493049150">"Alih keluar"</string>
+ <string name="delete_zone_label_all_apps" msgid="6664588234817475108">"Nyahpasang"</string>
+ <string name="delete_target_label" msgid="665300185123139530">"Alih keluar"</string>
+ <string name="delete_target_uninstall_label" msgid="748894921183769150">"Nyahpasang"</string>
+ <string name="info_target_label" msgid="4019495079517426980">"Maklumat apl"</string>
+ <string name="accessibility_search_button" msgid="816822994629942611">"Carian"</string>
+ <string name="accessibility_voice_search_button" msgid="3938249215065842475">"Carian Suara"</string>
+ <string name="accessibility_all_apps_button" msgid="8803738611398979849">"Aplikasi"</string>
+ <string name="accessibility_delete_button" msgid="3628162007991023603">"Alih keluar"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="3683920959591819044">"Nyahpasang kemas kini"</string>
+ <string name="menu_add" msgid="3065046628354640854">"Tambah"</string>
+ <string name="menu_manage_apps" msgid="2308685199463588895">"Mengurus apl"</string>
+ <string name="menu_wallpaper" msgid="5837429080911269832">"Kertas dinding"</string>
+ <string name="menu_search" msgid="4826514464423239041">"Cari"</string>
+ <string name="menu_notifications" msgid="6424587053194766192">"Pemberitahuan"</string>
+ <string name="menu_settings" msgid="3946232973327980394">"Tetapan sistem"</string>
+ <string name="menu_help" msgid="4901160661634590633">"Bantuan"</string>
+ <string name="cab_menu_delete_app" msgid="4089398025537640349">"Nyahpasang aplikasi"</string>
+ <string name="cab_menu_app_info" msgid="914548323652698884">"Butiran aplikasi"</string>
+ <string name="cab_app_selection_text" msgid="6378522164293415735">"1 aplikasi dipilih"</string>
+ <string name="cab_widget_selection_text" msgid="962527270506951955">"1 widget dipilih"</string>
+ <string name="cab_folder_selection_text" msgid="8916111874189565067">"1 folder dipilih"</string>
+ <string name="cab_shortcut_selection_text" msgid="8115847384500412878">"1 pintasan dipilih"</string>
+ <string name="permlab_install_shortcut" msgid="1201690825493376489">"pasang pintasan"</string>
+ <string name="permdesc_install_shortcut" msgid="8634424803272077038">"Membenarkan aplikasi menambah pintasan tanpa campur tangan pengguna."</string>
+ <string name="permlab_uninstall_shortcut" msgid="7696645932555926449">"nyahpasang pintasan"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="274355570620220977">"Membenarkan apl mengalih keluar pintasan tanpa campur tangan pengguna."</string>
+ <string name="permlab_read_settings" msgid="3452408290738106747">"membaca tetapan dan pintasan Laman Utama"</string>
+ <string name="permdesc_read_settings" msgid="5788109303585403679">"Membenarkan apl membaca tetapan dan pintasan di Laman Utama."</string>
+ <string name="permlab_write_settings" msgid="1360567537236705628">"menulis tetapan dan pintasan Laman Utama"</string>
+ <string name="permdesc_write_settings" msgid="8530105489115785531">"Membenarkan apl menukar tetapan dan pintasan di Laman Utama."</string>
+ <string name="gadget_error_text" msgid="8359351016167075858">"Masalah memuatkan widget"</string>
+ <string name="uninstall_system_app_text" msgid="6429814133777046491">"Ini adalah aplikasi sistem dan tidak boleh dinyahpasang."</string>
+ <string name="dream_name" msgid="2847171357608437154">"Pelancar Roket"</string>
+ <string name="folder_hint_text" msgid="8633351560105748141">"Folder Tanpa Nama"</string>
+ <string name="workspace_description_format" msgid="2968608205939373034">"Skrin utama %1$d"</string>
+ <string name="default_scroll_format" msgid="4057140866420001240">"Halaman %1$d dari %2$d"</string>
+ <string name="workspace_scroll_format" msgid="1704767047951143301">"Skrin utama %1$d dari %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="5494241912377704885">"Halaman apl %1$d dari %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="5383009742241717437">"Halaman widget %1$d dari %2$d"</string>
+ <string name="workspace_cling_title" msgid="738396473989890567">"Buat diri anda seperti di rumah"</string>
+ <string name="workspace_cling_move_item" msgid="791013895761065070">"Anda boleh meletakkan aplikasi kegemaran anda di sini."</string>
+ <string name="workspace_cling_open_all_apps" msgid="2459977609848572588">"Untuk melihat semua aplikasi anda, sentuh bulatan."</string>
+ <string name="all_apps_cling_title" msgid="2559734712581447107">"Pilih beberapa aplikasi"</string>
+ <string name="all_apps_cling_add_item" msgid="5665035103260318891">"Untuk menambahkan aplikasi pada skrin Utama anda, sentuh & tahankannya."</string>
+ <string name="folder_cling_title" msgid="4308949882377840953">"Susun aplikasi anda dengan folder"</string>
+ <string name="folder_cling_move_item" msgid="270598675060435169">"Untuk memindahkan aplikasi, sentuh & tahankannya."</string>
+ <string name="folder_cling_create_folder" msgid="8352867485656129478">"Untuk membuat folder baharu pada skrin Utama anda, tindihkan satu aplikasi di atas yang lain."</string>
+ <string name="cling_dismiss" msgid="2780907108735868381">"OK"</string>
+ <string name="folder_opened" msgid="1262064100943801533">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> kali <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1335478160661137579">"Sentuh untuk menutup folder"</string>
+ <string name="folder_tap_to_rename" msgid="5201612989905472442">"Sentuh untuk menyimpan penamaan semula"</string>
+ <string name="folder_closed" msgid="3130534551370511932">"Folder ditutup"</string>
+ <string name="folder_renamed" msgid="7951233572858053642">"Folder dinamakan semula kepada <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="3051680259794759037">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="1433009175359948587"></string>
+ <string name="custom_workspace_cling_description_1" msgid="6875529190849858047"></string>
+ <string name="custom_workspace_cling_title_2" msgid="5516006164661020362"></string>
+ <string name="custom_workspace_cling_description_2" msgid="2758258454975288377"></string>
+</resources>
diff --git a/res/values-nb-land/strings.xml b/res/values-nb-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-nb-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..ce50754
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Startside"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Kjerneapper for Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Angi bakgrunn"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d valgt"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d valgt"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d valgt"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Bakgrunn %1$d of %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Valgt <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Slett"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Velg bilde"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Bakgrunner"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Beskjær bakgrunnen"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installert."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Moduler"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Moduler"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Vis minne"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Trykk og hold inne for å plukke opp en modul."</string>
+ <string name="market" msgid="2619650989819296998">"Butikk"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Kunne ikke slippe elementet på denne startsiden."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Velg modul for oppretting"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Mappenavn"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Gi mappen nytt navn"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Avbryt"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Legg til på startsiden"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apper"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Snarveier"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Moduler"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Ikke mer plass på startsidene dine."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Denne startsiden er full."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Dokksonen er full."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Denne modulen er for stor for dokksonen."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Snarveien «<xliff:g id="NAME">%s</xliff:g>» er opprettet."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Snarveien «<xliff:g id="NAME">%s</xliff:g>» er fjernet."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Snarveien «<xliff:g id="NAME">%s</xliff:g>» fins allerede."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Valg av snarvei"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Velg app"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apper"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Startside"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Fjern"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Avinstaller"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Fjern"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Avinstaller"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"App-info"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Søk"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Talesøk"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apper"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Fjern"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Avinstaller oppdateringen"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Avinstaller appen"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Informasjon om appen"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Én app er valgt"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Én modul er valgt"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Én mappe er valgt"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Én snarvei er valgt"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"installere snarveier"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Gir apper tillatelse til å legge til snarveier uten innblanding fra brukeren."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"avinstallere snarveier"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Lar appen fjerne snarveier uten innblanding fra brukeren."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"lese startsideinnstillinger og -snarveier"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Lar appen lese innstillingene og snarveiene på startsiden."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"angi startsideinnstillinger og -snarveier"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Lar appen endre innstillingene og snarveiene på startsiden."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem ved innlasting av modul"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dette er en systemapp som ikke kan avinstalleres."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Mappe uten navn"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Startside %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Side %1$d av %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Startside %1$d av %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Appside %1$d av %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Modulside %1$d av %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Velkommen!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Føl deg som hjemme."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Opprett flere sider for apper og mapper"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organiser plassen din"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Trykk og hold på bakgrunnen for å administrere bakgrunnen, moduler og innstillinger."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Velg noen apper"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Trykk og hold på en app for å legge den til på startsiden."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Dette er en mappe"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"For å opprette en som denne, trykker og holder du på en app og flytter den over en annen."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Mappen er åpnet – <xliff:g id="WIDTH">%1$d</xliff:g> ganger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Trykk for å lukke mappen"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Trykk for å lagre det nye navnet"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Mappen ble lukket"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Mappen heter nå <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Moduler"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Bakgrunner"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Innstillinger"</string>
+</resources>
diff --git a/res/values-nl-land/strings.xml b/res/values-nl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-nl-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..e2a2a07
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Startpagina"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android-kernapps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Achtergrond instellen"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d geselecteerd"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d geselecteerd"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d geselecteerd"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Achtergrond %1$d van %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> is geselecteerd"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Verwijderen"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Afbeelding kiezen"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Achtergronden"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Achtergrond bijsnijden"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"App is niet geïnstalleerd."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Geheugen weergeven"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Blijf aanraken om een widget toe te voegen."</string>
+ <string name="market" msgid="2619650989819296998">"Winkelen"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Kan item niet neerzetten in dit startscherm."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Widget selecteren om te maken"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Mapnaam"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Naam van map wijzigen"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Annuleren"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Toevoegen aan startscherm"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Snelkoppelingen"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Er is geen ruimte meer op uw startschermen."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Er is geen ruimte meer op dit startscherm."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Er is geen ruimte meer op de hotseat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Deze widget is te groot voor de hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Snelkoppeling \'<xliff:g id="NAME">%s</xliff:g>\' is gemaakt."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Snelkoppeling \'<xliff:g id="NAME">%s</xliff:g>\' is verwijderd."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Snelkoppeling \'<xliff:g id="NAME">%s</xliff:g>\' bestaat al."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Snelkoppeling selecteren"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"App selecteren"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Startpagina"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Verwijderen"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Verwijderen"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Verwijderen"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Verwijderen"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"App-info"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Zoeken"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Gesproken zoekopdracht"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Verwijderen"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Update verwijderen"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"App verwijderen"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"App-details"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 app geselecteerd"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget geselecteerd"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 map geselecteerd"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 snelkoppeling geselecteerd"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"snelkoppelingen installeren"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"snelkoppelingen verwijderen"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"De app toestaan snelkoppelingen te verwijderen zonder tussenkomst van de gebruiker."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"instellingen en snelkoppelingen op de startpagina lezen"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"De app toestaan de instellingen en snelkoppelingen op de startpagina te lezen."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"instellingen en snelkoppelingen op de startpagina schrijven"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"De app toestaan de instellingen en snelkoppelingen op de startpagina te wijzigen."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Probleem bij het laden van widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Dit is een systeemapp die niet kan worden verwijderd."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Naamloze map"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Startscherm %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d van %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Startscherm %1$d van %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"App-pagina %1$d van %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widgetpagina %1$d van %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Welkom!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Personaliseer uw startscherm."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Meer schermen maken voor apps en mappen"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Uw ruimte indelen"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Blijf de achtergrond aanraken om de achtergrond, widgets en instellingen te beheren."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Selecteer een aantal apps"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Als u een app wilt toevoegen aan het startscherm, blijft u de app aanraken."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Dit is een map"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Als u een map zoals deze wilt maken, blijft u een app aanraken en schuift u deze boven op een andere app."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Map geopend, <xliff:g id="WIDTH">%1$d</xliff:g> bij <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Raak dit aan om de map te sluiten"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Raak dit aan om de gewijzigde naam op te slaan"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Map gesloten"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"De naam van de map is gewijzigd in <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Map: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Achtergronden"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Instellingen"</string>
+</resources>
diff --git a/res/values-nodpi/wallpapers.xml b/res/values-nodpi/wallpapers.xml
new file mode 100644
index 0000000..1e340e4
--- /dev/null
+++ b/res/values-nodpi/wallpapers.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources>
+ <string-array name="wallpapers" translatable="false">
+ </string-array>
+</resources>
diff --git a/res/values-pl-land/strings.xml b/res/values-pl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-pl-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..b629f3d
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Ekran główny"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Główne aplikacje Androida"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Ustaw tapetę"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Wybrane: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Wybrane: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Wybrane: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Tapeta %1$d z %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Wybrano <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Usuń"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Wybierz obraz"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Tapety"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Przytnij tapetę"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Aplikacja nie jest zainstalowana."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widżety"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widżety"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Pokaż pamięć"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Aby dodać widżet, kliknij go i przytrzymaj."</string>
+ <string name="market" msgid="2619650989819296998">"Sklep"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Nie można upuścić elementu na tym ekranie głównym."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Wybierz widżet, który chcesz dodać"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nazwa folderu"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Zmień nazwę folderu"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Anuluj"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Dodaj do ekranu głównego"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplikacje"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Skróty"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widżety"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Brak miejsca na ekranach głównych."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Brak miejsca w kieszonce."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ten widżet jest za duży, by umieścić go w kieszonce."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Skrót „<xliff:g id="NAME">%s</xliff:g>” został utworzony."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Skrót „<xliff:g id="NAME">%s</xliff:g>” został usunięty."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Skrót „<xliff:g id="NAME">%s</xliff:g>” już istnieje."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Wybierz skrót"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Wybierz aplikację"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacje"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Ekran główny"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Usuń"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odinstaluj"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Usuń"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odinstaluj"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informacje o aplikacji"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Szukaj"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Wyszukiwanie głosowe"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikacje"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Usuń"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odinstaluj aktualizację"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odinstaluj aplikację"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Szczegóły aplikacji"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Wybrano 1 aplikację"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Wybrano 1 widżet"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Wybrano 1 folder"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Wybrano 1 skrót"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalowanie skrótów"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Pozwala aplikacji dodawać skróty bez interwencji użytkownika."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odinstalowywanie skrótów"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Pozwala aplikacji usuwać skróty bez interwencji użytkownika."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"odczytywanie ustawień i skrótów na ekranie głównym"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Pozwala aplikacji na odczytywanie ustawień i skrótów na ekranie głównym."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"zapisywanie ustawień i skrótów na ekranie głównym"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Umożliwia aplikacji zmianę ustawień i skrótów na ekranie głównym."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problem podczas ładowania widżetu"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"To aplikacja systemowa i nie można jej odinstalować."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Wyrzutnia rakiet"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Folder bez nazwy"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Ekran główny %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Strona %1$d z %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Ekran główny %1$d z %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Strona aplikacji: %1$d z %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Strona widżetów: %1$d z %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Witamy"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Poczuj się jak u siebie."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Dodaj więcej ekranów na aplikacje i foldery"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Uporządkuj obszar roboczy"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Kliknij i przytrzymaj tło, by zmienić tapetę, widżety lub ustawienia."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Wybierz kilka aplikacji"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Aby dodać aplikację na ekran główny, dotknij i przytrzymaj jej ikonę."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Tu jest folder"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Aby utworzyć taki sam, kliknij i przytrzymaj aplikację, a następnie przenieś ją na następną."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folder otwarty, <xliff:g id="WIDTH">%1$d</xliff:g> na <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Kliknij, by zamknąć folder"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Kliknij, by zapisać zmianę nazwy"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folder zamknięty"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Nazwa folderu zmieniona na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widżety"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Ustawienia"</string>
+</resources>
diff --git a/res/values-port/dimens.xml b/res/values-port/dimens.xml
new file mode 100644
index 0000000..7194a2a
--- /dev/null
+++ b/res/values-port/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+<!-- Workspace -->
+ <dimen name="workspace_page_spacing">-1dp</dimen>
+
+<!-- AppsCustomize -->
+ <integer name="apps_customize_cling_focused_x">1</integer>
+ <integer name="apps_customize_cling_focused_y">1</integer>
+
+ <integer name="apps_customize_widget_cell_count_x">2</integer>
+ <integer name="apps_customize_widget_cell_count_y">3</integer>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-port/styles.xml
new file mode 100644
index 0000000..ab6a1eb
--- /dev/null
+++ b/res/values-port/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+-->
+
+<resources>
+<!-- AppsCustomize -->
+ <style name="TabIndicator.AppsCustomize">
+ <item name="android:maxWidth">130dp</item>
+ </style>
+</resources>
diff --git a/res/values-pt-land/strings.xml b/res/values-pt-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-pt-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-pt-rPT-land/strings.xml b/res/values-pt-rPT-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-pt-rPT-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..1ef3982
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Iniciador3"</string>
+ <string name="home" msgid="7658288663002113681">"Ecrã principal"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Aplicações principais do Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Definir imagem fundo"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d selecionado(s)"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d selecionado(s)"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d selecionado(s)"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Imagem de fundo %1$d de %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> selecionado"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Eliminar"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Escolher imagem"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Imagens de fundo"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Recortar imagem de fundo"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"A aplicação não está instalada."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Prima sem soltar para escolher um widget."</string>
+ <string name="market" msgid="2619650989819296998">"Comprar"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Não foi possível largar o item neste Ecrã Principal."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Escolher um widget para criar"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nome da pasta"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Mudar o nome da pasta"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Adicionar ao Ecrã principal"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplicações"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Atalhos"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Sem espaço suficiente nos Ecrãs principais."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Sem espaço suficiente na barra personalizável."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget é demasiado grande para a barra personalizável."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Atalho “<xliff:g id="NAME">%s</xliff:g>” criado."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"O atalho “<xliff:g id="NAME">%s</xliff:g>” foi removido."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"O atalho “<xliff:g id="NAME">%s</xliff:g>” já existe."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Escolher atalho"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Escolher aplicação"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicações"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Ecrã principal"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remover"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Remover"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informações da aplicação"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Pesquisar"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Pesquisa por Voz"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicações"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Remover"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar atualização"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar a aplicação"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalhes da aplicação"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicação selecionada"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selecionado"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 pasta selecionada"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 atalho selecionado"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atalhos"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite a uma aplicação adicionar atalhos sem a intervenção do utilizador."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar atalhos"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite à aplicação remover atalhos sem intervenção do utilizador."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"ler definições e atalhos do Ecrã Principal"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite à aplicação ler as definições e os atalhos no Ecrã Principal."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"escrever definições e atalhos do Ecrã principal"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite à aplicação alterar as definições e os atalhos no Ecrã Principal."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problema ao carregar o widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"É uma aplicação de sistema e não pode ser desinstalada."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Lança-mísseis"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Pasta sem nome"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Ecrã principal %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Ecrã principal %1$d de %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicações %1$d de %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets %1$d de %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Bem-vindo(a)!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Sinta-se em casa."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crie mais ecrãs para aplicações e pastas"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organizar o seu espaço"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Toque sem soltar no fundo para gerir a imagem de fundo, os widgets e as definições."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Escolher algumas aplicações"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para adicionar uma aplicação ao Ecrã principal, toque na mesma sem soltar."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Eis uma pasta"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para criar uma pasta, toque sem soltar numa aplicação e arraste-a para cima de outra aplicação."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Pasta aberta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Toque para fechar a pasta"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toque para guardar o nome novo"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Pasta fechada"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Nome de pasta alterado para <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagens de fundo"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Definições"</string>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..4d40388
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Início"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Principais aplicativos do Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Definir plano de fundo"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d selecionados"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d selecionados"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d selecionados"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Plano de fundo %1$d de %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> selecionado"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Excluir"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Escolher imagem"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Planos de fundo"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Cortar plano de fundo"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"O aplicativo não está instalado."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgets"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgets"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Mostrar memória"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Toque e pressione para selecionar um widget."</string>
+ <string name="market" msgid="2619650989819296998">"Comprar"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Não foi possível soltar o item nesta tela inicial."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Selecione um widget para criar"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Nome da pasta"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Renomear pasta"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Ok"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Cancelar"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Adicionar à tela inicial"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplicativos"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Atalhos"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgets"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Não há mais espaço nas telas iniciais."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Não há mais espaço na tela inicial."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Não há mais espaço no hotseat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Este widget é muito grande para o hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Atalho \"<xliff:g id="NAME">%s</xliff:g>\" criado."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"O atalho \"<xliff:g id="NAME">%s</xliff:g>\" foi removido."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"O atalho \"<xliff:g id="NAME">%s</xliff:g>\" já existe."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Selecione um atalho"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Selecione um aplicativo"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicativos"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Início"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Remover"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Desinstalar"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Remover"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Desinstalar"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informações do aplicativo"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Pesquisar"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Pesquisa por voz"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicativos"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Remover"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Desinstalar atualização"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Desinstalar aplicativo"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalhes do aplicativo"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Um aplicativo selecionado"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Um widget selecionado"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Uma pasta selecionada"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Um atalho selecionado"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalar atalhos"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite que um aplicativo adicione atalhos sem intervenção do usuário."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"desinstalar atalhos"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite que o aplicativo remova atalhos sem a intervenção do usuário."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"ler configurações e atalhos da tela inicial"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite que o aplicativo leia as configurações e os atalhos na tela inicial."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"gravar configurações e atalhos da tela inicial"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite que o aplicativo altere as configurações e os atalhos na tela inicial."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problema ao carregar o widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Este é um aplicativo do sistema e não pode ser desinstalado."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Pasta sem nome"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Tela inicial %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Tela inicial %1$d de %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Página de aplicativos, %1$d de %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Página de widgets, %1$d de %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Bem-vindo!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Fique à vontade."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Crie mais telas para aplicativos e pastas"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organize seu espaço"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Toque e mantenha pressionada a tela de fundo para gerenciar o plano de fundo, os widgets e as configurações."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Escolha alguns aplicativos"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Para adicionar um aplicativo a sua tela inicial, toque e mantenha-o pressionado."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Aqui está uma pasta"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Para criar uma pasta como esta, mantenha pressionado um aplicativo e mova-o para cima de outro."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Ok"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Pasta aberta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Toque para fechar a pasta"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Toque para salvar o novo nome"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Pasta fechada"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Pasta renomeada para <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Planos de fundo"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Configurações"</string>
+</resources>
diff --git a/res/values-rm-land/strings.xml b/res/values-rm-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-rm-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
new file mode 100644
index 0000000..ddcf404
--- /dev/null
+++ b/res/values-rm/strings.xml
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for application_name (5181331383435256801) -->
+ <skip />
+ <!-- no translation found for home (7658288663002113681) -->
+ <skip />
+ <!-- no translation found for uid_name (7820867637514617527) -->
+ <skip />
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <!-- no translation found for wallpaper_instructions (563973358787555519) -->
+ <skip />
+ <!-- no translation found for number_of_items_selected:zero (7464587177007785408) -->
+ <!-- no translation found for number_of_items_selected:one (142482526010824029) -->
+ <!-- no translation found for number_of_items_selected:other (1418352074806573570) -->
+ <!-- no translation found for wallpaper_accessibility_name (1655953108132967972) -->
+ <skip />
+ <!-- no translation found for announce_selection (8338254712932127413) -->
+ <skip />
+ <!-- no translation found for wallpaper_delete (8095005658756613921) -->
+ <skip />
+ <!-- no translation found for pick_image (1272073934062909527) -->
+ <skip />
+ <!-- no translation found for pick_wallpaper (8179698221502010609) -->
+ <skip />
+ <!-- no translation found for crop_wallpaper (8334345984491368009) -->
+ <skip />
+ <!-- no translation found for activity_not_found (8071924732094499514) -->
+ <skip />
+ <!-- no translation found for widgets_tab_label (2921133187116603919) -->
+ <skip />
+ <!-- no translation found for widget_adder (3201040140710381657) -->
+ <skip />
+ <!-- no translation found for toggle_weight_watcher (5645299835184636119) -->
+ <skip />
+ <!-- no translation found for long_press_widget_to_add (7699152356777458215) -->
+ <skip />
+ <!-- no translation found for market (2619650989819296998) -->
+ <skip />
+ <!-- no translation found for widget_dims_format (2370757736025621599) -->
+ <skip />
+ <!-- no translation found for external_drop_widget_error (3165821058322217155) -->
+ <skip />
+ <!-- no translation found for external_drop_widget_pick_title (3486317258037690630) -->
+ <skip />
+ <!-- no translation found for rename_folder_label (3727762225964550653) -->
+ <skip />
+ <!-- no translation found for rename_folder_title (3771389277707820891) -->
+ <skip />
+ <!-- no translation found for rename_action (5559600076028658757) -->
+ <skip />
+ <!-- no translation found for cancel_action (7009134900002915310) -->
+ <skip />
+ <!-- no translation found for menu_item_add_item (1264911265836810421) -->
+ <skip />
+ <!-- no translation found for group_applications (3797214114206693605) -->
+ <skip />
+ <!-- no translation found for group_shortcuts (6012256992764410535) -->
+ <skip />
+ <!-- no translation found for group_widgets (1569030723286851002) -->
+ <skip />
+ <!-- no translation found for completely_out_of_space (6106288382070760318) -->
+ <skip />
+ <!-- no translation found for out_of_space (4691004494942118364) -->
+ <skip />
+ <!-- no translation found for hotseat_out_of_space (9139760413395605841) -->
+ <skip />
+ <!-- no translation found for invalid_hotseat_item (1211534262129849507) -->
+ <skip />
+ <!-- no translation found for shortcut_installed (1701742129426969556) -->
+ <skip />
+ <!-- no translation found for shortcut_uninstalled (8176767991305701821) -->
+ <skip />
+ <!-- no translation found for shortcut_duplicate (9167217446062498127) -->
+ <skip />
+ <!-- no translation found for title_select_shortcut (6680642571148153868) -->
+ <skip />
+ <!-- no translation found for title_select_application (3280812711670683644) -->
+ <skip />
+ <!-- no translation found for all_apps_button_label (9110807029020582876) -->
+ <skip />
+ <!-- no translation found for all_apps_home_button_label (252062713717058851) -->
+ <skip />
+ <!-- no translation found for delete_zone_label_workspace (4009607676751398685) -->
+ <skip />
+ <!-- no translation found for delete_zone_label_all_apps (8083826390278958980) -->
+ <skip />
+ <!-- no translation found for delete_target_label (1822697352535677073) -->
+ <skip />
+ <!-- no translation found for delete_target_uninstall_label (5100785476250872595) -->
+ <skip />
+ <!-- no translation found for info_target_label (8053346143994679532) -->
+ <skip />
+ <!-- no translation found for accessibility_search_button (1628520399424565142) -->
+ <skip />
+ <!-- no translation found for accessibility_voice_search_button (4637324840434406584) -->
+ <skip />
+ <!-- no translation found for accessibility_all_apps_button (2603132375383800483) -->
+ <skip />
+ <!-- no translation found for accessibility_delete_button (6466114477993744621) -->
+ <skip />
+ <!-- no translation found for delete_zone_label_all_apps_system_app (449755632749610895) -->
+ <skip />
+ <!-- no translation found for cab_menu_delete_app (7435191475867183689) -->
+ <skip />
+ <!-- no translation found for cab_menu_app_info (8593722221450362342) -->
+ <skip />
+ <!-- no translation found for cab_app_selection_text (374688303047985416) -->
+ <skip />
+ <!-- no translation found for cab_widget_selection_text (1833458597831541241) -->
+ <skip />
+ <!-- no translation found for cab_folder_selection_text (7999992513806132118) -->
+ <skip />
+ <!-- no translation found for cab_shortcut_selection_text (2103811025667946450) -->
+ <skip />
+ <!-- no translation found for permlab_install_shortcut (5632423390354674437) -->
+ <skip />
+ <!-- no translation found for permdesc_install_shortcut (923466509822011139) -->
+ <skip />
+ <!-- no translation found for permlab_uninstall_shortcut (864595034498083837) -->
+ <skip />
+ <!-- no translation found for permdesc_uninstall_shortcut (5134129545001836849) -->
+ <skip />
+ <!-- no translation found for permlab_read_settings (1941457408239617576) -->
+ <skip />
+ <!-- no translation found for permdesc_read_settings (5833423719057558387) -->
+ <skip />
+ <!-- no translation found for permlab_write_settings (3574213698004620587) -->
+ <skip />
+ <!-- no translation found for permdesc_write_settings (5440712911516509985) -->
+ <skip />
+ <!-- no translation found for gadget_error_text (6081085226050792095) -->
+ <skip />
+ <!-- no translation found for uninstall_system_app_text (4172046090762920660) -->
+ <skip />
+ <!-- no translation found for dream_name (1530253749244328964) -->
+ <skip />
+ <!-- no translation found for folder_hint_text (6617836969016293992) -->
+ <skip />
+ <!-- no translation found for workspace_description_format (2950174241104043327) -->
+ <skip />
+ <!-- no translation found for default_scroll_format (7475544710230993317) -->
+ <skip />
+ <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
+ <skip />
+ <!-- no translation found for apps_customize_apps_scroll_format (370005296147130238) -->
+ <skip />
+ <!-- no translation found for apps_customize_widgets_scroll_format (3106209519974971521) -->
+ <skip />
+ <!-- no translation found for first_run_cling_title (7257389003637362144) -->
+ <skip />
+ <!-- no translation found for first_run_cling_description (6447072552696253358) -->
+ <skip />
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <!-- no translation found for first_run_cling_create_screens_hint (6950729526680114157) -->
+ <skip />
+ <!-- no translation found for workspace_cling_title (5626202359865825661) -->
+ <skip />
+ <!-- no translation found for workspace_cling_move_item (528201129978005352) -->
+ <skip />
+ <!-- no translation found for all_apps_cling_title (34929250753095858) -->
+ <skip />
+ <!-- no translation found for all_apps_cling_add_item (400866858451850784) -->
+ <skip />
+ <!-- no translation found for folder_cling_title (3894908818693254164) -->
+ <skip />
+ <!-- no translation found for folder_cling_create_folder (6158215559475836131) -->
+ <skip />
+ <!-- no translation found for cling_dismiss (8962359497601507581) -->
+ <skip />
+ <!-- no translation found for folder_opened (94695026776264709) -->
+ <skip />
+ <!-- no translation found for folder_tap_to_close (1884479294466410023) -->
+ <skip />
+ <!-- no translation found for folder_tap_to_rename (9191075570492871147) -->
+ <skip />
+ <!-- no translation found for folder_closed (4100806530910930934) -->
+ <skip />
+ <!-- no translation found for folder_renamed (1794088362165669656) -->
+ <skip />
+ <!-- no translation found for folder_name_format (6629239338071103179) -->
+ <skip />
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <!-- no translation found for widget_button_text (2880537293434387943) -->
+ <skip />
+ <!-- no translation found for wallpaper_button_text (8404103075899945851) -->
+ <skip />
+ <!-- no translation found for settings_button_text (8119458837558863227) -->
+ <skip />
+</resources>
diff --git a/res/values-ro-land/strings.xml b/res/values-ro-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ro-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000..78d9da4
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Ecran de pornire"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Setați imaginea de fundal"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d selectate"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d selectat"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d selectate"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Imaginea de fundal %1$d din %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"S-a selectat <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Ștergeți"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Alegeți imaginea"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Imagini de fundal"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Decupați imaginea de fundal"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Aplicația nu este instalată."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgeturi"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgeturi"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Afișați memoria"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Atingeți lung un widget pentru a-l alege."</string>
+ <string name="market" msgid="2619650989819296998">"Cumpărați"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Nu se poate plasa articolul pe ecranul de pornire."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Alegeți widgetul de creat"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Numele dosarului"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Redenumiți dosarul"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Anulați"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Adăugați la Ecranul de pornire"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplicații"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Comenzi rapide"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgeturi"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Nu mai este loc pe ecranele de pornire."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Nu mai este loc în bara de lansare rapidă."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Acest widget este prea mare pentru bara de lansare rapidă."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Comanda rapidă „<xliff:g id="NAME">%s</xliff:g>\" a fost creată."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Comanda rapidă „<xliff:g id="NAME">%s</xliff:g>” a fost eliminată."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Comanda rapidă „<xliff:g id="NAME">%s</xliff:g>” există deja."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Alegeți comanda rapidă"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Alegeți aplicația"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicații"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Ecran de pornire"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Eliminați"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Dezinstalați"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Eliminați"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Dezinstalați"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informații despre aplicație"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Căutați"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Căutare vocală"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplicații"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Eliminați"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Dezinstalați actualizarea"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Dezinstalați aplicația"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Detalii despre aplicație"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 aplicație selectată"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget selectat"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 dosar selectat"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 comandă rapidă selectată"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"instalează comenzi rapide"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permite unei aplicații să adauge comenzi rapide fără intervenția utilizatorului."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"dezinstalează comenzi rapide"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Permite aplicației să elimine comenzi rapide fără intervenția utilizatorului."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"citește setări și comenzi rapide pentru ecranul de pornire"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Permite aplicației să citească setările și comenzile rapide din ecranul de pornire."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"scrie setări și comenzi rapide pentru ecranul de pornire"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Permite aplicației să modifice setările și comenzile rapide din ecranul de pornire."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problemă la încărcarea widgetului"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Aceasta este o aplicație de sistem și nu poate fi dezinstalată."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Dosar fără nume"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Ecran de pornire %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d din %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Ecranul de pornire %1$d din %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pagina de aplicații %1$d din %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pagina de widgeturi %1$d din %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Bun venit!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Simțiți-vă ca acasă."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Creați mai multe ecrane pentru aplicații și dosare"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organizați-vă spațiul"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Atingeți lung fundalul pentru a gestiona imaginea de fundal, widgeturile și setările."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Alegeți unele aplicații"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Atingeți lung o aplicație pentru a o adăuga pe ecranul de pornire."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Iată un dosar"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Pentru a crea un dosar similar, atingeți și țineți degetul pe o aplicație, apoi mutați-o deasupra alteia."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Dosar deschis, <xliff:g id="WIDTH">%1$d</xliff:g> pe <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Atingeți pentru a închide dosarul"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Atingeți pentru a salva redenumirea"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Dosar închis"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Dosar redenumit <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Dosar: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgeturi"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Imagini de fundal"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Setări"</string>
+</resources>
diff --git a/res/values-ru-land/strings.xml b/res/values-ru-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-ru-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..06fbd26
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Главный экран"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Основные приложения Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Установить как обои"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Выбрано: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Выбрано: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Выбрано: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Обои %1$d из %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Выбран элемент \"<xliff:g id="LABEL">%1$s</xliff:g>\""</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Удалить"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Выбрать изображение"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Обои"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Обрезать обои"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Приложение удалено"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Виджеты"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Виджеты"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Сведения о памяти"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Чтобы выбрать виджет, нажмите на значок и удерживайте его."</string>
+ <string name="market" msgid="2619650989819296998">"Google Play"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d x %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Не удалось добавить элемент на главный экран"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Выберите виджет"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Название папки"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Переименование папки"</string>
+ <string name="rename_action" msgid="5559600076028658757">"ОК"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Отмена"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Добавление на главный экран"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Приложения"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Ярлыки"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Виджеты"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"На главных экранах все занято"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"На этом экране все занято"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Нет свободного места в слоте"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Виджет слишком велик для слота"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Ярлык \"<xliff:g id="NAME">%s</xliff:g>\" создан"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Ярлык \"<xliff:g id="NAME">%s</xliff:g>\" удален"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Ярлык \"<xliff:g id="NAME">%s</xliff:g>\" уже существует"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Выбор ярлыка"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Выбор приложения"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Главный экран"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Удалить"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Удалить"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Удалить"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Удалить"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"О приложении"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Поиск"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Голосовой поиск"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Приложения"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Удалить"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Удалить обновление"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Удалить приложение"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"О приложении"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Выбрано 1 приложение"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Выбран 1 виджет"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Выбрана 1 папка"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Выбран 1 ярлык"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"Создание ярлыков"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Приложение сможет самостоятельно добавлять ярлыки."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"Удаление ярлыков"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Приложение сможет самостоятельно удалять ярлыки."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"Доступ к настройкам и ярлыкам главного экрана"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Приложение получит доступ к данным о настройках и ярлыках на главном экране."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"Изменение настроек и ярлыков главного экрана"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Приложение сможет изменять настройки и ярлыки на главном экране."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Не удалось загрузить виджет"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Это системное приложение, его нельзя удалить."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Папка без названия"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Главный экран %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Стр. %1$d из %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Главные экран %1$d из %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Приложения: стр. %1$d из %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Виджеты: стр. %1$d из %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Добро пожаловать!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Будьте как дома"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Создание дополнительных экранов для приложений и папок"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Организация рабочего пространства"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Чтобы перейти к управлению обоями, виджетами и настройками, нажмите на фоновое изображение и удерживайте его."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Выберите приложения"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Чтобы добавить приложение на главный экран, нажмите на значок и удерживайте его."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Это папка"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Чтобы создать папку, нажмите и удерживайте значок приложения, а затем перетащите его на другой значок."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"ОК"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Папка открыта, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Нажмите, чтобы закрыть папку"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Нажмите, чтобы подтвердить переименование"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Папка закрыта"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Папка переименована в \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Папка: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Виджеты"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Обои"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Настройки"</string>
+</resources>
diff --git a/res/values-sk-land/strings.xml b/res/values-sk-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sk-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000..29de526
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Plocha"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Nastaviť tapetu"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Počet vybratých položiek: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Počet vybratých položiek: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Počet vybratých položiek: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Tapeta %1$d z %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Vybratá položka <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Odstrániť"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Vybrať obrázok"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Tapety"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Orezanie tapety"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Aplikácia nie je nainštalovaná."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Miniaplikácie"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Miniaplikácie"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Zobraziť pamäť"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Miniaplikáciu pridáte stlačením a podržaním."</string>
+ <string name="market" msgid="2619650989819296998">"Obchod"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Položku sa nepodarilo presunúť na túto plochu."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Zvoľte miniaplikáciu na vytvorenie"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Názov priečinka"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Premenovať priečinok"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Zrušiť"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Pridať na plochu"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplikácie"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Skratky"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Miniaplikácie"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Na plochách už nie je miesto."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Na tejto ploche už nie je miesto"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"V časti hotseat už nie je miesto."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Táto miniaplikácia je pre hotseat príliš veľká."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Odkaz <xliff:g id="NAME">%s</xliff:g> bol vytvorený."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Odkaz <xliff:g id="NAME">%s</xliff:g> bol odstránený."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Odkaz <xliff:g id="NAME">%s</xliff:g> už existuje."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Vybrať odkaz"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Vybrať aplikáciu"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikácie"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Domovská stránka"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Odstrániť"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odinštalovať"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Odstrániť"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odinštalovať"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Informácie o aplikácii"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Vyhľadať"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Hlasové vyhľadávanie"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikácie"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Odstrániť"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odinštalovať aktualizáciu"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odinštalovať aplikáciu"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Podrobnosti o aplikácii"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Vybratá 1 aplikácia"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Vybratá 1 miniaplikácia"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Vybratý 1 priečinok"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Vybratý 1 odkaz"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"inštalovať odkazy"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Povoľuje aplikácii pridať odkazy bez zásahu používateľa."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odinštalovať odkazy"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Povoľuje aplikácii odstrániť odkazy bez zásahu používateľa."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"čítanie nastavení a odkazov plochy"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Povoľuje aplikácii čítať nastavenia a odkazy na ploche."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"zápis nastavení a odkazov plochy"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Povoľuje aplikácii zmeniť nastavenia a odkazy na ploche."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problém s načítaním miniaplikácií"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Toto je systémová aplikácia a nedá sa odinštalovať."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Raketomet"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Nepomenovaný priečinok"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Plocha %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Stránka %1$d z %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Plocha %1$d z %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stránka aplikácií %1$d z %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stránka miniaplikácií %1$d z %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Vitajte!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Cíťte sa tu ako doma."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Vytvorte viac obrazoviek pre aplikácie a priečinky"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Usporiadajte svoj priestor"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Ak chcete spravovať tapetu, miniaplikácie a nastavenia, dotknite sa pozadia a podržte."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Vyberte niektoré aplikácie"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ak chcete pridať aplikáciu na plochu, dotknite sa jej a podržte."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Tu je priečinok"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Ak chcete vytvoriť takýto priečinok, dotknite sa príslušnej aplikácie a podržte ju. Potom ju presuňte na druhú aplikáciu."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Otvorený priečinok, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Dotykom zavriete priečinok"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dotykom premenovanie uložíte"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Priečinok je uzavretý"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Priečinok bol premenovaný na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Priečinok: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Miniaplikácie"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Tapety"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Nastavenia"</string>
+</resources>
diff --git a/res/values-sl-land/strings.xml b/res/values-sl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sl-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000..021eca8
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Zaganjalnik3"</string>
+ <string name="home" msgid="7658288663002113681">"Začetni zaslon"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Osnovne aplikacije sistema Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Nastavi ozadje"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Št. izbranih: %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Št. izbranih: %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Št. izbranih: %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Ozadje %1$d od %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Izbrano: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Izbriši"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Izberi sliko"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Ozadja"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Obrezovanje ozadja"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija ni nameščena."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Pripomočki"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Pripomočki"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Pokaži pomnilnik"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Za izbiro pripomočka se ga dotaknite in pridržite."</string>
+ <string name="market" msgid="2619650989819296998">"Nakup"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Elementa ni mogoče spustiti na začetni zaslon."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Izberite pripomoček za ustvarjanje"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Ime mape"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Preimenovanje mape"</string>
+ <string name="rename_action" msgid="5559600076028658757">"V redu"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Prekliči"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Dodaj na začetni zaslon"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Aplikacije"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Bližnjice"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Pripomočki"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Na začetnih zaslonih ni več prostora."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Na tem začetnem zaslonu ni več prostora."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"V vrstici z ikonami ni več prostora."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Ta pripomoček je prevelik za vrstico z ikonami."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Bližnjica »<xliff:g id="NAME">%s</xliff:g>« je ustvarjena."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Bližnjica »<xliff:g id="NAME">%s</xliff:g>« je bila odstranjena."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Bližnjica »<xliff:g id="NAME">%s</xliff:g>« že obstaja."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Izberite bližnjico"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Izberite aplikacijo"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Začetni zaslon"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Odstrani"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Odstrani"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Odstrani"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Odstrani"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Podatki o aplikaciji"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Iskanje"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Glasovno iskanje"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Aplikacije"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Odstrani"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Odstrani posodobitev"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Odstrani aplikacijo"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Podrobnosti o aplikaciji"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Izbrana je 1 aplikacija"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Izbran je 1 pripomoček"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Izbrana je 1 mapa"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Izbrana je 1 bližnjica"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"namestitev bližnjic"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Aplikaciji dovoli dodajanje bližnjic brez posredovanja uporabnika."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"odstranitev bližnjic"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Aplikaciji dovoli odstranjevanje bližnjic brez posredovanja uporabnika."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"branje nastavitev in bližnjic na začetnem zaslonu"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Aplikaciji dovoli branje nastavitev in bližnjic na začetnem zaslonu."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"zapis nastavitev in bližnjic na začetnem zaslonu"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Aplikaciji dovoli spreminjanje nastavitev in bližnjic na začetnem zaslonu."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Težava pri nalaganju pripomočka"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"To je sistemska aplikacija in je ni mogoče odstraniti."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Raketno izstrelišče"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Neimenovana mapa"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Začetni zaslon %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Stran %1$d od %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Začetni zaslon %1$d od %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Stran aplikacij %1$d od %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Stran pripomočkov %1$d od %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Pozdravljeni!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Počutite se kot doma."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Ustvarite več zaslonov za aplikacije in mape"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organizirajte svoj prostor"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Če želite upravljati ozadje, pripomočke in nastavitve, se dotaknite ozadja in ga pridržite."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Izberite nekaj aplikacij"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Če želite dodati aplikacijo na začetni zaslon, se je dotaknite in jo pridržite."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"To je mapa"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Če želite ustvariti mapo, podobno tej, se dotaknite aplikacije in jo pridržite, nato pa jo premaknite nad drugo."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"V redu"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Mapa je odprta, <xliff:g id="WIDTH">%1$d</xliff:g> krat <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Dotaknite se, da zaprete mapo"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Dotaknite se, da shranite preimenovanje"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Mapa je zaprta"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana v <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Pripomočki"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Ozadja"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Nastavitve"</string>
+</resources>
diff --git a/res/values-sr-land/strings.xml b/res/values-sr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sr-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000..06d80ef
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Почетна"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Основне Android апликације"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Подеси позадину"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Изабранo je %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Изабрана je %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Изабранo je %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Позадина %1$d од %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Изабрано је <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Избриши"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Изабери слику"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Позадине"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Опсецање позадине"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Апликација није инсталирана."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Виџети"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Виџети"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Прикажи меморију"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Додирните и задржите да бисте изабрали виџет."</string>
+ <string name="market" msgid="2619650989819296998">"Купујте"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Није могуће отпустити ставку на почетни екран."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Избор виџета за прављење"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Назив директоријума"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Преименовање директоријума"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Потврди"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Откажи"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Додавање на почетни екран"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Апликације"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Пречице"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Виџети"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Нема више простора на почетним екранима."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Нема више простора на овом почетном екрану."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Нема више простора на траци актуелности."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Овај виџет је превелики за траку актуелности."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Пречица „<xliff:g id="NAME">%s</xliff:g>“ је направљена."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Пречица „<xliff:g id="NAME">%s</xliff:g>“ је уклоњена."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Пречица „<xliff:g id="NAME">%s</xliff:g>“ већ постоји."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Избор пречице"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Избор апликације"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Апликације"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Почетна"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Уклони"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Деинсталирај"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Уклони"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Деинсталирај"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Информације о апликацији"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Претражи"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Гласовна претрага"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Апликације"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Уклони"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Деинсталирај ажурирање"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Деинсталирање апликације"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Детаљи о апликацији"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Изабрана је 1 апликација"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Изабран је 1 виџет"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Изабран је 1 директоријум"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Изабрана је 1 пречица"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"инсталирање пречица"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Дозвољава апликацији да додаје пречице без интервенције корисника."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"деинсталирање пречица"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Дозвољава апликацији да уклања пречице без интервенције корисника."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"читање подешавања и пречица на почетном екрану"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Дозвољава апликацији да чита подешавања и пречице на почетном екрану."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"уписивање подешавања и пречица на почетном екрану"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Дозвољава апликацији да мења подешавања и пречице на почетном екрану."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Проблем при учитавању виџета"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ово је системска апликација и не може да се деинсталира."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Лансер ракета"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Неименовани директоријум"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Почетни екран %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"%1$d. страница од %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d. почетни екран од %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"%1$d. страница апликација од %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"%1$d. страница виџета од %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Добро дошли!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Осећајте се као код куће."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Направите још екрана за апликације и директоријуме"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Организујте простор"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Додирните позадину и задржите да бисте управљали позадином, виџетима и подешавањима."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Изаберите неколико апликација"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Да бисте додали апликацију на почетни екран, додирните је и задржите."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Ево једног директоријума"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Да бисте направили директоријум попут овога, додирните и задржите апликацију, па је превуците преко друге."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Потврди"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Директоријум је отворен, <xliff:g id="WIDTH">%1$d</xliff:g> пута <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Додирните да бисте затворили директоријум"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Додирните да бисте сачували промену имена"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Директоријум је затворен"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Директоријум је преименован у <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Директоријум: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Виџети"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Позадине"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Подешавања"</string>
+</resources>
diff --git a/res/values-sv-land/strings.xml b/res/values-sv-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sv-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000..bf75200
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Startskärm"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Ange bakgrund"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d har valts"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d har valts"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d har valts"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Bakgrund %1$d av %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> har valts"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Ta bort"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Välj bild"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Bakgrunder"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Beskär bakgrund"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Appen är inte installerad."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widgetar"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widgetar"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Visa Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Tryck länge om du vill flytta en widget."</string>
+ <string name="market" msgid="2619650989819296998">"Butik"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Objektet kunde inte släppas på startskärmen."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Ange vilken widget du vill använda"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Mappnamn"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Byt namn på mapp"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Avbryt"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Lägg till på startskärmen"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Appar"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Genvägar"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widgetar"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Det finns inte plats för mer på dina startsidor."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Utrymmet på Hotseat är fullt."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Denna widget är för stor för Hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Genvägen <xliff:g id="NAME">%s</xliff:g> har skapats."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Genvägen <xliff:g id="NAME">%s</xliff:g> har tagits bort."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Genvägen <xliff:g id="NAME">%s</xliff:g> finns redan."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Välj genväg"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Välj app"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Appar"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Startskärm"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Ta bort"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Avinstallera"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Ta bort"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Avinstallera"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Info om appen"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Sök"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Röstsökning"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Appar"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Ta bort"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Avinstallera uppdatering"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Avinstallera appen"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Information om appen"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"En app har valts"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"En widget har valts"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"En mapp har valts"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"En genväg har valts"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"installera genvägar"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Tillåter att en app lägger till genvägar utan åtgärd från användaren."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"avinstallera genvägar"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Tillåter att appen tar bort genvägar utan åtgärd från användaren."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"läsa inställningar och genvägar för startsidan"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Tillåter att appen läser inställningar och genvägar på startsidan."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"skriva inställningar och genvägar för startsidan"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Tillåter att appen ändrar inställningar och genvägar på startsidan."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Det gick inte att läsa in widgeten"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Det här är en systemapp som inte kan avinstalleras."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Namnlös mapp"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Startskärmen %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Sidan %1$d av %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskärmen %1$d av %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Appsida %1$d av %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widget-sida %1$d av %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Välkommen!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Känn dig som hemma."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Skapa fler skärmar för appar och mappar"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Organisera ditt utrymme"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Tryck länge på bakgrunden om du vill hantera bakgrundsbilder, widgetar och inställningar."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Välj några appar"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Om du vill lägga till en app på startskärmen trycker du länge på den."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Det här är en mapp"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Om du vill skapa en till mapp av det här slaget trycker du länge på en app och drar den sedan ovanpå en annan."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Mappen är öppen, <xliff:g id="WIDTH">%1$d</xliff:g> gånger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Tryck om du vill stänga mappen"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Tryck om du vill spara det nya namnet"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Mappen är stängd"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Mappen har bytt namn till <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widgetar"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Bakgrunder"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Inställningar"</string>
+</resources>
diff --git a/res/values-sw-land/strings.xml b/res/values-sw-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-sw-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000..606a80d
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Kizindua3"</string>
+ <string name="home" msgid="7658288663002113681">"Mwanzo"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Programu Msingi za Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Weka mandhari"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d zimechaguliwa"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d zimechaguliwa"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d zimechaguliwa"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Mandhari %1$d ya %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> iliyochaguliwa"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Futa"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Chukua picha"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Mandhari"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Punguza mandhari"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Programu haijasakinishwa."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Wijeti"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Wijeti"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Onyesha Kumbukumbu"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Gusa na ushikilie ili kuteua wijeti."</string>
+ <string name="market" msgid="2619650989819296998">"Nunua"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Haikuweza kudondosha kipengee kwenye skrini hii ya Kwanza."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Chagua wijeti ili uunde"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Jina la folda"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"lipe folda jina jipya"</string>
+ <string name="rename_action" msgid="5559600076028658757">"SAWA"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Ghairi"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Ongeza kwenye skrini ya Mwanzo"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Programu"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Njia za mkato"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Wijeti"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Hakuna nafasi zaidi kwenye skrini zako za Nyumbani."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Hakuna nafasi zaidi kwenye eneo kali."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Wijeti hii ni kubwa zaidi kwa eneo kali."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Njia ya mkato ya \"<xliff:g id="NAME">%s</xliff:g>\" imeundwa."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Njia ya mkato ya \"<xliff:g id="NAME">%s</xliff:g>\" iliondolewa."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"\"<xliff:g id="NAME">%s</xliff:g>\" la njia ya mkato tayari lipo."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Chagua njia ya mkato"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Chagua programu"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Programu"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Mwanzo"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Ondoa"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Ondoa"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Ondoa"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Ondoa"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Maelezo ya programu"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Tafuta"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Kutafuta kwa Kutamka"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Programu"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Ondoa"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Ondoa sasisho"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Ondoa programu"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Maelezo ya programu"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Programu 1 imechaguliwa"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Wijeti 1 imechaguliwa"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Folda 1 limechaguliwa"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Njia 1 ya mkato imechaguliwa"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"sakinisha njia za mkato"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Huruhusu programu kuongeza njia za mkato bila mtumiaji kuingilia kati."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ondoa njia za mikato"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Huruhusu programu kuondoa njia za mikato bila mtumiaji kuingilia kati."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"soma mipangilio ya Mwanzo na njia za mkato"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Huruhusu programu kusoma mipangilio na njia za mikato zilizo katika skirini ya Mwanzo."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"andika mipangilio ya skrini ya Mwanzo na njia za mkato"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Huruhusu programu kubadilisha mipangilio na njia za mkato katika skrini ya Mwanzo."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Tatizo la kupakia wijeti"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Hii ni programu ya mfumo na haiwezi kuondolewa."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Kizinduzi cha Roketi"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Folda isiyo na jina"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Skrini ya mwazo %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Ukurasa%1$d wa %2$d"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
+ <skip />
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Ukurasa wa programu %1$d ya %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Ukurasa wa wijeti %1$d ya %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Karibu!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Jisikie huru."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Unda skrini zaidi za programu na folda"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Panga nafasi yako"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Gusa na ushikile mandharinyuma ili udhibiti mandhari, wijeti, na mipangilio."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Chagua programu kadhaa"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ili kuongeza programu kwenye Skrini yako Kuu, iguse na uishikilie."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Folda hii hapa"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Ili kuunda kama hii, gusa na ushikilie programu, kisha ipitishe juu ya nyingine."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"SAWA"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Folda imefunguliwa, <xliff:g id="WIDTH">%1$d</xliff:g> kwa <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Gusa ili ufunge folda"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Gusa ili uhifadhi jina jipya"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Folda imefungwa"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Folda imebadilishwa jina kuwa <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folda: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Wijeti"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Mandhari"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Mipangilio"</string>
+</resources>
diff --git a/res/values-sw340dp-land/dimens.xml b/res/values-sw340dp-land/dimens.xml
new file mode 100644
index 0000000..7901dc4
--- /dev/null
+++ b/res/values-sw340dp-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 201 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.
+-->
+
+<resources>
+<!-- Clings -->
+ <dimen name="folderClingMarginTop">50dp</dimen>
+</resources>
diff --git a/res/values-sw340dp-port/config.xml b/res/values-sw340dp-port/config.xml
new file mode 100644
index 0000000..d31ee59
--- /dev/null
+++ b/res/values-sw340dp-port/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+<!-- Workspace -->
+ <!-- Whether or not to fade the side pages -->
+ <bool name="config_workspaceFadeAdjacentScreens">false</bool>
+</resources>
diff --git a/res/values-sw340dp-port/dimens.xml b/res/values-sw340dp-port/dimens.xml
new file mode 100644
index 0000000..e360565
--- /dev/null
+++ b/res/values-sw340dp-port/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+<!-- Clings -->
+ <dimen name="folderClingMarginTop">70dp</dimen>
+</resources>
diff --git a/res/values-sw340dp-port/styles.xml b/res/values-sw340dp-port/styles.xml
new file mode 100644
index 0000000..24f4ba2
--- /dev/null
+++ b/res/values-sw340dp-port/styles.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+-->
+
+<resources>
+<!-- Workspace -->
+ <style name="SearchButton">
+ <item name="android:layout_gravity">center_vertical</item>
+ <item name="android:paddingTop">@dimen/toolbar_button_vertical_padding</item>
+ <item name="android:paddingBottom">@dimen/toolbar_button_vertical_padding</item>
+ </style>
+
+<!-- AppsCustomize -->
+ <style name="TabIndicator.AppsCustomize">
+ <item name="android:maxWidth">150dp</item>
+ </style>
+</resources>
diff --git a/res/values-sw340dp/dimens.xml b/res/values-sw340dp/dimens.xml
new file mode 100644
index 0000000..69d6e58
--- /dev/null
+++ b/res/values-sw340dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+ <!-- Drag padding to add to the bottom of drop targets -->
+ <dimen name="drop_target_drag_padding">20dp</dimen>
+</resources>
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000..7f5594d
--- /dev/null
+++ b/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+<!-- AppsCustomize -->
+ <dimen name="apps_customize_pageLayoutWidthGap">36dp</dimen>
+ <dimen name="apps_customize_pageLayoutHeightGap">8dp</dimen>
+ <dimen name="apps_customize_pageLayoutPaddingTop">20dp</dimen>
+ <dimen name="apps_customize_pageLayoutPaddingBottom">14dp</dimen>
+
+<!-- QSB -->
+ <dimen name="toolbar_button_vertical_padding">12dip</dimen>
+ <dimen name="toolbar_button_horizontal_padding">20dip</dimen>
+</resources>
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..2ec2f14
--- /dev/null
+++ b/res/values-sw600dp/config.xml
@@ -0,0 +1,14 @@
+<resources>
+ <bool name="is_tablet">true</bool>
+ <bool name="allow_rotation">true</bool>
+
+ <!-- Whether or not to use custom clings if a custom workspace layout is passed in -->
+ <bool name="config_useCustomClings">true</bool>
+
+<!-- DragController -->
+ <integer name="config_flingToDeleteMinVelocity">-1000</integer>
+
+ <!-- Camera distance for the overscroll effect. We use a higher value here because the
+ workspace screens run nearly flush to the edge of the screen-->
+ <integer name="config_cameraDistance">14000</integer>
+</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..8d6c7f4
--- /dev/null
+++ b/res/values-sw600dp/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+ <dimen name="app_icon_size">64dp</dimen>
+
+<!-- AppsCustomize -->
+ <dimen name="apps_customize_tab_bar_height">60dp</dimen>
+ <dimen name="apps_customize_tab_bar_margin_top">8dp</dimen>
+ <dimen name="app_widget_preview_label_margin_top">8dp</dimen>
+ <dimen name="app_widget_preview_label_margin_left">@dimen/app_widget_preview_padding_left</dimen>
+ <dimen name="app_widget_preview_label_margin_right">@dimen/app_widget_preview_padding_right</dimen>
+</resources>
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000..3754304
--- /dev/null
+++ b/res/values-sw600dp/styles.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2012 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.
+*/
+-->
+
+<resources>
+ <style name="ClingButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:paddingTop">10dp</item>
+ <item name="android:paddingBottom">15dp</item>
+ <item name="android:paddingStart">35dp</item>
+ <item name="android:paddingEnd">35dp</item>
+ <item name="android:text">@string/cling_dismiss</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:background">@drawable/cling_button_bg</item>
+ </style>
+ <style name="ClingTitleText">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginBottom">5dp</item>
+ <item name="android:textSize">30sp</item>
+ <item name="android:textColor">#33B5E5</item>
+ <item name="android:shadowColor">#000000</item>
+ <item name="android:shadowDy">2</item>
+ <item name="android:shadowRadius">2.0</item>
+ </style>
+ <style name="ClingText">
+ <item name="android:textSize">22sp</item>
+ <item name="android:textColor">#FFFFFF</item>
+ <item name="android:shadowColor">#000000</item>
+ <item name="android:shadowDy">2</item>
+ <item name="android:shadowRadius">2.0</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+</resources>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..eb8f83c
--- /dev/null
+++ b/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+<!-- AppsCustomize -->
+ <integer name="apps_customize_widget_cell_count_x">4</integer>
+ <integer name="apps_customize_widget_cell_count_y">2</integer>
+ <integer name="apps_customize_cling_focused_x">4</integer>
+ <integer name="apps_customize_cling_focused_y">2</integer>
+
+<!-- Workspace -->
+ <dimen name="workspace_page_spacing">50dp</dimen>
+
+ <!-- the area at the edge of the screen that makes the workspace go left
+ or right while you're dragging. -->
+ <dimen name="scroll_zone">100dip</dimen>
+
+<!-- Cling -->
+ <!-- The offset for the text in the cling -->
+ <dimen name="cling_text_block_offset_x">140dp</dimen>
+ <dimen name="cling_text_block_offset_y">80dp</dimen>
+</resources>
diff --git a/res/values-sw720dp-port/dimens.xml b/res/values-sw720dp-port/dimens.xml
new file mode 100644
index 0000000..62bdaaa
--- /dev/null
+++ b/res/values-sw720dp-port/dimens.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<resources>
+<!-- Workspace -->
+ <!-- the area at the edge of the screen that makes the workspace go left
+ or right while you're dragging. -->
+ <dimen name="scroll_zone">40dp</dimen>
+ <dimen name="workspace_page_spacing">24dp</dimen>
+
+ <integer name="apps_customize_cling_focused_x">2</integer>
+ <integer name="apps_customize_cling_focused_y">2</integer>
+
+<!-- Cling -->
+ <!-- The offset for the text in the cling -->
+ <dimen name="cling_text_block_offset_x">80dp</dimen>
+ <dimen name="cling_text_block_offset_y">160dp</dimen>
+</resources>
diff --git a/res/values-sw720dp-port/styles.xml b/res/values-sw720dp-port/styles.xml
new file mode 100644
index 0000000..57f07ac
--- /dev/null
+++ b/res/values-sw720dp-port/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+-->
+
+<resources>
+ <style name="TabIndicator.AppsCustomize">
+ <item name="android:maxWidth">180dp</item>
+ </style>
+</resources>
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
new file mode 100644
index 0000000..4f537a9
--- /dev/null
+++ b/res/values-sw720dp/config.xml
@@ -0,0 +1,20 @@
+<resources>
+ <bool name="config_largeHeap">true</bool>
+ <bool name="is_large_tablet">true</bool>
+
+<!-- AllApps/Customize/AppsCustomize -->
+ <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
+ <integer name="config_workspaceSpringLoadShrinkPercentage">90</integer>
+
+<!-- Workspace -->
+ <!-- Whether or not the drop targets drop down as opposed to fade in -->
+ <bool name="config_useDropTargetDownTransition">false</bool>
+ <!-- Whether or not to fade the side pages -->
+ <bool name="config_workspaceFadeAdjacentScreens">true</bool>
+
+ <!-- Camera distance for the overscroll effect -->
+ <integer name="config_cameraDistance">18000</integer>
+
+<!-- Hotseat -->
+ <bool name="hotseat_transpose_layout_with_orientation">false</bool>
+</resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..01227e1
--- /dev/null
+++ b/res/values-sw720dp/dimens.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<resources>
+ <dimen name="app_icon_size">72dp</dimen>
+
+<!-- QSB -->
+ <dimen name="toolbar_button_vertical_padding">8dip</dimen>
+ <dimen name="toolbar_button_horizontal_padding">8dip</dimen>
+
+ <!-- dimensions for the wallpaper picker wallpaper thumbnail width -->
+ <dimen name="wallpaper_chooser_grid_width">196dp</dimen>
+ <dimen name="wallpaper_chooser_grid_height">140dp</dimen>
+
+ <!-- When dragging items on the workspace, the number of dps by which the position of
+ the drag view should be offset from the position of the original view. -->
+ <dimen name="dragViewOffsetX">0dp</dimen>
+ <dimen name="dragViewOffsetY">0dp</dimen>
+</resources>
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
new file mode 100644
index 0000000..7269e8d
--- /dev/null
+++ b/res/values-sw720dp/styles.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources>
+<!-- Clings -->
+ <style name="ClingButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:paddingTop">10dp</item>
+ <item name="android:paddingBottom">15dp</item>
+ <item name="android:paddingStart">35dp</item>
+ <item name="android:paddingEnd">35dp</item>
+ <item name="android:text">@string/cling_dismiss</item>
+ <item name="android:textSize">20sp</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:background">@drawable/cling_button_bg</item>
+ </style>
+ <style name="ClingTitleText">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginBottom">5dp</item>
+ <item name="android:textSize">32sp</item>
+ <item name="android:textColor">#49C0EC</item>
+ <item name="android:shadowColor">#000000</item>
+ <item name="android:shadowDy">2</item>
+ <item name="android:shadowRadius">2.0</item>
+ </style>
+ <style name="ClingText">
+ <item name="android:textSize">22sp</item>
+ <item name="android:textColor">#FFFFFF</item>
+ <item name="android:shadowColor">#000000</item>
+ <item name="android:shadowDy">2</item>
+ <item name="android:shadowRadius">2.0</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+
+<!-- Workspace -->
+ <style name="Theme" parent="android:Theme.Holo.Wallpaper.NoTitleBar">
+ <item name="android:windowActionModeOverlay">true</item>
+ </style>
+
+ <style name="TabIndicator.AppsCustomize">
+ <item name="android:paddingStart">32dp</item>
+ <item name="android:paddingEnd">32dp</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:maxWidth">240dp</item>
+ </style>
+
+ <!-- QSB Search / Drop Target bar -->
+ <style name="QSBBar">
+ </style>
+ <style name="SearchDropTargetBar">
+ </style>
+ <style name="SearchButton">
+ </style>
+ <style name="DropTargetButtonContainer">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">match_parent</item>
+ </style>
+ <style name="DropTargetButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:drawablePadding">7.5dp</item>
+ <item name="android:paddingStart">60dp</item>
+ <item name="android:paddingEnd">60dp</item>
+ <item name="android:textColor">#FFFFFFFF</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:shadowColor">#393939</item>
+ <item name="android:shadowDx">0.0</item>
+ <item name="android:shadowDy">0.0</item>
+ <item name="android:shadowRadius">2.0</item>
+ </style>
+</resources>
diff --git a/res/values-th-land/strings.xml b/res/values-th-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-th-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000..68118a7
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"หน้าแรก"</string>
+ <string name="uid_name" msgid="7820867637514617527">"แอปหลักของแอนดรอยด์"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"ตั้งค่าวอลเปเปอร์"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"เลือกไว้ %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"เลือกไว้ %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"เลือกไว้ %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"วอลเปเปอร์ %1$d จาก %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"เลือก <xliff:g id="LABEL">%1$s</xliff:g> แล้ว"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"ลบ"</string>
+ <string name="pick_image" msgid="1272073934062909527">"เลือกภาพ"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"วอลเปเปอร์"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"ครอบตัดวอลล์เปเปอร์"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"ไม่ได้ติดตั้งแอป"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"วิดเจ็ต"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"วิดเจ็ต"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"แสดง Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"แตะค้างเพื่อรับวิดเจ็ต"</string>
+ <string name="market" msgid="2619650989819296998">"เลือกซื้อ"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"ไม่สามารถวางรายการลงในหน้าจอหลักนี้"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"เลือกวิดเจ็ตที่จะสร้าง"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"ชื่อโฟลเดอร์"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"เปลี่ยนชื่อโฟลเดอร์"</string>
+ <string name="rename_action" msgid="5559600076028658757">"ตกลง"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"ยกเลิก"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"เพิ่มลงในหน้าแรก"</string>
+ <string name="group_applications" msgid="3797214114206693605">"แอป"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"ทางลัด"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"วิดเจ็ต"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"ไม่มีที่ว่างในหน้าจอหลักของคุณ"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"ไม่มีที่ว่างในหน้าจอหลักนี้"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"ไม่มีที่ว่างใน hotseat"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"วิดเจ็ตนี้มีขนาดใหญ่เกินไปสำหรับ hotseat"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"สร้างทางลัด \"<xliff:g id="NAME">%s</xliff:g>\" แล้ว"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"นำทางลัด \"<xliff:g id="NAME">%s</xliff:g>\" ออกแล้ว"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"มีทางลัด \"<xliff:g id="NAME">%s</xliff:g>\" อยู่แล้ว"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"เลือกทางลัด"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"เลือกแอป"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"แอป"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"หน้าแรก"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"ลบ"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"ถอนการติดตั้ง"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"ลบ"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"ถอนการติดตั้ง"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"ข้อมูลแอป"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"ค้นหา"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"ค้นหาด้วยเสียง"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"แอป"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"ลบ"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"ถอนการติดตั้งการอัปเดต"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"ถอนการติดตั้งแอป"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"รายละเอียดแอป"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"เลือกไว้ 1 แอป"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"เลือกไว้ 1 วิดเจ็ต"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"เลือกไว้ 1 โฟลเดอร์"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"เลือกไว้ 1 ทางลัด"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"ติดตั้งทางลัด"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"อนุญาตให้แอปเพิ่มทางลัดโดยไม่ต้องให้ผู้ใช้จัดการ"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"ถอนการติดตั้งทางลัด"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"อนุญาตให้แอปนำทางลัดออกโดยไม่ต้องให้ผู้ใช้จัดการ"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"อ่านการตั้งค่าและทางลัดหน้าแรกแล้ว"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"อนุญาตให้แอปอ่านการตั้งค่าและทางลัดในหน้าแรก"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"เขียนการตั้งค่าและทางลัดหน้าแรกแล้ว"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"อนุญาตให้แอปเปลี่ยนการตั้งค่าและทางลัดในหน้าแรก"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"มีปัญหาขณะโหลดวิดเจ็ต"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"นี่เป็นแอประบบและไม่สามารถถอนการติดตั้งได้"</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"โฟลเดอร์ที่ไม่มีชื่อ"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"หน้าจอหลัก %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"หน้า %1$d จาก %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"หน้าจอหลัก %1$d จาก %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"แอปหน้า %1$d จาก %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"วิดเจ็ตหน้า %1$d จาก %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"ยินดีต้อนรับ!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"ทำตัวตามสบาย"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"สร้างหน้าจอเพิ่มสำหรับแอปและโฟลเดอร์"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"จัดระเบียบพื้นที่ของคุณ"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"แตะพื้นหลังค้างไว้เพื่อจัดการวอลเปเปอร์ วิดเจ็ต และการตั้งค่า"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"เลือกบางแอป"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"หากต้องการเพิ่มแอปลงในหน้าจอหลัก ให้แตะแอปค้างไว้"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"นี่คือโฟลเดอร์"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"หากต้องการสร้างโฟลเดอร์ลักษณะนี้ แตะแอปค้างไว้ แล้วย้ายไปทับอีกแอปหนึ่ง"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"ตกลง"</string>
+ <string name="folder_opened" msgid="94695026776264709">"เปิดโฟลเดอร์ <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"แตะเพื่อปิดโฟลเดอร์"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"แตะเพื่อบันทึกการเปลี่ยนชื่อ"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"โฟลเดอร์ปิดอยู่"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"เปลี่ยนชื่อโฟลเดอร์เป็น <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"วิดเจ็ต"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"วอลเปเปอร์"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"การตั้งค่า"</string>
+</resources>
diff --git a/res/values-tl-land/strings.xml b/res/values-tl-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-tl-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000..56801b4
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Home"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Core Apps"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Itakda ang wallpaper"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d ang napili"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d ang napili"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d ang napili"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Wallpaper %1$d ng %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Napili ang <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Tanggalin"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Pumili ng larawan"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Mga Wallpaper"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"I-crop ang wallpaper"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Hindi naka-install ang app."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Mga Widget"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Mga Widget"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Ipakita ang Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Pindutin nang matagal upang kumuha ng widget."</string>
+ <string name="market" msgid="2619650989819296998">"Mamili"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Hindi ma-drop ang item sa Home screen na ito."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Pumili ng widget na gagawin"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Pangalan ng folder"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Palitan ang pangalan ng folder"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Kanselahin"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Idagdag sa Home screen"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Apps"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Mga Shortcut"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Mga Widget"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Wala nang lugar sa iyong mga Home screen."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Wala nang lugar sa Home screen na ito."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Wala nang lugar sa hotseat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Masyadong malaki ang widget na ito para sa hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Nagawa ang shortcut na \"<xliff:g id="NAME">%s</xliff:g>.\""</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Inalis ang shortcut na \"<xliff:g id="NAME">%s</xliff:g>.\""</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Umiiral na ang shortcut na \"<xliff:g id="NAME">%s</xliff:g>.\""</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Pumili ng shortcut"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Pumili ng app"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Alisin"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"I-uninstall"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Alisin"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"I-uninstall"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Impormasyon ng app"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Hanapin"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Paghahanap Gamit ang Boses"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Apps"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Alisin"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"I-uninstall ang update"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"I-uninstall ang app"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Mga detalye ng app"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 app ang napili"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget ang napili"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 folder ang napili"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 shortcut ang napili"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"i-install ang mga shortcut"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Pinapayagan ang isang app na magdagdag ng mga shortcut nang walang panghihimasok ng user."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"i-uninstall ang mga shortcut"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Pinapayagan ang app na mag-alis ng mga shortcut nang walang panghihimasok ng user."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"basahin ang mga setting at shortcut ng Home"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Pinapayagan ang app na basahin ang mga setting at shortcut sa Home."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"magsulat ng mga setting at shortcut ng Home"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Pinapayagan ang app na baguhin ang mga setting at shortcut sa Home."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Problema sa pag-load ng widget"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Isa itong app ng system at hindi maaaring i-uninstall."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Walang Pangalang Folder"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Home screen %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Pahina %1$d ng %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d ng %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Pahina ng apps %1$d ng %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Pahina ng widget %1$d ng %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Maligayang pagdating!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Gawing kumportable ang iyong sarili."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Gumawa ng higit pang mga screen para sa apps at mga folder"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Ayusin ang iyong espasyo"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Pindutin nang matagal ang background upang pamahalaan ang wallpaper, mga widget at setting"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Pumili ng ilang apps"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Upang magdagdag ng app sa iyong Home screen, pindutin ito nang matagal."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Narito ang isang folder"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Upang gumawa ng katulad nito, pindutin nang matagal ang isang app, pagkatapos ay ilipat ito sa isa pang folder."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Binuksan ang folder, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Pindutin upang isara ang folder"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Pindutin upang i-save ang pagpapalit ng pangalan"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Nakasara ang folder"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Pinalitan ang pangalan ng folder ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Mga Widget"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Mga Wallpaper"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Mga Setting"</string>
+</resources>
diff --git a/res/values-tr-land/strings.xml b/res/values-tr-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-tr-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000..dbbdee2
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Ana ekran"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android Çekirdek Uygulamaları"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Duvar kağıdını ayarla"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d tane seçildi"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d tane seçildi"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d tane seçildi"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Duvar kağıdı %1$d / %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> seçildi"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Sil"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Resim seç"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Duvar Kağıtları"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Duvar kağıdını kırp"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Uygulama yüklü değil."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Widget\'lar"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Widget\'lar"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Belleği Göster"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Widget seçmek için dokunun ve basılı tutun."</string>
+ <string name="market" msgid="2619650989819296998">"Alışveriş yap"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Öğe bu Ana ekrana bırakılamadı."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Oluşturmak için widget seçin"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Klasör adı"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Klasörü yeniden adlandırın"</string>
+ <string name="rename_action" msgid="5559600076028658757">"Tamam"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"İptal"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Ana ekrana ekleyin"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Uygulamalar"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Kısayollar"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Widget\'lar"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Ana ekranlarınızda yer kalmadı."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Bu Ana ekranda yer kalmadı."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Favori kısa yollarda yer yok"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Bu widget, favori kısa yollar için çok büyük."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"\"<xliff:g id="NAME">%s</xliff:g>\" kısayolu oluşturuldu."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"\"<xliff:g id="NAME">%s</xliff:g>\" kısayolu kaldırıldı."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"\"<xliff:g id="NAME">%s</xliff:g>\" kısayolu zaten var."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Kısayolu seçin"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Uygulama seçin"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Uygulamalar"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Ana ekran"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Kaldır"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Yüklemeyi kaldır"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Kaldır"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Yüklemeyi kaldır"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Uygulama bilgileri"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Ara"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Sesli Arama"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Uygulamalar"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Kaldır"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Güncellemeyi kaldır"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Uygulamanın yüklemesini kaldır"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Uygulama ayrıntıları"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 uygulama seçildi"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 widget seçildi"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 klasör seçildi"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 kısayol seçildi"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"kısayolları yükle"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Uygulamaya, kullanıcı müdahalesi olmadan kısayol ekleme izni verir."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"kısayolların yüklemesini kaldır"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Uygulamaya, kullanıcı müdahalesi olmadan kısayolları kaldırma izni verir."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"Ana ekran ayarlarını ve kısayollarını oku"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Uygulamaya, Ana ekrandaki ayarları ve kısayolları okuma izni verir."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"Ana ekran ayarlarını ve kısayollarını yaz"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Uygulamaya, Ana ekrandaki ayarları ve kısayolları değiştirme izni verir."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Widget yüklenirken sorun oluştu"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Bu bir sistem uygulamasıdır ve yüklemesi kaldırılamaz."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Roket Fırlatıcı"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Adsız Klasör"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Ana ekran %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Sayfa %1$d / %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Ana ekran %1$d / %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Uygulama sayfası %1$d / %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Widget sayfası %1$d / %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Hoş geldiniz!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Rahatınıza bakın."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Uygulamalar ve klasörler için daha fazla ekran oluşturun"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Alanınızı düzenleyin"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Duvar kağıdını, widget\'ları ve ayarları yönetmek için arka plana uzun basın."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"İstediğiniz uygulamaları seçin"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Bir uygulamayı Ana ekranınıza eklemek için, ilgili uygulamayı basılı tutun."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"İşte bir klasör"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Buna benzer bir klasör oluşturmak için uygulamaya uzun basın ve sonra uygulamayı başka bir uygulamanın üzerine taşıyın."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"Tamam"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Klasör açıldı, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Klasörü kapatmak için dokunun"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Yeni adı kaydetmek için dokunun"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Klasör kapatıldı"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Klasörün adı <xliff:g id="NAME">%1$s</xliff:g> olarak değiştirildi"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Widget\'lar"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Duvar Kağıtları"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Ayarlar"</string>
+</resources>
diff --git a/res/values-uk-land/strings.xml b/res/values-uk-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-uk-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000..e0f3211
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Головний екран"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Базові програми Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Установити фон"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Вибрано %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Вибрано %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Вибрано %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Фоновий малюнок %1$d з %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"Вибрано <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Видалити"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Вибрати зображення"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Фонові малюнки"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Обрізати фоновий малюнок"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Програму не встановлено."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Віджети"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Віджети"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Показати пам’ять"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Натисніть і утримуйте, щоб вибрати віджет."</string>
+ <string name="market" msgid="2619650989819296998">"Магазин"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Не вдалося додати елемент на цей головний екран."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Вибрати віджет для створення"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Назва папки"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Перейменувати папку"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OК"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Скасувати"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Додати на головний екран"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Програми"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Ярлики"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Віджети"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"На головних екранах більше немає місця."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"На цьому головному екрані більше немає місця."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Немає вільного місця."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Цей віджет завеликий."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Ярлик \"<xliff:g id="NAME">%s</xliff:g>\" створено."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Ярлик \"<xliff:g id="NAME">%s</xliff:g>\" вилучено."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Ярлик \"<xliff:g id="NAME">%s</xliff:g>\" уже існує."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Вибрати ярлик"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Вибрати програму"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Програми"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Головний екран"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Вилучити"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Видалити"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Вилучити"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Видалити"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Про програму"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Пошук"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Голосовий пошук"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Програми"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Вилучити"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Видалити оновлення"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Видалити програму"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Відомості про програму"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Вибрано 1 програму"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Вибрано 1 віджет"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Вибрано 1 папку"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Вибрано 1 ярлик"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"установлювати ярлики"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Дозволяє програмі самостійно додавати ярлики."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"видаляти ярлики"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Дозволяє програмі самостійно вилучати ярлики."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"читати налаштування та ярлики головного екрана"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Дозволяє програмі читати налаштування та ярлики на головному екрані."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"записувати налаштування та ярлики головного екрана"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Дозволяє програмі змінювати налаштування та ярлики на головному екрані."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Проблема із завантаженням віджета"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Це системна програма, її неможливо видалити."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Папка без назви"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Головний екран %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Сторінка %1$d з %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Головний екран %1$d з %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Сторінка програм %1$d з %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Сторінка віджетів %1$d з %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Вітаємо!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Будьте як удома."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Створюйте нові екрани для програм і папок"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Організуйте робочий простір"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Натисніть і утримуйте фон, щоб керувати фоновим малюнком, віджетами та налаштуваннями."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Виберіть програми"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Щоб додати програму на головний екран, торкніться й утримуйте її."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Це папка"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Щоб створити папку, натисніть і утримуйте програму, а потім перетягніть її на іншу."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OК"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Папку відкрито (<xliff:g id="WIDTH">%1$d</xliff:g> х <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Торкніться, щоб закрити папку"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Торкніться, щоб зберегти нову назву"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Папку закрито"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Папку перейменовано на <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Папка <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Віджети"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Фонові малюнки"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Налаштування"</string>
+</resources>
diff --git a/res/values-vi-land/strings.xml b/res/values-vi-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-vi-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000..3b081d0
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"Màn hình chính"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Ứng dụng lõi Android"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Đặt hình nền"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"Đã chọn %1$d"</item>
+ <item quantity="one" msgid="142482526010824029">"Đã chọn %1$d"</item>
+ <item quantity="other" msgid="1418352074806573570">"Đã chọn %1$d"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Hình nền %1$d / %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"<xliff:g id="LABEL">%1$s</xliff:g> được chọn"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Xóa"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Chọn hình ảnh"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Hình nền"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Cắt hình nền"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Ứng dụng chưa được cài đặt."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Tiện ích con"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Tiện ích con"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Hiển thị bộ nhớ"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Chạm và giữ để chọn tiện ích con."</string>
+ <string name="market" msgid="2619650989819296998">"Mua"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Không thể thả mục vào Màn hình chính này."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Chọn tiện ích con để tạo"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Tên thư mục"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Đổi tên thư mục"</string>
+ <string name="rename_action" msgid="5559600076028658757">"OK"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Hủy"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Thêm vào Màn hình chính"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Ứng dụng"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Lối tắt"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Tiện ích con"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Không còn chỗ trên Màn hình chính của bạn."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Không còn chỗ trên Màn hình chính này."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Không còn chỗ trên vùng gắn."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Tiện ích con này quá lớn cho vùng gắn."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Lối tắt \"<xliff:g id="NAME">%s</xliff:g>\" đã được tạo."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Lối tắt \"<xliff:g id="NAME">%s</xliff:g>\" đã bị xóa."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Lối tắt \"<xliff:g id="NAME">%s</xliff:g>\" đã tồn tại."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Chọn lối tắt"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Chọn ứng dụng"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Ứng dụng"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Màn hình chính"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Xóa"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Gỡ cài đặt"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Xóa"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Gỡ cài đặt"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Thông tin ứng dụng"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Tìm kiếm"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Tìm kiếm bằng giọng nói"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Ứng dụng"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Xóa"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Gỡ cài đặt cập nhật"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Gỡ cài đặt ứng dụng"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Thông tin chi tiết về ứng dụng"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"Đã chọn 1 ứng dụng"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"Đã chọn 1 tiện ích con"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"Đã chọn 1 thư mục"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"Đã chọn 1 lối tắt"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"cài đặt lối tắt"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Cho phép ứng dụng thêm lối tắt mà không cần sự can thiệp của người dùng."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"gỡ cài đặt lối tắt"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Cho phép ứng dụng xóa lối tắt mà không cần sự can thiệp của người dùng."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"đọc cài đặt và lối tắt trên Màn hình chính"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Cho phép ứng dụng đọc cài đặt và lối tắt trên Màn hình chính."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"ghi cài đặt và lối tắt trên Màn hình chính"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Cho phép ứng dụng thay đổi cài đặt và lối tắt trên Màn hình chính."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Sự cố khi tải tiện ích con"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Đây là ứng dụng hệ thống và không thể gỡ cài đặt."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Thư mục chưa đặt tên"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Màn hình chính %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Trang %1$d / %2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Màn hình chính %1$d / %2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Trang ứng dụng %1$d / %2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Trang tiện ích con %1$d / %2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Xin chào!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Tự nhiên như ở nhà."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Tạo thêm màn hình cho ứng dụng và thư mục"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Sắp xếp không gian của bạn"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Chạm và giữ nền để quản lý hình nền, tiện ích con và cài đặt."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Chọn một số ứng dụng"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Để thêm ứng dụng vào Màn hình chính của bạn, chạm và giữ ứng dụng đó."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Đây là một thư mục"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Để tạo thư mục như thế này, hãy chạm và giữ một ứng dụng, sau đó di chuyển ứng dụng đó lên trên một ứng dụng khác."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"OK"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Đã mở thư mục, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Chạm để đóng thư mục"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Chạm để lưu tên mới"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Đã đóng thư mục"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Đã đổi tên thư mục thành <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Thư mục: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Tiện ích con"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Hình nền"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Cài đặt"</string>
+</resources>
diff --git a/res/values-zh-rCN-land/strings.xml b/res/values-zh-rCN-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-zh-rCN-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..43a57c4
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"主屏"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android 核心应用"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"设置壁纸"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"已选择%1$d项"</item>
+ <item quantity="one" msgid="142482526010824029">"已选择%1$d项"</item>
+ <item quantity="other" msgid="1418352074806573570">"已选择%1$d项"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"第%1$d张壁纸,共%2$d张"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"已选择<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"删除"</string>
+ <string name="pick_image" msgid="1272073934062909527">"选择图片"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"壁纸"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"剪裁壁纸"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"未安装该应用。"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"小部件"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"小部件"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"显示内存空间"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"触摸并按住小部件即可选择。"</string>
+ <string name="market" msgid="2619650989819296998">"商店"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"无法将相关内容拖放到此主屏幕上。"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"选择要创建的小部件"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"文件夹名称"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"重命名文件夹"</string>
+ <string name="rename_action" msgid="5559600076028658757">"确定"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"取消"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"添加到主屏幕"</string>
+ <string name="group_applications" msgid="3797214114206693605">"应用"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"快捷方式"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"小部件"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"您的主屏幕上没有空间了。"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"此主屏幕上已没有空间。"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"底部区域已无空间。"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"该小部件太大,底部区域容纳不下。"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"已创建“<xliff:g id="NAME">%s</xliff:g>”快捷方式。"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"已删除“<xliff:g id="NAME">%s</xliff:g>”快捷方式。"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"“<xliff:g id="NAME">%s</xliff:g>”快捷方式已存在。"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"选择快捷方式"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"选择应用"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"应用"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"主屏幕"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"删除"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"卸载"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"删除"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"卸载"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"应用信息"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"搜索"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"语音搜索"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"应用"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"删除"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"卸载更新内容"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"卸载应用"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"应用详情"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"已选择1个应用"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"已选择1个小部件"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"已选择1个文件夹"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"已选择1个快捷方式"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"安装快捷方式"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"允许应用自行添加快捷方式。"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"卸载快捷方式"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"允许应用自行删除快捷方式。"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"读取主屏幕设置和快捷方式"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"允许应用读取主屏幕中的设置和快捷方式。"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"写入主屏幕设置和快捷方式"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"允许应用更改主屏幕中的设置和快捷方式。"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"加载小部件时出现问题"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"这是系统应用,无法卸载。"</string>
+ <string name="dream_name" msgid="1530253749244328964">"火箭发射器"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"未命名文件夹"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"主屏幕%1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"第%1$d页,共%2$d页"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"主屏幕:第%1$d屏,共%2$d屏"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"应用:第%1$d页,共%2$d页"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"小部件:第%1$d页,共%2$d页"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"欢迎!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"您的主屏幕您做主。"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"添加更多屏幕来容纳应用和文件夹"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"整理您的空间"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"触摸并按住背景,即可管理壁纸、小部件和设置。"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"选择一些应用"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"要将应用添加到主屏幕,请触摸并按住该应用。"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"这是一个文件夹"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"要创建一个类似的文件夹,请触摸并按住某个应用,然后将其移至另一个应用上。"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"确定"</string>
+ <string name="folder_opened" msgid="94695026776264709">"文件夹已打开,大小为<xliff:g id="WIDTH">%1$d</xliff:g>×<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"触摸可关闭文件夹"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"触摸可保存新名称"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"文件夹已关闭"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"已将文件夹重命名为“<xliff:g id="NAME">%1$s</xliff:g>”"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"小部件"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"壁纸"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"设置"</string>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..eb3def4
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"主畫面"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android 核心應用程式"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"設定桌布"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"已選取 %1$d 個"</item>
+ <item quantity="one" msgid="142482526010824029">"已選取 %1$d 個"</item>
+ <item quantity="other" msgid="1418352074806573570">"已選取 %1$d 個"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"第 %1$d 張桌布,共 %2$d 張"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"已選取「<xliff:g id="LABEL">%1$s</xliff:g>」"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"刪除"</string>
+ <string name="pick_image" msgid="1272073934062909527">"選擇圖片"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"桌布"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"裁剪桌布"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"尚未安裝應用程式。"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"小工具"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"小工具"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"顯示記憶體"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
+ <string name="market" msgid="2619650989819296998">"商店"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"無法將項目拖放至主畫面。"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"選擇要建立的小工具"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"資料夾名稱"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"重新命名資料夾"</string>
+ <string name="rename_action" msgid="5559600076028658757">"確定"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"取消"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"新增至主畫面"</string>
+ <string name="group_applications" msgid="3797214114206693605">"應用程式"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"捷徑"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"小工具"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"主畫面已無空間。"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"停駐區已無可用空間。"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"這個小工具過大,停駐區沒有足夠空間。"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"已建立「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"已移除「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"「<xliff:g id="NAME">%s</xliff:g>」捷徑已存在。"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"選擇捷徑"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"選擇應用程式"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"主畫面"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"移除"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"解除安裝"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"移除"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"解除安裝"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"應用程式資料"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"搜尋"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"語音搜尋"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"應用程式"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"移除"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"解除安裝更新"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"解除安裝應用程式"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"應用程式詳細資料"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"已選取 1 個應用程式"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"已選取 1 個小工具"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"已選取 1 個資料夾"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"已選取 1 個捷徑"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式無需用戶許可也可新增捷徑。"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"解除安裝捷徑"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"允許應用程式無需用戶許可也可移除捷徑。"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"讀取主畫面的設定和捷徑"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"允許應用程式讀取主畫面中的設定和捷徑。"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"寫入主畫面的設定和捷徑"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"允許應用程式更改主畫面中的設定和捷徑。"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"載入小工具時發生問題"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"這是系統應用程式,無法將其解除安裝。"</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"未命名的資料夾"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"主畫面 %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"第 %1$d 頁,共 %2$d 頁"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"主畫面 %1$d,共 %2$d 個"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"第 %1$d 個應用程式頁面,共 %2$d 頁"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"第 %1$d 個小工具頁面,共 %2$d 頁"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"歡迎!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"自訂主畫面。"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"建立更多應用程式和資料夾的畫面"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"管理您的空間"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"輕觸並按住背景,即可管理桌布、小工具和設定。"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"選擇一些應用程式"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"輕觸並按住應用程式,即可加到主畫面。"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"資料夾顯示如下"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"如要建立類似的資料夾,請輕觸並按住某個應用程式,然後疊到另一個應用程式之上。"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"確定"</string>
+ <string name="folder_opened" msgid="94695026776264709">"資料夾已開啟 (<xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"輕觸即可關閉資料夾"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"輕觸即可儲存新改的名稱"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"已關閉資料夾"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"資料夾已重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"小工具"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
+</resources>
diff --git a/res/values-zh-rTW-land/strings.xml b/res/values-zh-rTW-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-zh-rTW-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..a68f163
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Launcher3"</string>
+ <string name="home" msgid="7658288663002113681">"主螢幕"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Android 核心應用程式"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"設定桌布"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"已選取 %1$d 個項目"</item>
+ <item quantity="one" msgid="142482526010824029">"已選取 %1$d 個項目"</item>
+ <item quantity="other" msgid="1418352074806573570">"已選取 %1$d 個項目"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"第 %1$d 張桌布,共 %2$d 張"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"已選取<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"刪除"</string>
+ <string name="pick_image" msgid="1272073934062909527">"選擇圖片"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"桌布"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"裁剪桌布"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"應用程式未安裝。"</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"小工具"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"小工具"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"顯示記憶體"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"輕觸並按住小工具即可選取。"</string>
+ <string name="market" msgid="2619650989819296998">"購物"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"無法將項目拖放至這個主螢幕上。"</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"選擇要建立的小工具"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"資料夾名稱"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"重新命名資料夾"</string>
+ <string name="rename_action" msgid="5559600076028658757">"確定"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"取消"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"新增至主螢幕"</string>
+ <string name="group_applications" msgid="3797214114206693605">"應用程式"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"捷徑"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"小工具"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"主螢幕已無空間。"</string>
+ <string name="out_of_space" msgid="4691004494942118364">"這個主螢幕已無空間。"</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"停駐區已無空間。"</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"這個小工具過大,停駐區無法容納。"</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"已建立「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"已移除「<xliff:g id="NAME">%s</xliff:g>」捷徑。"</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"「<xliff:g id="NAME">%s</xliff:g>」捷徑已存在。"</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"選擇捷徑"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"選擇應用程式"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"主螢幕"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"移除"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"解除安裝"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"移除"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"解除安裝"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"應用程式資訊"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"搜尋"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"語音搜尋"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"應用程式"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"移除"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"解除安裝更新"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"解除安裝應用程式"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"應用程式詳細資料"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"已選取 1 個應用程式"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"已選取 1 個小工具"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"已選取 1 個資料夾"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"已選取 1 個捷徑"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式自動新增捷徑。"</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"解除安裝捷徑"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"允許應用程式自動移除捷徑。"</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"讀取主螢幕的設定和捷徑"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"允許應用程式讀取主螢幕中的設定和捷徑。"</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"寫入主螢幕設定和捷徑"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"允許應用程式變更主螢幕中的設定和捷徑。"</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"載入小工具時發生問題"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"這是系統應用程式,不可解除安裝。"</string>
+ <string name="dream_name" msgid="1530253749244328964">"Rocket Launcher"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"未命名的資料夾"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"主螢幕 %1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"第 %1$d 頁,共 %2$d 頁"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"主螢幕:第 %1$d 頁,共 %2$d 頁"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"應用程式:第 %1$d 頁,共 %2$d 頁"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"小工具:第 %1$d 頁,共 %2$d 頁"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"歡迎使用!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"主螢幕由您作主。"</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"建立更多畫面容納應用程式和資料夾"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"管理您的空間"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"輕觸並按住背景,即可管理桌布、小工具和設定。"</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"選擇一些應用程式"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"如要將應用程式新增至主螢幕,請輕觸並按住目標。"</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"資料夾顯示如下"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"如要建立類似的資料夾,請輕觸並按住應用程式,然後將應用程式疊放在另一個應用程式上。"</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"確定"</string>
+ <string name="folder_opened" msgid="94695026776264709">"資料夾已開啟 (<xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"輕觸即可關閉資料夾"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"輕觸即可儲存新名稱"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"資料夾已關閉"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"已將資料夾重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"小工具"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"桌布"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"設定"</string>
+</resources>
diff --git a/res/values-zu-land/strings.xml b/res/values-zu-land/strings.xml
new file mode 100644
index 0000000..b976926
--- /dev/null
+++ b/res/values-zu-land/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2011 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="delete_target_label" msgid="4155210680095864979"></string>
+ <string name="delete_target_uninstall_label" msgid="1839407506844917298"></string>
+ <string name="info_target_label" msgid="1424400595004570393"></string>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000..da790e6
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="application_name" msgid="5181331383435256801">"Isiqalisi3"</string>
+ <string name="home" msgid="7658288663002113681">"Ikhaya"</string>
+ <string name="uid_name" msgid="7820867637514617527">"Izinhlelo zokusebenza ze-Android Core"</string>
+ <string name="folder_name" msgid="7371454440695724752"></string>
+ <string name="wallpaper_instructions" msgid="563973358787555519">"Setha isithombe sangemuva"</string>
+ <plurals name="number_of_items_selected">
+ <item quantity="zero" msgid="7464587177007785408">"%1$d khethiwe"</item>
+ <item quantity="one" msgid="142482526010824029">"%1$d khethiwe"</item>
+ <item quantity="other" msgid="1418352074806573570">"%1$d khethiwe"</item>
+ </plurals>
+ <string name="wallpaper_accessibility_name" msgid="1655953108132967972">"Isithombe sangemuva se-%1$d of %2$d"</string>
+ <string name="announce_selection" msgid="8338254712932127413">"I-<xliff:g id="LABEL">%1$s</xliff:g> ekhethiwe"</string>
+ <string name="wallpaper_delete" msgid="8095005658756613921">"Susa"</string>
+ <string name="pick_image" msgid="1272073934062909527">"Thatha isithombe"</string>
+ <string name="pick_wallpaper" msgid="8179698221502010609">"Izithombe zangemuva"</string>
+ <string name="crop_wallpaper" msgid="8334345984491368009">"Nqampuna isithombe sangemuva"</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"Uhlelo lokusebenza alufakiwe."</string>
+ <string name="widgets_tab_label" msgid="2921133187116603919">"Amawijethi"</string>
+ <string name="widget_adder" msgid="3201040140710381657">"Amawijethi"</string>
+ <string name="toggle_weight_watcher" msgid="5645299835184636119">"Bonisa i-Mem"</string>
+ <string name="long_press_widget_to_add" msgid="7699152356777458215">"Thinta uphinde ubambe ukuze uphakamise iwijethi."</string>
+ <string name="market" msgid="2619650989819296998">"Thenga"</string>
+ <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+ <string name="external_drop_widget_error" msgid="3165821058322217155">"Ayikwazanga ukwehlisela into kulesi sikrini se-Ikhaya."</string>
+ <string name="external_drop_widget_pick_title" msgid="3486317258037690630">"Khetha iwijethi ongayidala"</string>
+ <string name="rename_folder_label" msgid="3727762225964550653">"Igama lefolda"</string>
+ <string name="rename_folder_title" msgid="3771389277707820891">"Qamba kabusha ifolda"</string>
+ <string name="rename_action" msgid="5559600076028658757">"KULUNGILE"</string>
+ <string name="cancel_action" msgid="7009134900002915310">"Khansela"</string>
+ <string name="menu_item_add_item" msgid="1264911265836810421">"Faka kwisikrini saseKhaya"</string>
+ <string name="group_applications" msgid="3797214114206693605">"Izinhlelo zokusebenza"</string>
+ <string name="group_shortcuts" msgid="6012256992764410535">"Izinqamuleli"</string>
+ <string name="group_widgets" msgid="1569030723286851002">"Amawijethi"</string>
+ <string name="completely_out_of_space" msgid="6106288382070760318">"Akusenagumbi ezikrinini zakho Zekhaya."</string>
+ <string name="out_of_space" msgid="4691004494942118364">"Asisekho isikhala kulesi sikrini Sasekhaya."</string>
+ <string name="hotseat_out_of_space" msgid="9139760413395605841">"Akusenagumbi ku-hotseat."</string>
+ <string name="invalid_hotseat_item" msgid="1211534262129849507">"Le wijethi inkulu kakhulu ukuba ku-hotseat."</string>
+ <string name="shortcut_installed" msgid="1701742129426969556">"Isinqamuleli esithi \"<xliff:g id="NAME">%s</xliff:g>\" sidaliwe."</string>
+ <string name="shortcut_uninstalled" msgid="8176767991305701821">"Isinqamuleli esithi \"<xliff:g id="NAME">%s</xliff:g>\" sisusiwe."</string>
+ <string name="shortcut_duplicate" msgid="9167217446062498127">"Isinqamuleli esithi \"<xliff:g id="NAME">%s</xliff:g>\" sesivele sikhona."</string>
+ <string name="title_select_shortcut" msgid="6680642571148153868">"Khetha isinqamulelo"</string>
+ <string name="title_select_application" msgid="3280812711670683644">"Khetha uhlelo lokusebenza"</string>
+ <string name="all_apps_button_label" msgid="9110807029020582876">"Izinhlelo zokusebenza"</string>
+ <string name="all_apps_home_button_label" msgid="252062713717058851">"Ikhaya"</string>
+ <string name="delete_zone_label_workspace" msgid="4009607676751398685">"Susa"</string>
+ <string name="delete_zone_label_all_apps" msgid="8083826390278958980">"Khipha"</string>
+ <string name="delete_target_label" msgid="1822697352535677073">"Susa"</string>
+ <string name="delete_target_uninstall_label" msgid="5100785476250872595">"Khipha"</string>
+ <string name="info_target_label" msgid="8053346143994679532">"Ulwazi lohlelo lokusebenza"</string>
+ <string name="accessibility_search_button" msgid="1628520399424565142">"Sesha"</string>
+ <string name="accessibility_voice_search_button" msgid="4637324840434406584">"Ukusesha ngezwi"</string>
+ <string name="accessibility_all_apps_button" msgid="2603132375383800483">"Izinhlelo zokusebenza"</string>
+ <string name="accessibility_delete_button" msgid="6466114477993744621">"Susa"</string>
+ <string name="delete_zone_label_all_apps_system_app" msgid="449755632749610895">"Khipha isibuyekezo"</string>
+ <string name="cab_menu_delete_app" msgid="7435191475867183689">"Khipha uhlelo lokusebenza"</string>
+ <string name="cab_menu_app_info" msgid="8593722221450362342">"Imininingwane yohlelo lokusebenza"</string>
+ <string name="cab_app_selection_text" msgid="374688303047985416">"1 uhlelo lokusebenza olukhethiwe"</string>
+ <string name="cab_widget_selection_text" msgid="1833458597831541241">"1 iwijethi ekhethiwe"</string>
+ <string name="cab_folder_selection_text" msgid="7999992513806132118">"1 ifolda ekhethiwe"</string>
+ <string name="cab_shortcut_selection_text" msgid="2103811025667946450">"1 isinqamuleli esikhethiwe"</string>
+ <string name="permlab_install_shortcut" msgid="5632423390354674437">"faka izinqamuleli"</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Ivumela uhlelo lokusebenza ukufaka izinqamuleli ngaphandle kokungenelela komsebenzisi."</string>
+ <string name="permlab_uninstall_shortcut" msgid="864595034498083837">"khipha izinqamuleli"</string>
+ <string name="permdesc_uninstall_shortcut" msgid="5134129545001836849">"Ivumela uhlelo lokusebenza ukuthi lisuse izinqamuleli ngaphandle kokungenelela komsebenzisi."</string>
+ <string name="permlab_read_settings" msgid="1941457408239617576">"funda izilungiselelo zokuthi Ikhaya nezinqamuleli"</string>
+ <string name="permdesc_read_settings" msgid="5833423719057558387">"Ivumela uhlelo lokusebenza ukuthi lifunde izilungiselelo nezinqamuleli ekhaya."</string>
+ <string name="permlab_write_settings" msgid="3574213698004620587">"bhala izilungiselelo zokuthi Ikhaya nezinqamuleli"</string>
+ <string name="permdesc_write_settings" msgid="5440712911516509985">"Ivumela uhlelo lokusebenza ukuthi lushintshe izilungiselelo nezinqamuleli Ekhaya."</string>
+ <string name="gadget_error_text" msgid="6081085226050792095">"Inkinga yokulayisha iwijethi"</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Lolu uhlelo lokusebenza lwesistimu futhi alikwazi ukukhishwa."</string>
+ <string name="dream_name" msgid="1530253749244328964">"Isiqalisi se-Rocket"</string>
+ <string name="folder_hint_text" msgid="6617836969016293992">"Ifolda engenagama"</string>
+ <string name="workspace_description_format" msgid="2950174241104043327">"Isikrini sasekhaya esingu-%1$d"</string>
+ <string name="default_scroll_format" msgid="7475544710230993317">"Ikhasi elingu-%1$d kwangu-%2$d"</string>
+ <string name="workspace_scroll_format" msgid="8458889198184077399">"Isikrini sasekhaya esingu-%1$d se-%2$d"</string>
+ <string name="apps_customize_apps_scroll_format" msgid="370005296147130238">"Ikhasi lezinhlelo zokusebenza elingu-%1$d le-%2$d"</string>
+ <string name="apps_customize_widgets_scroll_format" msgid="3106209519974971521">"Ikhasi lamawijethi elingu-%1$d le-%2$d"</string>
+ <string name="first_run_cling_title" msgid="7257389003637362144">"Siyakwamukela!"</string>
+ <string name="first_run_cling_description" msgid="6447072552696253358">"Zizwe usekhaya."</string>
+ <string name="first_run_cling_custom_content_hint" msgid="6090628589029352439"></string>
+ <string name="first_run_cling_search_bar_hint" msgid="5909062802402452582"></string>
+ <string name="first_run_cling_create_screens_hint" msgid="6950729526680114157">"Dala izikrini eziningi zezinhlelo zokusebenza namafolda"</string>
+ <string name="workspace_cling_title" msgid="5626202359865825661">"Hlela isikhala sakho"</string>
+ <string name="workspace_cling_move_item" msgid="528201129978005352">"Thinta uphinde ubambe okungemuva ukuze uphathe isithombe sangemuva, amawijethi nezilungiselelo."</string>
+ <string name="all_apps_cling_title" msgid="34929250753095858">"Khetha izinhlelo zokusebenza ezithile"</string>
+ <string name="all_apps_cling_add_item" msgid="400866858451850784">"Ukuze ungeze uhlelo lokusebenza kusikrini sakho se-Ikhaya, thinta futhi uyibambe."</string>
+ <string name="folder_cling_title" msgid="3894908818693254164">"Nayi ifolda"</string>
+ <string name="folder_cling_create_folder" msgid="6158215559475836131">"Ukuze udale eyodwa efana nale, thinta uphinde ubambe uhlelo lokusebenza, bese ulidlulisa ngaphezulu kwelinye."</string>
+ <string name="cling_dismiss" msgid="8962359497601507581">"KULUNGILE"</string>
+ <string name="folder_opened" msgid="94695026776264709">"Ifolda ivuliwe, <xliff:g id="WIDTH">%1$d</xliff:g> nge-<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+ <string name="folder_tap_to_close" msgid="1884479294466410023">"Thinta ukuze uvale ifolda"</string>
+ <string name="folder_tap_to_rename" msgid="9191075570492871147">"Thinta ukuze ulondoloze ukuqamba kabusha"</string>
+ <string name="folder_closed" msgid="4100806530910930934">"Ifolda ivaliwe"</string>
+ <string name="folder_renamed" msgid="1794088362165669656">"Ifolda iqanjwe kabusha ngo-<xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="folder_name_format" msgid="6629239338071103179">"Ifolda: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="custom_workspace_cling_title_1" msgid="3750880082935033085"></string>
+ <string name="custom_workspace_cling_description_1" msgid="939966842147696724"></string>
+ <string name="custom_workspace_cling_title_2" msgid="662588444436552198"></string>
+ <string name="custom_workspace_cling_description_2" msgid="8097921091798539310"></string>
+ <string name="widget_button_text" msgid="2880537293434387943">"Amawijethi"</string>
+ <string name="wallpaper_button_text" msgid="8404103075899945851">"Izithombe zangemuva"</string>
+ <string name="settings_button_text" msgid="8119458837558863227">"Izilungiselelo"</string>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000..a2d3a83
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+
+ <!-- DrawableStateProxyView specific attributes. These attributes are used to customize
+ a DrawableStateProxyView view in XML files. -->
+ <declare-styleable name="DrawableStateProxyView">
+ <!-- The source view to delegate touch presses events to. -->
+ <attr name="sourceViewId" format="integer" />
+ </declare-styleable>
+
+ <!-- Cling specific attributes. These attributes are used to customize
+ the cling in XML files. -->
+ <declare-styleable name="Cling">
+ <!-- Used to identify how to draw the cling bg -->
+ <attr name="drawIdentifier" format="string" />
+ </declare-styleable>
+
+ <!-- Page Indicator specific attributes. These attributes are used to customize
+ the cling in XML files. -->
+ <declare-styleable name="PageIndicator">
+ <!-- Used to identify how to draw the cling bg -->
+ <attr name="windowSize" format="integer" />
+ </declare-styleable>
+
+ <!-- Workspace specific attributes. These attributes are used to customize
+ the workspace in XML files. -->
+ <declare-styleable name="Workspace">
+ <!-- The first screen the workspace should display. -->
+ <attr name="defaultScreen" format="integer" />
+ <!-- The number of horizontal cells in the CellLayout -->
+ <attr name="cellCountX" format="integer" />
+ <!-- The number of vertical cells in the CellLayout -->
+ <attr name="cellCountY" format="integer" />
+ </declare-styleable>
+
+ <!-- Hotseat specific attributes. These attributes are used to customize
+ the hotseat in XML files. -->
+ <declare-styleable name="Hotseat">
+ <!-- The number of horizontal cells in the CellLayout -->
+ <attr name="cellCountX" />
+ <!-- The number of vertical cells in the CellLayout -->
+ <attr name="cellCountY" />
+ </declare-styleable>
+
+ <!-- CellLayout specific attributes. These attributes are used to customize
+ a CellLayout view in XML files. -->
+ <declare-styleable name="CellLayout">
+ <!-- The width of a single cell -->
+ <attr name="cellWidth" format="dimension" />
+ <!-- The height of a single cell -->
+ <attr name="cellHeight" format="dimension" />
+ <!-- An override for the width and height gap to allow users to specify
+ a specific size for the page using spacing instead of resolving the
+ spacing from the width of the page -->
+ <attr name="widthGap" format="dimension" />
+ <attr name="heightGap" format="dimension" />
+ <!-- The max gap size for each dimension -->
+ <attr name="maxGap" format="dimension" />
+ </declare-styleable>
+
+ <!-- StrokedTextView specific attributes. -->
+ <declare-styleable name="StrokedTextView">
+ <!-- The color of the stroke outline -->
+ <attr name="strokeColor" format="color" />
+ <!-- The color of the text -->
+ <attr name="strokeTextColor" format="color" />
+ <!-- The width of the stroke -->
+ <attr name="strokeWidth" format="float" />
+ </declare-styleable>
+
+ <!-- HolographicLinearLayout specific attributes. -->
+ <declare-styleable name="HolographicLinearLayout">
+ <!-- The source view to generate and apply the drawable states to/from -->
+ <attr name="sourceImageViewId" format="integer" />
+ <attr name="stateHotwordOn" format="boolean" />
+ </declare-styleable>
+
+ <!-- PagedView specific attributes. These attributes are used to customize
+ a PagedView view in XML files. -->
+ <declare-styleable name="PagedView">
+ <!-- A spacing override for the icons within a page -->
+ <attr name="pageLayoutWidthGap" format="dimension" />
+ <attr name="pageLayoutHeightGap" format="dimension" />
+ <!-- The padding of the pages that are dynamically created per page -->
+ <attr name="pageLayoutPaddingTop" format="dimension" />
+ <attr name="pageLayoutPaddingBottom" format="dimension" />
+ <attr name="pageLayoutPaddingLeft" format="dimension" />
+ <attr name="pageLayoutPaddingRight" format="dimension" />
+ <!-- The space between adjacent pages of the PagedView. -->
+ <attr name="pageSpacing" format="dimension" />
+ <!-- The page indicator for this workspace -->
+ <attr name="pageIndicator" format="reference" />
+ </declare-styleable>
+
+ <!-- AppsCustomizePagedView specific attributes. These attributes are used to
+ customize an AppsCustomizePagedView in xml files. -->
+ <declare-styleable name="AppsCustomizePagedView">
+ <!-- Max number of cells of applications horizontally -->
+ <attr name="maxAppCellCountX" format="integer" />
+ <!-- Max number of cells of applications vertically -->
+ <attr name="maxAppCellCountY" format="integer" />
+ <!-- Horizontal spacing between widgets and wallpapers -->
+ <attr name="widgetCellWidthGap" format="dimension" />
+ <!-- Vertical spacing between widgets -->
+ <attr name="widgetCellHeightGap" format="dimension" />
+ <!-- Number of widgets horizontally -->
+ <attr name="widgetCountX" format="integer" />
+ <!-- Number of widgets vertically -->
+ <attr name="widgetCountY" format="integer" />
+ <!-- The x index of the item to be focused in the cling -->
+ <attr name="clingFocusedX" format="integer" />
+ <!-- The y index of the item to be focused in the cling -->
+ <attr name="clingFocusedY" format="integer" />
+ </declare-styleable>
+
+ <!-- XML attributes used by default_workspace.xml -->
+ <declare-styleable name="Favorite">
+ <attr name="className" format="string" />
+ <attr name="packageName" format="string" />
+ <attr name="container" format="string" />
+ <attr name="screen" format="string" />
+ <attr name="x" format="string" />
+ <attr name="y" format="string" />
+ <attr name="spanX" format="string" />
+ <attr name="spanY" format="string" />
+ <attr name="icon" format="reference" />
+ <attr name="title" format="reference" />
+ <attr name="uri" format="string" />
+ </declare-styleable>
+ <declare-styleable name="Extra">
+ <attr name="key" format="string" />
+ <attr name="value" format="string" />
+ </declare-styleable>
+ <declare-styleable name="Include">
+ <attr name="workspace" format="reference" />
+ </declare-styleable>
+
+ <!-- Only used in the device overlays -->
+ <declare-styleable name="CustomClingTitleText">
+ </declare-styleable>
+ <declare-styleable name="CustomClingText">
+ </declare-styleable>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..dc35a3f
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/colors.xml
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <!-- The color tints to apply to the text and drag view when hovering
+ over the delete target or the info target -->
+ <color name="delete_target_hover_tint">#DAFF0000</color>
+ <color name="info_target_hover_tint">#DA0099CC</color>
+
+ <color name="bubble_dark_background">#20000000</color>
+
+ <color name="appwidget_error_color">#FCCC</color>
+
+ <color name="workspace_all_apps_and_delete_zone_text_color">#CCFFFFFF</color>
+ <color name="workspace_all_apps_and_delete_zone_text_shadow_color">#A0000000</color>
+ <color name="workspace_icon_text_color">#FFF</color>
+
+ <color name="apps_customize_icon_text_color">#FFF</color>
+ <color name="wallpaper_picker_translucent_gray">#66000000</color>
+ <color name="folder_items_text_color">#FF333333</color>
+ <color name="outline_color">#FFFFFFFF</color>
+
+ <color name="first_run_cling_circle_background_color">#64b1ea</color>
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..4978281
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,91 @@
+<resources>
+ <bool name="config_largeHeap">false</bool>
+ <bool name="is_tablet">false</bool>
+ <bool name="is_large_tablet">false</bool>
+ <bool name="allow_rotation">false</bool>
+
+ <!-- Max number of page indicators to show -->
+ <integer name="config_maxNumberOfPageIndicatorsToShow">21</integer>
+
+<!-- DragController -->
+ <integer name="config_flingToDeleteMinVelocity">-1500</integer>
+
+<!-- AllApps/Customize/AppsCustomize -->
+ <!-- The alpha of the AppsCustomize bg in spring loaded mode -->
+ <integer name="config_appsCustomizeSpringLoadedBgAlpha">65</integer>
+ <integer name="config_workspaceUnshrinkTime">300</integer>
+ <integer name="config_overviewTransitionTime">250</integer>
+
+ <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
+ <integer name="config_workspaceSpringLoadShrinkPercentage">80</integer>
+ <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
+ <integer name="config_workspaceOverviewShrinkPercentage">58</integer>
+
+ <!-- Fade/zoom in/out duration & scale in the AllApps transition.
+ Note: This should be less than the workspaceShrinkTime as they happen together. -->
+ <integer name="config_appsCustomizeZoomInTime">350</integer>
+ <integer name="config_appsCustomizeZoomOutTime">600</integer>
+ <integer name="config_appsCustomizeZoomScaleFactor">7</integer>
+ <integer name="config_appsCustomizeFadeInTime">250</integer>
+ <integer name="config_appsCustomizeFadeOutTime">200</integer>
+ <integer name="config_appsCustomizeWorkspaceShrinkTime">300</integer>
+ <integer name="config_appsCustomizeWorkspaceAnimationStagger">40</integer>
+ <integer name="config_workspaceAppsCustomizeAnimationStagger">100</integer>
+
+ <integer name="config_workspaceDefaultScreen">0</integer>
+
+ <!-- Tab transition animation duration -->
+ <integer name="config_tabTransitionDuration">250</integer>
+
+ <!-- The slope, in percent, of the drag movement needed to drag an item out of
+ AppsCustomize (y / x * 100%) -->
+ <integer name="config_appsCustomizeDragSlopeThreshold">150</integer>
+
+<!-- Workspace -->
+ <!-- Whether or not the drop targets drop down as opposed to fade in -->
+ <bool name="config_useDropTargetDownTransition">false</bool>
+ <!-- Whether or not to fade the side pages -->
+ <bool name="config_workspaceFadeAdjacentScreens">false</bool>
+
+ <!-- The transition duration for the background of the drop targets -->
+ <integer name="config_dropTargetBgTransitionDuration">0</integer>
+
+ <!-- The duration (in ms) of the fade animation on the object outlines, used when
+ we are dragging objects around on the home screen. -->
+ <integer name="config_dragOutlineFadeTime">900</integer>
+
+ <!-- The alpha value at which to show the most recent drop visualization outline. -->
+ <integer name="config_dragOutlineMaxAlpha">128</integer>
+
+ <!-- Parameters controlling the animation for when an item is dropped on the home screen,
+ and it animates from its old position to the new one. -->
+ <integer name="config_dropAnimMinDuration">100</integer>
+ <integer name="config_dropAnimMaxDuration">500</integer>
+
+ <!-- The duration of the UserFolder opening and closing animation -->
+ <integer name="config_folderAnimDuration">120</integer>
+
+ <!-- The distance at which the animation should take the max duration -->
+ <integer name="config_dropAnimMaxDist">800</integer>
+
+ <!-- Properties controlling the workspace fade-out during dragging -->
+ <integer name="config_dragFadeOutAlpha">80</integer>
+ <integer name="config_dragFadeOutDuration">250</integer>
+
+ <!-- Camera distance for the overscroll effect -->
+ <integer name="config_cameraDistance">8000</integer>
+
+ <!-- Whether or not to use custom clings if a custom workspace layout is passed in -->
+ <bool name="config_useCustomClings">false</bool>
+
+<!-- Hotseat -->
+ <bool name="hotseat_transpose_layout_with_orientation">true</bool>
+
+ <!-- Memory debugging, including a memory dump icon -->
+ <bool name="debug_memory_enabled">false</bool>
+
+ <!-- Name of a subclass of com.android.launcher3.AppFilter used to
+ filter the activities shown in the launcher. Can be empty. -->
+ <string name="app_filter_class" translatable="false"></string>
+
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..b4b2367
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+<!-- Dynamic Grid -->
+ <dimen name="dynamic_grid_edge_margin">6dp</dimen>
+ <dimen name="dynamic_grid_search_bar_max_width">500dp</dimen>
+ <dimen name="dynamic_grid_search_bar_height">48dp</dimen>
+ <dimen name="dynamic_grid_page_indicator_height">24dp</dimen>
+
+<!-- Wallpaper picker -->
+ <dimen name="wallpaperThumbnailWidth">106.5dp</dimen>
+ <dimen name="wallpaperThumbnailHeight">94.5dp</dimen>
+ <dimen name="wallpaperItemIconSize">32dp</dimen>
+
+<!-- Cling -->
+ <dimen name="clingPunchThroughGraphicCenterRadius">94dp</dimen>
+ <dimen name="folderClingMarginTop">20dp</dimen>
+ <!-- The offset for the text in the cling -->
+ <dimen name="cling_text_block_offset_x">0dp</dimen>
+ <dimen name="cling_text_block_offset_y">0dp</dimen>
+ <!-- entries for custom clings, will be set in overlays -->
+ <add-resource type="dimen" name="custom_cling_margin_top" />
+ <add-resource type="dimen" name="custom_cling_margin_right" />
+ <add-resource type="dimen" name="custom_cling_margin_left" />
+
+<!-- Workspace -->
+ <dimen name="workspace_max_gap">16dp</dimen>
+ <dimen name="workspace_overscroll_drawable_padding">0dp</dimen>
+ <dimen name="workspace_spring_loaded_page_spacing">15dp</dimen>
+ <dimen name="overview_panel_bottom_padding">50dp</dimen>
+ <dimen name="overview_panel_buttonSpacing">60dp</dimen>
+ <dimen name="overview_mode_page_offset">130dp</dimen>
+
+<!-- QSB -->
+ <dimen name="toolbar_button_vertical_padding">4dip</dimen>
+ <dimen name="toolbar_button_horizontal_padding">12dip</dimen>
+ <!-- External toolbar icon size (for bounds) -->
+ <dimen name="toolbar_external_icon_width">36dp</dimen>
+ <dimen name="toolbar_external_icon_height">36dp</dimen>
+
+<!-- AllApps/Customize/AppsCustomize -->
+ <!-- The height of the tab bar - if this changes, we should update the
+ external icon width/height above to compensate -->
+ <dimen name="apps_customize_tab_bar_height">52dp</dimen>
+ <dimen name="apps_customize_tab_bar_margin_top">0dp</dimen>
+ <dimen name="app_icon_size">48dp</dimen>
+
+ <!-- The AppsCustomize page indicator -->
+ <dimen name="apps_customize_page_indicator_height">12dp</dimen>
+ <dimen name="apps_customize_page_indicator_margin">4dp</dimen>
+ <dimen name="apps_customize_page_indicator_offset">16dp</dimen>
+
+ <!-- Drag padding to add to the bottom of drop targets -->
+ <dimen name="drop_target_drag_padding">14dp</dimen>
+
+<!-- Dragging -->
+ <!-- the area at the edge of the screen that makes the workspace go left
+ or right while you're dragging. -->
+ <dimen name="scroll_zone">20dp</dimen>
+
+ <!-- When dragging items on the workspace, the number of dps by which the position of
+ the drag view should be offset from the position of the original view. -->
+ <dimen name="dragViewOffsetX">0dp</dimen>
+ <dimen name="dragViewOffsetY">0dp</dimen>
+ <!-- When dragging an item, how much bigger (fixed dps) the dragged view
+ should be. If 0, it will not be scaled at all. -->
+ <dimen name="dragViewScale">12dp</dimen>
+
+ <!-- Padding applied to AppWidget previews -->
+ <dimen name="app_widget_preview_padding_left">16dp</dimen>
+ <dimen name="app_widget_preview_padding_right">16dp</dimen>
+ <dimen name="app_widget_preview_padding_top">32dp</dimen>
+ <dimen name="app_widget_preview_label_margin_top">4dp</dimen>
+ <dimen name="app_widget_preview_label_margin_left">2dp</dimen>
+ <dimen name="app_widget_preview_label_margin_right">2dp</dimen>
+
+ <!-- Padding applied to shortcut previews -->
+ <dimen name="shortcut_preview_padding_left">0dp</dimen>
+ <dimen name="shortcut_preview_padding_right">0dp</dimen>
+ <dimen name="shortcut_preview_padding_top">0dp</dimen>
+
+<!-- Folders -->
+ <!-- The amount that the preview contents are inset from the preview background -->
+ <dimen name="folder_preview_padding">4dp</dimen>
+ <dimen name="folder_name_padding">10dp</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..cafa424
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- General -->
+ <skip />
+ <!-- Application name -->
+ <string name="application_name">Launcher3</string>
+ <!-- Accessibility-facing application name -->
+ <string name="home">Home</string>
+ <!-- Name for all applications running as this uid. -->
+ <string name="uid_name">Android Core Apps</string>
+ <!-- Default folder name -->
+ <string name="folder_name"></string>
+ <!-- Button label on Wallpaper picker screen; user selects this button to set a specific wallpaper -->
+ <string name="wallpaper_instructions">Set wallpaper</string>
+ <!-- Shown when wallpapers are selected in Wallpaper picker -->
+ <!-- String indicating how many media item(s) is(are) selected
+ eg. 1 selected [CHAR LIMIT=30] -->
+ <plurals name="number_of_items_selected">
+ <item quantity="zero">%1$d selected</item>
+ <item quantity="one">%1$d selected</item>
+ <item quantity="other">%1$d selected</item>
+ </plurals>
+ <!-- Accessibility string used as a label for a particular wallpaper in the Wallpaper Picker list.
+ e.g. "Wallpaper 3 of 10" -->
+ <string name="wallpaper_accessibility_name">Wallpaper %1$d of %2$d</string>
+ <!-- Accessibility string used to announce that a wallpaper has been selected. -->
+ <string name="announce_selection">Selected <xliff:g id="label" example="Wallpaper 3 of 10">%1$s</xliff:g></string>
+
+ <!-- Label on button to delete wallpaper(s) -->
+ <string name="wallpaper_delete">Delete</string>
+ <!-- Label on button in Wallpaper Picker to pick an image -->
+ <string name="pick_image">Pick image</string>
+ <!-- Option in "Select wallpaper from" dialog box -->
+ <string name="pick_wallpaper">Wallpapers</string>
+ <!-- Title of activity for cropping wallpapers -->
+ <string name="crop_wallpaper">Crop wallpaper</string>
+ <!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]-->
+ <string name="activity_not_found">App isn\'t installed.</string>
+ <!-- Labels for the tabs in the customize drawer -->
+ <string name="widgets_tab_label">Widgets</string>
+
+ <string name="widget_adder">Widgets</string>
+
+ <string name="toggle_weight_watcher">Show Mem</string>
+
+ <!-- AppsCustomize pane -->
+ <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
+ <string name="long_press_widget_to_add">Touch & hold to pick up a widget.</string>
+ <!-- Market button text. The market button text is removed in Launcher.java
+ in the Phone UI. [CHAR LIMIT=32] -->
+ <string name="market">Shop</string>
+ <!-- The format string for the dimensions of a widget in the drawer -->
+ <!-- There is a special version of this format string for Farsi -->
+ <string name="widget_dims_format">%1$d \u00d7 %2$d</string>
+
+ <!-- External-drop widget pick label format string [CHAR_LIMIT=25] -->
+ <string name="external_drop_widget_pick_format" translatable="false">%1$s (%2$d \u00d7 %3$d)</string>
+ <!-- External-drop widget error string. This is the error that is shown
+ when you drag and item into the homescreen and it is unable to fit,
+ or an error is encountered. [CHAR_LIMIT=50] -->
+ <string name="external_drop_widget_error">Couldn\'t drop item on this Home screen.</string>
+ <!-- External-drop widget pick title. This is shown as the title of the
+ dialog which allows you to pick which widgets to handle a particular
+ drop if there are multiple choices. [CHAR_LIMIT=35] -->
+ <string name="external_drop_widget_pick_title">Choose widget to create</string>
+
+ <!-- Folders -->
+ <skip />
+ <!-- Label of Folder name field in Rename folder dialog box -->
+ <string name="rename_folder_label">Folder name</string>
+ <!-- Title of dialog box -->
+ <string name="rename_folder_title">Rename folder</string>
+ <!-- Buttons in Rename folder dialog box -->
+ <string name="rename_action">OK</string>
+ <!-- Buttons in Rename folder dialog box -->
+ <string name="cancel_action">Cancel</string>
+
+ <!-- Shortcuts -->
+ <skip />
+ <!-- Title of dialog box -->
+ <string name="menu_item_add_item">Add to Home screen</string>
+ <!-- Options in "Add to Home" dialog box; Title of the group containing the list of all apps -->
+ <string name="group_applications">Apps</string>
+ <!-- Options in "Add to Home" dialog box; Title of the group containing the list of all shortcut
+s -->
+ <string name="group_shortcuts">Shortcuts</string>
+ <!-- Options in "Add to Home" dialog box; Title of the group containing the list of all widgets/gadgets -->
+ <string name="group_widgets">Widgets</string>
+ <!-- Error message when user has filled all their home screens -->
+ <string name="completely_out_of_space">No more room on your Home screens.</string>
+ <!-- Error message when user has filled a home screen -->
+ <string name="out_of_space">No more room on this Home screen.</string>
+ <!-- Error message when user has filled the hotseat -->
+ <string name="hotseat_out_of_space">No more room on the hotseat.</string>
+ <!-- Error message when user tries to drop an invalid item on the hotseat -->
+ <string name="invalid_hotseat_item">This widget is too large for the hotseat.</string>
+ <!-- Message displayed when a shortcut is created by an external application -->
+ <string name="shortcut_installed">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" created.</string>
+ <!-- Message displayed when a shortcut is uninstalled by an external application -->
+ <string name="shortcut_uninstalled">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" was removed.</string>
+ <!-- Message displayed when an external application attemps to create a shortcut that already exists -->
+ <string name="shortcut_duplicate">Shortcut \"<xliff:g id="name" example="Browser">%s</xliff:g>\" already exists.</string>
+
+ <!-- Title of dialog when user is selecting shortcut to add to homescreen -->
+ <string name="title_select_shortcut">Choose shortcut</string>
+ <!-- Title of dialog when user is selecting an application to add to homescreen -->
+ <string name="title_select_application">Choose app</string>
+
+ <!-- All applications label -->
+ <string name="all_apps_button_label">Apps</string>
+ <!-- Label for button in all applications label to go back home (to the workspace / desktop)
+ for accessibilty (spoken when the button gets focus). -->
+ <string name="all_apps_home_button_label">Home</string>
+ <!-- Label for trash icon on workspace. Meant to communicate the idea of removing the
+ icon/widget from the home screen, but not permanently. [CHAR_LIMIT=30] -->
+ <string name="delete_zone_label_workspace">Remove</string>
+ <!-- Label for trash icon in All Apps. The icon/widget will become completely unavailable on the
+ device. [CHAR_LIMIT=30]-->
+ <string name="delete_zone_label_all_apps">Uninstall</string>
+
+ <!-- Label for delete drop target. [CHAR_LIMIT=30] -->
+ <string name="delete_target_label">Remove</string>
+ <!-- Label for uninstall drop target. [CHAR_LIMIT=30]-->
+ <string name="delete_target_uninstall_label">Uninstall</string>
+ <!-- Label for the info icon. [CHAR_LIMIT=30] -->
+ <string name="info_target_label">App info</string>
+
+ <!-- Accessibility: Search button -->
+ <string name="accessibility_search_button">Search</string>
+ <!-- Accessibility: Voice Search button -->
+ <string name="accessibility_voice_search_button">Voice Search</string>
+ <!-- Accessibility: AllApps button -->
+ <string name="accessibility_all_apps_button">Apps</string>
+ <!-- Accessibility: Delete button -->
+ <string name="accessibility_delete_button">Remove</string>
+
+ <!-- Label for trash icon in All Apps, when an updated system app is selected. The update will
+ be uninstalled. [CHAR_LIMIT=30] -->
+ <string name="delete_zone_label_all_apps_system_app">Uninstall update</string>
+
+ <!-- URL pointing to help text. If empty, no link to help will be created [DO NOT TRANSLATE] -->
+ <string name="help_url" translatable="false"></string>
+
+ <!-- Strings for the contextual action bar (CAB) in All Apps -->
+ <skip />
+ <!-- Describes the button for uninstalling the currently selected application.
+ Text is not displayed, but provided for accessibility. [CHAR_LIMIT=none] -->
+ <string name="cab_menu_delete_app">Uninstall app</string>
+ <!-- Describes the button for getting details/info about currently selected application.
+ Text is not displayed, but provided for accessibility. [CHAR_LIMIT=none] -->
+ <string name="cab_menu_app_info">App details</string>
+ <!-- Appears in the CAB when an app is selected in All Apps or Customize mode. [CHAR_LIMIT=50] -->
+ <string name="cab_app_selection_text">1 app selected</string>
+ <!-- Appears in the CAB when a widget is selected in Customize mode. [CHAR_LIMIT=50] -->
+ <string name="cab_widget_selection_text">1 widget selected</string>
+ <!-- Appears in the CAB when a folder is selected in Customize mode. [CHAR_LIMIT=50] -->
+ <string name="cab_folder_selection_text">1 folder selected</string>
+ <!-- Appears in the CAB when a shortcut is selected in Customize mode. [CHAR_LIMIT=50] -->
+ <string name="cab_shortcut_selection_text">1 shortcut selected</string>
+
+ <!-- Permissions: -->
+ <skip />
+ <!-- Permission short label -->
+ <string name="permlab_install_shortcut">install shortcuts</string>
+ <!-- Permission description -->
+ <string name="permdesc_install_shortcut">Allows an app to add
+ shortcuts without user intervention.</string>
+ <!-- Permission short label -->
+ <string name="permlab_uninstall_shortcut">uninstall shortcuts</string>
+ <!-- Permission description -->
+ <string name="permdesc_uninstall_shortcut">Allows the app to remove
+ shortcuts without user intervention.</string>
+ <!-- Permission short label -->
+ <string name="permlab_read_settings">read Home settings and shortcuts</string>
+ <!-- Permission description -->
+ <string name="permdesc_read_settings">Allows the app to read the settings and
+ shortcuts in Home.</string>
+ <!-- Permission short label -->
+ <string name="permlab_write_settings">write Home settings and shortcuts</string>
+ <!-- Permission description -->
+ <string name="permdesc_write_settings">Allows the app to change the settings and
+ shortcuts in Home.</string>
+
+ <!-- Widgets: -->
+ <skip />
+
+ <!-- Text to show user in place of a gadget when we can't display it properly -->
+ <string name="gadget_error_text">Problem loading widget</string>
+
+ <!-- Text to inform the user that they can't uninstall a system application -->
+ <string name="uninstall_system_app_text">This is a system app and can\'t be uninstalled.</string>
+
+ <!-- Title of the Android Dreams (screensaver) module -->
+ <string name="dream_name">Rocket Launcher</string>
+
+ <!-- Default folder title -->
+ <string name="folder_hint_text">Unnamed Folder</string>
+
+ <!-- Accessibility -->
+ <skip />
+
+ <!-- The format string for Workspace descriptions [CHAR_LIMIT=none] -->
+ <string name="workspace_description_format">Home screen %1$d</string>
+
+ <!-- The format string for default page scroll text [CHAR_LIMIT=none] -->
+ <string name="default_scroll_format">Page %1$d of %2$d</string>
+ <!-- The format string for Workspace page scroll text [CHAR_LIMIT=none] -->
+ <string name="workspace_scroll_format">Home screen %1$d of %2$d</string>
+ <!-- The format string for AppsCustomize Apps page scroll text [CHAR_LIMIT=none] -->
+ <string name="apps_customize_apps_scroll_format">Apps page %1$d of %2$d</string>
+ <!-- The format string for AppsCustomize Apps page scroll text [CHAR_LIMIT=none] -->
+ <string name="apps_customize_widgets_scroll_format">Widgets page %1$d of %2$d</string>
+
+ <!-- Clings -->
+ <!-- The title text for the workspace cling [CHAR_LIMIT=60] -->
+ <string name="first_run_cling_title">Welcome!</string>
+ <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+ <string name="first_run_cling_description">Make yourself at home.</string>
+ <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+ <string name="first_run_cling_custom_content_hint"></string>
+ <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+ <string name="first_run_cling_search_bar_hint"></string>
+ <!-- The description of how to use the workspace [CHAR_LIMIT=60] -->
+ <string name="first_run_cling_create_screens_hint">Create more screens for apps and folders</string>
+ <!-- The title text for the workspace cling [CHAR_LIMIT=60] -->
+ <string name="workspace_cling_title">Organize your space</string>
+ <!-- The description of how to use the workspace [CHAR_LIMIT=160] -->
+ <string name="workspace_cling_move_item">Touch & hold background to manage wallpaper, widgets and settings.</string>
+ <!-- The title text for the All Apps cling [CHAR_LIMIT=60] -->
+ <string name="all_apps_cling_title">Choose some apps</string>
+ <!-- The description of how to pick up and add an item to the workspace [CHAR_LIMIT=160] -->
+ <string name="all_apps_cling_add_item">To add an app to your Home screen, touch & hold it.</string>
+ <!-- The title text for the Folder cling [CHAR_LIMIT=60] -->
+ <string name="folder_cling_title">Here\'s a folder</string>
+ <!-- The description of how to create a folder [CHAR_LIMIT=160] -->
+ <string name="folder_cling_create_folder">To create one like this, touch & hold an app, then move it over another.</string>
+ <!-- The text on the button to dismiss a cling [CHAR_LIMIT=30] -->
+ <string name="cling_dismiss">OK</string>
+ <!-- Error message on dummy custom cling layout [DO NOT TRANSLATE] -->
+ <string name="dummy_custom_cling_error_message">Error: custom workspace layout passed in but custom cling was not overwritten</string>
+ <add-resource type="string" name="default_folder_name" />
+
+ <!-- Folder accessibility -->
+ <!-- The format string for when a folder is opened, speaks the dimensions -->
+ <string name="folder_opened">Folder opened, <xliff:g id="width" example="5">%1$d</xliff:g> by <xliff:g id="height" example="3">%2$d</xliff:g></string>
+ <!-- Instruction that clicking outside will close folder -->
+ <string name="folder_tap_to_close">Touch to close folder</string>
+ <!-- Instruction that clicking outside will commit folder rename -->
+ <string name="folder_tap_to_rename">Touch to save rename</string>
+ <!-- Indication that folder closed -->
+ <string name="folder_closed">Folder closed</string>
+ <!-- Folder renamed format -->
+ <string name="folder_renamed">Folder renamed to <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+ <!-- Folder name format -->
+ <string name="folder_name_format">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+
+ <!-- Strings used in device overlays -->
+
+ <!-- Clings -->
+ <!-- Dummy string [CHAR_LIMIT=40] -->
+ <string name="custom_workspace_cling_title_1"></string>
+ <!-- Dummy string [CHAR_LIMIT=60] -->
+ <string name="custom_workspace_cling_description_1"></string>
+ <!-- Dummy string [CHAR_LIMIT=40] -->
+ <string name="custom_workspace_cling_title_2"></string>
+ <!-- Dummy string [CHAR_LIMIT=60] -->
+ <string name="custom_workspace_cling_description_2"></string>
+
+ <!-- Debug-only activity name. [DO NOT TRANSLATE] -->
+ <string name="debug_memory_activity">* HPROF</string>
+
+ <!-- Strings for the customization mode -->
+ <!-- Text for widget add button -->
+ <string name="widget_button_text">Widgets</string>
+ <!-- Text for wallpaper change button -->
+ <string name="wallpaper_button_text">Wallpapers</string>
+ <!-- Text for settings button -->
+ <string name="settings_button_text">Settings</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..a1d2c5c
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources>
+ <style name="Theme.WallpaperCropper" parent="@android:style/Theme.Holo">
+ <item name="android:actionBarStyle">@style/WallpaperCropperActionBar</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowActionBarOverlay">true</item>
+ <item name="android:windowTranslucentNavigation">true</item>
+ </style>
+
+ <style name="WallpaperCropperActionBar" parent="android:style/Widget.Holo.ActionBar">
+ <item name="android:displayOptions">showCustom</item>
+ <item name="android:background">#88000000</item>
+ </style>
+
+ <style name="Theme" parent="@android:style/Theme.Holo.Wallpaper.NoTitleBar">
+ <item name="android:windowTranslucentStatus">true</item>
+ <item name="android:windowTranslucentNavigation">true</item>
+ </style>
+
+ <style name="ClingButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:paddingTop">15dp</item>
+ <item name="android:paddingBottom">15dp</item>
+ <item name="android:paddingStart">50dp</item>
+ <item name="android:paddingEnd">50dp</item>
+ <item name="android:text">@string/cling_dismiss</item>
+ <item name="android:textColor">#ffffff</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:background">@drawable/cling_button_bg</item>
+ </style>
+ <style name="ClingTitleText">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginBottom">5dp</item>
+ <item name="android:textSize">22sp</item>
+ <item name="android:textColor">#ffffff</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
+ </style>
+ <style name="ClingAltTitleText">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textSize">24sp</item>
+ <item name="android:textColor">#49C0EC</item>
+ </style>
+ <style name="ClingText">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">#80000000</item>
+ <item name="android:lineSpacingMultiplier">1.1</item>
+ </style>
+ <style name="ClingHintText">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:textColor">#80ffffff</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
+ </style>
+
+ <style name="WorkspaceIcon">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center_horizontal</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:ellipsize">marquee</item>
+ <item name="android:textColor">@color/workspace_icon_text_color</item>
+ <item name="android:shadowRadius">2.0</item>
+ <item name="android:shadowColor">#B0000000</item>
+ <item name="android:fontFamily">sans-serif-condensed</item>
+ </style>
+
+ <style name="WorkspaceIcon.Portrait">
+ </style>
+ <style name="WorkspaceIcon.Landscape">
+ </style>
+
+ <style name="WorkspaceIcon.AppsCustomize">
+ <item name="android:background">@null</item>
+ <item name="android:textColor">@color/apps_customize_icon_text_color</item>
+ <item name="android:drawablePadding">4dp</item>
+ <item name="android:shadowRadius">4.0</item>
+ <item name="android:shadowColor">#FF000000</item>
+ </style>
+
+ <style name="QSBBar">
+ <item name="android:orientation">horizontal</item>
+ </style>
+ <style name="SearchDropTargetBar">
+ </style>
+ <style name="SearchButton">
+ </style>
+ <style name="DropTargetButtonContainer">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">match_parent</item>
+ </style>
+ <style name="DropTargetButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:drawablePadding">7.5dp</item>
+ <item name="android:paddingStart">25dp</item>
+ <item name="android:paddingEnd">25dp</item>
+ <item name="android:textColor">#FFFFFFFF</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:shadowColor">#FF000000</item>
+ <item name="android:shadowDx">0.0</item>
+ <item name="android:shadowDy">1.0</item>
+ <item name="android:shadowRadius">4.0</item>
+ </style>
+
+ <style name="TabIndicator">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:gravity">center</item>
+ <item name="android:paddingStart">20dp</item>
+ <item name="android:paddingEnd">20dp</item>
+ <item name="android:background">@drawable/tab_widget_indicator_selector</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">12sp</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+ <style name="TabIndicator.AppsCustomize">
+ <!-- Overridden in values-land -->
+ </style>
+
+ <style name="MarketButton">
+ <item name="android:paddingStart">5dp</item>
+ <item name="android:paddingEnd">5dp</item>
+ <item name="android:textColor">@color/workspace_all_apps_and_delete_zone_text_color</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:shadowColor">@color/workspace_all_apps_and_delete_zone_text_shadow_color</item>
+ <item name="android:shadowDx">0.0</item>
+ <item name="android:shadowDy">0.0</item>
+ <item name="android:shadowRadius">2.0</item>
+ </style>
+
+ <!-- Overridden in device overlays -->
+ <style name="CustomClingTitleText">
+ </style>
+ <style name="CustomClingText">
+ </style>
+</resources>
diff --git a/res/xml-sw600dp/default_workspace.xml b/res/xml-sw600dp/default_workspace.xml
new file mode 100644
index 0000000..090c7a7
--- /dev/null
+++ b/res/xml-sw600dp/default_workspace.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Far-left screen [0] -->
+
+ <!-- Left screen [1] -->
+ <appwidget
+ launcher:packageName="com.android.settings"
+ launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
+ launcher:screen="1"
+ launcher:x="0"
+ launcher:y="3"
+ launcher:spanX="4"
+ launcher:spanY="1" />
+
+ <!-- Middle screen [2] -->
+ <appwidget
+ launcher:packageName="com.android.deskclock"
+ launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
+ launcher:screen="2"
+ launcher:x="1"
+ launcher:y="0"
+ launcher:spanX="2"
+ launcher:spanY="2" />
+ <favorite
+ launcher:packageName="com.android.camera"
+ launcher:className="com.android.camera.Camera"
+ launcher:screen="2"
+ launcher:x="0"
+ launcher:y="3" />
+
+ <!-- Right screen [3] -->
+ <favorite
+ launcher:packageName="com.android.gallery3d"
+ launcher:className="com.android.gallery3d.app.Gallery"
+ launcher:screen="3"
+ launcher:x="1"
+ launcher:y="3" />
+ <favorite
+ launcher:packageName="com.android.settings"
+ launcher:className="com.android.settings.Settings"
+ launcher:screen="3"
+ launcher:x="2"
+ launcher:y="3" />
+
+ <!-- Far-right screen [4] -->
+
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <favorite
+ launcher:packageName="com.android.dialer"
+ launcher:className="com.android.dialer.DialtactsActivity"
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.contacts"
+ launcher:className="com.android.contacts.activities.PeopleActivity"
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.mms"
+ launcher:className="com.android.mms.ui.ConversationList"
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.browser"
+ launcher:className="com.android.browser.BrowserActivity"
+ launcher:container="-101"
+ launcher:screen="5"
+ launcher:x="5"
+ launcher:y="0" />
+</favorites>
diff --git a/res/xml-sw720dp/default_workspace.xml b/res/xml-sw720dp/default_workspace.xml
new file mode 100644
index 0000000..1c1d70e
--- /dev/null
+++ b/res/xml-sw720dp/default_workspace.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Far-left screen [0] -->
+
+ <!-- Left screen [1] -->
+ <appwidget
+ launcher:packageName="com.android.settings"
+ launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
+ launcher:screen="1"
+ launcher:x="0"
+ launcher:y="3"
+ launcher:spanX="4"
+ launcher:spanY="1" />
+
+ <!-- Middle screen [2] -->
+ <appwidget
+ launcher:packageName="com.android.deskclock"
+ launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
+ launcher:screen="2"
+ launcher:x="1"
+ launcher:y="0"
+ launcher:spanX="2"
+ launcher:spanY="2" />
+ <favorite
+ launcher:packageName="com.android.camera"
+ launcher:className="com.android.camera.Camera"
+ launcher:screen="2"
+ launcher:x="0"
+ launcher:y="3" />
+
+ <!-- Right screen [3] -->
+ <favorite
+ launcher:packageName="com.android.gallery3d"
+ launcher:className="com.android.gallery3d.app.Gallery"
+ launcher:screen="3"
+ launcher:x="1"
+ launcher:y="3" />
+ <favorite
+ launcher:packageName="com.android.settings"
+ launcher:className="com.android.settings.Settings"
+ launcher:screen="3"
+ launcher:x="2"
+ launcher:y="3" />
+
+ <!-- Far-right screen [4] -->
+</favorites>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml
new file mode 100644
index 0000000..26fc504
--- /dev/null
+++ b/res/xml/default_workspace.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Far-left screen [0] -->
+
+ <!-- Left screen [1] -->
+ <appwidget
+ launcher:packageName="com.android.settings"
+ launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
+ launcher:screen="1"
+ launcher:x="0"
+ launcher:y="3"
+ launcher:spanX="4"
+ launcher:spanY="1" />
+
+ <!-- Middle screen [2] -->
+ <appwidget
+ launcher:packageName="com.android.deskclock"
+ launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
+ launcher:screen="2"
+ launcher:x="1"
+ launcher:y="0"
+ launcher:spanX="2"
+ launcher:spanY="2" />
+ <favorite
+ launcher:packageName="com.android.camera"
+ launcher:className="com.android.camera.Camera"
+ launcher:screen="2"
+ launcher:x="0"
+ launcher:y="3" />
+
+ <!-- Right screen [3] -->
+ <favorite
+ launcher:packageName="com.android.gallery3d"
+ launcher:className="com.android.gallery3d.app.Gallery"
+ launcher:screen="3"
+ launcher:x="1"
+ launcher:y="3" />
+ <favorite
+ launcher:packageName="com.android.settings"
+ launcher:className="com.android.settings.Settings"
+ launcher:screen="3"
+ launcher:x="2"
+ launcher:y="3" />
+
+ <!-- Far-right screen [4] -->
+
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <favorite
+ launcher:packageName="com.android.dialer"
+ launcher:className="com.android.dialer.DialtactsActivity"
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.contacts"
+ launcher:className="com.android.contacts.activities.PeopleActivity"
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.mms"
+ launcher:className="com.android.mms.ui.ConversationList"
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.browser"
+ launcher:className="com.android.browser.BrowserActivity"
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" />
+</favorites>
diff --git a/res/xml/update_workspace.xml b/res/xml/update_workspace.xml
new file mode 100644
index 0000000..38442b9
--- /dev/null
+++ b/res/xml/update_workspace.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+ <!-- Update the db with new hotseat items. Note that we reference the browser's original
+ package name. -->
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <favorite
+ launcher:packageName="com.android.dialer"
+ launcher:className="com.android.dialer.DialtactsActivity"
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.contacts"
+ launcher:className="com.android.contacts.activities.PeopleActivity"
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.mms"
+ launcher:className="com.android.mms.ui.ConversationList"
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0" />
+ <favorite
+ launcher:packageName="com.android.browser"
+ launcher:className="com.android.browser.BrowserActivity"
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" />
+</favorites>
diff --git a/src/android/util/Pools.java b/src/android/util/Pools.java
new file mode 100644
index 0000000..40bab1e
--- /dev/null
+++ b/src/android/util/Pools.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009 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 android.util;
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ * private static final SynchronizedPool<MyPooledClass> sPool =
+ * new SynchronizedPool<MyPooledClass>(10);
+ *
+ * public static MyPooledClass obtain() {
+ * MyPooledClass instance = sPool.acquire();
+ * return (instance != null) ? instance : new MyPooledClass();
+ * }
+ *
+ * public void recycle() {
+ * // Clear state if needed.
+ * sPool.release(this);
+ * }
+ *
+ * . . .
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+public final class Pools {
+
+ /**
+ * Interface for managing a pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static interface Pool<T> {
+
+ /**
+ * @return An instance from the pool if such, null otherwise.
+ */
+ public T acquire();
+
+ /**
+ * Release an instance to the pool.
+ *
+ * @param instance The instance to release.
+ * @return Whether the instance was put in the pool.
+ *
+ * @throws IllegalStateException If the instance is already in the pool.
+ */
+ public boolean release(T instance);
+ }
+
+ private Pools() {
+ /* do nothing - hiding constructor */
+ }
+
+ /**
+ * Simple (non-synchronized) pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static class SimplePool<T> implements Pool<T> {
+ private final Object[] mPool;
+
+ private int mPoolSize;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SimplePool(int maxPoolSize) {
+ if (maxPoolSize <= 0) {
+ throw new IllegalArgumentException("The max pool size must be > 0");
+ }
+ mPool = new Object[maxPoolSize];
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T acquire() {
+ if (mPoolSize > 0) {
+ final int lastPooledIndex = mPoolSize - 1;
+ T instance = (T) mPool[lastPooledIndex];
+ mPool[lastPooledIndex] = null;
+ mPoolSize--;
+ return instance;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean release(T instance) {
+ if (isInPool(instance)) {
+ throw new IllegalStateException("Already in the pool!");
+ }
+ if (mPoolSize < mPool.length) {
+ mPool[mPoolSize] = instance;
+ mPoolSize++;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isInPool(T instance) {
+ for (int i = 0; i < mPoolSize; i++) {
+ if (mPool[i] == instance) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Synchronized) pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static class SynchronizedPool<T> extends SimplePool<T> {
+ private final Object mLock = new Object();
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SynchronizedPool(int maxPoolSize) {
+ super(maxPoolSize);
+ }
+
+ @Override
+ public T acquire() {
+ synchronized (mLock) {
+ return super.acquire();
+ }
+ }
+
+ @Override
+ public boolean release(T element) {
+ synchronized (mLock) {
+ return super.release(element);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/common/BitmapUtils.java b/src/com/android/gallery3d/common/BitmapUtils.java
new file mode 100644
index 0000000..a671ed2
--- /dev/null
+++ b/src/com/android/gallery3d/common/BitmapUtils.java
@@ -0,0 +1,260 @@
+/*
+ * 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.gallery3d.common;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.os.Build;
+import android.util.FloatMath;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class BitmapUtils {
+ private static final String TAG = "BitmapUtils";
+ private static final int DEFAULT_JPEG_QUALITY = 90;
+ public static final int UNCONSTRAINED = -1;
+
+ private BitmapUtils(){}
+
+ /*
+ * Compute the sample size as a function of minSideLength
+ * and maxNumOfPixels.
+ * minSideLength is used to specify that minimal width or height of a
+ * bitmap.
+ * maxNumOfPixels is used to specify the maximal size in pixels that is
+ * tolerable in terms of memory usage.
+ *
+ * The function returns a sample size based on the constraints.
+ * Both size and minSideLength can be passed in as UNCONSTRAINED,
+ * which indicates no care of the corresponding constraint.
+ * The functions prefers returning a sample size that
+ * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED.
+ *
+ * Also, the function rounds up the sample size to a power of 2 or multiple
+ * of 8 because BitmapFactory only honors sample size this way.
+ * For example, BitmapFactory downsamples an image by 2 even though the
+ * request is 3. So we round up the sample size to avoid OOM.
+ */
+ public static int computeSampleSize(int width, int height,
+ int minSideLength, int maxNumOfPixels) {
+ int initialSize = computeInitialSampleSize(
+ width, height, minSideLength, maxNumOfPixels);
+
+ return initialSize <= 8
+ ? Utils.nextPowerOf2(initialSize)
+ : (initialSize + 7) / 8 * 8;
+ }
+
+ private static int computeInitialSampleSize(int w, int h,
+ int minSideLength, int maxNumOfPixels) {
+ if (maxNumOfPixels == UNCONSTRAINED
+ && minSideLength == UNCONSTRAINED) return 1;
+
+ int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
+ (int) FloatMath.ceil(FloatMath.sqrt((float) (w * h) / maxNumOfPixels));
+
+ if (minSideLength == UNCONSTRAINED) {
+ return lowerBound;
+ } else {
+ int sampleSize = Math.min(w / minSideLength, h / minSideLength);
+ return Math.max(sampleSize, lowerBound);
+ }
+ }
+
+ // This computes a sample size which makes the longer side at least
+ // minSideLength long. If that's not possible, return 1.
+ public static int computeSampleSizeLarger(int w, int h,
+ int minSideLength) {
+ int initialSize = Math.max(w / minSideLength, h / minSideLength);
+ if (initialSize <= 1) return 1;
+
+ return initialSize <= 8
+ ? Utils.prevPowerOf2(initialSize)
+ : initialSize / 8 * 8;
+ }
+
+ // Find the min x that 1 / x >= scale
+ public static int computeSampleSizeLarger(float scale) {
+ int initialSize = (int) FloatMath.floor(1f / scale);
+ if (initialSize <= 1) return 1;
+
+ return initialSize <= 8
+ ? Utils.prevPowerOf2(initialSize)
+ : initialSize / 8 * 8;
+ }
+
+ // Find the max x that 1 / x <= scale.
+ public static int computeSampleSize(float scale) {
+ Utils.assertTrue(scale > 0);
+ int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
+ return initialSize <= 8
+ ? Utils.nextPowerOf2(initialSize)
+ : (initialSize + 7) / 8 * 8;
+ }
+
+ public static Bitmap resizeBitmapByScale(
+ Bitmap bitmap, float scale, boolean recycle) {
+ int width = Math.round(bitmap.getWidth() * scale);
+ int height = Math.round(bitmap.getHeight() * scale);
+ if (width == bitmap.getWidth()
+ && height == bitmap.getHeight()) return bitmap;
+ Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
+ Canvas canvas = new Canvas(target);
+ canvas.scale(scale, scale);
+ Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
+ canvas.drawBitmap(bitmap, 0, 0, paint);
+ if (recycle) bitmap.recycle();
+ return target;
+ }
+
+ private static Bitmap.Config getConfig(Bitmap bitmap) {
+ Bitmap.Config config = bitmap.getConfig();
+ if (config == null) {
+ config = Bitmap.Config.ARGB_8888;
+ }
+ return config;
+ }
+
+ public static Bitmap resizeDownBySideLength(
+ Bitmap bitmap, int maxLength, boolean recycle) {
+ int srcWidth = bitmap.getWidth();
+ int srcHeight = bitmap.getHeight();
+ float scale = Math.min(
+ (float) maxLength / srcWidth, (float) maxLength / srcHeight);
+ if (scale >= 1.0f) return bitmap;
+ return resizeBitmapByScale(bitmap, scale, recycle);
+ }
+
+ public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ if (w == size && h == size) return bitmap;
+
+ // scale the image so that the shorter side equals to the target;
+ // the longer side will be center-cropped.
+ float scale = (float) size / Math.min(w, h);
+
+ Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
+ int width = Math.round(scale * bitmap.getWidth());
+ int height = Math.round(scale * bitmap.getHeight());
+ Canvas canvas = new Canvas(target);
+ canvas.translate((size - width) / 2f, (size - height) / 2f);
+ canvas.scale(scale, scale);
+ Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
+ canvas.drawBitmap(bitmap, 0, 0, paint);
+ if (recycle) bitmap.recycle();
+ return target;
+ }
+
+ public static void recycleSilently(Bitmap bitmap) {
+ if (bitmap == null) return;
+ try {
+ bitmap.recycle();
+ } catch (Throwable t) {
+ Log.w(TAG, "unable recycle bitmap", t);
+ }
+ }
+
+ public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
+ if (rotation == 0) return source;
+ int w = source.getWidth();
+ int h = source.getHeight();
+ Matrix m = new Matrix();
+ m.postRotate(rotation);
+ Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
+ if (recycle) source.recycle();
+ return bitmap;
+ }
+
+ public static Bitmap createVideoThumbnail(String filePath) {
+ // MediaMetadataRetriever is available on API Level 8
+ // but is hidden until API Level 10
+ Class<?> clazz = null;
+ Object instance = null;
+ try {
+ clazz = Class.forName("android.media.MediaMetadataRetriever");
+ instance = clazz.newInstance();
+
+ Method method = clazz.getMethod("setDataSource", String.class);
+ method.invoke(instance, filePath);
+
+ // The method name changes between API Level 9 and 10.
+ if (Build.VERSION.SDK_INT <= 9) {
+ return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
+ } else {
+ byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
+ if (data != null) {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+ if (bitmap != null) return bitmap;
+ }
+ return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
+ }
+ } catch (IllegalArgumentException ex) {
+ // Assume this is a corrupt video file
+ } catch (RuntimeException ex) {
+ // Assume this is a corrupt video file.
+ } catch (InstantiationException e) {
+ Log.e(TAG, "createVideoThumbnail", e);
+ } catch (InvocationTargetException e) {
+ Log.e(TAG, "createVideoThumbnail", e);
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "createVideoThumbnail", e);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "createVideoThumbnail", e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "createVideoThumbnail", e);
+ } finally {
+ try {
+ if (instance != null) {
+ clazz.getMethod("release").invoke(instance);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ return null;
+ }
+
+ public static byte[] compressToBytes(Bitmap bitmap) {
+ return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
+ }
+
+ public static byte[] compressToBytes(Bitmap bitmap, int quality) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
+ bitmap.compress(CompressFormat.JPEG, quality, baos);
+ return baos.toByteArray();
+ }
+
+ public static boolean isSupportedByRegionDecoder(String mimeType) {
+ if (mimeType == null) return false;
+ mimeType = mimeType.toLowerCase();
+ return mimeType.startsWith("image/") &&
+ (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
+ }
+
+ public static boolean isRotationSupported(String mimeType) {
+ if (mimeType == null) return false;
+ mimeType = mimeType.toLowerCase();
+ return mimeType.equals("image/jpeg");
+ }
+}
diff --git a/src/com/android/gallery3d/common/Utils.java b/src/com/android/gallery3d/common/Utils.java
new file mode 100644
index 0000000..614a081
--- /dev/null
+++ b/src/com/android/gallery3d/common/Utils.java
@@ -0,0 +1,340 @@
+/*
+ * 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.gallery3d.common;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+public class Utils {
+ private static final String TAG = "Utils";
+ private static final String DEBUG_TAG = "GalleryDebug";
+
+ private static final long POLY64REV = 0x95AC9329AC4BC9B5L;
+ private static final long INITIALCRC = 0xFFFFFFFFFFFFFFFFL;
+
+ private static long[] sCrcTable = new long[256];
+
+ private static final boolean IS_DEBUG_BUILD =
+ Build.TYPE.equals("eng") || Build.TYPE.equals("userdebug");
+
+ private static final String MASK_STRING = "********************************";
+
+ // Throws AssertionError if the input is false.
+ public static void assertTrue(boolean cond) {
+ if (!cond) {
+ throw new AssertionError();
+ }
+ }
+
+ // Throws AssertionError with the message. We had a method having the form
+ // assertTrue(boolean cond, String message, Object ... args);
+ // However a call to that method will cause memory allocation even if the
+ // condition is false (due to autoboxing generated by "Object ... args"),
+ // so we don't use that anymore.
+ public static void fail(String message, Object ... args) {
+ throw new AssertionError(
+ args.length == 0 ? message : String.format(message, args));
+ }
+
+ // Throws NullPointerException if the input is null.
+ public static <T> T checkNotNull(T object) {
+ if (object == null) throw new NullPointerException();
+ return object;
+ }
+
+ // Returns true if two input Object are both null or equal
+ // to each other.
+ public static boolean equals(Object a, Object b) {
+ return (a == b) || (a == null ? false : a.equals(b));
+ }
+
+ // Returns the next power of two.
+ // Returns the input if it is already power of 2.
+ // Throws IllegalArgumentException if the input is <= 0 or
+ // the answer overflows.
+ public static int nextPowerOf2(int n) {
+ if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n);
+ n -= 1;
+ n |= n >> 16;
+ n |= n >> 8;
+ n |= n >> 4;
+ n |= n >> 2;
+ n |= n >> 1;
+ return n + 1;
+ }
+
+ // Returns the previous power of two.
+ // Returns the input if it is already power of 2.
+ // Throws IllegalArgumentException if the input is <= 0
+ public static int prevPowerOf2(int n) {
+ if (n <= 0) throw new IllegalArgumentException();
+ return Integer.highestOneBit(n);
+ }
+
+ // Returns the input value x clamped to the range [min, max].
+ public static int clamp(int x, int min, int max) {
+ if (x > max) return max;
+ if (x < min) return min;
+ return x;
+ }
+
+ // Returns the input value x clamped to the range [min, max].
+ public static float clamp(float x, float min, float max) {
+ if (x > max) return max;
+ if (x < min) return min;
+ return x;
+ }
+
+ // Returns the input value x clamped to the range [min, max].
+ public static long clamp(long x, long min, long max) {
+ if (x > max) return max;
+ if (x < min) return min;
+ return x;
+ }
+
+ public static boolean isOpaque(int color) {
+ return color >>> 24 == 0xFF;
+ }
+
+ public static void swap(int[] array, int i, int j) {
+ int temp = array[i];
+ array[i] = array[j];
+ array[j] = temp;
+ }
+
+ /**
+ * A function thats returns a 64-bit crc for string
+ *
+ * @param in input string
+ * @return a 64-bit crc value
+ */
+ public static final long crc64Long(String in) {
+ if (in == null || in.length() == 0) {
+ return 0;
+ }
+ return crc64Long(getBytes(in));
+ }
+
+ static {
+ // http://bioinf.cs.ucl.ac.uk/downloads/crc64/crc64.c
+ long part;
+ for (int i = 0; i < 256; i++) {
+ part = i;
+ for (int j = 0; j < 8; j++) {
+ long x = ((int) part & 1) != 0 ? POLY64REV : 0;
+ part = (part >> 1) ^ x;
+ }
+ sCrcTable[i] = part;
+ }
+ }
+
+ public static final long crc64Long(byte[] buffer) {
+ long crc = INITIALCRC;
+ for (int k = 0, n = buffer.length; k < n; ++k) {
+ crc = sCrcTable[(((int) crc) ^ buffer[k]) & 0xff] ^ (crc >> 8);
+ }
+ return crc;
+ }
+
+ public static byte[] getBytes(String in) {
+ byte[] result = new byte[in.length() * 2];
+ int output = 0;
+ for (char ch : in.toCharArray()) {
+ result[output++] = (byte) (ch & 0xFF);
+ result[output++] = (byte) (ch >> 8);
+ }
+ return result;
+ }
+
+ public static void closeSilently(Closeable c) {
+ if (c == null) return;
+ try {
+ c.close();
+ } catch (IOException t) {
+ Log.w(TAG, "close fail ", t);
+ }
+ }
+
+ public static int compare(long a, long b) {
+ return a < b ? -1 : a == b ? 0 : 1;
+ }
+
+ public static int ceilLog2(float value) {
+ int i;
+ for (i = 0; i < 31; i++) {
+ if ((1 << i) >= value) break;
+ }
+ return i;
+ }
+
+ public static int floorLog2(float value) {
+ int i;
+ for (i = 0; i < 31; i++) {
+ if ((1 << i) > value) break;
+ }
+ return i - 1;
+ }
+
+ public static void closeSilently(ParcelFileDescriptor fd) {
+ try {
+ if (fd != null) fd.close();
+ } catch (Throwable t) {
+ Log.w(TAG, "fail to close", t);
+ }
+ }
+
+ public static void closeSilently(Cursor cursor) {
+ try {
+ if (cursor != null) cursor.close();
+ } catch (Throwable t) {
+ Log.w(TAG, "fail to close", t);
+ }
+ }
+
+ public static float interpolateAngle(
+ float source, float target, float progress) {
+ // interpolate the angle from source to target
+ // We make the difference in the range of [-179, 180], this is the
+ // shortest path to change source to target.
+ float diff = target - source;
+ if (diff < 0) diff += 360f;
+ if (diff > 180) diff -= 360f;
+
+ float result = source + diff * progress;
+ return result < 0 ? result + 360f : result;
+ }
+
+ public static float interpolateScale(
+ float source, float target, float progress) {
+ return source + progress * (target - source);
+ }
+
+ public static String ensureNotNull(String value) {
+ return value == null ? "" : value;
+ }
+
+ public static float parseFloatSafely(String content, float defaultValue) {
+ if (content == null) return defaultValue;
+ try {
+ return Float.parseFloat(content);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ public static int parseIntSafely(String content, int defaultValue) {
+ if (content == null) return defaultValue;
+ try {
+ return Integer.parseInt(content);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ public static boolean isNullOrEmpty(String exifMake) {
+ return TextUtils.isEmpty(exifMake);
+ }
+
+ public static void waitWithoutInterrupt(Object object) {
+ try {
+ object.wait();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "unexpected interrupt: " + object);
+ }
+ }
+
+ public static boolean handleInterrruptedException(Throwable e) {
+ // A helper to deal with the interrupt exception
+ // If an interrupt detected, we will setup the bit again.
+ if (e instanceof InterruptedIOException
+ || e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return String with special XML characters escaped.
+ */
+ public static String escapeXml(String s) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, len = s.length(); i < len; ++i) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '<': sb.append("<"); break;
+ case '>': sb.append(">"); break;
+ case '\"': sb.append("""); break;
+ case '\'': sb.append("'"); break;
+ case '&': sb.append("&"); break;
+ default: sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ public static String getUserAgent(Context context) {
+ PackageInfo packageInfo;
+ try {
+ packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("getPackageInfo failed");
+ }
+ return String.format("%s/%s; %s/%s/%s/%s; %s/%s/%s",
+ packageInfo.packageName,
+ packageInfo.versionName,
+ Build.BRAND,
+ Build.DEVICE,
+ Build.MODEL,
+ Build.ID,
+ Build.VERSION.SDK_INT,
+ Build.VERSION.RELEASE,
+ Build.VERSION.INCREMENTAL);
+ }
+
+ public static String[] copyOf(String[] source, int newSize) {
+ String[] result = new String[newSize];
+ newSize = Math.min(source.length, newSize);
+ System.arraycopy(source, 0, result, 0, newSize);
+ return result;
+ }
+
+ // Mask information for debugging only. It returns <code>info.toString()</code> directly
+ // for debugging build (i.e., 'eng' and 'userdebug') and returns a mask ("****")
+ // in release build to protect the information (e.g. for privacy issue).
+ public static String maskDebugInfo(Object info) {
+ if (info == null) return null;
+ String s = info.toString();
+ int length = Math.min(s.length(), MASK_STRING.length());
+ return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
+ }
+
+ // This method should be ONLY used for debugging.
+ public static void debug(String message, Object ... args) {
+ Log.v(DEBUG_TAG, String.format(message, args));
+ }
+}
diff --git a/src/com/android/gallery3d/exif/ByteBufferInputStream.java b/src/com/android/gallery3d/exif/ByteBufferInputStream.java
new file mode 100644
index 0000000..7fb9f22
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ByteBufferInputStream.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+class ByteBufferInputStream extends InputStream {
+
+ private ByteBuffer mBuf;
+
+ public ByteBufferInputStream(ByteBuffer buf) {
+ mBuf = buf;
+ }
+
+ @Override
+ public int read() {
+ if (!mBuf.hasRemaining()) {
+ return -1;
+ }
+ return mBuf.get() & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) {
+ if (!mBuf.hasRemaining()) {
+ return -1;
+ }
+
+ len = Math.min(len, mBuf.remaining());
+ mBuf.get(bytes, off, len);
+ return len;
+ }
+}
diff --git a/src/com/android/gallery3d/exif/CountedDataInputStream.java b/src/com/android/gallery3d/exif/CountedDataInputStream.java
new file mode 100644
index 0000000..dfd4a1a
--- /dev/null
+++ b/src/com/android/gallery3d/exif/CountedDataInputStream.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+class CountedDataInputStream extends FilterInputStream {
+
+ private int mCount = 0;
+
+ // allocate a byte buffer for a long value;
+ private final byte mByteArray[] = new byte[8];
+ private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
+
+ protected CountedDataInputStream(InputStream in) {
+ super(in);
+ }
+
+ public int getReadByteCount() {
+ return mCount;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ int r = in.read(b);
+ mCount += (r >= 0) ? r : 0;
+ return r;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int r = in.read(b, off, len);
+ mCount += (r >= 0) ? r : 0;
+ return r;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int r = in.read();
+ mCount += (r >= 0) ? 1 : 0;
+ return r;
+ }
+
+ @Override
+ public long skip(long length) throws IOException {
+ long skip = in.skip(length);
+ mCount += skip;
+ return skip;
+ }
+
+ public void skipOrThrow(long length) throws IOException {
+ if (skip(length) != length) throw new EOFException();
+ }
+
+ public void skipTo(long target) throws IOException {
+ long cur = mCount;
+ long diff = target - cur;
+ assert(diff >= 0);
+ skipOrThrow(diff);
+ }
+
+ public void readOrThrow(byte[] b, int off, int len) throws IOException {
+ int r = read(b, off, len);
+ if (r != len) throw new EOFException();
+ }
+
+ public void readOrThrow(byte[] b) throws IOException {
+ readOrThrow(b, 0, b.length);
+ }
+
+ public void setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ }
+
+ public ByteOrder getByteOrder() {
+ return mByteBuffer.order();
+ }
+
+ public short readShort() throws IOException {
+ readOrThrow(mByteArray, 0 ,2);
+ mByteBuffer.rewind();
+ return mByteBuffer.getShort();
+ }
+
+ public int readUnsignedShort() throws IOException {
+ return readShort() & 0xffff;
+ }
+
+ public int readInt() throws IOException {
+ readOrThrow(mByteArray, 0 , 4);
+ mByteBuffer.rewind();
+ return mByteBuffer.getInt();
+ }
+
+ public long readUnsignedInt() throws IOException {
+ return readInt() & 0xffffffffL;
+ }
+
+ public long readLong() throws IOException {
+ readOrThrow(mByteArray, 0 , 8);
+ mByteBuffer.rewind();
+ return mByteBuffer.getLong();
+ }
+
+ public String readString(int n) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, "UTF8");
+ }
+
+ public String readString(int n, Charset charset) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, charset);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java
new file mode 100644
index 0000000..8422382
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifData.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class stores the EXIF header in IFDs according to the JPEG
+ * specification. It is the result produced by {@link ExifReader}.
+ *
+ * @see ExifReader
+ * @see IfdData
+ */
+class ExifData {
+ private static final String TAG = "ExifData";
+ private static final byte[] USER_COMMENT_ASCII = {
+ 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
+ };
+ private static final byte[] USER_COMMENT_JIS = {
+ 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ private static final byte[] USER_COMMENT_UNICODE = {
+ 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
+ };
+
+ private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
+ private byte[] mThumbnail;
+ private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
+ private final ByteOrder mByteOrder;
+
+ ExifData(ByteOrder order) {
+ mByteOrder = order;
+ }
+
+ /**
+ * Gets the compressed thumbnail. Returns null if there is no compressed
+ * thumbnail.
+ *
+ * @see #hasCompressedThumbnail()
+ */
+ protected byte[] getCompressedThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * Sets the compressed thumbnail.
+ */
+ protected void setCompressedThumbnail(byte[] thumbnail) {
+ mThumbnail = thumbnail;
+ }
+
+ /**
+ * Returns true it this header contains a compressed thumbnail.
+ */
+ protected boolean hasCompressedThumbnail() {
+ return mThumbnail != null;
+ }
+
+ /**
+ * Adds an uncompressed strip.
+ */
+ protected void setStripBytes(int index, byte[] strip) {
+ if (index < mStripBytes.size()) {
+ mStripBytes.set(index, strip);
+ } else {
+ for (int i = mStripBytes.size(); i < index; i++) {
+ mStripBytes.add(null);
+ }
+ mStripBytes.add(strip);
+ }
+ }
+
+ /**
+ * Gets the strip count.
+ */
+ protected int getStripCount() {
+ return mStripBytes.size();
+ }
+
+ /**
+ * Gets the strip at the specified index.
+ *
+ * @exceptions #IndexOutOfBoundException
+ */
+ protected byte[] getStrip(int index) {
+ return mStripBytes.get(index);
+ }
+
+ /**
+ * Returns true if this header contains uncompressed strip.
+ */
+ protected boolean hasUncompressedStrip() {
+ return mStripBytes.size() != 0;
+ }
+
+ /**
+ * Gets the byte order.
+ */
+ protected ByteOrder getByteOrder() {
+ return mByteOrder;
+ }
+
+ /**
+ * Returns the {@link IfdData} object corresponding to a given IFD if it
+ * exists or null.
+ */
+ protected IfdData getIfdData(int ifdId) {
+ if (ExifTag.isValidIfd(ifdId)) {
+ return mIfdDatas[ifdId];
+ }
+ return null;
+ }
+
+ /**
+ * Adds IFD data. If IFD data of the same type already exists, it will be
+ * replaced by the new data.
+ */
+ protected void addIfdData(IfdData data) {
+ mIfdDatas[data.getId()] = data;
+ }
+
+ /**
+ * Returns the {@link IfdData} object corresponding to a given IFD or
+ * generates one if none exist.
+ */
+ protected IfdData getOrCreateIfdData(int ifdId) {
+ IfdData ifdData = mIfdDatas[ifdId];
+ if (ifdData == null) {
+ ifdData = new IfdData(ifdId);
+ mIfdDatas[ifdId] = ifdData;
+ }
+ return ifdData;
+ }
+
+ /**
+ * Returns the tag with a given TID in the given IFD if the tag exists.
+ * Otherwise returns null.
+ */
+ protected ExifTag getTag(short tag, int ifd) {
+ IfdData ifdData = mIfdDatas[ifd];
+ return (ifdData == null) ? null : ifdData.getTag(tag);
+ }
+
+ /**
+ * Adds the given ExifTag to its default IFD and returns an existing ExifTag
+ * with the same TID or null if none exist.
+ */
+ protected ExifTag addTag(ExifTag tag) {
+ if (tag != null) {
+ int ifd = tag.getIfd();
+ return addTag(tag, ifd);
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given ExifTag to the given IFD and returns an existing ExifTag
+ * with the same TID or null if none exist.
+ */
+ protected ExifTag addTag(ExifTag tag, int ifdId) {
+ if (tag != null && ExifTag.isValidIfd(ifdId)) {
+ IfdData ifdData = getOrCreateIfdData(ifdId);
+ return ifdData.setTag(tag);
+ }
+ return null;
+ }
+
+ protected void clearThumbnailAndStrips() {
+ mThumbnail = null;
+ mStripBytes.clear();
+ }
+
+ /**
+ * Removes the thumbnail and its related tags. IFD1 will be removed.
+ */
+ protected void removeThumbnailData() {
+ clearThumbnailAndStrips();
+ mIfdDatas[IfdId.TYPE_IFD_1] = null;
+ }
+
+ /**
+ * Removes the tag with a given TID and IFD.
+ */
+ protected void removeTag(short tagId, int ifdId) {
+ IfdData ifdData = mIfdDatas[ifdId];
+ if (ifdData == null) {
+ return;
+ }
+ ifdData.removeTag(tagId);
+ }
+
+ /**
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
+ */
+ protected String getUserComment() {
+ IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
+ if (ifdData == null) {
+ return null;
+ }
+ ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
+ if (tag == null) {
+ return null;
+ }
+ if (tag.getComponentCount() < 8) {
+ return null;
+ }
+
+ byte[] buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+
+ byte[] code = new byte[8];
+ System.arraycopy(buf, 0, code, 0, 8);
+
+ try {
+ if (Arrays.equals(code, USER_COMMENT_ASCII)) {
+ return new String(buf, 8, buf.length - 8, "US-ASCII");
+ } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
+ return new String(buf, 8, buf.length - 8, "EUC-JP");
+ } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
+ return new String(buf, 8, buf.length - 8, "UTF-16");
+ } else {
+ return null;
+ }
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG, "Failed to decode the user comment");
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of all {@link ExifTag}s in the ExifData or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTags() {
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+ for (IfdData d : mIfdDatas) {
+ if (d != null) {
+ ExifTag[] tags = d.getAllTags();
+ if (tags != null) {
+ for (ExifTag t : tags) {
+ ret.add(t);
+ }
+ }
+ }
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link ExifTag}s in a given IFD or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTagsForIfd(int ifd) {
+ IfdData d = mIfdDatas[ifd];
+ if (d == null) {
+ return null;
+ }
+ ExifTag[] tags = d.getAllTags();
+ if (tags == null) {
+ return null;
+ }
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
+ for (ExifTag t : tags) {
+ ret.add(t);
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link ExifTag}s with a given TID or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTagsForTagId(short tag) {
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+ for (IfdData d : mIfdDatas) {
+ if (d != null) {
+ ExifTag t = d.getTag(tag);
+ if (t != null) {
+ ret.add(t);
+ }
+ }
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof ExifData) {
+ ExifData data = (ExifData) obj;
+ if (data.mByteOrder != mByteOrder ||
+ data.mStripBytes.size() != mStripBytes.size() ||
+ !Arrays.equals(data.mThumbnail, mThumbnail)) {
+ return false;
+ }
+ for (int i = 0; i < mStripBytes.size(); i++) {
+ if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
+ return false;
+ }
+ }
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ IfdData ifd1 = data.getIfdData(i);
+ IfdData ifd2 = getIfdData(i);
+ if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/android/gallery3d/exif/ExifInterface.java b/src/com/android/gallery3d/exif/ExifInterface.java
new file mode 100644
index 0000000..a1cf0fc
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifInterface.java
@@ -0,0 +1,2407 @@
+/*
+ * Copyright (C) 2013 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.gallery3d.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.SparseIntArray;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel.MapMode;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file
+ * metadata. It contains a collection of ExifTags, and a collection of
+ * definitions for creating valid ExifTags. The collection of ExifTags can be
+ * updated by: reading new ones from a file, deleting or adding existing ones,
+ * or building new ExifTags from a tag definition. These ExifTags can be written
+ * to a valid jpeg image as exif metadata.
+ * <p>
+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file
+ * directory (IFD) as specified by the exif standard. A tag definition can be
+ * looked up with a constant that is a combination of TID and IFD. This
+ * definition has information about the type, number of components, and valid
+ * IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+ public static final int TAG_NULL = -1;
+ public static final int IFD_NULL = -1;
+ public static final int DEFINITION_NULL = 0;
+
+ /**
+ * Tag constants for Jeita EXIF 2.2
+ */
+
+ // IFD 0
+ public static final int TAG_IMAGE_WIDTH =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
+ public static final int TAG_IMAGE_LENGTH =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
+ public static final int TAG_BITS_PER_SAMPLE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
+ public static final int TAG_COMPRESSION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
+ public static final int TAG_PHOTOMETRIC_INTERPRETATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
+ public static final int TAG_IMAGE_DESCRIPTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
+ public static final int TAG_MAKE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
+ public static final int TAG_MODEL =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
+ public static final int TAG_STRIP_OFFSETS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
+ public static final int TAG_ORIENTATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
+ public static final int TAG_SAMPLES_PER_PIXEL =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
+ public static final int TAG_ROWS_PER_STRIP =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
+ public static final int TAG_STRIP_BYTE_COUNTS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
+ public static final int TAG_X_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
+ public static final int TAG_Y_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
+ public static final int TAG_PLANAR_CONFIGURATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
+ public static final int TAG_RESOLUTION_UNIT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
+ public static final int TAG_TRANSFER_FUNCTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
+ public static final int TAG_SOFTWARE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
+ public static final int TAG_DATE_TIME =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
+ public static final int TAG_ARTIST =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
+ public static final int TAG_WHITE_POINT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
+ public static final int TAG_PRIMARY_CHROMATICITIES =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
+ public static final int TAG_Y_CB_CR_COEFFICIENTS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
+ public static final int TAG_Y_CB_CR_SUB_SAMPLING =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
+ public static final int TAG_Y_CB_CR_POSITIONING =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
+ public static final int TAG_REFERENCE_BLACK_WHITE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
+ public static final int TAG_COPYRIGHT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
+ public static final int TAG_EXIF_IFD =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
+ public static final int TAG_GPS_IFD =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
+ // IFD 1
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT =
+ defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
+ defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
+ // IFD Exif Tags
+ public static final int TAG_EXPOSURE_TIME =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
+ public static final int TAG_F_NUMBER =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
+ public static final int TAG_EXPOSURE_PROGRAM =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
+ public static final int TAG_SPECTRAL_SENSITIVITY =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
+ public static final int TAG_ISO_SPEED_RATINGS =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
+ public static final int TAG_OECF =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
+ public static final int TAG_EXIF_VERSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
+ public static final int TAG_DATE_TIME_ORIGINAL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
+ public static final int TAG_DATE_TIME_DIGITIZED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
+ public static final int TAG_COMPONENTS_CONFIGURATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
+ public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
+ public static final int TAG_SHUTTER_SPEED_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
+ public static final int TAG_APERTURE_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
+ public static final int TAG_BRIGHTNESS_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
+ public static final int TAG_EXPOSURE_BIAS_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
+ public static final int TAG_MAX_APERTURE_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
+ public static final int TAG_SUBJECT_DISTANCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
+ public static final int TAG_METERING_MODE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
+ public static final int TAG_LIGHT_SOURCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
+ public static final int TAG_FLASH =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
+ public static final int TAG_FOCAL_LENGTH =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
+ public static final int TAG_SUBJECT_AREA =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
+ public static final int TAG_MAKER_NOTE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
+ public static final int TAG_USER_COMMENT =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
+ public static final int TAG_SUB_SEC_TIME =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
+ public static final int TAG_SUB_SEC_TIME_ORIGINAL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
+ public static final int TAG_SUB_SEC_TIME_DIGITIZED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
+ public static final int TAG_FLASHPIX_VERSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
+ public static final int TAG_COLOR_SPACE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
+ public static final int TAG_PIXEL_X_DIMENSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
+ public static final int TAG_PIXEL_Y_DIMENSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
+ public static final int TAG_RELATED_SOUND_FILE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
+ public static final int TAG_INTEROPERABILITY_IFD =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
+ public static final int TAG_FLASH_ENERGY =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
+ public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
+ public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
+ public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
+ public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
+ public static final int TAG_SUBJECT_LOCATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
+ public static final int TAG_EXPOSURE_INDEX =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
+ public static final int TAG_SENSING_METHOD =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
+ public static final int TAG_FILE_SOURCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
+ public static final int TAG_SCENE_TYPE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
+ public static final int TAG_CFA_PATTERN =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
+ public static final int TAG_CUSTOM_RENDERED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
+ public static final int TAG_EXPOSURE_MODE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
+ public static final int TAG_WHITE_BALANCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
+ public static final int TAG_DIGITAL_ZOOM_RATIO =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
+ public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
+ public static final int TAG_SCENE_CAPTURE_TYPE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
+ public static final int TAG_GAIN_CONTROL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
+ public static final int TAG_CONTRAST =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
+ public static final int TAG_SATURATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
+ public static final int TAG_SHARPNESS =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
+ public static final int TAG_DEVICE_SETTING_DESCRIPTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
+ public static final int TAG_SUBJECT_DISTANCE_RANGE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
+ public static final int TAG_IMAGE_UNIQUE_ID =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
+ // IFD GPS tags
+ public static final int TAG_GPS_VERSION_ID =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
+ public static final int TAG_GPS_LATITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
+ public static final int TAG_GPS_LATITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
+ public static final int TAG_GPS_LONGITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
+ public static final int TAG_GPS_LONGITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
+ public static final int TAG_GPS_ALTITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
+ public static final int TAG_GPS_ALTITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
+ public static final int TAG_GPS_TIME_STAMP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
+ public static final int TAG_GPS_SATTELLITES =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
+ public static final int TAG_GPS_STATUS =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
+ public static final int TAG_GPS_MEASURE_MODE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
+ public static final int TAG_GPS_DOP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
+ public static final int TAG_GPS_SPEED_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
+ public static final int TAG_GPS_SPEED =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
+ public static final int TAG_GPS_TRACK_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
+ public static final int TAG_GPS_TRACK =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
+ public static final int TAG_GPS_IMG_DIRECTION_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
+ public static final int TAG_GPS_IMG_DIRECTION =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
+ public static final int TAG_GPS_MAP_DATUM =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
+ public static final int TAG_GPS_DEST_LATITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
+ public static final int TAG_GPS_DEST_LATITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
+ public static final int TAG_GPS_DEST_LONGITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
+ public static final int TAG_GPS_DEST_LONGITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
+ public static final int TAG_GPS_DEST_BEARING_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
+ public static final int TAG_GPS_DEST_BEARING =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
+ public static final int TAG_GPS_DEST_DISTANCE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
+ public static final int TAG_GPS_DEST_DISTANCE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
+ public static final int TAG_GPS_PROCESSING_METHOD =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
+ public static final int TAG_GPS_AREA_INFORMATION =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
+ public static final int TAG_GPS_DATE_STAMP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
+ public static final int TAG_GPS_DIFFERENTIAL =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
+ // IFD Interoperability tags
+ public static final int TAG_INTEROPERABILITY_INDEX =
+ defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
+
+ /**
+ * Tags that contain offset markers. These are included in the banned
+ * defines.
+ */
+ private static HashSet<Short> sOffsetTags = new HashSet<Short>();
+ static {
+ sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
+ sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
+ }
+
+ /**
+ * Tags with definitions that cannot be overridden (banned defines).
+ */
+ protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
+ static {
+ sBannedDefines.add(getTrueTagKey(TAG_NULL));
+ sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
+ }
+
+ /**
+ * Returns the constant representing a tag with a given TID and default IFD.
+ */
+ public static int defineTag(int ifdId, short tagId) {
+ return (tagId & 0x0000ffff) | (ifdId << 16);
+ }
+
+ /**
+ * Returns the TID for a tag constant.
+ */
+ public static short getTrueTagKey(int tag) {
+ // Truncate
+ return (short) tag;
+ }
+
+ /**
+ * Returns the default IFD for a tag constant.
+ */
+ public static int getTrueIfd(int tag) {
+ return tag >>> 16;
+ }
+
+ /**
+ * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
+ * follows:
+ * <ul>
+ * <li>TOP_LEFT is the normal orientation.</li>
+ * <li>TOP_RIGHT is a left-right mirror.</li>
+ * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
+ * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
+ * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
+ * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
+ * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
+ * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
+ * </ul>
+ */
+ public static interface Orientation {
+ public static final short TOP_LEFT = 1;
+ public static final short TOP_RIGHT = 2;
+ public static final short BOTTOM_LEFT = 3;
+ public static final short BOTTOM_RIGHT = 4;
+ public static final short LEFT_TOP = 5;
+ public static final short RIGHT_TOP = 6;
+ public static final short LEFT_BOTTOM = 7;
+ public static final short RIGHT_BOTTOM = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_Y_CB_CR_POSITIONING}
+ */
+ public static interface YCbCrPositioning {
+ public static final short CENTERED = 1;
+ public static final short CO_SITED = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_COMPRESSION}
+ */
+ public static interface Compression {
+ public static final short UNCOMPRESSION = 1;
+ public static final short JPEG = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_RESOLUTION_UNIT}
+ */
+ public static interface ResolutionUnit {
+ public static final short INCHES = 2;
+ public static final short CENTIMETERS = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
+ */
+ public static interface PhotometricInterpretation {
+ public static final short RGB = 2;
+ public static final short YCBCR = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_PLANAR_CONFIGURATION}
+ */
+ public static interface PlanarConfiguration {
+ public static final short CHUNKY = 1;
+ public static final short PLANAR = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_EXPOSURE_PROGRAM}
+ */
+ public static interface ExposureProgram {
+ public static final short NOT_DEFINED = 0;
+ public static final short MANUAL = 1;
+ public static final short NORMAL_PROGRAM = 2;
+ public static final short APERTURE_PRIORITY = 3;
+ public static final short SHUTTER_PRIORITY = 4;
+ public static final short CREATIVE_PROGRAM = 5;
+ public static final short ACTION_PROGRAM = 6;
+ public static final short PROTRAIT_MODE = 7;
+ public static final short LANDSCAPE_MODE = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_METERING_MODE}
+ */
+ public static interface MeteringMode {
+ public static final short UNKNOWN = 0;
+ public static final short AVERAGE = 1;
+ public static final short CENTER_WEIGHTED_AVERAGE = 2;
+ public static final short SPOT = 3;
+ public static final short MULTISPOT = 4;
+ public static final short PATTERN = 5;
+ public static final short PARTAIL = 6;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
+ * standard, we can treat this constant as bitwise flag.
+ * <p>
+ * e.g.
+ * <p>
+ * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
+ * MODE_AUTO_MODE
+ */
+ public static interface Flash {
+ // LSB
+ public static final short DID_NOT_FIRED = 0;
+ public static final short FIRED = 1;
+ // 1st~2nd bits
+ public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
+ public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
+ public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
+ // 3rd~4th bits
+ public static final short MODE_UNKNOWN = 0 << 3;
+ public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
+ public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
+ public static final short MODE_AUTO_MODE = 3 << 3;
+ // 5th bit
+ public static final short FUNCTION_PRESENT = 0 << 5;
+ public static final short FUNCTION_NO_FUNCTION = 1 << 5;
+ // 6th bit
+ public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
+ public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
+ }
+
+ /**
+ * Constants for {@link TAG_COLOR_SPACE}
+ */
+ public static interface ColorSpace {
+ public static final short SRGB = 1;
+ public static final short UNCALIBRATED = (short) 0xFFFF;
+ }
+
+ /**
+ * Constants for {@link TAG_EXPOSURE_MODE}
+ */
+ public static interface ExposureMode {
+ public static final short AUTO_EXPOSURE = 0;
+ public static final short MANUAL_EXPOSURE = 1;
+ public static final short AUTO_BRACKET = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_WHITE_BALANCE}
+ */
+ public static interface WhiteBalance {
+ public static final short AUTO = 0;
+ public static final short MANUAL = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
+ */
+ public static interface SceneCapture {
+ public static final short STANDARD = 0;
+ public static final short LANDSCAPE = 1;
+ public static final short PROTRAIT = 2;
+ public static final short NIGHT_SCENE = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
+ */
+ public static interface ComponentsConfiguration {
+ public static final short NOT_EXIST = 0;
+ public static final short Y = 1;
+ public static final short CB = 2;
+ public static final short CR = 3;
+ public static final short R = 4;
+ public static final short G = 5;
+ public static final short B = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_LIGHT_SOURCE}
+ */
+ public static interface LightSource {
+ public static final short UNKNOWN = 0;
+ public static final short DAYLIGHT = 1;
+ public static final short FLUORESCENT = 2;
+ public static final short TUNGSTEN = 3;
+ public static final short FLASH = 4;
+ public static final short FINE_WEATHER = 9;
+ public static final short CLOUDY_WEATHER = 10;
+ public static final short SHADE = 11;
+ public static final short DAYLIGHT_FLUORESCENT = 12;
+ public static final short DAY_WHITE_FLUORESCENT = 13;
+ public static final short COOL_WHITE_FLUORESCENT = 14;
+ public static final short WHITE_FLUORESCENT = 15;
+ public static final short STANDARD_LIGHT_A = 17;
+ public static final short STANDARD_LIGHT_B = 18;
+ public static final short STANDARD_LIGHT_C = 19;
+ public static final short D55 = 20;
+ public static final short D65 = 21;
+ public static final short D75 = 22;
+ public static final short D50 = 23;
+ public static final short ISO_STUDIO_TUNGSTEN = 24;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link TAG_SENSING_METHOD}
+ */
+ public static interface SensingMethod {
+ public static final short NOT_DEFINED = 1;
+ public static final short ONE_CHIP_COLOR = 2;
+ public static final short TWO_CHIP_COLOR = 3;
+ public static final short THREE_CHIP_COLOR = 4;
+ public static final short COLOR_SEQUENTIAL_AREA = 5;
+ public static final short TRILINEAR = 7;
+ public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_FILE_SOURCE}
+ */
+ public static interface FileSource {
+ public static final short DSC = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_SCENE_TYPE}
+ */
+ public static interface SceneType {
+ public static final short DIRECT_PHOTOGRAPHED = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_GAIN_CONTROL}
+ */
+ public static interface GainControl {
+ public static final short NONE = 0;
+ public static final short LOW_UP = 1;
+ public static final short HIGH_UP = 2;
+ public static final short LOW_DOWN = 3;
+ public static final short HIGH_DOWN = 4;
+ }
+
+ /**
+ * Constants for {@link TAG_CONTRAST}
+ */
+ public static interface Contrast {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SATURATION}
+ */
+ public static interface Saturation {
+ public static final short NORMAL = 0;
+ public static final short LOW = 1;
+ public static final short HIGH = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SHARPNESS}
+ */
+ public static interface Sharpness {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SUBJECT_DISTANCE}
+ */
+ public static interface SubjectDistance {
+ public static final short UNKNOWN = 0;
+ public static final short MACRO = 1;
+ public static final short CLOSE_VIEW = 2;
+ public static final short DISTANT_VIEW = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_LATITUDE_REF},
+ * {@link TAG_GPS_DEST_LATITUDE_REF}
+ */
+ public static interface GpsLatitudeRef {
+ public static final String NORTH = "N";
+ public static final String SOUTH = "S";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_LONGITUDE_REF},
+ * {@link TAG_GPS_DEST_LONGITUDE_REF}
+ */
+ public static interface GpsLongitudeRef {
+ public static final String EAST = "E";
+ public static final String WEST = "W";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_ALTITUDE_REF}
+ */
+ public static interface GpsAltitudeRef {
+ public static final short SEA_LEVEL = 0;
+ public static final short SEA_LEVEL_NEGATIVE = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_STATUS}
+ */
+ public static interface GpsStatus {
+ public static final String IN_PROGRESS = "A";
+ public static final String INTEROPERABILITY = "V";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_MEASURE_MODE}
+ */
+ public static interface GpsMeasureMode {
+ public static final String MODE_2_DIMENSIONAL = "2";
+ public static final String MODE_3_DIMENSIONAL = "3";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_SPEED_REF},
+ * {@link TAG_GPS_DEST_DISTANCE_REF}
+ */
+ public static interface GpsSpeedRef {
+ public static final String KILOMETERS = "K";
+ public static final String MILES = "M";
+ public static final String KNOTS = "N";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_TRACK_REF},
+ * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
+ */
+ public static interface GpsTrackRef {
+ public static final String TRUE_DIRECTION = "T";
+ public static final String MAGNETIC_DIRECTION = "M";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_DIFFERENTIAL}
+ */
+ public static interface GpsDifferential {
+ public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+ public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+ }
+
+ private static final String NULL_ARGUMENT_STRING = "Argument is null";
+ private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
+ public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+ public ExifInterface() {
+ mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ /**
+ * Reads the exif tags from a byte array, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @throws IOException
+ */
+ public void readExif(byte[] jpeg) throws IOException {
+ readExif(new ByteArrayInputStream(jpeg));
+ }
+
+ /**
+ * Reads the exif tags from an InputStream, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * @param inStream an InputStream containing a jpeg compressed image.
+ * @throws IOException
+ */
+ public void readExif(InputStream inStream) throws IOException {
+ if (inStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ ExifData d = null;
+ try {
+ d = new ExifReader(this).read(inStream);
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : " + e);
+ }
+ mData = d;
+ }
+
+ /**
+ * Reads the exif tags from a file, clearing this ExifInterface object's
+ * existing exif tags.
+ *
+ * @param inFileName a string representing the filepath to jpeg file.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void readExif(String inFileName) throws FileNotFoundException, IOException {
+ if (inFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ InputStream is = null;
+ try {
+ is = (InputStream) new BufferedInputStream(new FileInputStream(inFileName));
+ readExif(is);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ }
+ is.close();
+ }
+
+ /**
+ * Sets the exif tags, clearing this ExifInterface object's existing exif
+ * tags.
+ *
+ * @param tags a collection of exif tags to set.
+ */
+ public void setExif(Collection<ExifTag> tags) {
+ clearExif();
+ setTags(tags);
+ }
+
+ /**
+ * Clears this ExifInterface object's existing exif tags.
+ */
+ public void clearExif() {
+ mData = new ExifData(DEFAULT_BYTE_ORDER);
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg image,
+ * removing prior exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @param exifOutStream an OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws IOException
+ */
+ public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
+ if (jpeg == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ s.write(jpeg, 0, jpeg.length);
+ s.flush();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg compressed
+ * bitmap, removing prior exif tags.
+ *
+ * @param bmap a bitmap to compress and write exif into.
+ * @param exifOutStream the OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws IOException
+ */
+ public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
+ if (bmap == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+ s.flush();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg stream,
+ * removing prior exif tags.
+ *
+ * @param jpegStream an InputStream containing a jpeg compressed image.
+ * @param exifOutStream an OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws IOException
+ */
+ public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
+ if (jpegStream == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ doExifStreamIO(jpegStream, s);
+ s.flush();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg image,
+ * removing prior exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
+ IOException {
+ if (jpeg == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ s.write(jpeg, 0, jpeg.length);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg compressed
+ * bitmap, removing prior exif tags.
+ *
+ * @param bmap a bitmap to compress and write exif into.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
+ IOException {
+ if (bmap == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg stream,
+ * removing prior exif tags.
+ *
+ * @param jpegStream an InputStream containing a jpeg compressed image.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(InputStream jpegStream, String exifOutFileName)
+ throws FileNotFoundException, IOException {
+ if (jpegStream == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ doExifStreamIO(jpegStream, s);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg file, removing
+ * prior exif tags.
+ *
+ * @param jpegFileName a String containing the filepath for a jpeg file.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(String jpegFileName, String exifOutFileName)
+ throws FileNotFoundException, IOException {
+ if (jpegFileName == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ InputStream is = null;
+ try {
+ is = new FileInputStream(jpegFileName);
+ writeExif(is, exifOutFileName);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ }
+ is.close();
+ }
+
+ /**
+ * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
+ * ExifInterface object will be added to a jpeg image written to this
+ * stream, removing prior exif tags. Other methods of this ExifInterface
+ * object should not be called until the returned OutputStream has been
+ * closed.
+ *
+ * @param outStream an OutputStream to wrap.
+ * @return an OutputStream that wraps the outStream parameter, and adds exif
+ * metadata. A jpeg image should be written to this stream.
+ */
+ public OutputStream getExifWriterStream(OutputStream outStream) {
+ if (outStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ ExifOutputStream eos = new ExifOutputStream(outStream, this);
+ eos.setExifData(mData);
+ return eos;
+ }
+
+ /**
+ * Returns an OutputStream object that writes to a file. Exif tags in this
+ * ExifInterface object will be added to a jpeg image written to this
+ * stream, removing prior exif tags. Other methods of this ExifInterface
+ * object should not be called until the returned OutputStream has been
+ * closed.
+ *
+ * @param exifOutFileName an String containing a filepath for a jpeg file.
+ * @return an OutputStream that writes to the exifOutFileName file, and adds
+ * exif metadata. A jpeg image should be written to this stream.
+ * @throws FileNotFoundException
+ */
+ public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
+ if (exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream out = null;
+ try {
+ out = (OutputStream) new FileOutputStream(exifOutFileName);
+ } catch (FileNotFoundException e) {
+ closeSilently(out);
+ throw e;
+ }
+ return getExifWriterStream(out);
+ }
+
+ /**
+ * Attempts to do an in-place rewrite the exif metadata in a file for the
+ * given tags. If tags do not exist or do not have the same size as the
+ * existing exif tags, this method will fail.
+ *
+ * @param filename a String containing a filepath for a jpeg file with exif
+ * tags to rewrite.
+ * @param tags tags that will be written into the jpeg file over existing
+ * tags if possible.
+ * @return true if success, false if could not overwrite. If false, no
+ * changes are made to the file.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public boolean rewriteExif(String filename, Collection<ExifTag> tags)
+ throws FileNotFoundException, IOException {
+ RandomAccessFile file = null;
+ InputStream is = null;
+ boolean ret;
+ try {
+ File temp = new File(filename);
+ is = new BufferedInputStream(new FileInputStream(temp));
+
+ // Parse beginning of APP1 in exif to find size of exif header.
+ ExifParser parser = null;
+ try {
+ parser = ExifParser.parse(is, this);
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : ", e);
+ }
+ long exifSize = parser.getOffsetToExifEndFromSOF();
+
+ // Free up resources
+ is.close();
+ is = null;
+
+ // Open file for memory mapping.
+ file = new RandomAccessFile(temp, "rw");
+ long fileLength = file.length();
+ if (fileLength < exifSize) {
+ throw new IOException("Filesize changed during operation");
+ }
+
+ // Map only exif header into memory.
+ ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
+
+ // Attempt to overwrite tag values without changing lengths (avoids
+ // file copy).
+ ret = rewriteExif(buf, tags);
+ } catch (IOException e) {
+ closeSilently(file);
+ throw e;
+ } finally {
+ closeSilently(is);
+ }
+ file.close();
+ return ret;
+ }
+
+ /**
+ * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
+ * the given tags. If tags do not exist or do not have the same size as the
+ * existing exif tags, this method will fail.
+ *
+ * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
+ * rewrite.
+ * @param tags tags that will be written into the jpeg ByteBuffer over
+ * existing tags if possible.
+ * @return true if success, false if could not overwrite. If false, no
+ * changes are made to the ByteBuffer.
+ * @throws IOException
+ */
+ public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
+ ExifModifier mod = null;
+ try {
+ mod = new ExifModifier(buf, this);
+ for (ExifTag t : tags) {
+ mod.modifyTag(t);
+ }
+ return mod.commit();
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : " + e);
+ }
+ }
+
+ /**
+ * Attempts to do an in-place rewrite of the exif metadata. If this fails,
+ * fall back to overwriting file. This preserves tags that are not being
+ * rewritten.
+ *
+ * @param filename a String containing a filepath for a jpeg file.
+ * @param tags tags that will be written into the jpeg file over existing
+ * tags if possible.
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @see #rewriteExif
+ */
+ public void forceRewriteExif(String filename, Collection<ExifTag> tags)
+ throws FileNotFoundException,
+ IOException {
+ // Attempt in-place write
+ if (!rewriteExif(filename, tags)) {
+ // Fall back to doing a copy
+ ExifData tempData = mData;
+ mData = new ExifData(DEFAULT_BYTE_ORDER);
+ FileInputStream is = null;
+ ByteArrayOutputStream bytes = null;
+ try {
+ is = new FileInputStream(filename);
+ bytes = new ByteArrayOutputStream();
+ doExifStreamIO(is, bytes);
+ byte[] imageBytes = bytes.toByteArray();
+ readExif(imageBytes);
+ setTags(tags);
+ writeExif(imageBytes, filename);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ } finally {
+ is.close();
+ // Prevent clobbering of mData
+ mData = tempData;
+ }
+ }
+ }
+
+ /**
+ * Attempts to do an in-place rewrite of the exif metadata using the tags in
+ * this ExifInterface object. If this fails, fall back to overwriting file.
+ * This preserves tags that are not being rewritten.
+ *
+ * @param filename a String containing a filepath for a jpeg file.
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @see #rewriteExif
+ */
+ public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
+ forceRewriteExif(filename, getAllTags());
+ }
+
+ /**
+ * Get the exif tags in this ExifInterface object or null if none exist.
+ *
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getAllTags() {
+ return mData.getAllTags();
+ }
+
+ /**
+ * Returns a list of ExifTags that share a TID (which can be obtained by
+ * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param tagId a TID as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getTagsForTagId(short tagId) {
+ return mData.getAllTagsForTagId(tagId);
+ }
+
+ /**
+ * Returns a list of ExifTags that share an IFD (which can be obtained by
+ * calling {@link #getTrueIFD} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param ifdId an IFD as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getTagsForIfdId(int ifdId) {
+ return mData.getAllTagsForIfd(ifdId);
+ }
+
+ /**
+ * Gets an ExifTag for an IFD other than the tag's default.
+ *
+ * @see #getTag
+ */
+ public ExifTag getTag(int tagId, int ifdId) {
+ if (!ExifTag.isValidIfd(ifdId)) {
+ return null;
+ }
+ return mData.getTag(getTrueTagKey(tagId), ifdId);
+ }
+
+ /**
+ * Returns the ExifTag in that tag's default IFD for a defined tag constant
+ * or null if none exists.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return an {@link ExifTag} or null if none exists.
+ */
+ public ExifTag getTag(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTag(tagId, ifdId);
+ }
+
+ /**
+ * Gets a tag value for an IFD other than the tag's default.
+ *
+ * @see #getTagValue
+ */
+ public Object getTagValue(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ return (t == null) ? null : t.getValue();
+ }
+
+ /**
+ * Returns the value of the ExifTag in that tag's default IFD for a defined
+ * tag constant or null if none exists or the value could not be cast into
+ * the return type.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the value of the ExifTag or null if none exists.
+ */
+ public Object getTagValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagValue(tagId, ifdId);
+ }
+
+ /*
+ * Getter methods that are similar to getTagValue. Null is returned if the
+ * tag value cannot be cast into the return type.
+ */
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsString();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagStringValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Long getTagLongValue(int tagId, int ifdId) {
+ long[] l = getTagLongValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Long(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Long getTagLongValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagLongValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue(int tagId, int ifdId) {
+ int[] l = getTagIntValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Integer(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagIntValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue(int tagId, int ifdId) {
+ byte[] l = getTagByteValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Byte(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagByteValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue(int tagId, int ifdId) {
+ Rational[] l = getTagRationalValues(tagId, ifdId);
+ if (l == null || l.length == 0) {
+ return null;
+ }
+ return new Rational(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagRationalValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public long[] getTagLongValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsLongs();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public long[] getTagLongValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagLongValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public int[] getTagIntValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsInts();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public int[] getTagIntValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagIntValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public byte[] getTagByteValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsBytes();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public byte[] getTagByteValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagByteValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsRationals();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagRationalValues(tagId, ifdId);
+ }
+
+ /**
+ * Checks whether a tag has a defined number of elements.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return true if the tag has a defined number of elements.
+ */
+ public boolean isTagCountDefined(int tagId) {
+ int info = getTagInfo().get(tagId);
+ // No value in info can be zero, as all tags have a non-zero type
+ if (info == 0) {
+ return false;
+ }
+ return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
+ }
+
+ /**
+ * Gets the defined number of elements for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
+ * tag or the number of elements is not defined.
+ */
+ public int getDefinedTagCount(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return ExifTag.SIZE_UNDEFINED;
+ }
+ return getComponentCountFromInfo(info);
+ }
+
+ /**
+ * Gets the number of elements for an ExifTag in a given IFD.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD containing the ExifTag to check.
+ * @return the number of elements in the ExifTag, if the tag's size is
+ * undefined this will return the actual number of elements that is
+ * in the ExifTag's value.
+ */
+ public int getActualTagCount(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return 0;
+ }
+ return t.getComponentCount();
+ }
+
+ /**
+ * Gets the default IFD for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
+ * definition exists.
+ */
+ public int getDefinedTagDefaultIfd(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == DEFINITION_NULL) {
+ return IFD_NULL;
+ }
+ return getTrueIfd(tagId);
+ }
+
+ /**
+ * Gets the defined type for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the type.
+ * @see ExifTag#getDataType()
+ */
+ public short getDefinedTagType(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return -1;
+ }
+ return getTypeFromInfo(info);
+ }
+
+ /**
+ * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
+ * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
+ * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
+ * <p>
+ * Note: defining tags with these TID's is disallowed.
+ *
+ * @param tag a tag's TID (can be obtained from a defined tag constant with
+ * {@link #getTrueTagKey}).
+ * @return true if the TID is that of an offset tag.
+ */
+ protected static boolean isOffsetTag(short tag) {
+ return sOffsetTags.contains(tag);
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in a given IFD if that IFD is
+ * allowed for the tag. This method will fail anytime the appropriate
+ * {@link ExifTag#setValue} for this tag's datatype would fail.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the tag should be in.
+ * @param val the value of the tag to set.
+ * @return an ExifTag object or null if one could not be constructed.
+ * @see #buildTag
+ */
+ public ExifTag buildTag(int tagId, int ifdId, Object val) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0 || val == null) {
+ return null;
+ }
+ short type = getTypeFromInfo(info);
+ int definedCount = getComponentCountFromInfo(info);
+ boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+ if (!ExifInterface.isIfdAllowed(info, ifdId)) {
+ return null;
+ }
+ ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+ if (!t.setValue(val)) {
+ return null;
+ }
+ return t;
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in the tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the tag's value.
+ * @return an ExifTag object.
+ */
+ public ExifTag buildTag(int tagId, Object val) {
+ int ifdId = getTrueIfd(tagId);
+ return buildTag(tagId, ifdId, val);
+ }
+
+ protected ExifTag buildUninitializedTag(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return null;
+ }
+ short type = getTypeFromInfo(info);
+ int definedCount = getComponentCountFromInfo(info);
+ boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+ int ifdId = getTrueIfd(tagId);
+ ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+ return t;
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists in the given IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the ExifTag is in.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ * @see #setTagValue
+ */
+ public boolean setTagValue(int tagId, int ifdId, Object val) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return false;
+ }
+ return t.setValue(val);
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists it's default IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ */
+ public boolean setTagValue(int tagId, Object val) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return setTagValue(tagId, ifdId, val);
+ }
+
+ /**
+ * Puts an ExifTag into this ExifInterface object's tags, removing a
+ * previous ExifTag with the same TID and IFD. The IFD it is put into will
+ * be the one the tag was created with in {@link #buildTag}.
+ *
+ * @param tag an ExifTag to put into this ExifInterface's tags.
+ * @return the previous ExifTag with the same TID and IFD or null if none
+ * exists.
+ */
+ public ExifTag setTag(ExifTag tag) {
+ return mData.addTag(tag);
+ }
+
+ /**
+ * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
+ * previous ExifTags with the same TID and IFDs will be removed.
+ *
+ * @param tags a Collection of ExifTags.
+ * @see #setTag
+ */
+ public void setTags(Collection<ExifTag> tags) {
+ for (ExifTag t : tags) {
+ setTag(t);
+ }
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from the given IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD of the ExifTag to remove.
+ */
+ public void deleteTag(int tagId, int ifdId) {
+ mData.removeTag(getTrueTagKey(tagId), ifdId);
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from that tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ public void deleteTag(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ deleteTag(tagId, ifdId);
+ }
+
+ /**
+ * Creates a new tag definition in this ExifInterface object for a given TID
+ * and default IFD. Creating a definition with the same TID and default IFD
+ * as a previous definition will override it.
+ *
+ * @param tagId the TID for the tag.
+ * @param defaultIfd the default IFD for the tag.
+ * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
+ * @param defaultComponentCount the number of elements of this tag's type in
+ * the tags value.
+ * @param allowedIfds the IFD's this tag is allowed to be put in.
+ * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
+ * {@link #TAG_NULL} if the definition could not be made.
+ */
+ public int setTagDefinition(short tagId, int defaultIfd, short tagType,
+ short defaultComponentCount, int[] allowedIfds) {
+ if (sBannedDefines.contains(tagId)) {
+ return TAG_NULL;
+ }
+ if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
+ int tagDef = defineTag(defaultIfd, tagId);
+ if (tagDef == TAG_NULL) {
+ return TAG_NULL;
+ }
+ int[] otherDefs = getTagDefinitionsForTagId(tagId);
+ SparseIntArray infos = getTagInfo();
+ // Make sure defaultIfd is in allowedIfds
+ boolean defaultCheck = false;
+ for (int i : allowedIfds) {
+ if (defaultIfd == i) {
+ defaultCheck = true;
+ }
+ if (!ExifTag.isValidIfd(i)) {
+ return TAG_NULL;
+ }
+ }
+ if (!defaultCheck) {
+ return TAG_NULL;
+ }
+
+ int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
+ // Make sure no identical tags can exist in allowedIfds
+ if (otherDefs != null) {
+ for (int def : otherDefs) {
+ int tagInfo = infos.get(def);
+ int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
+ if ((ifdFlags & allowedFlags) != 0) {
+ return TAG_NULL;
+ }
+ }
+ }
+ getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
+ return tagDef;
+ }
+ return TAG_NULL;
+ }
+
+ protected int getTagDefinition(short tagId, int defaultIfd) {
+ return getTagInfo().get(defineTag(defaultIfd, tagId));
+ }
+
+ protected int[] getTagDefinitionsForTagId(short tagId) {
+ int[] ifds = IfdData.getIfds();
+ int[] defs = new int[ifds.length];
+ int counter = 0;
+ SparseIntArray infos = getTagInfo();
+ for (int i : ifds) {
+ int def = defineTag(i, tagId);
+ if (infos.get(def) != DEFINITION_NULL) {
+ defs[counter++] = def;
+ }
+ }
+ if (counter == 0) {
+ return null;
+ }
+
+ return Arrays.copyOfRange(defs, 0, counter);
+ }
+
+ protected int getTagDefinitionForTag(ExifTag tag) {
+ short type = tag.getDataType();
+ int count = tag.getComponentCount();
+ int ifd = tag.getIfd();
+ return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
+ }
+
+ protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
+ int[] defs = getTagDefinitionsForTagId(tagId);
+ if (defs == null) {
+ return TAG_NULL;
+ }
+ SparseIntArray infos = getTagInfo();
+ int ret = TAG_NULL;
+ for (int i : defs) {
+ int info = infos.get(i);
+ short def_type = getTypeFromInfo(info);
+ int def_count = getComponentCountFromInfo(info);
+ int[] def_ifds = getAllowedIfdsFromInfo(info);
+ boolean valid_ifd = false;
+ for (int j : def_ifds) {
+ if (j == ifd) {
+ valid_ifd = true;
+ break;
+ }
+ }
+ if (valid_ifd && type == def_type
+ && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) {
+ ret = i;
+ break;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Removes a tag definition for given defined tag constant.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ public void removeTagDefinition(int tagId) {
+ getTagInfo().delete(tagId);
+ }
+
+ /**
+ * Resets tag definitions to the default ones.
+ */
+ public void resetTagDefinitions() {
+ mTagInfo = null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+ *
+ * @return the thumbnail as a bitmap.
+ */
+ public Bitmap getThumbnailBitmap() {
+ if (mData.hasCompressedThumbnail()) {
+ byte[] thumb = mData.getCompressedThumbnail();
+ return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
+ } else if (mData.hasUncompressedStrip()) {
+ // TODO: implement uncompressed
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
+ * The bytes may either be an uncompressed strip as specified in the exif
+ * standard or a jpeg compressed image.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ public byte[] getThumbnailBytes() {
+ if (mData.hasCompressedThumbnail()) {
+ return mData.getCompressedThumbnail();
+ } else if (mData.hasUncompressedStrip()) {
+ // TODO: implement this
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ public byte[] getThumbnail() {
+ return mData.getCompressedThumbnail();
+ }
+
+ /**
+ * Check if thumbnail is compressed.
+ *
+ * @return true if the thumbnail is compressed.
+ */
+ public boolean isThumbnailCompressed() {
+ return mData.hasCompressedThumbnail();
+ }
+
+ /**
+ * Check if thumbnail exists.
+ *
+ * @return true if a compressed thumbnail exists.
+ */
+ public boolean hasThumbnail() {
+ // TODO: add back in uncompressed strip
+ return mData.hasCompressedThumbnail();
+ }
+
+ // TODO: uncompressed thumbnail setters
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed image. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a byte array containing a jpeg compressed image.
+ * @return true if the thumbnail was set.
+ */
+ public boolean setCompressedThumbnail(byte[] thumb) {
+ mData.clearThumbnailAndStrips();
+ mData.setCompressedThumbnail(thumb);
+ return true;
+ }
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a bitmap to compress to a jpeg thumbnail.
+ * @return true if the thumbnail was set.
+ */
+ public boolean setCompressedThumbnail(Bitmap thumb) {
+ ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
+ if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
+ return false;
+ }
+ return setCompressedThumbnail(thumbnail.toByteArray());
+ }
+
+ /**
+ * Clears the compressed thumbnail if it exists.
+ */
+ public void removeCompressedThumbnail() {
+ mData.setCompressedThumbnail(null);
+ }
+
+ // Convenience methods:
+
+ /**
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
+ */
+ public String getUserComment() {
+ return mData.getUserComment();
+ }
+
+ /**
+ * Returns the Orientation ExifTag value for a given number of degrees.
+ *
+ * @param degrees the amount an image is rotated in degrees.
+ */
+ public static short getOrientationValueForRotation(int degrees) {
+ degrees %= 360;
+ if (degrees < 0) {
+ degrees += 360;
+ }
+ if (degrees < 90) {
+ return Orientation.TOP_LEFT; // 0 degrees
+ } else if (degrees < 180) {
+ return Orientation.RIGHT_TOP; // 90 degrees cw
+ } else if (degrees < 270) {
+ return Orientation.BOTTOM_LEFT; // 180 degrees
+ } else {
+ return Orientation.RIGHT_BOTTOM; // 270 degrees cw
+ }
+ }
+
+ /**
+ * Returns the rotation degrees corresponding to an ExifTag Orientation
+ * value.
+ *
+ * @param orientation the ExifTag Orientation value.
+ */
+ public static int getRotationForOrientationValue(short orientation) {
+ switch (orientation) {
+ case Orientation.TOP_LEFT:
+ return 0;
+ case Orientation.RIGHT_TOP:
+ return 90;
+ case Orientation.BOTTOM_LEFT:
+ return 180;
+ case Orientation.RIGHT_BOTTOM:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Gets the double representation of the GPS latitude or longitude
+ * coordinate.
+ *
+ * @param coordinate an array of 3 Rationals representing the degrees,
+ * minutes, and seconds of the GPS location as defined in the
+ * exif specification.
+ * @param reference a GPS reference reperesented by a String containing "N",
+ * "S", "E", or "W".
+ * @return the GPS coordinate represented as degrees + minutes/60 +
+ * seconds/3600
+ */
+ public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
+ try {
+ double degrees = coordinate[0].toDouble();
+ double minutes = coordinate[1].toDouble();
+ double seconds = coordinate[2].toDouble();
+ double result = degrees + minutes / 60.0 + seconds / 3600.0;
+ if ((reference.equals("S") || reference.equals("W"))) {
+ return -result;
+ }
+ return result;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Gets the GPS latitude and longitude as a pair of doubles from this
+ * ExifInterface object's tags, or null if the necessary tags do not exist.
+ *
+ * @return an array of 2 doubles containing the latitude, and longitude
+ * respectively.
+ * @see #convertLatOrLongToDouble
+ */
+ public double[] getLatLongAsDoubles() {
+ Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
+ String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
+ Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
+ String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
+ if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
+ || latitude.length < 3 || longitude.length < 3) {
+ return null;
+ }
+ double[] latLon = new double[2];
+ latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
+ latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
+ return latLon;
+ }
+
+ private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+ private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
+ private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
+ private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+ private final Calendar mGPSTimeStampCalendar = Calendar
+ .getInstance(TimeZone.getTimeZone("UTC"));
+
+ /**
+ * Creates, formats, and sets the DateTimeStamp tag for one of:
+ * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
+ * {@link #TAG_DATE_TIME_ORIGINAL}.
+ *
+ * @param tagId one of the DateTimeStamp tags.
+ * @param timestamp a timestamp to format.
+ * @param timezone a TimeZone object.
+ * @return true if success, false if the tag could not be set.
+ */
+ public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
+ if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
+ || tagId == TAG_DATE_TIME_ORIGINAL) {
+ mDateTimeStampFormat.setTimeZone(timezone);
+ ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Creates and sets all to the GPS tags for a give latitude and longitude.
+ *
+ * @param latitude a GPS latitude coordinate.
+ * @param longitude a GPS longitude coordinate.
+ * @return true if success, false if they could not be created or set.
+ */
+ public boolean addGpsTags(double latitude, double longitude) {
+ ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
+ ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
+ ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
+ latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH
+ : ExifInterface.GpsLatitudeRef.SOUTH);
+ ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
+ longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST
+ : ExifInterface.GpsLongitudeRef.WEST);
+ if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
+ return false;
+ }
+ setTag(latTag);
+ setTag(longTag);
+ setTag(latRefTag);
+ setTag(longRefTag);
+ return true;
+ }
+
+ /**
+ * Creates and sets the GPS timestamp tag.
+ *
+ * @param timestamp a GPS timestamp.
+ * @return true if success, false if could not be created or set.
+ */
+ public boolean addGpsDateTimeStampTag(long timestamp) {
+ ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ mGPSTimeStampCalendar.setTimeInMillis(timestamp);
+ t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
+ new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
+ new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
+ new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
+ });
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ return true;
+ }
+
+ private static Rational[] toExifLatLong(double value) {
+ // convert to the format dd/1 mm/1 ssss/100
+ value = Math.abs(value);
+ int degrees = (int) value;
+ value = (value - degrees) * 60;
+ int minutes = (int) value;
+ value = (value - minutes) * 6000;
+ int seconds = (int) value;
+ return new Rational[] {
+ new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
+ };
+ }
+
+ private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
+ byte[] buf = new byte[1024];
+ int ret = is.read(buf, 0, 1024);
+ while (ret != -1) {
+ os.write(buf, 0, ret);
+ ret = is.read(buf, 0, 1024);
+ }
+ }
+
+ protected static void closeSilently(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (Throwable e) {
+ // ignored
+ }
+ }
+ }
+
+ private SparseIntArray mTagInfo = null;
+
+ protected SparseIntArray getTagInfo() {
+ if (mTagInfo == null) {
+ mTagInfo = new SparseIntArray();
+ initTagInfo();
+ }
+ return mTagInfo;
+ }
+
+ private void initTagInfo() {
+ /**
+ * We put tag information in a 4-bytes integer. The first byte a bitmask
+ * representing the allowed IFDs of the tag, the second byte is the data
+ * type, and the last two byte are a short value indicating the default
+ * component count of this tag.
+ */
+ // IFD0 tags
+ int[] ifdAllowedIfds = {
+ IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
+ };
+ int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_MAKE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_COMPRESSION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
+ | 1);
+ mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
+ mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_MAKE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_MODEL,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SOFTWARE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ARTIST,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_IFD,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // IFD1 tags
+ int[] ifd1AllowedIfds = {
+ IfdId.TYPE_IFD_1
+ };
+ int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // Exif tags
+ int[] exifAllowedIfds = {
+ IfdId.TYPE_IFD_EXIF
+ };
+ int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_F_NUMBER,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_OECF,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_METERING_MODE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FLASH,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_CONTRAST,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SATURATION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SHARPNESS,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
+ | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // GPS tag
+ int[] gpsAllowedIfds = {
+ IfdId.TYPE_IFD_GPS
+ };
+ int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
+ gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
+ gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DOP,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
+ gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
+ gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
+ mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
+ // Interoperability tag
+ int[] interopAllowedIfds = {
+ IfdId.TYPE_IFD_INTEROPERABILITY
+ };
+ int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
+ mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
+ | ExifTag.SIZE_UNDEFINED);
+ }
+
+ protected static int getAllowedIfdFlagsFromInfo(int info) {
+ return info >>> 24;
+ }
+
+ protected static int[] getAllowedIfdsFromInfo(int info) {
+ int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+ int[] ifds = IfdData.getIfds();
+ ArrayList<Integer> l = new ArrayList<Integer>();
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ int flag = (ifdFlags >> i) & 1;
+ if (flag == 1) {
+ l.add(ifds[i]);
+ }
+ }
+ if (l.size() <= 0) {
+ return null;
+ }
+ int[] ret = new int[l.size()];
+ int j = 0;
+ for (int i : l) {
+ ret[j++] = i;
+ }
+ return ret;
+ }
+
+ protected static boolean isIfdAllowed(int info, int ifd) {
+ int[] ifds = IfdData.getIfds();
+ int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+ for (int i = 0; i < ifds.length; i++) {
+ if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
+ if (allowedIfds == null || allowedIfds.length == 0) {
+ return 0;
+ }
+ int flags = 0;
+ int[] ifds = IfdData.getIfds();
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ for (int j : allowedIfds) {
+ if (ifds[i] == j) {
+ flags |= 1 << i;
+ break;
+ }
+ }
+ }
+ return flags;
+ }
+
+ protected static short getTypeFromInfo(int info) {
+ return (short) ((info >> 16) & 0x0ff);
+ }
+
+ protected static int getComponentCountFromInfo(int info) {
+ return info & 0x0ffff;
+ }
+
+}
diff --git a/src/com/android/gallery3d/exif/ExifInvalidFormatException.java b/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
new file mode 100644
index 0000000..bf923ec
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+public class ExifInvalidFormatException extends Exception {
+ public ExifInvalidFormatException(String meg) {
+ super(meg);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/ExifModifier.java b/src/com/android/gallery3d/exif/ExifModifier.java
new file mode 100644
index 0000000..f00362b
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifModifier.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+class ExifModifier {
+ public static final String TAG = "ExifModifier";
+ public static final boolean DEBUG = false;
+ private final ByteBuffer mByteBuffer;
+ private final ExifData mTagToModified;
+ private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
+ private final ExifInterface mInterface;
+ private int mOffsetBase;
+
+ private static class TagOffset {
+ final int mOffset;
+ final ExifTag mTag;
+
+ TagOffset(ExifTag tag, int offset) {
+ mTag = tag;
+ mOffset = offset;
+ }
+ }
+
+ protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
+ ExifInvalidFormatException {
+ mByteBuffer = byteBuffer;
+ mOffsetBase = byteBuffer.position();
+ mInterface = iRef;
+ InputStream is = null;
+ try {
+ is = new ByteBufferInputStream(byteBuffer);
+ // Do not require any IFD;
+ ExifParser parser = ExifParser.parse(is, mInterface);
+ mTagToModified = new ExifData(parser.getByteOrder());
+ mOffsetBase += parser.getTiffStartPosition();
+ mByteBuffer.position(0);
+ } finally {
+ ExifInterface.closeSilently(is);
+ }
+ }
+
+ protected ByteOrder getByteOrder() {
+ return mTagToModified.getByteOrder();
+ }
+
+ protected boolean commit() throws IOException, ExifInvalidFormatException {
+ InputStream is = null;
+ try {
+ is = new ByteBufferInputStream(mByteBuffer);
+ int flag = 0;
+ IfdData[] ifdDatas = new IfdData[] {
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
+ };
+
+ if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
+ flag |= ExifParser.OPTION_IFD_0;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
+ flag |= ExifParser.OPTION_IFD_1;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
+ flag |= ExifParser.OPTION_IFD_EXIF;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
+ flag |= ExifParser.OPTION_IFD_GPS;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
+ flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
+ }
+
+ ExifParser parser = ExifParser.parse(is, flag, mInterface);
+ int event = parser.next();
+ IfdData currIfd = null;
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ currIfd = ifdDatas[parser.getCurrentIfd()];
+ if (currIfd == null) {
+ parser.skipRemainingTagsInCurrentIfd();
+ }
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ ExifTag oldTag = parser.getTag();
+ ExifTag newTag = currIfd.getTag(oldTag.getTagId());
+ if (newTag != null) {
+ if (newTag.getComponentCount() != oldTag.getComponentCount()
+ || newTag.getDataType() != oldTag.getDataType()) {
+ return false;
+ } else {
+ mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
+ currIfd.removeTag(oldTag.getTagId());
+ if (currIfd.getTagCount() == 0) {
+ parser.skipRemainingTagsInCurrentIfd();
+ }
+ }
+ }
+ break;
+ }
+ event = parser.next();
+ }
+ for (IfdData ifd : ifdDatas) {
+ if (ifd != null && ifd.getTagCount() > 0) {
+ return false;
+ }
+ }
+ modify();
+ } finally {
+ ExifInterface.closeSilently(is);
+ }
+ return true;
+ }
+
+ private void modify() {
+ mByteBuffer.order(getByteOrder());
+ for (TagOffset tagOffset : mTagOffsets) {
+ writeTagValue(tagOffset.mTag, tagOffset.mOffset);
+ }
+ }
+
+ private void writeTagValue(ExifTag tag, int offset) {
+ if (DEBUG) {
+ Log.v(TAG, "modifying tag to: \n" + tag.toString());
+ Log.v(TAG, "at offset: " + offset);
+ }
+ mByteBuffer.position(offset + mOffsetBase);
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if (buf.length == tag.getComponentCount()) {
+ buf[buf.length - 1] = 0;
+ mByteBuffer.put(buf);
+ } else {
+ mByteBuffer.put(buf);
+ mByteBuffer.put((byte) 0);
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ mByteBuffer.putInt((int) tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ Rational v = tag.getRational(i);
+ mByteBuffer.putInt((int) v.getNumerator());
+ mByteBuffer.putInt((int) v.getDenominator());
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ mByteBuffer.put(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ mByteBuffer.putShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+
+ public void modifyTag(ExifTag tag) {
+ mTagToModified.addTag(tag);
+ }
+}
diff --git a/src/com/android/gallery3d/exif/ExifOutputStream.java b/src/com/android/gallery3d/exif/ExifOutputStream.java
new file mode 100644
index 0000000..7ca05f2
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+/**
+ * This class provides a way to replace the Exif header of a JPEG image.
+ * <p>
+ * Below is an example of writing EXIF data into a file
+ *
+ * <pre>
+ * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
+ * OutputStream os = null;
+ * try {
+ * os = new FileOutputStream(path);
+ * ExifOutputStream eos = new ExifOutputStream(os);
+ * // Set the exif header
+ * eos.setExifData(exif);
+ * // Write the original jpeg out, the header will be add into the file.
+ * eos.write(jpeg);
+ * } catch (FileNotFoundException e) {
+ * e.printStackTrace();
+ * } catch (IOException e) {
+ * e.printStackTrace();
+ * } finally {
+ * if (os != null) {
+ * try {
+ * os.close();
+ * } catch (IOException e) {
+ * e.printStackTrace();
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ */
+class ExifOutputStream extends FilterOutputStream {
+ private static final String TAG = "ExifOutputStream";
+ private static final boolean DEBUG = false;
+ private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
+
+ private static final int STATE_SOI = 0;
+ private static final int STATE_FRAME_HEADER = 1;
+ private static final int STATE_JPEG_DATA = 2;
+
+ private static final int EXIF_HEADER = 0x45786966;
+ private static final short TIFF_HEADER = 0x002A;
+ private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+ private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+ private static final short TAG_SIZE = 12;
+ private static final short TIFF_HEADER_SIZE = 8;
+ private static final int MAX_EXIF_SIZE = 65535;
+
+ private ExifData mExifData;
+ private int mState = STATE_SOI;
+ private int mByteToSkip;
+ private int mByteToCopy;
+ private byte[] mSingleByteArray = new byte[1];
+ private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+ private final ExifInterface mInterface;
+
+ protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
+ super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
+ mInterface = iRef;
+ }
+
+ /**
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
+ */
+ protected void setExifData(ExifData exifData) {
+ mExifData = exifData;
+ }
+
+ /**
+ * Gets the Exif header to be written into the JPEF file.
+ */
+ protected ExifData getExifData() {
+ return mExifData;
+ }
+
+ private int requestByteToBuffer(int requestByteCount, byte[] buffer
+ , int offset, int length) {
+ int byteNeeded = requestByteCount - mBuffer.position();
+ int byteToRead = length > byteNeeded ? byteNeeded : length;
+ mBuffer.put(buffer, offset, byteToRead);
+ return byteToRead;
+ }
+
+ /**
+ * Writes the image out. The input data should be a valid JPEG format. After
+ * writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(byte[] buffer, int offset, int length) throws IOException {
+ while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+ && length > 0) {
+ if (mByteToSkip > 0) {
+ int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+ length -= byteToProcess;
+ mByteToSkip -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (mByteToCopy > 0) {
+ int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+ out.write(buffer, offset, byteToProcess);
+ length -= byteToProcess;
+ mByteToCopy -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (length == 0) {
+ return;
+ }
+ switch (mState) {
+ case STATE_SOI:
+ int byteRead = requestByteToBuffer(2, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ if (mBuffer.position() < 2) {
+ return;
+ }
+ mBuffer.rewind();
+ if (mBuffer.getShort() != JpegHeader.SOI) {
+ throw new IOException("Not a valid jpeg image, cannot write exif");
+ }
+ out.write(mBuffer.array(), 0, 2);
+ mState = STATE_FRAME_HEADER;
+ mBuffer.rewind();
+ writeExifData();
+ break;
+ case STATE_FRAME_HEADER:
+ // We ignore the APP1 segment and copy all other segments
+ // until SOF tag.
+ byteRead = requestByteToBuffer(4, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ // Check if this image data doesn't contain SOF.
+ if (mBuffer.position() == 2) {
+ short tag = mBuffer.getShort();
+ if (tag == JpegHeader.EOI) {
+ out.write(mBuffer.array(), 0, 2);
+ mBuffer.rewind();
+ }
+ }
+ if (mBuffer.position() < 4) {
+ return;
+ }
+ mBuffer.rewind();
+ short marker = mBuffer.getShort();
+ if (marker == JpegHeader.APP1) {
+ mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
+ mState = STATE_JPEG_DATA;
+ } else if (!JpegHeader.isSofMarker(marker)) {
+ out.write(mBuffer.array(), 0, 4);
+ mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
+ } else {
+ out.write(mBuffer.array(), 0, 4);
+ mState = STATE_JPEG_DATA;
+ }
+ mBuffer.rewind();
+ }
+ }
+ if (length > 0) {
+ out.write(buffer, offset, length);
+ }
+ }
+
+ /**
+ * Writes the one bytes out. The input data should be a valid JPEG format.
+ * After writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(int oneByte) throws IOException {
+ mSingleByteArray[0] = (byte) (0xff & oneByte);
+ write(mSingleByteArray);
+ }
+
+ /**
+ * Equivalent to calling write(buffer, 0, buffer.length).
+ */
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ write(buffer, 0, buffer.length);
+ }
+
+ private void writeExifData() throws IOException {
+ if (mExifData == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Writing exif data...");
+ }
+ ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
+ createRequiredIfdAndTag();
+ int exifSize = calculateAllOffset();
+ if (exifSize + 8 > MAX_EXIF_SIZE) {
+ throw new IOException("Exif header is too large (>64Kb)");
+ }
+ OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+ dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ dataOutputStream.writeShort(JpegHeader.APP1);
+ dataOutputStream.writeShort((short) (exifSize + 8));
+ dataOutputStream.writeInt(EXIF_HEADER);
+ dataOutputStream.writeShort((short) 0x0000);
+ if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+ dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+ } else {
+ dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+ }
+ dataOutputStream.setByteOrder(mExifData.getByteOrder());
+ dataOutputStream.writeShort(TIFF_HEADER);
+ dataOutputStream.writeInt(8);
+ writeAllTags(dataOutputStream);
+ writeThumbnail(dataOutputStream);
+ for (ExifTag t : nullTags) {
+ mExifData.addTag(t);
+ }
+ }
+
+ private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
+ ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
+ for(ExifTag t : data.getAllTags()) {
+ if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
+ data.removeTag(t.getTagId(), t.getIfd());
+ nullTags.add(t);
+ }
+ }
+ return nullTags;
+ }
+
+ private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
+ if (mExifData.hasCompressedThumbnail()) {
+ dataOutputStream.write(mExifData.getCompressedThumbnail());
+ } else if (mExifData.hasUncompressedStrip()) {
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ dataOutputStream.write(mExifData.getStrip(i));
+ }
+ }
+ }
+
+ private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
+ IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interoperabilityIfd != null) {
+ writeIfd(interoperabilityIfd, dataOutputStream);
+ }
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ writeIfd(gpsIfd, dataOutputStream);
+ }
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
+ }
+ }
+
+ private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ ExifTag[] tags = ifd.getAllTags();
+ dataOutputStream.writeShort((short) tags.length);
+ for (ExifTag tag : tags) {
+ dataOutputStream.writeShort(tag.getTagId());
+ dataOutputStream.writeShort(tag.getDataType());
+ dataOutputStream.writeInt(tag.getComponentCount());
+ if (DEBUG) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
+ if (tag.getDataSize() > 4) {
+ dataOutputStream.writeInt(tag.getOffset());
+ } else {
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
+ for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
+ dataOutputStream.write(0);
+ }
+ }
+ }
+ dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+ for (ExifTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
+ }
+ }
+ }
+
+ private int calculateOffsetOfIfd(IfdData ifd, int offset) {
+ offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+ ExifTag[] tags = ifd.getAllTags();
+ for (ExifTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ tag.setOffset(offset);
+ offset += tag.getDataSize();
+ }
+ }
+ return offset;
+ }
+
+ private void createRequiredIfdAndTag() throws IOException {
+ // IFD0 is required for all file
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ if (ifd0 == null) {
+ ifd0 = new IfdData(IfdId.TYPE_IFD_0);
+ mExifData.addIfdData(ifd0);
+ }
+ ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
+ if (exifOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_EXIF_IFD);
+ }
+ ifd0.setTag(exifOffsetTag);
+
+ // Exif IFD is required for all files.
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ if (exifIfd == null) {
+ exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
+ mExifData.addIfdData(exifIfd);
+ }
+
+ // GPS IFD
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
+ if (gpsOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_GPS_IFD);
+ }
+ ifd0.setTag(gpsOffsetTag);
+ }
+
+ // Interoperability IFD
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ ExifTag interOffsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
+ if (interOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_INTEROPERABILITY_IFD);
+ }
+ exifIfd.setTag(interOffsetTag);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+
+ ExifTag offsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ }
+
+ ifd1.setTag(offsetTag);
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ }
+
+ lengthTag.setValue(mExifData.getCompressedThumbnail().length);
+ ifd1.setTag(lengthTag);
+
+ // Get rid of tags for uncompressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ } else if (mExifData.hasUncompressedStrip()) {
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+ int stripCount = mExifData.getStripCount();
+ ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_OFFSETS);
+ }
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ }
+ long[] lengths = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ lengths[i] = mExifData.getStrip(i).length;
+ }
+ lengthTag.setValue(lengths);
+ ifd1.setTag(offsetTag);
+ ifd1.setTag(lengthTag);
+ // Get rid of tags for compressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ } else if (ifd1 != null) {
+ // Get rid of offset and length tags if there is no thumbnail.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ }
+ }
+
+ private int calculateAllOffset() {
+ int offset = TIFF_HEADER_SIZE;
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ offset = calculateOffsetOfIfd(ifd0, offset);
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
+
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ offset = calculateOffsetOfIfd(exifIfd, offset);
+
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+ .setValue(offset);
+ offset = calculateOffsetOfIfd(interIfd, offset);
+ }
+
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
+ offset = calculateOffsetOfIfd(gpsIfd, offset);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ ifd0.setOffsetToNextIfd(offset);
+ offset = calculateOffsetOfIfd(ifd1, offset);
+ }
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+ .setValue(offset);
+ offset += mExifData.getCompressedThumbnail().length;
+ } else if (mExifData.hasUncompressedStrip()) {
+ int stripCount = mExifData.getStripCount();
+ long[] offsets = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ offsets[i] = offset;
+ offset += mExifData.getStrip(i).length;
+ }
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
+ offsets);
+ }
+ return offset;
+ }
+
+ static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if (buf.length == tag.getComponentCount()) {
+ buf[buf.length - 1] = 0;
+ dataOutputStream.write(buf);
+ } else {
+ dataOutputStream.write(buf);
+ dataOutputStream.write(0);
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt((int) tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java
new file mode 100644
index 0000000..5467d42
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifParser.java
@@ -0,0 +1,916 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * This class provides a low-level EXIF parsing API. Given a JPEG format
+ * InputStream, the caller can request which IFD's to read via
+ * {@link #parse(InputStream, int)} with given options.
+ * <p>
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
+ * parser.
+ *
+ * <pre>
+ * void parse() {
+ * ExifParser parser = ExifParser.parse(mImageInputStream,
+ * ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
+ * int event = parser.next();
+ * while (event != ExifParser.EVENT_END) {
+ * switch (event) {
+ * case ExifParser.EVENT_START_OF_IFD:
+ * break;
+ * case ExifParser.EVENT_NEW_TAG:
+ * ExifTag tag = parser.getTag();
+ * if (!tag.hasValue()) {
+ * parser.registerForTagValue(tag);
+ * } else {
+ * processTag(tag);
+ * }
+ * break;
+ * case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ * tag = parser.getTag();
+ * if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ * processTag(tag);
+ * }
+ * break;
+ * }
+ * event = parser.next();
+ * }
+ * }
+ *
+ * void processTag(ExifTag tag) {
+ * // process the tag as you like.
+ * }
+ * </pre>
+ */
+class ExifParser {
+ private static final boolean LOGV = false;
+ private static final String TAG = "ExifParser";
+ /**
+ * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
+ * know which IFD we are in.
+ */
+ public static final int EVENT_START_OF_IFD = 0;
+ /**
+ * When the parser reaches a new tag. Call {@link #getTag()}to get the
+ * corresponding tag.
+ */
+ public static final int EVENT_NEW_TAG = 1;
+ /**
+ * When the parser reaches the value area of tag that is registered by
+ * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
+ * to get the corresponding tag.
+ */
+ public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
+
+ /**
+ * When the parser reaches the compressed image area.
+ */
+ public static final int EVENT_COMPRESSED_IMAGE = 3;
+ /**
+ * When the parser reaches the uncompressed image strip. Call
+ * {@link #getStripIndex()} to get the index of the strip.
+ *
+ * @see #getStripIndex()
+ * @see #getStripCount()
+ */
+ public static final int EVENT_UNCOMPRESSED_STRIP = 4;
+ /**
+ * When there is nothing more to parse.
+ */
+ public static final int EVENT_END = 5;
+
+ /**
+ * Option bit to request to parse IFD0.
+ */
+ public static final int OPTION_IFD_0 = 1 << 0;
+ /**
+ * Option bit to request to parse IFD1.
+ */
+ public static final int OPTION_IFD_1 = 1 << 1;
+ /**
+ * Option bit to request to parse Exif-IFD.
+ */
+ public static final int OPTION_IFD_EXIF = 1 << 2;
+ /**
+ * Option bit to request to parse GPS-IFD.
+ */
+ public static final int OPTION_IFD_GPS = 1 << 3;
+ /**
+ * Option bit to request to parse Interoperability-IFD.
+ */
+ public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
+ /**
+ * Option bit to request to parse thumbnail.
+ */
+ public static final int OPTION_THUMBNAIL = 1 << 5;
+
+ protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+ protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+
+ // TIFF header
+ protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+ protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+ protected static final short TIFF_HEADER_TAIL = 0x002A;
+
+ protected static final int TAG_SIZE = 12;
+ protected static final int OFFSET_SIZE = 2;
+
+ private static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+ protected static final int DEFAULT_IFD0_OFFSET = 8;
+
+ private final CountedDataInputStream mTiffStream;
+ private final int mOptions;
+ private int mIfdStartOffset = 0;
+ private int mNumOfTagInIfd = 0;
+ private int mIfdType;
+ private ExifTag mTag;
+ private ImageEvent mImageEvent;
+ private int mStripCount;
+ private ExifTag mStripSizeTag;
+ private ExifTag mJpegSizeTag;
+ private boolean mNeedToParseOffsetsInCurrentIfd;
+ private boolean mContainExifData = false;
+ private int mApp1End;
+ private int mOffsetToApp1EndFromSOF = 0;
+ private byte[] mDataAboveIfd0;
+ private int mIfd0Position;
+ private int mTiffStartPosition;
+ private final ExifInterface mInterface;
+
+ private static final short TAG_EXIF_IFD = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
+ private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
+ private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ private static final short TAG_STRIP_OFFSETS = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
+ private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+
+ private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
+
+ private boolean isIfdRequested(int ifdType) {
+ switch (ifdType) {
+ case IfdId.TYPE_IFD_0:
+ return (mOptions & OPTION_IFD_0) != 0;
+ case IfdId.TYPE_IFD_1:
+ return (mOptions & OPTION_IFD_1) != 0;
+ case IfdId.TYPE_IFD_EXIF:
+ return (mOptions & OPTION_IFD_EXIF) != 0;
+ case IfdId.TYPE_IFD_GPS:
+ return (mOptions & OPTION_IFD_GPS) != 0;
+ case IfdId.TYPE_IFD_INTEROPERABILITY:
+ return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
+ }
+ return false;
+ }
+
+ private boolean isThumbnailRequested() {
+ return (mOptions & OPTION_THUMBNAIL) != 0;
+ }
+
+ private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
+ throws IOException, ExifInvalidFormatException {
+ if (inputStream == null) {
+ throw new IOException("Null argument inputStream to ExifParser");
+ }
+ if (LOGV) {
+ Log.v(TAG, "Reading exif...");
+ }
+ mInterface = iRef;
+ mContainExifData = seekTiffData(inputStream);
+ mTiffStream = new CountedDataInputStream(inputStream);
+ mOptions = options;
+ if (!mContainExifData) {
+ return;
+ }
+
+ parseTiffHeader();
+ long offset = mTiffStream.readUnsignedInt();
+ if (offset > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException("Invalid offset " + offset);
+ }
+ mIfd0Position = (int) offset;
+ mIfdType = IfdId.TYPE_IFD_0;
+ if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
+ registerIfd(IfdId.TYPE_IFD_0, offset);
+ if (offset != DEFAULT_IFD0_OFFSET) {
+ mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
+ read(mDataAboveIfd0);
+ }
+ }
+ }
+
+ /**
+ * Parses the the given InputStream with the given options
+ *
+ * @exception IOException
+ * @exception ExifInvalidFormatException
+ */
+ protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
+ throws IOException, ExifInvalidFormatException {
+ return new ExifParser(inputStream, options, iRef);
+ }
+
+ /**
+ * Parses the the given InputStream with default options; that is, every IFD
+ * and thumbnaill will be parsed.
+ *
+ * @exception IOException
+ * @exception ExifInvalidFormatException
+ * @see #parse(InputStream, int)
+ */
+ protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
+ throws IOException, ExifInvalidFormatException {
+ return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
+ | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
+ | OPTION_THUMBNAIL, iRef);
+ }
+
+ /**
+ * Moves the parser forward and returns the next parsing event
+ *
+ * @exception IOException
+ * @exception ExifInvalidFormatException
+ * @see #EVENT_START_OF_IFD
+ * @see #EVENT_NEW_TAG
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ * @see #EVENT_COMPRESSED_IMAGE
+ * @see #EVENT_UNCOMPRESSED_STRIP
+ * @see #EVENT_END
+ */
+ protected int next() throws IOException, ExifInvalidFormatException {
+ if (!mContainExifData) {
+ return EVENT_END;
+ }
+ int offset = mTiffStream.getReadByteCount();
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ if (offset < endOfTags) {
+ mTag = readTag();
+ if (mTag == null) {
+ return next();
+ }
+ if (mNeedToParseOffsetsInCurrentIfd) {
+ checkOffsetOrImageTag(mTag);
+ }
+ return EVENT_NEW_TAG;
+ } else if (offset == endOfTags) {
+ // There is a link to ifd1 at the end of ifd0
+ if (mIfdType == IfdId.TYPE_IFD_0) {
+ long ifdOffset = readUnsignedLong();
+ if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
+ if (ifdOffset != 0) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+ }
+ }
+ } else {
+ int offsetSize = 4;
+ // Some camera models use invalid length of the offset
+ if (mCorrespondingEvent.size() > 0) {
+ offsetSize = mCorrespondingEvent.firstEntry().getKey() -
+ mTiffStream.getReadByteCount();
+ }
+ if (offsetSize < 4) {
+ Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
+ } else {
+ long ifdOffset = readUnsignedLong();
+ if (ifdOffset != 0) {
+ Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
+ }
+ }
+ }
+ }
+ while (mCorrespondingEvent.size() != 0) {
+ Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+ Object event = entry.getValue();
+ try {
+ skipTo(entry.getKey());
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
+ " for " + event.getClass().getName() + ", the file may be broken.");
+ continue;
+ }
+ if (event instanceof IfdEvent) {
+ mIfdType = ((IfdEvent) event).ifd;
+ mNumOfTagInIfd = mTiffStream.readUnsignedShort();
+ mIfdStartOffset = entry.getKey();
+
+ if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
+ Log.w(TAG, "Invalid size of IFD " + mIfdType);
+ return EVENT_END;
+ }
+
+ mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
+ if (((IfdEvent) event).isRequested) {
+ return EVENT_START_OF_IFD;
+ } else {
+ skipRemainingTagsInCurrentIfd();
+ }
+ } else if (event instanceof ImageEvent) {
+ mImageEvent = (ImageEvent) event;
+ return mImageEvent.type;
+ } else {
+ ExifTagEvent tagEvent = (ExifTagEvent) event;
+ mTag = tagEvent.tag;
+ if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ readFullTagValue(mTag);
+ checkOffsetOrImageTag(mTag);
+ }
+ if (tagEvent.isRequested) {
+ return EVENT_VALUE_OF_REGISTERED_TAG;
+ }
+ }
+ }
+ return EVENT_END;
+ }
+
+ /**
+ * Skips the tags area of current IFD, if the parser is not in the tag area,
+ * nothing will happen.
+ *
+ * @throws IOException
+ * @throws ExifInvalidFormatException
+ */
+ protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ int offset = mTiffStream.getReadByteCount();
+ if (offset > endOfTags) {
+ return;
+ }
+ if (mNeedToParseOffsetsInCurrentIfd) {
+ while (offset < endOfTags) {
+ mTag = readTag();
+ offset += TAG_SIZE;
+ if (mTag == null) {
+ continue;
+ }
+ checkOffsetOrImageTag(mTag);
+ }
+ } else {
+ skipTo(endOfTags);
+ }
+ long ifdOffset = readUnsignedLong();
+ // For ifd0, there is a link to ifd1 in the end of all tags
+ if (mIfdType == IfdId.TYPE_IFD_0
+ && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
+ if (ifdOffset > 0) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+ }
+ }
+ }
+
+ private boolean needToParseOffsetsInCurrentIfd() {
+ switch (mIfdType) {
+ case IfdId.TYPE_IFD_0:
+ return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
+ || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
+ || isIfdRequested(IfdId.TYPE_IFD_1);
+ case IfdId.TYPE_IFD_1:
+ return isThumbnailRequested();
+ case IfdId.TYPE_IFD_EXIF:
+ // The offset to interoperability IFD is located in Exif IFD
+ return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * If {@link #next()} return {@link #EVENT_NEW_TAG} or
+ * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
+ * corresponding tag.
+ * <p>
+ * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
+ * of the value is greater than 4 bytes. One should call
+ * {@link ExifTag#hasValue()} to check if the tag contains value. If there
+ * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
+ * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+ * pointed by the offset.
+ * <p>
+ * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
+ * tag will have already been read except for tags of undefined type. For
+ * tags of undefined type, call one of the read methods to get the value.
+ *
+ * @see #registerForTagValue(ExifTag)
+ * @see #read(byte[])
+ * @see #read(byte[], int, int)
+ * @see #readLong()
+ * @see #readRational()
+ * @see #readString(int)
+ * @see #readString(int, Charset)
+ */
+ protected ExifTag getTag() {
+ return mTag;
+ }
+
+ /**
+ * Gets number of tags in the current IFD area.
+ */
+ protected int getTagCountInCurrentIfd() {
+ return mNumOfTagInIfd;
+ }
+
+ /**
+ * Gets the ID of current IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ * @see IfdId#TYPE_IFD_EXIF
+ */
+ protected int getCurrentIfd() {
+ return mIfdType;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the index of this strip.
+ *
+ * @see #getStripCount()
+ */
+ protected int getStripIndex() {
+ return mImageEvent.stripIndex;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the number of strip data.
+ *
+ * @see #getStripIndex()
+ */
+ protected int getStripCount() {
+ return mStripCount;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the strip size.
+ */
+ protected int getStripSize() {
+ if (mStripSizeTag == null)
+ return 0;
+ return (int) mStripSizeTag.getValueAt(0);
+ }
+
+ /**
+ * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
+ * the image data size.
+ */
+ protected int getCompressedImageSize() {
+ if (mJpegSizeTag == null) {
+ return 0;
+ }
+ return (int) mJpegSizeTag.getValueAt(0);
+ }
+
+ private void skipTo(int offset) throws IOException {
+ mTiffStream.skipTo(offset);
+ while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
+ mCorrespondingEvent.pollFirstEntry();
+ }
+ }
+
+ /**
+ * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
+ * not contain the value if the size of the value is greater than 4 bytes.
+ * When the value is not available here, call this method so that the parser
+ * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+ * where the value is located.
+ *
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ */
+ protected void registerForTagValue(ExifTag tag) {
+ if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
+ mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+ }
+ }
+
+ private void registerIfd(int ifdType, long offset) {
+ // Cast unsigned int to int since the offset is always smaller
+ // than the size of APP1 (65536)
+ mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
+ }
+
+ private void registerCompressedImage(long offset) {
+ mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
+ }
+
+ private void registerUncompressedStrip(int stripIndex, long offset) {
+ mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
+ , stripIndex));
+ }
+
+ private ExifTag readTag() throws IOException, ExifInvalidFormatException {
+ short tagId = mTiffStream.readShort();
+ short dataFormat = mTiffStream.readShort();
+ long numOfComp = mTiffStream.readUnsignedInt();
+ if (numOfComp > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException(
+ "Number of component is larger then Integer.MAX_VALUE");
+ }
+ // Some invalid image file contains invalid data type. Ignore those tags
+ if (!ExifTag.isValidType(dataFormat)) {
+ Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
+ mTiffStream.skip(4);
+ return null;
+ }
+ // TODO: handle numOfComp overflow
+ ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
+ ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
+ int dataSize = tag.getDataSize();
+ if (dataSize > 4) {
+ long offset = mTiffStream.readUnsignedInt();
+ if (offset > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException(
+ "offset is larger then Integer.MAX_VALUE");
+ }
+ // Some invalid images put some undefined data before IFD0.
+ // Read the data here.
+ if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
+ byte[] buf = new byte[(int) numOfComp];
+ System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
+ buf, 0, (int) numOfComp);
+ tag.setValue(buf);
+ } else {
+ tag.setOffset((int) offset);
+ }
+ } else {
+ boolean defCount = tag.hasDefinedCount();
+ // Set defined count to 0 so we can add \0 to non-terminated strings
+ tag.setHasDefinedCount(false);
+ // Read value
+ readFullTagValue(tag);
+ tag.setHasDefinedCount(defCount);
+ mTiffStream.skip(4 - dataSize);
+ // Set the offset to the position of value.
+ tag.setOffset(mTiffStream.getReadByteCount() - 4);
+ }
+ return tag;
+ }
+
+ /**
+ * Check the tag, if the tag is one of the offset tag that points to the IFD
+ * or image the caller is interested in, register the IFD or image.
+ */
+ private void checkOffsetOrImageTag(ExifTag tag) {
+ // Some invalid formattd image contains tag with 0 size.
+ if (tag.getComponentCount() == 0) {
+ return;
+ }
+ short tid = tag.getTagId();
+ int ifd = tag.getIfd();
+ if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+ || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+ registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_INTEROPERABILITY_IFD
+ && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
+ && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
+ if (isThumbnailRequested()) {
+ registerCompressedImage(tag.getValueAt(0));
+ }
+ } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
+ && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
+ if (isThumbnailRequested()) {
+ mJpegSizeTag = tag;
+ }
+ } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
+ if (isThumbnailRequested()) {
+ if (tag.hasValue()) {
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+ registerUncompressedStrip(i, tag.getValueAt(i));
+ } else {
+ registerUncompressedStrip(i, tag.getValueAt(i));
+ }
+ }
+ } else {
+ mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
+ }
+ }
+ } else if (tid == TAG_STRIP_BYTE_COUNTS
+ && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
+ &&isThumbnailRequested() && tag.hasValue()) {
+ mStripSizeTag = tag;
+ }
+ }
+
+ private boolean checkAllowed(int ifd, int tagId) {
+ int info = mInterface.getTagInfo().get(tagId);
+ if (info == ExifInterface.DEFINITION_NULL) {
+ return false;
+ }
+ return ExifInterface.isIfdAllowed(info, ifd);
+ }
+
+ protected void readFullTagValue(ExifTag tag) throws IOException {
+ // Some invalid images contains tags with wrong size, check it here
+ short type = tag.getDataType();
+ if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
+ type == ExifTag.TYPE_UNSIGNED_BYTE) {
+ int size = tag.getComponentCount();
+ if (mCorrespondingEvent.size() > 0) {
+ if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
+ + size) {
+ Object event = mCorrespondingEvent.firstEntry().getValue();
+ if (event instanceof ImageEvent) {
+ // Tag value overlaps thumbnail, ignore thumbnail.
+ Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
+ Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+ Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
+ } else {
+ // Tag value overlaps another tag, shorten count
+ if (event instanceof IfdEvent) {
+ Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
+ + " overlaps value for tag: \n" + tag.toString());
+ } else if (event instanceof ExifTagEvent) {
+ Log.w(TAG, "Tag value for tag: \n"
+ + ((ExifTagEvent) event).tag.toString()
+ + " overlaps value for tag: \n" + tag.toString());
+ }
+ size = mCorrespondingEvent.firstEntry().getKey()
+ - mTiffStream.getReadByteCount();
+ Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
+ + " setting count to: " + size);
+ tag.forceSetComponentCount(size);
+ }
+ }
+ }
+ }
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ case ExifTag.TYPE_UNDEFINED: {
+ byte buf[] = new byte[tag.getComponentCount()];
+ read(buf);
+ tag.setValue(buf);
+ }
+ break;
+ case ExifTag.TYPE_ASCII:
+ tag.setValue(readString(tag.getComponentCount()));
+ break;
+ case ExifTag.TYPE_UNSIGNED_LONG: {
+ long value[] = new long[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedLong();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_RATIONAL: {
+ Rational value[] = new Rational[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedRational();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT: {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedShort();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_LONG: {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readLong();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL: {
+ Rational value[] = new Rational[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readRational();
+ }
+ tag.setValue(value);
+ }
+ break;
+ }
+ if (LOGV) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
+ }
+
+ private void parseTiffHeader() throws IOException,
+ ExifInvalidFormatException {
+ short byteOrder = mTiffStream.readShort();
+ if (LITTLE_ENDIAN_TAG == byteOrder) {
+ mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ } else if (BIG_ENDIAN_TAG == byteOrder) {
+ mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ } else {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+
+ if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+ }
+
+ private boolean seekTiffData(InputStream inputStream) throws IOException,
+ ExifInvalidFormatException {
+ CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
+ if (dataStream.readShort() != JpegHeader.SOI) {
+ throw new ExifInvalidFormatException("Invalid JPEG format");
+ }
+
+ short marker = dataStream.readShort();
+ while (marker != JpegHeader.EOI
+ && !JpegHeader.isSofMarker(marker)) {
+ int length = dataStream.readUnsignedShort();
+ // Some invalid formatted image contains multiple APP1,
+ // try to find the one with Exif data.
+ if (marker == JpegHeader.APP1) {
+ int header = 0;
+ short headerTail = 0;
+ if (length >= 8) {
+ header = dataStream.readInt();
+ headerTail = dataStream.readShort();
+ length -= 6;
+ if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
+ mTiffStartPosition = dataStream.getReadByteCount();
+ mApp1End = length;
+ mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
+ return true;
+ }
+ }
+ }
+ if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
+ Log.w(TAG, "Invalid JPEG format.");
+ return false;
+ }
+ marker = dataStream.readShort();
+ }
+ return false;
+ }
+
+ protected int getOffsetToExifEndFromSOF() {
+ return mOffsetToApp1EndFromSOF;
+ }
+
+ protected int getTiffStartPosition() {
+ return mTiffStartPosition;
+ }
+
+ /**
+ * Reads bytes from the InputStream.
+ */
+ protected int read(byte[] buffer, int offset, int length) throws IOException {
+ return mTiffStream.read(buffer, offset, length);
+ }
+
+ /**
+ * Equivalent to read(buffer, 0, buffer.length).
+ */
+ protected int read(byte[] buffer) throws IOException {
+ return mTiffStream.read(buffer);
+ }
+
+ /**
+ * Reads a String from the InputStream with US-ASCII charset. The parser
+ * will read n bytes and convert it to ascii string. This is used for
+ * reading values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ protected String readString(int n) throws IOException {
+ return readString(n, US_ASCII);
+ }
+
+ /**
+ * Reads a String from the InputStream with the given charset. The parser
+ * will read n bytes and convert it to string. This is used for reading
+ * values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ protected String readString(int n, Charset charset) throws IOException {
+ if (n > 0) {
+ return mTiffStream.readString(n, charset);
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
+ * InputStream.
+ */
+ protected int readUnsignedShort() throws IOException {
+ return mTiffStream.readShort() & 0xffff;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
+ * InputStream.
+ */
+ protected long readUnsignedLong() throws IOException {
+ return readLong() & 0xffffffffL;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
+ * InputStream.
+ */
+ protected Rational readUnsignedRational() throws IOException {
+ long nomi = readUnsignedLong();
+ long denomi = readUnsignedLong();
+ return new Rational(nomi, denomi);
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
+ */
+ protected int readLong() throws IOException {
+ return mTiffStream.readInt();
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
+ */
+ protected Rational readRational() throws IOException {
+ int nomi = readLong();
+ int denomi = readLong();
+ return new Rational(nomi, denomi);
+ }
+
+ private static class ImageEvent {
+ int stripIndex;
+ int type;
+
+ ImageEvent(int type) {
+ this.stripIndex = 0;
+ this.type = type;
+ }
+
+ ImageEvent(int type, int stripIndex) {
+ this.type = type;
+ this.stripIndex = stripIndex;
+ }
+ }
+
+ private static class IfdEvent {
+ int ifd;
+ boolean isRequested;
+
+ IfdEvent(int ifd, boolean isInterestedIfd) {
+ this.ifd = ifd;
+ this.isRequested = isInterestedIfd;
+ }
+ }
+
+ private static class ExifTagEvent {
+ ExifTag tag;
+ boolean isRequested;
+
+ ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
+ this.tag = tag;
+ this.isRequested = isRequireByUser;
+ }
+ }
+
+ /**
+ * Gets the byte order of the current InputStream.
+ */
+ protected ByteOrder getByteOrder() {
+ return mTiffStream.getByteOrder();
+ }
+}
diff --git a/src/com/android/gallery3d/exif/ExifReader.java b/src/com/android/gallery3d/exif/ExifReader.java
new file mode 100644
index 0000000..68e972f
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifReader.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class reads the EXIF header of a JPEG file and stores it in
+ * {@link ExifData}.
+ */
+class ExifReader {
+ private static final String TAG = "ExifReader";
+
+ private final ExifInterface mInterface;
+
+ ExifReader(ExifInterface iRef) {
+ mInterface = iRef;
+ }
+
+ /**
+ * Parses the inputStream and and returns the EXIF data in an
+ * {@link ExifData}.
+ *
+ * @throws ExifInvalidFormatException
+ * @throws IOException
+ */
+ protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
+ IOException {
+ ExifParser parser = ExifParser.parse(inputStream, mInterface);
+ ExifData exifData = new ExifData(parser.getByteOrder());
+ ExifTag tag = null;
+
+ int event = parser.next();
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ tag = parser.getTag();
+ if (!tag.hasValue()) {
+ parser.registerForTagValue(tag);
+ } else {
+ exifData.getIfdData(tag.getIfd()).setTag(tag);
+ }
+ break;
+ case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ tag = parser.getTag();
+ if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+ parser.readFullTagValue(tag);
+ }
+ exifData.getIfdData(tag.getIfd()).setTag(tag);
+ break;
+ case ExifParser.EVENT_COMPRESSED_IMAGE:
+ byte buf[] = new byte[parser.getCompressedImageSize()];
+ if (buf.length == parser.read(buf)) {
+ exifData.setCompressedThumbnail(buf);
+ } else {
+ Log.w(TAG, "Failed to read the compressed thumbnail");
+ }
+ break;
+ case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+ buf = new byte[parser.getStripSize()];
+ if (buf.length == parser.read(buf)) {
+ exifData.setStripBytes(parser.getStripIndex(), buf);
+ } else {
+ Log.w(TAG, "Failed to read the strip bytes");
+ }
+ break;
+ }
+ event = parser.next();
+ }
+ return exifData;
+ }
+}
diff --git a/src/com/android/gallery3d/exif/ExifTag.java b/src/com/android/gallery3d/exif/ExifTag.java
new file mode 100644
index 0000000..b8b3872
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifTag.java
@@ -0,0 +1,1008 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
+ */
+public class ExifTag {
+ /**
+ * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
+ */
+ public static final short TYPE_UNSIGNED_BYTE = 1;
+ /**
+ * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
+ * ASCII code. The final byte is terminated with NULL.
+ */
+ public static final short TYPE_ASCII = 2;
+ /**
+ * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_SHORT = 3;
+ /**
+ * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_LONG = 4;
+ /**
+ * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
+ * one is the numerator and the second one expresses the denominator.
+ */
+ public static final short TYPE_UNSIGNED_RATIONAL = 5;
+ /**
+ * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+ * value depending on the field definition.
+ */
+ public static final short TYPE_UNDEFINED = 7;
+ /**
+ * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
+ * (2's complement notation).
+ */
+ public static final short TYPE_LONG = 9;
+ /**
+ * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
+ * one is the numerator and the second one is the denominator.
+ */
+ public static final short TYPE_RATIONAL = 10;
+
+ private static Charset US_ASCII = Charset.forName("US-ASCII");
+ private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+ private static final int UNSIGNED_SHORT_MAX = 65535;
+ private static final long UNSIGNED_LONG_MAX = 4294967295L;
+ private static final long LONG_MAX = Integer.MAX_VALUE;
+ private static final long LONG_MIN = Integer.MIN_VALUE;
+
+ static {
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
+ TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
+ }
+
+ static final int SIZE_UNDEFINED = 0;
+
+ // Exif TagId
+ private final short mTagId;
+ // Exif Tag Type
+ private final short mDataType;
+ // If tag has defined count
+ private boolean mHasDefinedDefaultComponentCount;
+ // Actual data count in tag (should be number of elements in value array)
+ private int mComponentCountActual;
+ // The ifd that this tag should be put in
+ private int mIfd;
+ // The value (array of elements of type Tag Type)
+ private Object mValue;
+ // Value offset in exif header.
+ private int mOffset;
+
+ private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+
+ /**
+ * Returns true if the given IFD is a valid IFD.
+ */
+ public static boolean isValidIfd(int ifdId) {
+ return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
+ || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
+ || ifdId == IfdId.TYPE_IFD_GPS;
+ }
+
+ /**
+ * Returns true if a given type is a valid tag type.
+ */
+ public static boolean isValidType(short type) {
+ return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
+ type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
+ type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
+ type == TYPE_LONG || type == TYPE_RATIONAL;
+ }
+
+ // Use builtTag in ExifInterface instead of constructor.
+ ExifTag(short tagId, short type, int componentCount, int ifd,
+ boolean hasDefinedComponentCount) {
+ mTagId = tagId;
+ mDataType = type;
+ mComponentCountActual = componentCount;
+ mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
+ mIfd = ifd;
+ mValue = null;
+ }
+
+ /**
+ * Gets the element size of the given data type in bytes.
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public static int getElementSize(short type) {
+ return TYPE_TO_SIZE_MAP[type];
+ }
+
+ /**
+ * Returns the ID of the IFD this tag belongs to.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ public int getIfd() {
+ return mIfd;
+ }
+
+ protected void setIfd(int ifdId) {
+ mIfd = ifdId;
+ }
+
+ /**
+ * Gets the TID of this tag.
+ */
+ public short getTagId() {
+ return mTagId;
+ }
+
+ /**
+ * Gets the data type of this tag
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public short getDataType() {
+ return mDataType;
+ }
+
+ /**
+ * Gets the total data size in bytes of the value of this tag.
+ */
+ public int getDataSize() {
+ return getComponentCount() * getElementSize(getDataType());
+ }
+
+ /**
+ * Gets the component count of this tag.
+ */
+
+ // TODO: fix integer overflows with this
+ public int getComponentCount() {
+ return mComponentCountActual;
+ }
+
+ /**
+ * Sets the component count of this tag. Call this function before
+ * setValue() if the length of value does not match the component count.
+ */
+ protected void forceSetComponentCount(int count) {
+ mComponentCountActual = count;
+ }
+
+ /**
+ * Returns true if this ExifTag contains value; otherwise, this tag will
+ * contain an offset value that is determined when the tag is written.
+ */
+ public boolean hasValue() {
+ return mValue != null;
+ }
+
+ /**
+ * Sets integer values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The value.length does NOT match the component count in the definition
+ * for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(int[] value) {
+ if (checkBadComponentCount(value.length)) {
+ return false;
+ }
+ if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+ mDataType != TYPE_UNSIGNED_LONG) {
+ return false;
+ }
+ if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+ return false;
+ } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+ return false;
+ }
+
+ long[] data = new long[value.length];
+ for (int i = 0; i < value.length; i++) {
+ data[i] = value[i];
+ }
+ mValue = data;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets integer value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+ * will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The component count in the definition of this tag is not 1.</li>
+ * </ul>
+ */
+ public boolean setValue(int value) {
+ return setValue(new int[] {
+ value
+ });
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The value.length does NOT match the component count in the definition
+ * for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(long[] value) {
+ if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
+ return false;
+ }
+ if (checkOverflowForUnsignedLong(value)) {
+ return false;
+ }
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The component count in the definition for this tag is not 1.</li>
+ * </ul>
+ */
+ public boolean setValue(long value) {
+ return setValue(new long[] {
+ value
+ });
+ }
+
+ /**
+ * Sets a string value into this tag. This method should be used for tags of
+ * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+ * Characters that cannot be converted are replaced with '?'. The length of
+ * the string must be equal to either (component count -1) or (component
+ * count). The final byte will be set to the string null terminator '\0',
+ * overwriting the last character in the string if the value.length is equal
+ * to the component count. This method will fail if:
+ * <ul>
+ * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
+ * <li>The length of the string is not equal to (component count -1) or
+ * (component count) in the definition for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(String value) {
+ if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
+ return false;
+ }
+
+ byte[] buf = value.getBytes(US_ASCII);
+ byte[] finalBuf = buf;
+ if (buf.length > 0) {
+ finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
+ .copyOf(buf, buf.length + 1);
+ } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
+ finalBuf = new byte[] { 0 };
+ }
+ int count = finalBuf.length;
+ if (checkBadComponentCount(count)) {
+ return false;
+ }
+ mComponentCountActual = count;
+ mValue = finalBuf;
+ return true;
+ }
+
+ /**
+ * Sets Rational values into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.</li>
+ * <li>The value overflows.</li>
+ * <li>The value.length does NOT match the component count in the definition
+ * for this tag.</li>
+ * </ul>
+ *
+ * @see Rational
+ */
+ public boolean setValue(Rational[] value) {
+ if (checkBadComponentCount(value.length)) {
+ return false;
+ }
+ if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
+ return false;
+ }
+ if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+ return false;
+ } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+ return false;
+ }
+
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets a Rational value into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.</li>
+ * <li>The value overflows.</li>
+ * <li>The component count in the definition for this tag is not 1.</li>
+ * </ul>
+ *
+ * @see Rational
+ */
+ public boolean setValue(Rational value) {
+ return setValue(new Rational[] {
+ value
+ });
+ }
+
+ /**
+ * Sets byte values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .</li>
+ * <li>The length does NOT match the component count in the definition for
+ * this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(byte[] value, int offset, int length) {
+ if (checkBadComponentCount(length)) {
+ return false;
+ }
+ if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+ return false;
+ }
+ mValue = new byte[length];
+ System.arraycopy(value, offset, mValue, 0, length);
+ mComponentCountActual = length;
+ return true;
+ }
+
+ /**
+ * Equivalent to setValue(value, 0, value.length).
+ */
+ public boolean setValue(byte[] value) {
+ return setValue(value, 0, value.length);
+ }
+
+ /**
+ * Sets byte value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .</li>
+ * <li>The component count in the definition for this tag is not 1.</li>
+ * </ul>
+ */
+ public boolean setValue(byte value) {
+ return setValue(new byte[] {
+ value
+ });
+ }
+
+ /**
+ * Sets the value for this tag using an appropriate setValue method for the
+ * given object. This method will fail if:
+ * <ul>
+ * <li>The corresponding setValue method for the class of the object passed
+ * in would fail.</li>
+ * <li>There is no obvious way to cast the object passed in into an EXIF tag
+ * type.</li>
+ * </ul>
+ */
+ public boolean setValue(Object obj) {
+ if (obj == null) {
+ return false;
+ } else if (obj instanceof Short) {
+ return setValue(((Short) obj).shortValue() & 0x0ffff);
+ } else if (obj instanceof String) {
+ return setValue((String) obj);
+ } else if (obj instanceof int[]) {
+ return setValue((int[]) obj);
+ } else if (obj instanceof long[]) {
+ return setValue((long[]) obj);
+ } else if (obj instanceof Rational) {
+ return setValue((Rational) obj);
+ } else if (obj instanceof Rational[]) {
+ return setValue((Rational[]) obj);
+ } else if (obj instanceof byte[]) {
+ return setValue((byte[]) obj);
+ } else if (obj instanceof Integer) {
+ return setValue(((Integer) obj).intValue());
+ } else if (obj instanceof Long) {
+ return setValue(((Long) obj).longValue());
+ } else if (obj instanceof Byte) {
+ return setValue(((Byte) obj).byteValue());
+ } else if (obj instanceof Short[]) {
+ // Nulls in this array are treated as zeroes.
+ Short[] arr = (Short[]) obj;
+ int[] fin = new int[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
+ }
+ return setValue(fin);
+ } else if (obj instanceof Integer[]) {
+ // Nulls in this array are treated as zeroes.
+ Integer[] arr = (Integer[]) obj;
+ int[] fin = new int[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
+ }
+ return setValue(fin);
+ } else if (obj instanceof Long[]) {
+ // Nulls in this array are treated as zeroes.
+ Long[] arr = (Long[]) obj;
+ long[] fin = new long[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
+ }
+ return setValue(fin);
+ } else if (obj instanceof Byte[]) {
+ // Nulls in this array are treated as zeroes.
+ Byte[] arr = (Byte[]) obj;
+ byte[] fin = new byte[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
+ }
+ return setValue(fin);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets a timestamp to this tag. The method converts the timestamp with the
+ * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+ * method will fail if the data type is not {@link #TYPE_ASCII} or the
+ * component count of this tag is not 20 or undefined.
+ *
+ * @param time the number of milliseconds since Jan. 1, 1970 GMT
+ * @return true on success
+ */
+ public boolean setTimeValue(long time) {
+ // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+ synchronized (TIME_FORMAT) {
+ return setValue(TIME_FORMAT.format(new Date(time)));
+ }
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
+ *
+ * @return the value as a String, or null if the tag's value does not exist
+ * or cannot be converted to a String.
+ */
+ public String getValueAsString() {
+ if (mValue == null) {
+ return null;
+ } else if (mValue instanceof String) {
+ return (String) mValue;
+ } else if (mValue instanceof byte[]) {
+ return new String((byte[]) mValue, US_ASCII);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
+ *
+ * @param defaultValue the String to return if the tag's value does not
+ * exist or cannot be converted to a String.
+ * @return the tag's value as a String, or the defaultValue.
+ */
+ public String getValueAsString(String defaultValue) {
+ String s = getValueAsString();
+ if (s == null) {
+ return defaultValue;
+ }
+ return s;
+ }
+
+ /**
+ * Gets the value as a byte array. This method should be used for tags of
+ * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @return the value as a byte array, or null if the tag's value does not
+ * exist or cannot be converted to a byte array.
+ */
+ public byte[] getValueAsBytes() {
+ if (mValue instanceof byte[]) {
+ return (byte[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a byte. If there are more than 1 bytes in this value,
+ * gets the first byte. This method should be used for tags of type
+ * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @param defaultValue the byte to return if tag's value does not exist or
+ * cannot be converted to a byte.
+ * @return the tag's value as a byte, or the defaultValue.
+ */
+ public byte getValueAsByte(byte defaultValue) {
+ byte[] b = getValueAsBytes();
+ if (b == null || b.length < 1) {
+ return defaultValue;
+ }
+ return b[0];
+ }
+
+ /**
+ * Gets the value as an array of Rationals. This method should be used for
+ * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @return the value as as an array of Rationals, or null if the tag's value
+ * does not exist or cannot be converted to an array of Rationals.
+ */
+ public Rational[] getValueAsRationals() {
+ if (mValue instanceof Rational[]) {
+ return (Rational[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the Rational to return if tag's value does not exist
+ * or cannot be converted to a Rational.
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational(Rational defaultValue) {
+ Rational[] r = getValueAsRationals();
+ if (r == null || r.length < 1) {
+ return defaultValue;
+ }
+ return r[0];
+ }
+
+ /**
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the numerator of the Rational to return if tag's
+ * value does not exist or cannot be converted to a Rational (the
+ * denominator will be 1).
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational(long defaultValue) {
+ Rational defaultVal = new Rational(defaultValue, 1);
+ return getValueAsRational(defaultVal);
+ }
+
+ /**
+ * Gets the value as an array of ints. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of ints, or null if the tag's value does
+ * not exist or cannot be converted to an array of ints.
+ */
+ public int[] getValueAsInts() {
+ if (mValue == null) {
+ return null;
+ } else if (mValue instanceof long[]) {
+ long[] val = (long[]) mValue;
+ int[] arr = new int[val.length];
+ for (int i = 0; i < val.length; i++) {
+ arr[i] = (int) val[i]; // Truncates
+ }
+ return arr;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as an int. If there are more than 1 ints in this value,
+ * gets the first one. This method should be used for tags of type
+ * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the int to return if tag's value does not exist or
+ * cannot be converted to an int.
+ * @return the tag's value as a int, or the defaultValue.
+ */
+ public int getValueAsInt(int defaultValue) {
+ int[] i = getValueAsInts();
+ if (i == null || i.length < 1) {
+ return defaultValue;
+ }
+ return i[0];
+ }
+
+ /**
+ * Gets the value as an array of longs. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of longs, or null if the tag's value
+ * does not exist or cannot be converted to an array of longs.
+ */
+ public long[] getValueAsLongs() {
+ if (mValue instanceof long[]) {
+ return (long[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value or null if none exists. If there are more than 1 longs in
+ * this value, gets the first one. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the long to return if tag's value does not exist or
+ * cannot be converted to a long.
+ * @return the tag's value as a long, or the defaultValue.
+ */
+ public long getValueAsLong(long defaultValue) {
+ long[] l = getValueAsLongs();
+ if (l == null || l.length < 1) {
+ return defaultValue;
+ }
+ return l[0];
+ }
+
+ /**
+ * Gets the tag's value or null if none exists.
+ */
+ public Object getValue() {
+ return mValue;
+ }
+
+ /**
+ * Gets a long representation of the value.
+ *
+ * @param defaultValue value to return if there is no value or value is a
+ * rational with a denominator of 0.
+ * @return the tag's value as a long, or defaultValue if no representation
+ * exists.
+ */
+ public long forceGetValueAsLong(long defaultValue) {
+ long[] l = getValueAsLongs();
+ if (l != null && l.length >= 1) {
+ return l[0];
+ }
+ byte[] b = getValueAsBytes();
+ if (b != null && b.length >= 1) {
+ return b[0];
+ }
+ Rational[] r = getValueAsRationals();
+ if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
+ return (long) r[0].toDouble();
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets a string representation of the value.
+ */
+ public String forceGetValueAsString() {
+ if (mValue == null) {
+ return "";
+ } else if (mValue instanceof byte[]) {
+ if (mDataType == TYPE_ASCII) {
+ return new String((byte[]) mValue, US_ASCII);
+ } else {
+ return Arrays.toString((byte[]) mValue);
+ }
+ } else if (mValue instanceof long[]) {
+ if (((long[]) mValue).length == 1) {
+ return String.valueOf(((long[]) mValue)[0]);
+ } else {
+ return Arrays.toString((long[]) mValue);
+ }
+ } else if (mValue instanceof Object[]) {
+ if (((Object[]) mValue).length == 1) {
+ Object val = ((Object[]) mValue)[0];
+ if (val == null) {
+ return "";
+ } else {
+ return val.toString();
+ }
+ } else {
+ return Arrays.toString((Object[]) mValue);
+ }
+ } else {
+ return mValue.toString();
+ }
+ }
+
+ /**
+ * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
+ * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+ * {@link #getRational(int)} instead.
+ *
+ * @exception IllegalArgumentException if the data type is
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ */
+ protected long getValueAt(int index) {
+ if (mValue instanceof long[]) {
+ return ((long[]) mValue)[index];
+ } else if (mValue instanceof byte[]) {
+ return ((byte[]) mValue)[index];
+ }
+ throw new IllegalArgumentException("Cannot get integer value from "
+ + convertTypeToString(mDataType));
+ }
+
+ /**
+ * Gets the {@link #TYPE_ASCII} data.
+ *
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_ASCII}.
+ */
+ protected String getString() {
+ if (mDataType != TYPE_ASCII) {
+ throw new IllegalArgumentException("Cannot get ASCII value from "
+ + convertTypeToString(mDataType));
+ }
+ return new String((byte[]) mValue, US_ASCII);
+ }
+
+ /*
+ * Get the converted ascii byte. Used by ExifOutputStream.
+ */
+ protected byte[] getStringByte() {
+ return (byte[]) mValue;
+ }
+
+ /**
+ * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
+ *
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ */
+ protected Rational getRational(int index) {
+ if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
+ throw new IllegalArgumentException("Cannot get RATIONAL value from "
+ + convertTypeToString(mDataType));
+ }
+ return ((Rational[]) mValue)[index];
+ }
+
+ /**
+ * Equivalent to getBytes(buffer, 0, buffer.length).
+ */
+ protected void getBytes(byte[] buf) {
+ getBytes(buf, 0, buf.length);
+ }
+
+ /**
+ * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
+ *
+ * @param buf the byte array in which to store the bytes read.
+ * @param offset the initial position in buffer to store the bytes.
+ * @param length the maximum number of bytes to store in buffer. If length >
+ * component count, only the valid bytes will be stored.
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ */
+ protected void getBytes(byte[] buf, int offset, int length) {
+ if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
+ throw new IllegalArgumentException("Cannot get BYTE value from "
+ + convertTypeToString(mDataType));
+ }
+ System.arraycopy(mValue, 0, buf, offset,
+ (length > mComponentCountActual) ? mComponentCountActual : length);
+ }
+
+ /**
+ * Gets the offset of this tag. This is only valid if this data size > 4 and
+ * contains an offset to the location of the actual value.
+ */
+ protected int getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Sets the offset of this tag.
+ */
+ protected void setOffset(int offset) {
+ mOffset = offset;
+ }
+
+ protected void setHasDefinedCount(boolean d) {
+ mHasDefinedDefaultComponentCount = d;
+ }
+
+ protected boolean hasDefinedCount() {
+ return mHasDefinedDefaultComponentCount;
+ }
+
+ private boolean checkBadComponentCount(int count) {
+ if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String convertTypeToString(short type) {
+ switch (type) {
+ case TYPE_UNSIGNED_BYTE:
+ return "UNSIGNED_BYTE";
+ case TYPE_ASCII:
+ return "ASCII";
+ case TYPE_UNSIGNED_SHORT:
+ return "UNSIGNED_SHORT";
+ case TYPE_UNSIGNED_LONG:
+ return "UNSIGNED_LONG";
+ case TYPE_UNSIGNED_RATIONAL:
+ return "UNSIGNED_RATIONAL";
+ case TYPE_UNDEFINED:
+ return "UNDEFINED";
+ case TYPE_LONG:
+ return "LONG";
+ case TYPE_RATIONAL:
+ return "RATIONAL";
+ default:
+ return "";
+ }
+ }
+
+ private boolean checkOverflowForUnsignedShort(int[] value) {
+ for (int v : value) {
+ if (v > UNSIGNED_SHORT_MAX || v < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedLong(long[] value) {
+ for (long v : value) {
+ if (v < 0 || v > UNSIGNED_LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedLong(int[] value) {
+ for (int v : value) {
+ if (v < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedRational(Rational[] value) {
+ for (Rational v : value) {
+ if (v.getNumerator() < 0 || v.getDenominator() < 0
+ || v.getNumerator() > UNSIGNED_LONG_MAX
+ || v.getDenominator() > UNSIGNED_LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForRational(Rational[] value) {
+ for (Rational v : value) {
+ if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
+ || v.getNumerator() > LONG_MAX
+ || v.getDenominator() > LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof ExifTag) {
+ ExifTag tag = (ExifTag) obj;
+ if (tag.mTagId != this.mTagId
+ || tag.mComponentCountActual != this.mComponentCountActual
+ || tag.mDataType != this.mDataType) {
+ return false;
+ }
+ if (mValue != null) {
+ if (tag.mValue == null) {
+ return false;
+ } else if (mValue instanceof long[]) {
+ if (!(tag.mValue instanceof long[])) {
+ return false;
+ }
+ return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
+ } else if (mValue instanceof Rational[]) {
+ if (!(tag.mValue instanceof Rational[])) {
+ return false;
+ }
+ return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
+ } else if (mValue instanceof byte[]) {
+ if (!(tag.mValue instanceof byte[])) {
+ return false;
+ }
+ return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
+ } else {
+ return mValue.equals(tag.mValue);
+ }
+ } else {
+ return tag.mValue == null;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
+ + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
+ + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+ }
+
+}
diff --git a/src/com/android/gallery3d/exif/IfdData.java b/src/com/android/gallery3d/exif/IfdData.java
new file mode 100644
index 0000000..093944a
--- /dev/null
+++ b/src/com/android/gallery3d/exif/IfdData.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an IFD.
+ *
+ * @see ExifData
+ * @see ExifTag
+ */
+class IfdData {
+
+ private final int mIfdId;
+ private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
+ private int mOffsetToNextIfd = 0;
+ private static final int[] sIfds = {
+ IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
+ IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
+ };
+ /**
+ * Creates an IfdData with given IFD ID.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ IfdData(int ifdId) {
+ mIfdId = ifdId;
+ }
+
+ static protected int[] getIfds() {
+ return sIfds;
+ }
+
+ /**
+ * Get a array the contains all {@link ExifTag} in this IFD.
+ */
+ protected ExifTag[] getAllTags() {
+ return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
+ }
+
+ /**
+ * Gets the ID of this IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ protected int getId() {
+ return mIfdId;
+ }
+
+ /**
+ * Gets the {@link ExifTag} with given tag id. Return null if there is no
+ * such tag.
+ */
+ protected ExifTag getTag(short tagId) {
+ return mExifTags.get(tagId);
+ }
+
+ /**
+ * Adds or replaces a {@link ExifTag}.
+ */
+ protected ExifTag setTag(ExifTag tag) {
+ tag.setIfd(mIfdId);
+ return mExifTags.put(tag.getTagId(), tag);
+ }
+
+ protected boolean checkCollision(short tagId) {
+ return mExifTags.get(tagId) != null;
+ }
+
+ /**
+ * Removes the tag of the given ID
+ */
+ protected void removeTag(short tagId) {
+ mExifTags.remove(tagId);
+ }
+
+ /**
+ * Gets the tags count in the IFD.
+ */
+ protected int getTagCount() {
+ return mExifTags.size();
+ }
+
+ /**
+ * Sets the offset of next IFD.
+ */
+ protected void setOffsetToNextIfd(int offset) {
+ mOffsetToNextIfd = offset;
+ }
+
+ /**
+ * Gets the offset of next IFD.
+ */
+ protected int getOffsetToNextIfd() {
+ return mOffsetToNextIfd;
+ }
+
+ /**
+ * Returns true if all tags in this two IFDs are equal. Note that tags of
+ * IFDs offset or thumbnail offset will be ignored.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof IfdData) {
+ IfdData data = (IfdData) obj;
+ if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
+ ExifTag[] tags = data.getAllTags();
+ for (ExifTag tag : tags) {
+ if (ExifInterface.isOffsetTag(tag.getTagId())) {
+ continue;
+ }
+ ExifTag tag2 = mExifTags.get(tag.getTagId());
+ if (!tag.equals(tag2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/gallery3d/exif/IfdId.java b/src/com/android/gallery3d/exif/IfdId.java
new file mode 100644
index 0000000..7842edb
--- /dev/null
+++ b/src/com/android/gallery3d/exif/IfdId.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+/**
+ * The constants of the IFD ID defined in EXIF spec.
+ */
+public interface IfdId {
+ public static final int TYPE_IFD_0 = 0;
+ public static final int TYPE_IFD_1 = 1;
+ public static final int TYPE_IFD_EXIF = 2;
+ public static final int TYPE_IFD_INTEROPERABILITY = 3;
+ public static final int TYPE_IFD_GPS = 4;
+ /* This is used in ExifData to allocate enough IfdData */
+ static final int TYPE_IFD_COUNT = 5;
+
+}
diff --git a/src/com/android/gallery3d/exif/JpegHeader.java b/src/com/android/gallery3d/exif/JpegHeader.java
new file mode 100644
index 0000000..e3e787e
--- /dev/null
+++ b/src/com/android/gallery3d/exif/JpegHeader.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+class JpegHeader {
+ public static final short SOI = (short) 0xFFD8;
+ public static final short APP1 = (short) 0xFFE1;
+ public static final short APP0 = (short) 0xFFE0;
+ public static final short EOI = (short) 0xFFD9;
+
+ /**
+ * SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
+ * and DAC marker.
+ */
+ public static final short SOF0 = (short) 0xFFC0;
+ public static final short SOF15 = (short) 0xFFCF;
+ public static final short DHT = (short) 0xFFC4;
+ public static final short JPG = (short) 0xFFC8;
+ public static final short DAC = (short) 0xFFCC;
+
+ public static final boolean isSofMarker(short marker) {
+ return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
+ && marker != DAC;
+ }
+}
diff --git a/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
new file mode 100644
index 0000000..428e6b9
--- /dev/null
+++ b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class OrderedDataOutputStream extends FilterOutputStream {
+ private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
+
+ public OrderedDataOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ public OrderedDataOutputStream setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ return this;
+ }
+
+ public OrderedDataOutputStream writeShort(short value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putShort(value);
+ out.write(mByteBuffer.array(), 0, 2);
+ return this;
+ }
+
+ public OrderedDataOutputStream writeInt(int value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putInt(value);
+ out.write(mByteBuffer.array());
+ return this;
+ }
+
+ public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
+ writeInt((int) rational.getNumerator());
+ writeInt((int) rational.getDenominator());
+ return this;
+ }
+}
diff --git a/src/com/android/gallery3d/exif/Rational.java b/src/com/android/gallery3d/exif/Rational.java
new file mode 100644
index 0000000..591d63f
--- /dev/null
+++ b/src/com/android/gallery3d/exif/Rational.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+/**
+ * The rational data type of EXIF tag. Contains a pair of longs representing the
+ * numerator and denominator of a Rational number.
+ */
+public class Rational {
+
+ private final long mNumerator;
+ private final long mDenominator;
+
+ /**
+ * Create a Rational with a given numerator and denominator.
+ *
+ * @param nominator
+ * @param denominator
+ */
+ public Rational(long nominator, long denominator) {
+ mNumerator = nominator;
+ mDenominator = denominator;
+ }
+
+ /**
+ * Create a copy of a Rational.
+ */
+ public Rational(Rational r) {
+ mNumerator = r.mNumerator;
+ mDenominator = r.mDenominator;
+ }
+
+ /**
+ * Gets the numerator of the rational.
+ */
+ public long getNumerator() {
+ return mNumerator;
+ }
+
+ /**
+ * Gets the denominator of the rational
+ */
+ public long getDenominator() {
+ return mDenominator;
+ }
+
+ /**
+ * Gets the rational value as type double. Will cause a divide-by-zero error
+ * if the denominator is 0.
+ */
+ public double toDouble() {
+ return mNumerator / (double) mDenominator;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Rational) {
+ Rational data = (Rational) obj;
+ return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return mNumerator + "/" + mDenominator;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java
new file mode 100644
index 0000000..2e77b90
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -0,0 +1,212 @@
+/*
+ * 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.gallery3d.glrenderer;
+
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+
+import java.util.WeakHashMap;
+
+// BasicTexture is a Texture corresponds to a real GL texture.
+// The state of a BasicTexture indicates whether its data is loaded to GL memory.
+// If a BasicTexture is loaded into GL memory, it has a GL texture id.
+public abstract class BasicTexture implements Texture {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "BasicTexture";
+ protected static final int UNSPECIFIED = -1;
+
+ protected static final int STATE_UNLOADED = 0;
+ protected static final int STATE_LOADED = 1;
+ protected static final int STATE_ERROR = -1;
+
+ // Log a warning if a texture is larger along a dimension
+ private static final int MAX_TEXTURE_SIZE = 4096;
+
+ protected int mId = -1;
+ protected int mState;
+
+ protected int mWidth = UNSPECIFIED;
+ protected int mHeight = UNSPECIFIED;
+
+ protected int mTextureWidth;
+ protected int mTextureHeight;
+
+ private boolean mHasBorder;
+
+ protected GLCanvas mCanvasRef = null;
+ private static WeakHashMap<BasicTexture, Object> sAllTextures
+ = new WeakHashMap<BasicTexture, Object>();
+ private static ThreadLocal sInFinalizer = new ThreadLocal();
+
+ protected BasicTexture(GLCanvas canvas, int id, int state) {
+ setAssociatedCanvas(canvas);
+ mId = id;
+ mState = state;
+ synchronized (sAllTextures) {
+ sAllTextures.put(this, null);
+ }
+ }
+
+ protected BasicTexture() {
+ this(null, 0, STATE_UNLOADED);
+ }
+
+ protected void setAssociatedCanvas(GLCanvas canvas) {
+ mCanvasRef = canvas;
+ }
+
+ /**
+ * Sets the content size of this texture. In OpenGL, the actual texture
+ * size must be of power of 2, the size of the content may be smaller.
+ */
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ mTextureWidth = width > 0 ? Utils.nextPowerOf2(width) : 0;
+ mTextureHeight = height > 0 ? Utils.nextPowerOf2(height) : 0;
+ if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
+ Log.w(TAG, String.format("texture is too large: %d x %d",
+ mTextureWidth, mTextureHeight), new Exception());
+ }
+ }
+
+ public boolean isFlippedVertically() {
+ return false;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ // Returns the width rounded to the next power of 2.
+ public int getTextureWidth() {
+ return mTextureWidth;
+ }
+
+ // Returns the height rounded to the next power of 2.
+ public int getTextureHeight() {
+ return mTextureHeight;
+ }
+
+ // Returns true if the texture has one pixel transparent border around the
+ // actual content. This is used to avoid jigged edges.
+ //
+ // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
+ // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
+ // covered by the texture will use the color of the edge texel. If we add
+ // the transparent border, the color of the edge texel will be mixed with
+ // appropriate amount of transparent.
+ //
+ // Currently our background is black, so we can draw the thumbnails without
+ // enabling blending.
+ public boolean hasBorder() {
+ return mHasBorder;
+ }
+
+ protected void setBorder(boolean hasBorder) {
+ mHasBorder = hasBorder;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y) {
+ canvas.drawTexture(this, x, y, getWidth(), getHeight());
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ canvas.drawTexture(this, x, y, w, h);
+ }
+
+ // onBind is called before GLCanvas binds this texture.
+ // It should make sure the data is uploaded to GL memory.
+ abstract protected boolean onBind(GLCanvas canvas);
+
+ // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
+ abstract protected int getTarget();
+
+ public boolean isLoaded() {
+ return mState == STATE_LOADED;
+ }
+
+ // recycle() is called when the texture will never be used again,
+ // so it can free all resources.
+ public void recycle() {
+ freeResource();
+ }
+
+ // yield() is called when the texture will not be used temporarily,
+ // so it can free some resources.
+ // The default implementation unloads the texture from GL memory, so
+ // the subclass should make sure it can reload the texture to GL memory
+ // later, or it will have to override this method.
+ public void yield() {
+ freeResource();
+ }
+
+ private void freeResource() {
+ GLCanvas canvas = mCanvasRef;
+ if (canvas != null && mId != -1) {
+ canvas.unloadTexture(this);
+ mId = -1; // Don't free it again.
+ }
+ mState = STATE_UNLOADED;
+ setAssociatedCanvas(null);
+ }
+
+ @Override
+ protected void finalize() {
+ sInFinalizer.set(BasicTexture.class);
+ recycle();
+ sInFinalizer.set(null);
+ }
+
+ // This is for deciding if we can call Bitmap's recycle().
+ // We cannot call Bitmap's recycle() in finalizer because at that point
+ // the finalizer of Bitmap may already be called so recycle() will crash.
+ public static boolean inFinalizer() {
+ return sInFinalizer.get() != null;
+ }
+
+ public static void yieldAllTextures() {
+ synchronized (sAllTextures) {
+ for (BasicTexture t : sAllTextures.keySet()) {
+ t.yield();
+ }
+ }
+ }
+
+ public static void invalidateAllTextures() {
+ synchronized (sAllTextures) {
+ for (BasicTexture t : sAllTextures.keySet()) {
+ t.mState = STATE_UNLOADED;
+ t.setAssociatedCanvas(null);
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
new file mode 100644
index 0000000..100b0b3
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -0,0 +1,54 @@
+/*
+ * 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+
+import junit.framework.Assert;
+
+// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
+//
+// The texture does not own the Bitmap. The user should make sure the Bitmap
+// is valid during the texture's lifetime. When the texture is recycled, it
+// does not free the Bitmap.
+public class BitmapTexture extends UploadedTexture {
+ protected Bitmap mContentBitmap;
+
+ public BitmapTexture(Bitmap bitmap) {
+ this(bitmap, false);
+ }
+
+ public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
+ super(hasBorder);
+ Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
+ mContentBitmap = bitmap;
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ // Do nothing.
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ return mContentBitmap;
+ }
+
+ public Bitmap getBitmap() {
+ return mContentBitmap;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLCanvas.java b/src/com/android/gallery3d/glrenderer/GLCanvas.java
new file mode 100644
index 0000000..305e905
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLCanvas.java
@@ -0,0 +1,217 @@
+/*
+ * 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import javax.microedition.khronos.opengles.GL11;
+
+//
+// GLCanvas gives a convenient interface to draw using OpenGL.
+//
+// When a rectangle is specified in this interface, it means the region
+// [x, x+width) * [y, y+height)
+//
+public interface GLCanvas {
+
+ public GLId getGLId();
+
+ // Tells GLCanvas the size of the underlying GL surface. This should be
+ // called before first drawing and when the size of GL surface is changed.
+ // This is called by GLRoot and should not be called by the clients
+ // who only want to draw on the GLCanvas. Both width and height must be
+ // nonnegative.
+ public abstract void setSize(int width, int height);
+
+ // Clear the drawing buffers. This should only be used by GLRoot.
+ public abstract void clearBuffer();
+
+ public abstract void clearBuffer(float[] argb);
+
+ // Sets and gets the current alpha, alpha must be in [0, 1].
+ public abstract void setAlpha(float alpha);
+
+ public abstract float getAlpha();
+
+ // (current alpha) = (current alpha) * alpha
+ public abstract void multiplyAlpha(float alpha);
+
+ // Change the current transform matrix.
+ public abstract void translate(float x, float y, float z);
+
+ public abstract void translate(float x, float y);
+
+ public abstract void scale(float sx, float sy, float sz);
+
+ public abstract void rotate(float angle, float x, float y, float z);
+
+ public abstract void multiplyMatrix(float[] mMatrix, int offset);
+
+ // Pushes the configuration state (matrix, and alpha) onto
+ // a private stack.
+ public abstract void save();
+
+ // Same as save(), but only save those specified in saveFlags.
+ public abstract void save(int saveFlags);
+
+ public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
+ public static final int SAVE_FLAG_ALPHA = 0x01;
+ public static final int SAVE_FLAG_MATRIX = 0x02;
+
+ // Pops from the top of the stack as current configuration state (matrix,
+ // alpha, and clip). This call balances a previous call to save(), and is
+ // used to remove all modifications to the configuration state since the
+ // last save call.
+ public abstract void restore();
+
+ // Draws a line using the specified paint from (x1, y1) to (x2, y2).
+ // (Both end points are included).
+ public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
+
+ // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
+ // (Both end points are included).
+ public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
+
+ // Fills the specified rectangle with the specified color.
+ public abstract void fillRect(float x, float y, float width, float height, int color);
+
+ // Draws a texture to the specified rectangle.
+ public abstract void drawTexture(
+ BasicTexture texture, int x, int y, int width, int height);
+
+ public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
+ int uvBuffer, int indexBuffer, int indexCount);
+
+ // Draws the source rectangle part of the texture to the target rectangle.
+ public abstract void drawTexture(BasicTexture texture, RectF source, RectF target);
+
+ // Draw a texture with a specified texture transform.
+ public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform,
+ int x, int y, int w, int h);
+
+ // Draw two textures to the specified rectangle. The actual texture used is
+ // from * (1 - ratio) + to * ratio
+ // The two textures must have the same size.
+ public abstract void drawMixed(BasicTexture from, int toColor,
+ float ratio, int x, int y, int w, int h);
+
+ // Draw a region of a texture and a specified color to the specified
+ // rectangle. The actual color used is from * (1 - ratio) + to * ratio.
+ // The region of the texture is defined by parameter "src". The target
+ // rectangle is specified by parameter "target".
+ public abstract void drawMixed(BasicTexture from, int toColor,
+ float ratio, RectF src, RectF target);
+
+ // Unloads the specified texture from the canvas. The resource allocated
+ // to draw the texture will be released. The specified texture will return
+ // to the unloaded state. This function should be called only from
+ // BasicTexture or its descendant
+ public abstract boolean unloadTexture(BasicTexture texture);
+
+ // Delete the specified buffer object, similar to unloadTexture.
+ public abstract void deleteBuffer(int bufferId);
+
+ // Delete the textures and buffers in GL side. This function should only be
+ // called in the GL thread.
+ public abstract void deleteRecycledResources();
+
+ // Dump statistics information and clear the counters. For debug only.
+ public abstract void dumpStatisticsAndClear();
+
+ public abstract void beginRenderTarget(RawTexture texture);
+
+ public abstract void endRenderTarget();
+
+ /**
+ * Sets texture parameters to use GL_CLAMP_TO_EDGE for both
+ * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
+ * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
+ * bindTexture() must be called prior to this.
+ *
+ * @param texture The texture to set parameters on.
+ */
+ public abstract void setTextureParameters(BasicTexture texture);
+
+ /**
+ * Initializes the texture to a size by calling texImage2D on it.
+ *
+ * @param texture The texture to initialize the size.
+ * @param format The texture format (e.g. GL_RGBA)
+ * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+ */
+ public abstract void initializeTextureSize(BasicTexture texture, int format, int type);
+
+ /**
+ * Initializes the texture to a size by calling texImage2D on it.
+ *
+ * @param texture The texture to initialize the size.
+ * @param bitmap The bitmap to initialize the bitmap with.
+ */
+ public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap);
+
+ /**
+ * Calls glTexSubImage2D to upload a bitmap to the texture.
+ *
+ * @param texture The target texture to write to.
+ * @param xOffset Specifies a texel offset in the x direction within the
+ * texture array.
+ * @param yOffset Specifies a texel offset in the y direction within the
+ * texture array.
+ * @param format The texture format (e.g. GL_RGBA)
+ * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+ */
+ public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset,
+ Bitmap bitmap,
+ int format, int type);
+
+ /**
+ * Generates buffers and uploads the buffer data.
+ *
+ * @param buffer The buffer to upload
+ * @return The buffer ID that was generated.
+ */
+ public abstract int uploadBuffer(java.nio.FloatBuffer buffer);
+
+ /**
+ * Generates buffers and uploads the element array buffer data.
+ *
+ * @param buffer The buffer to upload
+ * @return The buffer ID that was generated.
+ */
+ public abstract int uploadBuffer(java.nio.ByteBuffer buffer);
+
+ /**
+ * After LightCycle makes GL calls, this method is called to restore the GL
+ * configuration to the one expected by GLCanvas.
+ */
+ public abstract void recoverFromLightCycle();
+
+ /**
+ * Gets the bounds given by x, y, width, and height as well as the internal
+ * matrix state. There is no special handling for non-90-degree rotations.
+ * It only considers the lower-left and upper-right corners as the bounds.
+ *
+ * @param bounds The output bounds to write to.
+ * @param x The left side of the input rectangle.
+ * @param y The bottom of the input rectangle.
+ * @param width The width of the input rectangle.
+ * @param height The height of the input rectangle.
+ */
+ public abstract void getBounds(Rect bounds, int x, int y, int width, int height);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
new file mode 100644
index 0000000..4ead131
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.android.gallery3d.util.IntArray;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class GLES20Canvas implements GLCanvas {
+ // ************** Constants **********************
+ private static final String TAG = GLES20Canvas.class.getSimpleName();
+ private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
+ private static final float OPAQUE_ALPHA = 0.95f;
+
+ private static final int COORDS_PER_VERTEX = 2;
+ private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE;
+
+ private static final int COUNT_FILL_VERTEX = 4;
+ private static final int COUNT_LINE_VERTEX = 2;
+ private static final int COUNT_RECT_VERTEX = 4;
+ private static final int OFFSET_FILL_RECT = 0;
+ private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX;
+ private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX;
+
+ private static final float[] BOX_COORDINATES = {
+ 0, 0, // Fill rectangle
+ 1, 0,
+ 0, 1,
+ 1, 1,
+ 0, 0, // Draw line
+ 1, 1,
+ 0, 0, // Draw rectangle outline
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ };
+
+ private static final float[] BOUNDS_COORDINATES = {
+ 0, 0, 0, 1,
+ 1, 1, 0, 1,
+ };
+
+ private static final String POSITION_ATTRIBUTE = "aPosition";
+ private static final String COLOR_UNIFORM = "uColor";
+ private static final String MATRIX_UNIFORM = "uMatrix";
+ private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix";
+ private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler";
+ private static final String ALPHA_UNIFORM = "uAlpha";
+ private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate";
+
+ private static final String DRAW_VERTEX_SHADER = ""
+ + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+ + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+ + "void main() {\n"
+ + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+ + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+ + "}\n";
+
+ private static final String DRAW_FRAGMENT_SHADER = ""
+ + "precision mediump float;\n"
+ + "uniform vec4 " + COLOR_UNIFORM + ";\n"
+ + "void main() {\n"
+ + " gl_FragColor = " + COLOR_UNIFORM + ";\n"
+ + "}\n";
+
+ private static final String TEXTURE_VERTEX_SHADER = ""
+ + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+ + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n"
+ + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n"
+ + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+ + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+ + " vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n"
+ + "}\n";
+
+ private static final String MESH_VERTEX_SHADER = ""
+ + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+ + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+ + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n"
+ + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+ + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+ + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+ + "}\n";
+
+ private static final String TEXTURE_FRAGMENT_SHADER = ""
+ + "precision mediump float;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "uniform float " + ALPHA_UNIFORM + ";\n"
+ + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+ + "void main() {\n"
+ + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
+ + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
+ + "}\n";
+
+ private static final String OES_TEXTURE_FRAGMENT_SHADER = ""
+ + "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "uniform float " + ALPHA_UNIFORM + ";\n"
+ + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+ + "void main() {\n"
+ + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
+ + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
+ + "}\n";
+
+ private static final int INITIAL_RESTORE_STATE_SIZE = 8;
+ private static final int MATRIX_SIZE = 16;
+
+ // Keep track of restore state
+ private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
+ private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE];
+ private IntArray mSaveFlags = new IntArray();
+
+ private int mCurrentAlphaIndex = 0;
+ private int mCurrentMatrixIndex = 0;
+
+ // Viewport size
+ private int mWidth;
+ private int mHeight;
+
+ // Projection matrix
+ private float[] mProjectionMatrix = new float[MATRIX_SIZE];
+
+ // Screen size for when we aren't bound to a texture
+ private int mScreenWidth;
+ private int mScreenHeight;
+
+ // GL programs
+ private int mDrawProgram;
+ private int mTextureProgram;
+ private int mOesTextureProgram;
+ private int mMeshProgram;
+
+ // GL buffer containing BOX_COORDINATES
+ private int mBoxCoordinates;
+
+ // Handle indices -- common
+ private static final int INDEX_POSITION = 0;
+ private static final int INDEX_MATRIX = 1;
+
+ // Handle indices -- draw
+ private static final int INDEX_COLOR = 2;
+
+ // Handle indices -- texture
+ private static final int INDEX_TEXTURE_MATRIX = 2;
+ private static final int INDEX_TEXTURE_SAMPLER = 3;
+ private static final int INDEX_ALPHA = 4;
+
+ // Handle indices -- mesh
+ private static final int INDEX_TEXTURE_COORD = 2;
+
+ private abstract static class ShaderParameter {
+ public int handle;
+ protected final String mName;
+
+ public ShaderParameter(String name) {
+ mName = name;
+ }
+
+ public abstract void loadHandle(int program);
+ }
+
+ private static class UniformShaderParameter extends ShaderParameter {
+ public UniformShaderParameter(String name) {
+ super(name);
+ }
+
+ @Override
+ public void loadHandle(int program) {
+ handle = GLES20.glGetUniformLocation(program, mName);
+ checkError();
+ }
+ }
+
+ private static class AttributeShaderParameter extends ShaderParameter {
+ public AttributeShaderParameter(String name) {
+ super(name);
+ }
+
+ @Override
+ public void loadHandle(int program) {
+ handle = GLES20.glGetAttribLocation(program, mName);
+ checkError();
+ }
+ }
+
+ ShaderParameter[] mDrawParameters = {
+ new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR
+ };
+ ShaderParameter[] mTextureParameters = {
+ new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+ new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+ new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+ };
+ ShaderParameter[] mOesTextureParameters = {
+ new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+ new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+ new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+ };
+ ShaderParameter[] mMeshParameters = {
+ new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+ new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+ new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD
+ new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+ new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+ };
+
+ private final IntArray mUnboundTextures = new IntArray();
+ private final IntArray mDeleteBuffers = new IntArray();
+
+ // Keep track of statistics for debugging
+ private int mCountDrawMesh = 0;
+ private int mCountTextureRect = 0;
+ private int mCountFillRect = 0;
+ private int mCountDrawLine = 0;
+
+ // Buffer for framebuffer IDs -- we keep track so we can switch the attached
+ // texture.
+ private int[] mFrameBuffer = new int[1];
+
+ // Bound textures.
+ private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
+
+ // Temporary variables used within calculations
+ private final float[] mTempMatrix = new float[32];
+ private final float[] mTempColor = new float[4];
+ private final RectF mTempSourceRect = new RectF();
+ private final RectF mTempTargetRect = new RectF();
+ private final float[] mTempTextureMatrix = new float[MATRIX_SIZE];
+ private final int[] mTempIntArray = new int[1];
+
+ private static final GLId mGLId = new GLES20IdImpl();
+
+ public GLES20Canvas() {
+ Matrix.setIdentityM(mTempTextureMatrix, 0);
+ Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+ mAlphas[mCurrentAlphaIndex] = 1f;
+ mTargetTextures.add(null);
+
+ FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
+ mBoxCoordinates = uploadBuffer(boxBuffer);
+
+ int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER);
+ int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
+ int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER);
+ int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER);
+ int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
+ int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
+ OES_TEXTURE_FRAGMENT_SHADER);
+
+ mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters);
+ mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
+ mTextureParameters);
+ mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader,
+ mOesTextureParameters);
+ mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters);
+ GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ checkError();
+ }
+
+ private static FloatBuffer createBuffer(float[] values) {
+ // First create an nio buffer, then create a VBO from it.
+ int size = values.length * FLOAT_SIZE;
+ FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
+ .asFloatBuffer();
+ buffer.put(values, 0, values.length).position(0);
+ return buffer;
+ }
+
+ private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
+ int program = GLES20.glCreateProgram();
+ checkError();
+ if (program == 0) {
+ throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
+ }
+ GLES20.glAttachShader(program, vertexShader);
+ checkError();
+ GLES20.glAttachShader(program, fragmentShader);
+ checkError();
+ GLES20.glLinkProgram(program);
+ checkError();
+ int[] mLinkStatus = mTempIntArray;
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
+ if (mLinkStatus[0] != GLES20.GL_TRUE) {
+ Log.e(TAG, "Could not link program: ");
+ Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ for (int i = 0; i < params.length; i++) {
+ params[i].loadHandle(program);
+ }
+ return program;
+ }
+
+ private static int loadShader(int type, String shaderCode) {
+ // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+ // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+ int shader = GLES20.glCreateShader(type);
+
+ // add the source code to the shader and compile it
+ GLES20.glShaderSource(shader, shaderCode);
+ checkError();
+ GLES20.glCompileShader(shader);
+ checkError();
+
+ return shader;
+ }
+
+ @Override
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ GLES20.glViewport(0, 0, mWidth, mHeight);
+ checkError();
+ Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+ Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
+ if (getTargetTexture() == null) {
+ mScreenWidth = width;
+ mScreenHeight = height;
+ Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
+ Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
+ }
+ }
+
+ @Override
+ public void clearBuffer() {
+ GLES20.glClearColor(0f, 0f, 0f, 1f);
+ checkError();
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ checkError();
+ }
+
+ @Override
+ public void clearBuffer(float[] argb) {
+ GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]);
+ checkError();
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ checkError();
+ }
+
+ @Override
+ public float getAlpha() {
+ return mAlphas[mCurrentAlphaIndex];
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ mAlphas[mCurrentAlphaIndex] = alpha;
+ }
+
+ @Override
+ public void multiplyAlpha(float alpha) {
+ setAlpha(getAlpha() * alpha);
+ }
+
+ @Override
+ public void translate(float x, float y, float z) {
+ Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z);
+ }
+
+ // This is a faster version of translate(x, y, z) because
+ // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
+ // (3) we unroll the loop
+ @Override
+ public void translate(float x, float y) {
+ int index = mCurrentMatrixIndex;
+ float[] m = mMatrices;
+ m[index + 12] += m[index + 0] * x + m[index + 4] * y;
+ m[index + 13] += m[index + 1] * x + m[index + 5] * y;
+ m[index + 14] += m[index + 2] * x + m[index + 6] * y;
+ m[index + 15] += m[index + 3] * x + m[index + 7] * y;
+ }
+
+ @Override
+ public void scale(float sx, float sy, float sz) {
+ Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz);
+ }
+
+ @Override
+ public void rotate(float angle, float x, float y, float z) {
+ if (angle == 0f) {
+ return;
+ }
+ float[] temp = mTempMatrix;
+ Matrix.setRotateM(temp, 0, angle, x, y, z);
+ float[] matrix = mMatrices;
+ int index = mCurrentMatrixIndex;
+ Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0);
+ System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE);
+ }
+
+ @Override
+ public void multiplyMatrix(float[] matrix, int offset) {
+ float[] temp = mTempMatrix;
+ float[] currentMatrix = mMatrices;
+ int index = mCurrentMatrixIndex;
+ Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset);
+ System.arraycopy(temp, 0, currentMatrix, index, 16);
+ }
+
+ @Override
+ public void save() {
+ save(SAVE_FLAG_ALL);
+ }
+
+ @Override
+ public void save(int saveFlags) {
+ boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+ if (saveAlpha) {
+ float currentAlpha = getAlpha();
+ mCurrentAlphaIndex++;
+ if (mAlphas.length <= mCurrentAlphaIndex) {
+ mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2);
+ }
+ mAlphas[mCurrentAlphaIndex] = currentAlpha;
+ }
+ boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+ if (saveMatrix) {
+ int currentIndex = mCurrentMatrixIndex;
+ mCurrentMatrixIndex += MATRIX_SIZE;
+ if (mMatrices.length <= mCurrentMatrixIndex) {
+ mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2);
+ }
+ System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE);
+ }
+ mSaveFlags.add(saveFlags);
+ }
+
+ @Override
+ public void restore() {
+ int restoreFlags = mSaveFlags.removeLast();
+ boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+ if (restoreAlpha) {
+ mCurrentAlphaIndex--;
+ }
+ boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+ if (restoreMatrix) {
+ mCurrentMatrixIndex -= MATRIX_SIZE;
+ }
+ }
+
+ @Override
+ public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
+ draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1,
+ paint);
+ mCountDrawLine++;
+ }
+
+ @Override
+ public void drawRect(float x, float y, float width, float height, GLPaint paint) {
+ draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint);
+ mCountDrawLine++;
+ }
+
+ private void draw(int type, int offset, int count, float x, float y, float width, float height,
+ GLPaint paint) {
+ draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth());
+ }
+
+ private void draw(int type, int offset, int count, float x, float y, float width, float height,
+ int color, float lineWidth) {
+ prepareDraw(offset, color, lineWidth);
+ draw(mDrawParameters, type, count, x, y, width, height);
+ }
+
+ private void prepareDraw(int offset, int color, float lineWidth) {
+ GLES20.glUseProgram(mDrawProgram);
+ checkError();
+ if (lineWidth > 0) {
+ GLES20.glLineWidth(lineWidth);
+ checkError();
+ }
+ float[] colorArray = getColor(color);
+ boolean blendingEnabled = (colorArray[3] < 1f);
+ enableBlending(blendingEnabled);
+ if (blendingEnabled) {
+ GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
+ checkError();
+ }
+
+ GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0);
+ setPosition(mDrawParameters, offset);
+ checkError();
+ }
+
+ private float[] getColor(int color) {
+ float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha();
+ float red = ((color >>> 16) & 0xFF) / 255f * alpha;
+ float green = ((color >>> 8) & 0xFF) / 255f * alpha;
+ float blue = (color & 0xFF) / 255f * alpha;
+ mTempColor[0] = red;
+ mTempColor[1] = green;
+ mTempColor[2] = blue;
+ mTempColor[3] = alpha;
+ return mTempColor;
+ }
+
+ private void enableBlending(boolean enableBlending) {
+ if (enableBlending) {
+ GLES20.glEnable(GLES20.GL_BLEND);
+ checkError();
+ } else {
+ GLES20.glDisable(GLES20.GL_BLEND);
+ checkError();
+ }
+ }
+
+ private void setPosition(ShaderParameter[] params, int offset) {
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
+ checkError();
+ GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
+ checkError();
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+ checkError();
+ }
+
+ private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
+ float height) {
+ setMatrix(params, x, y, width, height);
+ int positionHandle = params[INDEX_POSITION].handle;
+ GLES20.glEnableVertexAttribArray(positionHandle);
+ checkError();
+ GLES20.glDrawArrays(type, 0, count);
+ checkError();
+ GLES20.glDisableVertexAttribArray(positionHandle);
+ checkError();
+ }
+
+ private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
+ Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
+ Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
+ Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
+ GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
+ checkError();
+ }
+
+ @Override
+ public void fillRect(float x, float y, float width, float height, int color) {
+ draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height,
+ color, 0f);
+ mCountFillRect++;
+ }
+
+ @Override
+ public void drawTexture(BasicTexture texture, int x, int y, int width, int height) {
+ if (width <= 0 || height <= 0) {
+ return;
+ }
+ copyTextureCoordinates(texture, mTempSourceRect);
+ mTempTargetRect.set(x, y, x + width, y + height);
+ convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+ drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+ }
+
+ private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) {
+ int left = 0;
+ int top = 0;
+ int right = texture.getWidth();
+ int bottom = texture.getHeight();
+ if (texture.hasBorder()) {
+ left = 1;
+ top = 1;
+ right -= 1;
+ bottom -= 1;
+ }
+ outRect.set(left, top, right, bottom);
+ }
+
+ @Override
+ public void drawTexture(BasicTexture texture, RectF source, RectF target) {
+ if (target.width() <= 0 || target.height() <= 0) {
+ return;
+ }
+ mTempSourceRect.set(source);
+ mTempTargetRect.set(target);
+
+ convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+ drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+ }
+
+ @Override
+ public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w,
+ int h) {
+ if (w <= 0 || h <= 0) {
+ return;
+ }
+ mTempTargetRect.set(x, y, x + w, y + h);
+ drawTextureRect(texture, textureTransform, mTempTargetRect);
+ }
+
+ private void drawTextureRect(BasicTexture texture, RectF source, RectF target) {
+ setTextureMatrix(source);
+ drawTextureRect(texture, mTempTextureMatrix, target);
+ }
+
+ private void setTextureMatrix(RectF source) {
+ mTempTextureMatrix[0] = source.width();
+ mTempTextureMatrix[5] = source.height();
+ mTempTextureMatrix[12] = source.left;
+ mTempTextureMatrix[13] = source.top;
+ }
+
+ // This function changes the source coordinate to the texture coordinates.
+ // It also clips the source and target coordinates if it is beyond the
+ // bound of the texture.
+ private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) {
+ int width = texture.getWidth();
+ int height = texture.getHeight();
+ int texWidth = texture.getTextureWidth();
+ int texHeight = texture.getTextureHeight();
+ // Convert to texture coordinates
+ source.left /= texWidth;
+ source.right /= texWidth;
+ source.top /= texHeight;
+ source.bottom /= texHeight;
+
+ // Clip if the rendering range is beyond the bound of the texture.
+ float xBound = (float) width / texWidth;
+ if (source.right > xBound) {
+ target.right = target.left + target.width() * (xBound - source.left) / source.width();
+ source.right = xBound;
+ }
+ float yBound = (float) height / texHeight;
+ if (source.bottom > yBound) {
+ target.bottom = target.top + target.height() * (yBound - source.top) / source.height();
+ source.bottom = yBound;
+ }
+ }
+
+ private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) {
+ ShaderParameter[] params = prepareTexture(texture);
+ setPosition(params, OFFSET_FILL_RECT);
+ GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0);
+ checkError();
+ if (texture.isFlippedVertically()) {
+ save(SAVE_FLAG_MATRIX);
+ translate(0, target.centerY());
+ scale(1, -1, 1);
+ translate(0, -target.centerY());
+ }
+ draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
+ target.width(), target.height());
+ if (texture.isFlippedVertically()) {
+ restore();
+ }
+ mCountTextureRect++;
+ }
+
+ private ShaderParameter[] prepareTexture(BasicTexture texture) {
+ ShaderParameter[] params;
+ int program;
+ if (texture.getTarget() == GLES20.GL_TEXTURE_2D) {
+ params = mTextureParameters;
+ program = mTextureProgram;
+ } else {
+ params = mOesTextureParameters;
+ program = mOesTextureProgram;
+ }
+ prepareTexture(texture, program, params);
+ return params;
+ }
+
+ private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
+ GLES20.glUseProgram(program);
+ checkError();
+ enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ checkError();
+ texture.onBind(this);
+ GLES20.glBindTexture(texture.getTarget(), texture.getId());
+ checkError();
+ GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
+ checkError();
+ GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
+ checkError();
+ }
+
+ @Override
+ public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer,
+ int indexBuffer, int indexCount) {
+ prepareTexture(texture, mMeshProgram, mMeshParameters);
+
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+ checkError();
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer);
+ checkError();
+ int positionHandle = mMeshParameters[INDEX_POSITION].handle;
+ GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
+ VERTEX_STRIDE, 0);
+ checkError();
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer);
+ checkError();
+ int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle;
+ GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+ false, VERTEX_STRIDE, 0);
+ checkError();
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+ checkError();
+
+ GLES20.glEnableVertexAttribArray(positionHandle);
+ checkError();
+ GLES20.glEnableVertexAttribArray(texCoordHandle);
+ checkError();
+
+ setMatrix(mMeshParameters, x, y, 1, 1);
+ GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0);
+ checkError();
+
+ GLES20.glDisableVertexAttribArray(positionHandle);
+ checkError();
+ GLES20.glDisableVertexAttribArray(texCoordHandle);
+ checkError();
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+ checkError();
+ mCountDrawMesh++;
+ }
+
+ @Override
+ public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) {
+ copyTextureCoordinates(texture, mTempSourceRect);
+ mTempTargetRect.set(x, y, x + w, y + h);
+ drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect);
+ }
+
+ @Override
+ public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) {
+ if (target.width() <= 0 || target.height() <= 0) {
+ return;
+ }
+ save(SAVE_FLAG_ALPHA);
+
+ float currentAlpha = getAlpha();
+ float cappedRatio = Math.min(1f, Math.max(0f, ratio));
+
+ float textureAlpha = (1f - cappedRatio) * currentAlpha;
+ setAlpha(textureAlpha);
+ drawTexture(texture, source, target);
+
+ float colorAlpha = cappedRatio * currentAlpha;
+ setAlpha(colorAlpha);
+ fillRect(target.left, target.top, target.width(), target.height(), toColor);
+
+ restore();
+ }
+
+ @Override
+ public boolean unloadTexture(BasicTexture texture) {
+ boolean unload = texture.isLoaded();
+ if (unload) {
+ synchronized (mUnboundTextures) {
+ mUnboundTextures.add(texture.getId());
+ }
+ }
+ return unload;
+ }
+
+ @Override
+ public void deleteBuffer(int bufferId) {
+ synchronized (mUnboundTextures) {
+ mDeleteBuffers.add(bufferId);
+ }
+ }
+
+ @Override
+ public void deleteRecycledResources() {
+ synchronized (mUnboundTextures) {
+ IntArray ids = mUnboundTextures;
+ if (mUnboundTextures.size() > 0) {
+ mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
+ ids.clear();
+ }
+
+ ids = mDeleteBuffers;
+ if (ids.size() > 0) {
+ mGLId.glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0);
+ ids.clear();
+ }
+ }
+ }
+
+ @Override
+ public void dumpStatisticsAndClear() {
+ String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh,
+ mCountTextureRect, mCountFillRect, mCountDrawLine);
+ mCountDrawMesh = 0;
+ mCountTextureRect = 0;
+ mCountFillRect = 0;
+ mCountDrawLine = 0;
+ Log.d(TAG, line);
+ }
+
+ @Override
+ public void endRenderTarget() {
+ RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1);
+ RawTexture texture = getTargetTexture();
+ setRenderTarget(oldTexture, texture);
+ restore(); // restore matrix and alpha
+ }
+
+ @Override
+ public void beginRenderTarget(RawTexture texture) {
+ save(); // save matrix and alpha and blending
+ RawTexture oldTexture = getTargetTexture();
+ mTargetTextures.add(texture);
+ setRenderTarget(oldTexture, texture);
+ }
+
+ private RawTexture getTargetTexture() {
+ return mTargetTextures.get(mTargetTextures.size() - 1);
+ }
+
+ private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) {
+ if (oldTexture == null && texture != null) {
+ GLES20.glGenFramebuffers(1, mFrameBuffer, 0);
+ checkError();
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]);
+ checkError();
+ } else if (oldTexture != null && texture == null) {
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ checkError();
+ GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0);
+ checkError();
+ }
+
+ if (texture == null) {
+ setSize(mScreenWidth, mScreenHeight);
+ } else {
+ setSize(texture.getWidth(), texture.getHeight());
+
+ if (!texture.isLoaded()) {
+ texture.prepare(this);
+ }
+
+ GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+ texture.getTarget(), texture.getId(), 0);
+ checkError();
+
+ checkFramebufferStatus();
+ }
+ }
+
+ private static void checkFramebufferStatus() {
+ int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
+ if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
+ String msg = "";
+ switch (status) {
+ case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+ break;
+ case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+ msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+ break;
+ case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+ break;
+ case GLES20.GL_FRAMEBUFFER_UNSUPPORTED:
+ msg = "GL_FRAMEBUFFER_UNSUPPORTED";
+ break;
+ }
+ throw new RuntimeException(msg + ":" + Integer.toHexString(status));
+ }
+ }
+
+ @Override
+ public void setTextureParameters(BasicTexture texture) {
+ int target = texture.getTarget();
+ GLES20.glBindTexture(target, texture.getId());
+ checkError();
+ GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ }
+
+ @Override
+ public void initializeTextureSize(BasicTexture texture, int format, int type) {
+ int target = texture.getTarget();
+ GLES20.glBindTexture(target, texture.getId());
+ checkError();
+ int width = texture.getTextureWidth();
+ int height = texture.getTextureHeight();
+ GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
+ }
+
+ @Override
+ public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+ int target = texture.getTarget();
+ GLES20.glBindTexture(target, texture.getId());
+ checkError();
+ GLUtils.texImage2D(target, 0, bitmap, 0);
+ }
+
+ @Override
+ public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+ int format, int type) {
+ int target = texture.getTarget();
+ GLES20.glBindTexture(target, texture.getId());
+ checkError();
+ GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+ }
+
+ @Override
+ public int uploadBuffer(FloatBuffer buf) {
+ return uploadBuffer(buf, FLOAT_SIZE);
+ }
+
+ @Override
+ public int uploadBuffer(ByteBuffer buf) {
+ return uploadBuffer(buf, 1);
+ }
+
+ private int uploadBuffer(Buffer buffer, int elementSize) {
+ mGLId.glGenBuffers(1, mTempIntArray, 0);
+ checkError();
+ int bufferId = mTempIntArray[0];
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
+ checkError();
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
+ GLES20.GL_STATIC_DRAW);
+ checkError();
+ return bufferId;
+ }
+
+ public static void checkError() {
+ int error = GLES20.glGetError();
+ if (error != 0) {
+ Throwable t = new Throwable();
+ Log.e(TAG, "GL error: " + error, t);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static void printMatrix(String message, float[] m, int offset) {
+ StringBuilder b = new StringBuilder(message);
+ for (int i = 0; i < MATRIX_SIZE; i++) {
+ b.append(' ');
+ if (i % 4 == 0) {
+ b.append('\n');
+ }
+ b.append(m[offset + i]);
+ }
+ Log.v(TAG, b.toString());
+ }
+
+ @Override
+ public void recoverFromLightCycle() {
+ GLES20.glViewport(0, 0, mWidth, mHeight);
+ GLES20.glDisable(GLES20.GL_DEPTH_TEST);
+ GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ checkError();
+ }
+
+ @Override
+ public void getBounds(Rect bounds, int x, int y, int width, int height) {
+ Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
+ Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
+ Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0);
+ Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4);
+ bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]);
+ bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]);
+ bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]);
+ bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]);
+ bounds.sort();
+ }
+
+ @Override
+ public GLId getGLId() {
+ return mGLId;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
new file mode 100644
index 0000000..6cd7149
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
@@ -0,0 +1,42 @@
+package com.android.gallery3d.glrenderer;
+
+import android.opengl.GLES20;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+public class GLES20IdImpl implements GLId {
+ private final int[] mTempIntArray = new int[1];
+
+ @Override
+ public int generateTexture() {
+ GLES20.glGenTextures(1, mTempIntArray, 0);
+ GLES20Canvas.checkError();
+ return mTempIntArray[0];
+ }
+
+ @Override
+ public void glGenBuffers(int n, int[] buffers, int offset) {
+ GLES20.glGenBuffers(n, buffers, offset);
+ GLES20Canvas.checkError();
+ }
+
+ @Override
+ public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+ GLES20.glDeleteTextures(n, textures, offset);
+ GLES20Canvas.checkError();
+ }
+
+
+ @Override
+ public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+ GLES20.glDeleteBuffers(n, buffers, offset);
+ GLES20Canvas.checkError();
+ }
+
+ @Override
+ public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+ GLES20.glDeleteFramebuffers(n, buffers, offset);
+ GLES20Canvas.checkError();
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLId.java b/src/com/android/gallery3d/glrenderer/GLId.java
new file mode 100644
index 0000000..3cec558
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLId.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+// This mimics corresponding GL functions.
+public interface GLId {
+ public int generateTexture();
+
+ public void glGenBuffers(int n, int[] buffers, int offset);
+
+ public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
+
+ public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
+
+ public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLPaint.java b/src/com/android/gallery3d/glrenderer/GLPaint.java
new file mode 100644
index 0000000..16b2206
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLPaint.java
@@ -0,0 +1,41 @@
+/*
+ * 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.gallery3d.glrenderer;
+
+import junit.framework.Assert;
+
+public class GLPaint {
+ private float mLineWidth = 1f;
+ private int mColor = 0;
+
+ public void setColor(int color) {
+ mColor = color;
+ }
+
+ public int getColor() {
+ return mColor;
+ }
+
+ public void setLineWidth(float width) {
+ Assert.assertTrue(width >= 0);
+ mLineWidth = width;
+ }
+
+ public float getLineWidth() {
+ return mLineWidth;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/RawTexture.java b/src/com/android/gallery3d/glrenderer/RawTexture.java
new file mode 100644
index 0000000..93f0fdf
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/RawTexture.java
@@ -0,0 +1,73 @@
+/*
+ * 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.gallery3d.glrenderer;
+
+import android.util.Log;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class RawTexture extends BasicTexture {
+ private static final String TAG = "RawTexture";
+
+ private final boolean mOpaque;
+ private boolean mIsFlipped;
+
+ public RawTexture(int width, int height, boolean opaque) {
+ mOpaque = opaque;
+ setSize(width, height);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ @Override
+ public boolean isFlippedVertically() {
+ return mIsFlipped;
+ }
+
+ public void setIsFlippedVertically(boolean isFlipped) {
+ mIsFlipped = isFlipped;
+ }
+
+ protected void prepare(GLCanvas canvas) {
+ GLId glId = canvas.getGLId();
+ mId = glId.generateTexture();
+ canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
+ canvas.setTextureParameters(this);
+ mState = STATE_LOADED;
+ setAssociatedCanvas(canvas);
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ if (isLoaded()) return true;
+ Log.w(TAG, "lost the content due to context change");
+ return false;
+ }
+
+ @Override
+ public void yield() {
+ // we cannot free the texture because we have no backup.
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+}
diff --git a/src/com/android/gallery3d/glrenderer/Texture.java b/src/com/android/gallery3d/glrenderer/Texture.java
new file mode 100644
index 0000000..3dcae4a
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/Texture.java
@@ -0,0 +1,44 @@
+/*
+ * 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.gallery3d.glrenderer;
+
+
+// Texture is a rectangular image which can be drawn on GLCanvas.
+// The isOpaque() function gives a hint about whether the texture is opaque,
+// so the drawing can be done faster.
+//
+// This is the current texture hierarchy:
+//
+// Texture
+// -- ColorTexture
+// -- FadeInTexture
+// -- BasicTexture
+// -- UploadedTexture
+// -- BitmapTexture
+// -- Tile
+// -- ResourceTexture
+// -- NinePatchTexture
+// -- CanvasTexture
+// -- StringTexture
+//
+public interface Texture {
+ public int getWidth();
+ public int getHeight();
+ public void draw(GLCanvas canvas, int x, int y);
+ public void draw(GLCanvas canvas, int x, int y, int w, int h);
+ public boolean isOpaque();
+}
diff --git a/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
new file mode 100644
index 0000000..f41a979
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -0,0 +1,298 @@
+/*
+ * 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.opengl.GLUtils;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+
+import javax.microedition.khronos.opengles.GL11;
+
+// UploadedTextures use a Bitmap for the content of the texture.
+//
+// Subclasses should implement onGetBitmap() to provide the Bitmap and
+// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
+// is not needed anymore.
+//
+// isContentValid() is meaningful only when the isLoaded() returns true.
+// It means whether the content needs to be updated.
+//
+// The user of this class should call recycle() when the texture is not
+// needed anymore.
+//
+// By default an UploadedTexture is opaque (so it can be drawn faster without
+// blending). The user or subclass can override it using setOpaque().
+public abstract class UploadedTexture extends BasicTexture {
+
+ // To prevent keeping allocation the borders, we store those used borders here.
+ // Since the length will be power of two, it won't use too much memory.
+ private static HashMap<BorderKey, Bitmap> sBorderLines =
+ new HashMap<BorderKey, Bitmap>();
+ private static BorderKey sBorderKey = new BorderKey();
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "Texture";
+ private boolean mContentValid = true;
+
+ // indicate this textures is being uploaded in background
+ private boolean mIsUploading = false;
+ private boolean mOpaque = true;
+ private boolean mThrottled = false;
+ private static int sUploadedCount;
+ private static final int UPLOAD_LIMIT = 100;
+
+ protected Bitmap mBitmap;
+ private int mBorder;
+
+ protected UploadedTexture() {
+ this(false);
+ }
+
+ protected UploadedTexture(boolean hasBorder) {
+ super(null, 0, STATE_UNLOADED);
+ if (hasBorder) {
+ setBorder(true);
+ mBorder = 1;
+ }
+ }
+
+ protected void setIsUploading(boolean uploading) {
+ mIsUploading = uploading;
+ }
+
+ public boolean isUploading() {
+ return mIsUploading;
+ }
+
+ private static class BorderKey implements Cloneable {
+ public boolean vertical;
+ public Config config;
+ public int length;
+
+ @Override
+ public int hashCode() {
+ int x = config.hashCode() ^ length;
+ return vertical ? x : -x;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof BorderKey)) return false;
+ BorderKey o = (BorderKey) object;
+ return vertical == o.vertical
+ && config == o.config && length == o.length;
+ }
+
+ @Override
+ public BorderKey clone() {
+ try {
+ return (BorderKey) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ protected void setThrottled(boolean throttled) {
+ mThrottled = throttled;
+ }
+
+ private static Bitmap getBorderLine(
+ boolean vertical, Config config, int length) {
+ BorderKey key = sBorderKey;
+ key.vertical = vertical;
+ key.config = config;
+ key.length = length;
+ Bitmap bitmap = sBorderLines.get(key);
+ if (bitmap == null) {
+ bitmap = vertical
+ ? Bitmap.createBitmap(1, length, config)
+ : Bitmap.createBitmap(length, 1, config);
+ sBorderLines.put(key.clone(), bitmap);
+ }
+ return bitmap;
+ }
+
+ private Bitmap getBitmap() {
+ if (mBitmap == null) {
+ mBitmap = onGetBitmap();
+ int w = mBitmap.getWidth() + mBorder * 2;
+ int h = mBitmap.getHeight() + mBorder * 2;
+ if (mWidth == UNSPECIFIED) {
+ setSize(w, h);
+ }
+ }
+ return mBitmap;
+ }
+
+ private void freeBitmap() {
+ Assert.assertTrue(mBitmap != null);
+ onFreeBitmap(mBitmap);
+ mBitmap = null;
+ }
+
+ @Override
+ public int getWidth() {
+ if (mWidth == UNSPECIFIED) getBitmap();
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ if (mWidth == UNSPECIFIED) getBitmap();
+ return mHeight;
+ }
+
+ protected abstract Bitmap onGetBitmap();
+
+ protected abstract void onFreeBitmap(Bitmap bitmap);
+
+ protected void invalidateContent() {
+ if (mBitmap != null) freeBitmap();
+ mContentValid = false;
+ mWidth = UNSPECIFIED;
+ mHeight = UNSPECIFIED;
+ }
+
+ /**
+ * Whether the content on GPU is valid.
+ */
+ public boolean isContentValid() {
+ return isLoaded() && mContentValid;
+ }
+
+ /**
+ * Updates the content on GPU's memory.
+ * @param canvas
+ */
+ public void updateContent(GLCanvas canvas) {
+ if (!isLoaded()) {
+ if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
+ return;
+ }
+ uploadToCanvas(canvas);
+ } else if (!mContentValid) {
+ Bitmap bitmap = getBitmap();
+ int format = GLUtils.getInternalFormat(bitmap);
+ int type = GLUtils.getType(bitmap);
+ canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+ freeBitmap();
+ mContentValid = true;
+ }
+ }
+
+ public static void resetUploadLimit() {
+ sUploadedCount = 0;
+ }
+
+ public static boolean uploadLimitReached() {
+ return sUploadedCount > UPLOAD_LIMIT;
+ }
+
+ private void uploadToCanvas(GLCanvas canvas) {
+
+ Bitmap bitmap = getBitmap();
+ if (bitmap != null) {
+ try {
+ int bWidth = bitmap.getWidth();
+ int bHeight = bitmap.getHeight();
+ int width = bWidth + mBorder * 2;
+ int height = bHeight + mBorder * 2;
+ int texWidth = getTextureWidth();
+ int texHeight = getTextureHeight();
+
+ Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
+
+ // Upload the bitmap to a new texture.
+ mId = canvas.getGLId().generateTexture();
+ canvas.setTextureParameters(this);
+
+ if (bWidth == texWidth && bHeight == texHeight) {
+ canvas.initializeTexture(this, bitmap);
+ } else {
+ int format = GLUtils.getInternalFormat(bitmap);
+ int type = GLUtils.getType(bitmap);
+ Config config = bitmap.getConfig();
+
+ canvas.initializeTextureSize(this, format, type);
+ canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+
+ if (mBorder > 0) {
+ // Left border
+ Bitmap line = getBorderLine(true, config, texHeight);
+ canvas.texSubImage2D(this, 0, 0, line, format, type);
+
+ // Top border
+ line = getBorderLine(false, config, texWidth);
+ canvas.texSubImage2D(this, 0, 0, line, format, type);
+ }
+
+ // Right border
+ if (mBorder + bWidth < texWidth) {
+ Bitmap line = getBorderLine(true, config, texHeight);
+ canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
+ }
+
+ // Bottom border
+ if (mBorder + bHeight < texHeight) {
+ Bitmap line = getBorderLine(false, config, texWidth);
+ canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
+ }
+ }
+ } finally {
+ freeBitmap();
+ }
+ // Update texture state.
+ setAssociatedCanvas(canvas);
+ mState = STATE_LOADED;
+ mContentValid = true;
+ } else {
+ mState = STATE_ERROR;
+ throw new RuntimeException("Texture load fail, no bitmap");
+ }
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ updateContent(canvas);
+ return isContentValid();
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
+ public void setOpaque(boolean isOpaque) {
+ mOpaque = isOpaque;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ if (mBitmap != null) freeBitmap();
+ }
+}
diff --git a/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java
new file mode 100644
index 0000000..2c4dc2c
--- /dev/null
+++ b/src/com/android/gallery3d/util/IntArray.java
@@ -0,0 +1,60 @@
+/*
+ * 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.gallery3d.util;
+
+public class IntArray {
+ private static final int INIT_CAPACITY = 8;
+
+ private int mData[] = new int[INIT_CAPACITY];
+ private int mSize = 0;
+
+ public void add(int value) {
+ if (mData.length == mSize) {
+ int temp[] = new int[mSize + mSize];
+ System.arraycopy(mData, 0, temp, 0, mSize);
+ mData = temp;
+ }
+ mData[mSize++] = value;
+ }
+
+ public int removeLast() {
+ mSize--;
+ return mData[mSize];
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ // For testing only
+ public int[] toArray(int[] result) {
+ if (result == null || result.length < mSize) {
+ result = new int[mSize];
+ }
+ System.arraycopy(mData, 0, result, 0, mSize);
+ return result;
+ }
+
+ public int[] getInternalArray() {
+ return mData;
+ }
+
+ public void clear() {
+ mSize = 0;
+ if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
+ }
+}
diff --git a/src/com/android/launcher3/AccessibleTabView.java b/src/com/android/launcher3/AccessibleTabView.java
new file mode 100644
index 0000000..90a7865
--- /dev/null
+++ b/src/com/android/launcher3/AccessibleTabView.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 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.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.TextView;
+
+/**
+ * We use a custom tab view to process our own focus traversals.
+ */
+public class AccessibleTabView extends TextView {
+ public AccessibleTabView(Context context) {
+ super(context);
+ }
+
+ public AccessibleTabView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AccessibleTabView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return FocusHelper.handleTabKeyEvent(this, keyCode, event)
+ || super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return FocusHelper.handleTabKeyEvent(this, keyCode, event)
+ || super.onKeyUp(keyCode, event);
+ }
+}
diff --git a/src/com/android/launcher3/AddAdapter.java b/src/com/android/launcher3/AddAdapter.java
new file mode 100644
index 0000000..ad15e75
--- /dev/null
+++ b/src/com/android/launcher3/AddAdapter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import com.android.launcher3.R;
+
+/**
+ * Adapter showing the types of items that can be added to a {@link Workspace}.
+ */
+public class AddAdapter extends BaseAdapter {
+
+ private final LayoutInflater mInflater;
+
+ private final ArrayList<ListItem> mItems = new ArrayList<ListItem>();
+
+ public static final int ITEM_SHORTCUT = 0;
+ public static final int ITEM_APPWIDGET = 1;
+ public static final int ITEM_APPLICATION = 2;
+ public static final int ITEM_WALLPAPER = 3;
+
+ /**
+ * Specific item in our list.
+ */
+ public class ListItem {
+ public final CharSequence text;
+ public final Drawable image;
+ public final int actionTag;
+
+ public ListItem(Resources res, int textResourceId, int imageResourceId, int actionTag) {
+ text = res.getString(textResourceId);
+ if (imageResourceId != -1) {
+ image = res.getDrawable(imageResourceId);
+ } else {
+ image = null;
+ }
+ this.actionTag = actionTag;
+ }
+ }
+
+ public AddAdapter(Launcher launcher) {
+ super();
+
+ mInflater = (LayoutInflater) launcher.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ // Create default actions
+ Resources res = launcher.getResources();
+
+ mItems.add(new ListItem(res, R.string.group_wallpapers,
+ R.mipmap.ic_launcher_wallpaper, ITEM_WALLPAPER));
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ListItem item = (ListItem) getItem(position);
+
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.add_list_item, parent, false);
+ }
+
+ TextView textView = (TextView) convertView;
+ textView.setTag(item);
+ textView.setText(item.text);
+ textView.setCompoundDrawablesWithIntrinsicBounds(item.image, null, null, null);
+
+ return convertView;
+ }
+
+ public int getCount() {
+ return mItems.size();
+ }
+
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+}
diff --git a/src/com/android/launcher3/Alarm.java b/src/com/android/launcher3/Alarm.java
new file mode 100644
index 0000000..91f9bd0
--- /dev/null
+++ b/src/com/android/launcher3/Alarm.java
@@ -0,0 +1,84 @@
+/*
+ * 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.os.Handler;
+
+public class Alarm implements Runnable{
+ // if we reach this time and the alarm hasn't been cancelled, call the listener
+ private long mAlarmTriggerTime;
+
+ // if we've scheduled a call to run() (ie called mHandler.postDelayed), this variable is true.
+ // We use this to avoid having multiple pending callbacks
+ private boolean mWaitingForCallback;
+
+ private Handler mHandler;
+ private OnAlarmListener mAlarmListener;
+ private boolean mAlarmPending = false;
+
+ public Alarm() {
+ mHandler = new Handler();
+ }
+
+ public void setOnAlarmListener(OnAlarmListener alarmListener) {
+ mAlarmListener = alarmListener;
+ }
+
+ // Sets the alarm to go off in a certain number of milliseconds. If the alarm is already set,
+ // it's overwritten and only the new alarm setting is used
+ public void setAlarm(long millisecondsInFuture) {
+ long currentTime = System.currentTimeMillis();
+ mAlarmPending = true;
+ mAlarmTriggerTime = currentTime + millisecondsInFuture;
+ if (!mWaitingForCallback) {
+ mHandler.postDelayed(this, mAlarmTriggerTime - currentTime);
+ mWaitingForCallback = true;
+ }
+ }
+
+ public void cancelAlarm() {
+ mAlarmTriggerTime = 0;
+ mAlarmPending = false;
+ }
+
+ // this is called when our timer runs out
+ public void run() {
+ mWaitingForCallback = false;
+ if (mAlarmTriggerTime != 0) {
+ long currentTime = System.currentTimeMillis();
+ if (mAlarmTriggerTime > currentTime) {
+ // We still need to wait some time to trigger spring loaded mode--
+ // post a new callback
+ mHandler.postDelayed(this, Math.max(0, mAlarmTriggerTime - currentTime));
+ mWaitingForCallback = true;
+ } else {
+ mAlarmPending = false;
+ if (mAlarmListener != null) {
+ mAlarmListener.onAlarm(this);
+ }
+ }
+ }
+ }
+
+ public boolean alarmPending() {
+ return mAlarmPending;
+ }
+}
+
+interface OnAlarmListener {
+ public void onAlarm(Alarm alarm);
+}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
new file mode 100644
index 0000000..d955e4e
--- /dev/null
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+
+/**
+ * Stores the list of all applications for the all apps view.
+ */
+class AllAppsList {
+ public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
+
+ /** The list off all apps. */
+ public ArrayList<AppInfo> data =
+ new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
+ /** The list of apps that have been added since the last notify() call. */
+ public ArrayList<AppInfo> added =
+ new ArrayList<AppInfo>(DEFAULT_APPLICATIONS_NUMBER);
+ /** The list of apps that have been removed since the last notify() call. */
+ public ArrayList<AppInfo> removed = new ArrayList<AppInfo>();
+ /** The list of apps that have been modified since the last notify() call. */
+ public ArrayList<AppInfo> modified = new ArrayList<AppInfo>();
+
+ private IconCache mIconCache;
+
+ private AppFilter mAppFilter;
+
+ /**
+ * Boring constructor.
+ */
+ public AllAppsList(IconCache iconCache, AppFilter appFilter) {
+ mIconCache = iconCache;
+ mAppFilter = appFilter;
+ }
+
+ /**
+ * Add the supplied ApplicationInfo objects to the list, and enqueue it into the
+ * list to broadcast when notify() is called.
+ *
+ * If the app is already in the list, doesn't add it.
+ */
+ public void add(AppInfo info) {
+ if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
+ return;
+ }
+ if (findActivity(data, info.componentName)) {
+ return;
+ }
+ data.add(info);
+ added.add(info);
+ }
+
+ public void clear() {
+ data.clear();
+ // TODO: do we clear these too?
+ added.clear();
+ removed.clear();
+ modified.clear();
+ }
+
+ public int size() {
+ return data.size();
+ }
+
+ public AppInfo get(int index) {
+ return data.get(index);
+ }
+
+ /**
+ * Add the icons for the supplied apk called packageName.
+ */
+ public void addPackage(Context context, String packageName) {
+ final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+
+ if (matches.size() > 0) {
+ for (ResolveInfo info : matches) {
+ add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+ }
+ }
+ }
+
+ /**
+ * Remove the apps for the given apk identified by packageName.
+ */
+ public void removePackage(String packageName) {
+ final List<AppInfo> data = this.data;
+ for (int i = data.size() - 1; i >= 0; i--) {
+ AppInfo info = data.get(i);
+ final ComponentName component = info.intent.getComponent();
+ if (packageName.equals(component.getPackageName())) {
+ removed.add(info);
+ data.remove(i);
+ }
+ }
+ // This is more aggressive than it needs to be.
+ mIconCache.flush();
+ }
+
+ /**
+ * Add and remove icons for this package which has been updated.
+ */
+ public void updatePackage(Context context, String packageName) {
+ final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+ if (matches.size() > 0) {
+ // Find disabled/removed activities and remove them from data and add them
+ // to the removed list.
+ for (int i = data.size() - 1; i >= 0; i--) {
+ final AppInfo applicationInfo = data.get(i);
+ final ComponentName component = applicationInfo.intent.getComponent();
+ if (packageName.equals(component.getPackageName())) {
+ if (!findActivity(matches, component)) {
+ removed.add(applicationInfo);
+ mIconCache.remove(component);
+ data.remove(i);
+ }
+ }
+ }
+
+ // Find enabled activities and add them to the adapter
+ // Also updates existing activities with new labels/icons
+ int count = matches.size();
+ for (int i = 0; i < count; i++) {
+ final ResolveInfo info = matches.get(i);
+ AppInfo applicationInfo = findApplicationInfoLocked(
+ info.activityInfo.applicationInfo.packageName,
+ info.activityInfo.name);
+ if (applicationInfo == null) {
+ add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+ } else {
+ mIconCache.remove(applicationInfo.componentName);
+ mIconCache.getTitleAndIcon(applicationInfo, info, null);
+ modified.add(applicationInfo);
+ }
+ }
+ } else {
+ // Remove all data for this package.
+ for (int i = data.size() - 1; i >= 0; i--) {
+ final AppInfo applicationInfo = data.get(i);
+ final ComponentName component = applicationInfo.intent.getComponent();
+ if (packageName.equals(component.getPackageName())) {
+ removed.add(applicationInfo);
+ mIconCache.remove(component);
+ data.remove(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Query the package manager for MAIN/LAUNCHER activities in the supplied package.
+ */
+ static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) {
+ final PackageManager packageManager = context.getPackageManager();
+
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mainIntent.setPackage(packageName);
+
+ final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
+ return apps != null ? apps : new ArrayList<ResolveInfo>();
+ }
+
+ /**
+ * Returns whether <em>apps</em> contains <em>component</em>.
+ */
+ private static boolean findActivity(List<ResolveInfo> apps, ComponentName component) {
+ final String className = component.getClassName();
+ for (ResolveInfo info : apps) {
+ final ActivityInfo activityInfo = info.activityInfo;
+ if (activityInfo.name.equals(className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether <em>apps</em> contains <em>component</em>.
+ */
+ private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component) {
+ final int N = apps.size();
+ for (int i=0; i<N; i++) {
+ final AppInfo info = apps.get(i);
+ if (info.componentName.equals(component)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find an ApplicationInfo object for the given packageName and className.
+ */
+ private AppInfo findApplicationInfoLocked(String packageName, String className) {
+ for (AppInfo info: data) {
+ final ComponentName component = info.intent.getComponent();
+ if (packageName.equals(component.getPackageName())
+ && className.equals(component.getClassName())) {
+ return info;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
new file mode 100644
index 0000000..e01436d
--- /dev/null
+++ b/src/com/android/launcher3/AppFilter.java
@@ -0,0 +1,35 @@
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.text.TextUtils;
+import android.util.Log;
+
+public abstract class AppFilter {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "AppFilter";
+
+ public abstract boolean shouldShowApp(ComponentName app);
+
+ public static AppFilter loadByName(String className) {
+ if (TextUtils.isEmpty(className)) return null;
+ if (DBG) Log.d(TAG, "Loading AppFilter: " + className);
+ try {
+ Class<?> cls = Class.forName(className);
+ return (AppFilter) cls.newInstance();
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "Bad AppFilter class", e);
+ return null;
+ } catch (InstantiationException e) {
+ Log.e(TAG, "Bad AppFilter class", e);
+ return null;
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Bad AppFilter class", e);
+ return null;
+ } catch (ClassCastException e) {
+ Log.e(TAG, "Bad AppFilter class", e);
+ return null;
+ }
+ }
+
+}
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
new file mode 100644
index 0000000..53f81bb
--- /dev/null
+++ b/src/com/android/launcher3/AppInfo.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Represents an app in AllAppsView.
+ */
+class AppInfo extends ItemInfo {
+ private static final String TAG = "Launcher3.AppInfo";
+
+ /**
+ * The intent used to start the application.
+ */
+ Intent intent;
+
+ /**
+ * A bitmap version of the application icon.
+ */
+ Bitmap iconBitmap;
+
+ /**
+ * The time at which the app was first installed.
+ */
+ long firstInstallTime;
+
+ ComponentName componentName;
+
+ static final int DOWNLOADED_FLAG = 1;
+ static final int UPDATED_SYSTEM_APP_FLAG = 2;
+
+ int flags = 0;
+
+ AppInfo() {
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
+ }
+
+ protected Intent getIntent() {
+ return intent;
+ }
+
+ /**
+ * Must not hold the Context.
+ */
+ public AppInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
+ HashMap<Object, CharSequence> labelCache) {
+ final String packageName = info.activityInfo.applicationInfo.packageName;
+
+ this.componentName = new ComponentName(packageName, info.activityInfo.name);
+ this.container = ItemInfo.NO_ID;
+ this.setActivity(componentName,
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+ try {
+ PackageInfo pi = pm.getPackageInfo(packageName, 0);
+ flags = initFlags(pi);
+ firstInstallTime = initFirstInstallTime(pi);
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
+ }
+
+ iconCache.getTitleAndIcon(this, info, labelCache);
+ }
+
+ public static int initFlags(PackageInfo pi) {
+ int appFlags = pi.applicationInfo.flags;
+ int flags = 0;
+ if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
+ flags |= DOWNLOADED_FLAG;
+
+ if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ flags |= UPDATED_SYSTEM_APP_FLAG;
+ }
+ }
+ return flags;
+ }
+
+ public static long initFirstInstallTime(PackageInfo pi) {
+ return pi.firstInstallTime;
+ }
+
+ public AppInfo(AppInfo info) {
+ super(info);
+ componentName = info.componentName;
+ title = info.title.toString();
+ intent = new Intent(info.intent);
+ flags = info.flags;
+ firstInstallTime = info.firstInstallTime;
+ }
+
+ /**
+ * Creates the application intent based on a component name and various launch flags.
+ * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
+ *
+ * @param className the class name of the component representing the intent
+ * @param launchFlags the launch flags
+ */
+ final void setActivity(ComponentName className, int launchFlags) {
+ intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(className);
+ intent.setFlags(launchFlags);
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
+ }
+
+ @Override
+ public String toString() {
+ return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
+ + " type=" + this.itemType + " container=" + this.container
+ + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
+ + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ }
+
+ public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
+ Log.d(tag, label + " size=" + list.size());
+ for (AppInfo info: list) {
+ Log.d(tag, " title=\"" + info.title + "\" iconBitmap="
+ + info.iconBitmap + " firstInstallTime="
+ + info.firstInstallTime);
+ }
+ }
+
+ public ShortcutInfo makeShortcut() {
+ return new ShortcutInfo(this);
+ }
+}
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
new file mode 100644
index 0000000..fcb04ea
--- /dev/null
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -0,0 +1,469 @@
+package com.android.launcher3;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+
+public class AppWidgetResizeFrame extends FrameLayout {
+ private LauncherAppWidgetHostView mWidgetView;
+ private CellLayout mCellLayout;
+ private DragLayer mDragLayer;
+ private ImageView mLeftHandle;
+ private ImageView mRightHandle;
+ private ImageView mTopHandle;
+ private ImageView mBottomHandle;
+
+ private boolean mLeftBorderActive;
+ private boolean mRightBorderActive;
+ private boolean mTopBorderActive;
+ private boolean mBottomBorderActive;
+
+ private int mWidgetPaddingLeft;
+ private int mWidgetPaddingRight;
+ private int mWidgetPaddingTop;
+ private int mWidgetPaddingBottom;
+
+ private int mBaselineWidth;
+ private int mBaselineHeight;
+ private int mBaselineX;
+ private int mBaselineY;
+ private int mResizeMode;
+
+ private int mRunningHInc;
+ private int mRunningVInc;
+ private int mMinHSpan;
+ private int mMinVSpan;
+ private int mDeltaX;
+ private int mDeltaY;
+ private int mDeltaXAddOn;
+ private int mDeltaYAddOn;
+
+ private int mBackgroundPadding;
+ private int mTouchTargetWidth;
+
+ private int mTopTouchRegionAdjustment = 0;
+ private int mBottomTouchRegionAdjustment = 0;
+
+ int[] mDirectionVector = new int[2];
+ int[] mLastDirectionVector = new int[2];
+ int[] mTmpPt = new int[2];
+
+ final int SNAP_DURATION = 150;
+ final int BACKGROUND_PADDING = 24;
+ final float DIMMED_HANDLE_ALPHA = 0f;
+ final float RESIZE_THRESHOLD = 0.66f;
+
+ private static Rect mTmpRect = new Rect();
+
+ public static final int LEFT = 0;
+ public static final int TOP = 1;
+ public static final int RIGHT = 2;
+ public static final int BOTTOM = 3;
+
+ private Launcher mLauncher;
+
+ public AppWidgetResizeFrame(Context context,
+ LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
+
+ super(context);
+ mLauncher = (Launcher) context;
+ mCellLayout = cellLayout;
+ mWidgetView = widgetView;
+ mResizeMode = widgetView.getAppWidgetInfo().resizeMode;
+ mDragLayer = dragLayer;
+
+ final AppWidgetProviderInfo info = widgetView.getAppWidgetInfo();
+ int[] result = Launcher.getMinSpanForWidget(mLauncher, info);
+ mMinHSpan = result[0];
+ mMinVSpan = result[1];
+
+ setBackgroundResource(R.drawable.widget_resize_frame_holo);
+ setPadding(0, 0, 0, 0);
+
+ LayoutParams lp;
+ mLeftHandle = new ImageView(context);
+ mLeftHandle.setImageResource(R.drawable.widget_resize_handle_left);
+ lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
+ Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ addView(mLeftHandle, lp);
+
+ mRightHandle = new ImageView(context);
+ mRightHandle.setImageResource(R.drawable.widget_resize_handle_right);
+ lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
+ Gravity.RIGHT | Gravity.CENTER_VERTICAL);
+ addView(mRightHandle, lp);
+
+ mTopHandle = new ImageView(context);
+ mTopHandle.setImageResource(R.drawable.widget_resize_handle_top);
+ lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+ addView(mTopHandle, lp);
+
+ mBottomHandle = new ImageView(context);
+ mBottomHandle.setImageResource(R.drawable.widget_resize_handle_bottom);
+ lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+ addView(mBottomHandle, lp);
+
+ Rect p = AppWidgetHostView.getDefaultPaddingForWidget(context,
+ widgetView.getAppWidgetInfo().provider, null);
+ mWidgetPaddingLeft = p.left;
+ mWidgetPaddingTop = p.top;
+ mWidgetPaddingRight = p.right;
+ mWidgetPaddingBottom = p.bottom;
+
+ if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
+ mTopHandle.setVisibility(GONE);
+ mBottomHandle.setVisibility(GONE);
+ } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
+ mLeftHandle.setVisibility(GONE);
+ mRightHandle.setVisibility(GONE);
+ }
+
+ final float density = mLauncher.getResources().getDisplayMetrics().density;
+ mBackgroundPadding = (int) Math.ceil(density * BACKGROUND_PADDING);
+ mTouchTargetWidth = 2 * mBackgroundPadding;
+
+ // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
+ // cells (same if not resized, or different) will be marked as occupied when the resize
+ // frame is dismissed.
+ mCellLayout.markCellsAsUnoccupiedForView(mWidgetView);
+ }
+
+ public boolean beginResizeIfPointInRegion(int x, int y) {
+ boolean horizontalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
+ boolean verticalActive = (mResizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
+
+ mLeftBorderActive = (x < mTouchTargetWidth) && horizontalActive;
+ mRightBorderActive = (x > getWidth() - mTouchTargetWidth) && horizontalActive;
+ mTopBorderActive = (y < mTouchTargetWidth + mTopTouchRegionAdjustment) && verticalActive;
+ mBottomBorderActive = (y > getHeight() - mTouchTargetWidth + mBottomTouchRegionAdjustment)
+ && verticalActive;
+
+ boolean anyBordersActive = mLeftBorderActive || mRightBorderActive
+ || mTopBorderActive || mBottomBorderActive;
+
+ mBaselineWidth = getMeasuredWidth();
+ mBaselineHeight = getMeasuredHeight();
+ mBaselineX = getLeft();
+ mBaselineY = getTop();
+
+ if (anyBordersActive) {
+ mLeftHandle.setAlpha(mLeftBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mRightHandle.setAlpha(mRightBorderActive ? 1.0f :DIMMED_HANDLE_ALPHA);
+ mTopHandle.setAlpha(mTopBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ mBottomHandle.setAlpha(mBottomBorderActive ? 1.0f : DIMMED_HANDLE_ALPHA);
+ }
+ return anyBordersActive;
+ }
+
+ /**
+ * Here we bound the deltas such that the frame cannot be stretched beyond the extents
+ * of the CellLayout, and such that the frame's borders can't cross.
+ */
+ public void updateDeltas(int deltaX, int deltaY) {
+ if (mLeftBorderActive) {
+ mDeltaX = Math.max(-mBaselineX, deltaX);
+ mDeltaX = Math.min(mBaselineWidth - 2 * mTouchTargetWidth, mDeltaX);
+ } else if (mRightBorderActive) {
+ mDeltaX = Math.min(mDragLayer.getWidth() - (mBaselineX + mBaselineWidth), deltaX);
+ mDeltaX = Math.max(-mBaselineWidth + 2 * mTouchTargetWidth, mDeltaX);
+ }
+
+ if (mTopBorderActive) {
+ mDeltaY = Math.max(-mBaselineY, deltaY);
+ mDeltaY = Math.min(mBaselineHeight - 2 * mTouchTargetWidth, mDeltaY);
+ } else if (mBottomBorderActive) {
+ mDeltaY = Math.min(mDragLayer.getHeight() - (mBaselineY + mBaselineHeight), deltaY);
+ mDeltaY = Math.max(-mBaselineHeight + 2 * mTouchTargetWidth, mDeltaY);
+ }
+ }
+
+ public void visualizeResizeForDelta(int deltaX, int deltaY) {
+ visualizeResizeForDelta(deltaX, deltaY, false);
+ }
+
+ /**
+ * Based on the deltas, we resize the frame, and, if needed, we resize the widget.
+ */
+ private void visualizeResizeForDelta(int deltaX, int deltaY, boolean onDismiss) {
+ updateDeltas(deltaX, deltaY);
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+
+ if (mLeftBorderActive) {
+ lp.x = mBaselineX + mDeltaX;
+ lp.width = mBaselineWidth - mDeltaX;
+ } else if (mRightBorderActive) {
+ lp.width = mBaselineWidth + mDeltaX;
+ }
+
+ if (mTopBorderActive) {
+ lp.y = mBaselineY + mDeltaY;
+ lp.height = mBaselineHeight - mDeltaY;
+ } else if (mBottomBorderActive) {
+ lp.height = mBaselineHeight + mDeltaY;
+ }
+
+ resizeWidgetIfNeeded(onDismiss);
+ requestLayout();
+ }
+
+ /**
+ * Based on the current deltas, we determine if and how to resize the widget.
+ */
+ private void resizeWidgetIfNeeded(boolean onDismiss) {
+ int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
+ int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+
+ int deltaX = mDeltaX + mDeltaXAddOn;
+ int deltaY = mDeltaY + mDeltaYAddOn;
+
+ float hSpanIncF = 1.0f * deltaX / xThreshold - mRunningHInc;
+ float vSpanIncF = 1.0f * deltaY / yThreshold - mRunningVInc;
+
+ int hSpanInc = 0;
+ int vSpanInc = 0;
+ int cellXInc = 0;
+ int cellYInc = 0;
+
+ int countX = mCellLayout.getCountX();
+ int countY = mCellLayout.getCountY();
+
+ if (Math.abs(hSpanIncF) > RESIZE_THRESHOLD) {
+ hSpanInc = Math.round(hSpanIncF);
+ }
+ if (Math.abs(vSpanIncF) > RESIZE_THRESHOLD) {
+ vSpanInc = Math.round(vSpanIncF);
+ }
+
+ if (!onDismiss && (hSpanInc == 0 && vSpanInc == 0)) return;
+
+
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mWidgetView.getLayoutParams();
+
+ int spanX = lp.cellHSpan;
+ int spanY = lp.cellVSpan;
+ int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
+ int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
+
+ int hSpanDelta = 0;
+ int vSpanDelta = 0;
+
+ // For each border, we bound the resizing based on the minimum width, and the maximum
+ // expandability.
+ if (mLeftBorderActive) {
+ cellXInc = Math.max(-cellX, hSpanInc);
+ cellXInc = Math.min(lp.cellHSpan - mMinHSpan, cellXInc);
+ hSpanInc *= -1;
+ hSpanInc = Math.min(cellX, hSpanInc);
+ hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
+ hSpanDelta = -hSpanInc;
+
+ } else if (mRightBorderActive) {
+ hSpanInc = Math.min(countX - (cellX + spanX), hSpanInc);
+ hSpanInc = Math.max(-(lp.cellHSpan - mMinHSpan), hSpanInc);
+ hSpanDelta = hSpanInc;
+ }
+
+ if (mTopBorderActive) {
+ cellYInc = Math.max(-cellY, vSpanInc);
+ cellYInc = Math.min(lp.cellVSpan - mMinVSpan, cellYInc);
+ vSpanInc *= -1;
+ vSpanInc = Math.min(cellY, vSpanInc);
+ vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
+ vSpanDelta = -vSpanInc;
+ } else if (mBottomBorderActive) {
+ vSpanInc = Math.min(countY - (cellY + spanY), vSpanInc);
+ vSpanInc = Math.max(-(lp.cellVSpan - mMinVSpan), vSpanInc);
+ vSpanDelta = vSpanInc;
+ }
+
+ mDirectionVector[0] = 0;
+ mDirectionVector[1] = 0;
+ // Update the widget's dimensions and position according to the deltas computed above
+ if (mLeftBorderActive || mRightBorderActive) {
+ spanX += hSpanInc;
+ cellX += cellXInc;
+ if (hSpanDelta != 0) {
+ mDirectionVector[0] = mLeftBorderActive ? -1 : 1;
+ }
+ }
+
+ if (mTopBorderActive || mBottomBorderActive) {
+ spanY += vSpanInc;
+ cellY += cellYInc;
+ if (vSpanDelta != 0) {
+ mDirectionVector[1] = mTopBorderActive ? -1 : 1;
+ }
+ }
+
+ if (!onDismiss && vSpanDelta == 0 && hSpanDelta == 0) return;
+
+ // We always want the final commit to match the feedback, so we make sure to use the
+ // last used direction vector when committing the resize / reorder.
+ if (onDismiss) {
+ mDirectionVector[0] = mLastDirectionVector[0];
+ mDirectionVector[1] = mLastDirectionVector[1];
+ } else {
+ mLastDirectionVector[0] = mDirectionVector[0];
+ mLastDirectionVector[1] = mDirectionVector[1];
+ }
+
+ if (mCellLayout.createAreaForResize(cellX, cellY, spanX, spanY, mWidgetView,
+ mDirectionVector, onDismiss)) {
+ lp.tmpCellX = cellX;
+ lp.tmpCellY = cellY;
+ lp.cellHSpan = spanX;
+ lp.cellVSpan = spanY;
+ mRunningVInc += vSpanDelta;
+ mRunningHInc += hSpanDelta;
+ if (!onDismiss) {
+ updateWidgetSizeRanges(mWidgetView, mLauncher, spanX, spanY);
+ }
+ }
+ mWidgetView.requestLayout();
+ }
+
+ static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
+ int spanX, int spanY) {
+
+ getWidgetSizeRanges(launcher, spanX, spanY, mTmpRect);
+ widgetView.updateAppWidgetSize(null, mTmpRect.left, mTmpRect.top,
+ mTmpRect.right, mTmpRect.bottom);
+ }
+
+ static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) {
+ if (rect == null) {
+ rect = new Rect();
+ }
+ Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE);
+ Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT);
+ final float density = launcher.getResources().getDisplayMetrics().density;
+
+ // Compute landscape size
+ int cellWidth = landMetrics.left;
+ int cellHeight = landMetrics.top;
+ int widthGap = landMetrics.right;
+ int heightGap = landMetrics.bottom;
+ int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
+ int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
+
+ // Compute portrait size
+ cellWidth = portMetrics.left;
+ cellHeight = portMetrics.top;
+ widthGap = portMetrics.right;
+ heightGap = portMetrics.bottom;
+ int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density);
+ int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density);
+ rect.set(portWidth, landHeight, landWidth, portHeight);
+ return rect;
+ }
+
+ /**
+ * This is the final step of the resize. Here we save the new widget size and position
+ * to LauncherModel and animate the resize frame.
+ */
+ public void commitResize() {
+ resizeWidgetIfNeeded(true);
+ requestLayout();
+ }
+
+ public void onTouchUp() {
+ int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
+ int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
+
+ mDeltaXAddOn = mRunningHInc * xThreshold;
+ mDeltaYAddOn = mRunningVInc * yThreshold;
+ mDeltaX = 0;
+ mDeltaY = 0;
+
+ post(new Runnable() {
+ @Override
+ public void run() {
+ snapToWidget(true);
+ }
+ });
+ }
+
+ public void snapToWidget(boolean animate) {
+ final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ int newWidth = mWidgetView.getWidth() + 2 * mBackgroundPadding - mWidgetPaddingLeft -
+ mWidgetPaddingRight;
+ int newHeight = mWidgetView.getHeight() + 2 * mBackgroundPadding - mWidgetPaddingTop -
+ mWidgetPaddingBottom;
+
+ mTmpPt[0] = mWidgetView.getLeft();
+ mTmpPt[1] = mWidgetView.getTop();
+ mDragLayer.getDescendantCoordRelativeToSelf(mCellLayout.getShortcutsAndWidgets(), mTmpPt);
+
+ int newX = mTmpPt[0] - mBackgroundPadding + mWidgetPaddingLeft;
+ int newY = mTmpPt[1] - mBackgroundPadding + mWidgetPaddingTop;
+
+ // We need to make sure the frame's touchable regions lie fully within the bounds of the
+ // DragLayer. We allow the actual handles to be clipped, but we shift the touch regions
+ // down accordingly to provide a proper touch target.
+ if (newY < 0) {
+ // In this case we shift the touch region down to start at the top of the DragLayer
+ mTopTouchRegionAdjustment = -newY;
+ } else {
+ mTopTouchRegionAdjustment = 0;
+ }
+ if (newY + newHeight > mDragLayer.getHeight()) {
+ // In this case we shift the touch region up to end at the bottom of the DragLayer
+ mBottomTouchRegionAdjustment = -(newY + newHeight - mDragLayer.getHeight());
+ } else {
+ mBottomTouchRegionAdjustment = 0;
+ }
+
+ if (!animate) {
+ lp.width = newWidth;
+ lp.height = newHeight;
+ lp.x = newX;
+ lp.y = newY;
+ mLeftHandle.setAlpha(1.0f);
+ mRightHandle.setAlpha(1.0f);
+ mTopHandle.setAlpha(1.0f);
+ mBottomHandle.setAlpha(1.0f);
+ requestLayout();
+ } else {
+ PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", lp.width, newWidth);
+ PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", lp.height,
+ newHeight);
+ PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", lp.x, newX);
+ PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY);
+ ObjectAnimator oa =
+ LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y);
+ ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, "alpha", 1.0f);
+ ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, "alpha", 1.0f);
+ ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, "alpha", 1.0f);
+ ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, "alpha", 1.0f);
+ oa.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ requestLayout();
+ }
+ });
+ AnimatorSet set = LauncherAnimUtils.createAnimatorSet();
+ if (mResizeMode == AppWidgetProviderInfo.RESIZE_VERTICAL) {
+ set.playTogether(oa, topOa, bottomOa);
+ } else if (mResizeMode == AppWidgetProviderInfo.RESIZE_HORIZONTAL) {
+ set.playTogether(oa, leftOa, rightOa);
+ } else {
+ set.playTogether(oa, leftOa, rightOa, topOa, bottomOa);
+ }
+
+ set.setDuration(SNAP_DURATION);
+ set.start();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/AppsCustomizeCellLayout.java b/src/com/android/launcher3/AppsCustomizeCellLayout.java
new file mode 100644
index 0000000..3c8bda9
--- /dev/null
+++ b/src/com/android/launcher3/AppsCustomizeCellLayout.java
@@ -0,0 +1,63 @@
+/*
+ * 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.view.View;
+
+public class AppsCustomizeCellLayout extends CellLayout implements Page {
+ public AppsCustomizeCellLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void removeAllViewsOnPage() {
+ removeAllViews();
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+
+ @Override
+ public void removeViewOnPageAt(int index) {
+ removeViewAt(index);
+ }
+
+ @Override
+ public int getPageChildCount() {
+ return getChildCount();
+ }
+
+ @Override
+ public View getChildOnPageAt(int i) {
+ return getChildAt(i);
+ }
+
+ @Override
+ public int indexOfChildOnPage(View v) {
+ return indexOfChild(v);
+ }
+
+ /**
+ * Clears all the key listeners for the individual icons.
+ */
+ public void resetChildrenOnKeyListeners() {
+ ShortcutAndWidgetContainer children = getShortcutsAndWidgets();
+ int childCount = children.getChildCount();
+ for (int j = 0; j < childCount; ++j) {
+ children.getChildAt(j).setOnKeyListener(null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
new file mode 100644
index 0000000..9b35bb5
--- /dev/null
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -0,0 +1,1693 @@
+/*
+ * Copyright (C) 2011 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.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.launcher3.DropTarget.DragObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A simple callback interface which also provides the results of the task.
+ */
+interface AsyncTaskCallback {
+ void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data);
+}
+
+/**
+ * The data needed to perform either of the custom AsyncTasks.
+ */
+class AsyncTaskPageData {
+ enum Type {
+ LoadWidgetPreviewData
+ }
+
+ AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR,
+ AsyncTaskCallback postR, WidgetPreviewLoader w) {
+ page = p;
+ items = l;
+ generatedImages = new ArrayList<Bitmap>();
+ maxImageWidth = cw;
+ maxImageHeight = ch;
+ doInBackgroundCallback = bgR;
+ postExecuteCallback = postR;
+ widgetPreviewLoader = w;
+ }
+ void cleanup(boolean cancelled) {
+ // Clean up any references to source/generated bitmaps
+ if (generatedImages != null) {
+ if (cancelled) {
+ for (int i = 0; i < generatedImages.size(); i++) {
+ widgetPreviewLoader.recycleBitmap(items.get(i), generatedImages.get(i));
+ }
+ }
+ generatedImages.clear();
+ }
+ }
+ int page;
+ ArrayList<Object> items;
+ ArrayList<Bitmap> sourceImages;
+ ArrayList<Bitmap> generatedImages;
+ int maxImageWidth;
+ int maxImageHeight;
+ AsyncTaskCallback doInBackgroundCallback;
+ AsyncTaskCallback postExecuteCallback;
+ WidgetPreviewLoader widgetPreviewLoader;
+}
+
+/**
+ * A generic template for an async task used in AppsCustomize.
+ */
+class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTaskPageData> {
+ AppsCustomizeAsyncTask(int p, AsyncTaskPageData.Type ty) {
+ page = p;
+ threadPriority = Process.THREAD_PRIORITY_DEFAULT;
+ dataType = ty;
+ }
+ @Override
+ protected AsyncTaskPageData doInBackground(AsyncTaskPageData... params) {
+ if (params.length != 1) return null;
+ // Load each of the widget previews in the background
+ params[0].doInBackgroundCallback.run(this, params[0]);
+ return params[0];
+ }
+ @Override
+ protected void onPostExecute(AsyncTaskPageData result) {
+ // All the widget previews are loaded, so we can just callback to inflate the page
+ result.postExecuteCallback.run(this, result);
+ }
+
+ void setThreadPriority(int p) {
+ threadPriority = p;
+ }
+ void syncThreadPriority() {
+ Process.setThreadPriority(threadPriority);
+ }
+
+ // The page that this async task is associated with
+ AsyncTaskPageData.Type dataType;
+ int page;
+ int threadPriority;
+}
+
+/**
+ * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
+ */
+public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
+ View.OnClickListener, View.OnKeyListener, DragSource,
+ PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
+ LauncherTransitionable {
+ static final String TAG = "AppsCustomizePagedView";
+
+ /**
+ * The different content types that this paged view can show.
+ */
+ public enum ContentType {
+ Applications,
+ Widgets
+ }
+ private ContentType mContentType = ContentType.Applications;
+
+ // Refs
+ private Launcher mLauncher;
+ private DragController mDragController;
+ private final LayoutInflater mLayoutInflater;
+ private final PackageManager mPackageManager;
+
+ // Save and Restore
+ private int mSaveInstanceStateItemIndex = -1;
+ private PagedViewIcon mPressedIcon;
+
+ // Content
+ private ArrayList<AppInfo> mApps;
+ private ArrayList<Object> mWidgets;
+
+ // Cling
+ private boolean mHasShownAllAppsCling;
+ private int mClingFocusedX;
+ private int mClingFocusedY;
+
+ // Caching
+ private Canvas mCanvas;
+ private IconCache mIconCache;
+
+ // Dimens
+ private int mContentWidth, mContentHeight;
+ private int mWidgetCountX, mWidgetCountY;
+ private int mWidgetWidthGap, mWidgetHeightGap;
+ private PagedViewCellLayout mWidgetSpacingLayout;
+ private int mNumAppsPages;
+ private int mNumWidgetPages;
+
+ // Relating to the scroll and overscroll effects
+ Workspace.ZInterpolator mZInterpolator = new Workspace.ZInterpolator(0.5f);
+ private static float CAMERA_DISTANCE = 6500;
+ private static float TRANSITION_SCALE_FACTOR = 0.74f;
+ private static float TRANSITION_PIVOT = 0.65f;
+ private static float TRANSITION_MAX_ROTATION = 22;
+ private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
+ private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
+ private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
+
+ public static boolean DISABLE_ALL_APPS = false;
+
+ // Previews & outlines
+ ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
+ private static final int sPageSleepDelay = 200;
+
+ private Runnable mInflateWidgetRunnable = null;
+ private Runnable mBindWidgetRunnable = null;
+ static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
+ static final int WIDGET_PRELOAD_PENDING = 0;
+ static final int WIDGET_BOUND = 1;
+ static final int WIDGET_INFLATED = 2;
+ int mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+ int mWidgetLoadingId = -1;
+ PendingAddWidgetInfo mCreateWidgetInfo = null;
+ private boolean mDraggingWidget = false;
+
+ private Toast mWidgetInstructionToast;
+
+ // Deferral of loading widget previews during launcher transitions
+ private boolean mInTransition;
+ private ArrayList<AsyncTaskPageData> mDeferredSyncWidgetPageItems =
+ new ArrayList<AsyncTaskPageData>();
+ private ArrayList<Runnable> mDeferredPrepareLoadWidgetPreviewsTasks =
+ new ArrayList<Runnable>();
+
+ private Rect mTmpRect = new Rect();
+
+ // Used for drawing shortcut previews
+ BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
+ PaintCache mCachedShortcutPreviewPaint = new PaintCache();
+ CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
+
+ // Used for drawing widget previews
+ CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
+ RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
+ RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
+ PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
+
+ WidgetPreviewLoader mWidgetPreviewLoader;
+
+ private boolean mInBulkBind;
+ private boolean mNeedToUpdatePageCountsAndInvalidateData;
+
+ public AppsCustomizePagedView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ mPackageManager = context.getPackageManager();
+ mApps = new ArrayList<AppInfo>();
+ mWidgets = new ArrayList<Object>();
+ mIconCache = (LauncherAppState.getInstance()).getIconCache();
+ mCanvas = new Canvas();
+ mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>();
+
+ // Save the default widget preview background
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mWidgetWidthGap = mWidgetHeightGap = grid.edgeMarginPx;
+ mWidgetCountX = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountX, 2);
+ mWidgetCountY = a.getInt(R.styleable.AppsCustomizePagedView_widgetCountY, 2);
+ mClingFocusedX = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedX, 0);
+ mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0);
+ a.recycle();
+ mWidgetSpacingLayout = new PagedViewCellLayout(getContext());
+
+ // The padding on the non-matched dimension for the default widget preview icons
+ // (top + bottom)
+ mFadeInAdjacentScreens = false;
+
+ // Unless otherwise specified this view is important for accessibility.
+ if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ mCenterPagesVertically = false;
+
+ Context context = getContext();
+ Resources r = context.getResources();
+ setDragSlopeThreshold(r.getInteger(R.integer.config_appsCustomizeDragSlopeThreshold)/100f);
+ }
+
+ public void onFinishInflate() {
+ super.onFinishInflate();
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ setPadding(grid.edgeMarginPx, 2 * grid.edgeMarginPx,
+ grid.edgeMarginPx, 2 * grid.edgeMarginPx);
+ }
+
+ /** Returns the item index of the center item on this page so that we can restore to this
+ * item index when we rotate. */
+ private int getMiddleComponentIndexOnCurrentPage() {
+ int i = -1;
+ if (getPageCount() > 0) {
+ int currentPage = getCurrentPage();
+ if (mContentType == ContentType.Applications) {
+ AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(currentPage);
+ ShortcutAndWidgetContainer childrenLayout = layout.getShortcutsAndWidgets();
+ int numItemsPerPage = mCellCountX * mCellCountY;
+ int childCount = childrenLayout.getChildCount();
+ if (childCount > 0) {
+ i = (currentPage * numItemsPerPage) + (childCount / 2);
+ }
+ } else if (mContentType == ContentType.Widgets) {
+ int numApps = mApps.size();
+ PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(currentPage);
+ int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+ int childCount = layout.getChildCount();
+ if (childCount > 0) {
+ i = numApps +
+ (currentPage * numItemsPerPage) + (childCount / 2);
+ }
+ } else {
+ throw new RuntimeException("Invalid ContentType");
+ }
+ }
+ return i;
+ }
+
+ /** Get the index of the item to restore to if we need to restore the current page. */
+ int getSaveInstanceStateIndex() {
+ if (mSaveInstanceStateItemIndex == -1) {
+ mSaveInstanceStateItemIndex = getMiddleComponentIndexOnCurrentPage();
+ }
+ return mSaveInstanceStateItemIndex;
+ }
+
+ /** Returns the page in the current orientation which is expected to contain the specified
+ * item index. */
+ int getPageForComponent(int index) {
+ if (index < 0) return 0;
+
+ if (index < mApps.size()) {
+ int numItemsPerPage = mCellCountX * mCellCountY;
+ return (index / numItemsPerPage);
+ } else {
+ int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+ return (index - mApps.size()) / numItemsPerPage;
+ }
+ }
+
+ /** Restores the page for an item at the specified index */
+ void restorePageForIndex(int index) {
+ if (index < 0) return;
+ mSaveInstanceStateItemIndex = index;
+ }
+
+ private void updatePageCounts() {
+ mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
+ (float) (mWidgetCountX * mWidgetCountY));
+ mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
+ }
+
+ protected void onDataReady(int width, int height) {
+ if (mWidgetPreviewLoader == null) {
+ mWidgetPreviewLoader = new WidgetPreviewLoader(mLauncher);
+ }
+
+ // Now that the data is ready, we can calculate the content width, the number of cells to
+ // use for each page
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mWidgetSpacingLayout.setPadding(mPageLayoutPaddingLeft, mPageLayoutPaddingTop,
+ mPageLayoutPaddingRight, mPageLayoutPaddingBottom);
+ mCellCountX = (int) grid.allAppsNumCols;
+ mCellCountY = (int) grid.allAppsNumRows;
+ updatePageCounts();
+
+ // Force a measure to update recalculate the gaps
+ mContentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+ mContentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+ int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
+ int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
+ mWidgetSpacingLayout.measure(widthSpec, heightSpec);
+
+ AppsCustomizeTabHost host = (AppsCustomizeTabHost) getTabHost();
+ final boolean hostIsTransitioning = host.isTransitioning();
+
+ // Restore the page
+ int page = getPageForComponent(mSaveInstanceStateItemIndex);
+ invalidatePageData(Math.max(0, page), hostIsTransitioning);
+
+ // Show All Apps cling if we are finished transitioning, otherwise, we will try again when
+ // the transition completes in AppsCustomizeTabHost (otherwise the wrong offsets will be
+ // returned while animating)
+ if (!hostIsTransitioning) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ showAllAppsCling();
+ }
+ });
+ }
+ }
+
+ void showAllAppsCling() {
+ if (!mHasShownAllAppsCling && isDataReady()) {
+ mHasShownAllAppsCling = true;
+ // Calculate the position for the cling punch through
+ int[] offset = new int[2];
+ int[] pos = mWidgetSpacingLayout.estimateCellPosition(mClingFocusedX, mClingFocusedY);
+ mLauncher.getDragLayer().getLocationInDragLayer(this, offset);
+ // PagedViews are centered horizontally but top aligned
+ // Note we have to shift the items up now that Launcher sits under the status bar
+ pos[0] += (getMeasuredWidth() - mWidgetSpacingLayout.getMeasuredWidth()) / 2 +
+ offset[0];
+ pos[1] += offset[1] - mLauncher.getDragLayer().getPaddingTop();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (!isDataReady()) {
+ if ((DISABLE_ALL_APPS || !mApps.isEmpty()) && !mWidgets.isEmpty()) {
+ setDataIsReady();
+ setMeasuredDimension(width, height);
+ onDataReady(width, height);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ public void onPackagesUpdated(ArrayList<Object> widgetsAndShortcuts) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ // Get the list of widgets and shortcuts
+ mWidgets.clear();
+ for (Object o : widgetsAndShortcuts) {
+ if (o instanceof AppWidgetProviderInfo) {
+ AppWidgetProviderInfo widget = (AppWidgetProviderInfo) o;
+ if (!app.shouldShowAppOrWidgetProvider(widget.provider)) {
+ continue;
+ }
+ widget.label = widget.label.trim();
+ if (widget.minWidth > 0 && widget.minHeight > 0) {
+ // Ensure that all widgets we show can be added on a workspace of this size
+ int[] spanXY = Launcher.getSpanForWidget(mLauncher, widget);
+ int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, widget);
+ int minSpanX = Math.min(spanXY[0], minSpanXY[0]);
+ int minSpanY = Math.min(spanXY[1], minSpanXY[1]);
+ if (minSpanX <= (int) grid.numColumns &&
+ minSpanY <= (int) grid.numRows) {
+ mWidgets.add(widget);
+ } else {
+ Log.e(TAG, "Widget " + widget.provider + " can not fit on this device (" +
+ widget.minWidth + ", " + widget.minHeight + ")");
+ }
+ } else {
+ Log.e(TAG, "Widget " + widget.provider + " has invalid dimensions (" +
+ widget.minWidth + ", " + widget.minHeight + ")");
+ }
+ } else {
+ // just add shortcuts
+ mWidgets.add(o);
+ }
+ }
+ updatePageCountsAndInvalidateData();
+ }
+
+ public void setBulkBind(boolean bulkBind) {
+ if (bulkBind) {
+ mInBulkBind = true;
+ } else {
+ mInBulkBind = false;
+ if (mNeedToUpdatePageCountsAndInvalidateData) {
+ updatePageCountsAndInvalidateData();
+ }
+ }
+ }
+
+ private void updatePageCountsAndInvalidateData() {
+ if (mInBulkBind) {
+ mNeedToUpdatePageCountsAndInvalidateData = true;
+ } else {
+ updatePageCounts();
+ invalidateOnDataChange();
+ mNeedToUpdatePageCountsAndInvalidateData = false;
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ // When we have exited all apps or are in transition, disregard clicks
+ if (!mLauncher.isAllAppsVisible() ||
+ mLauncher.getWorkspace().isSwitchingState()) return;
+
+ if (v instanceof PagedViewIcon) {
+ // Animate some feedback to the click
+ final AppInfo appInfo = (AppInfo) v.getTag();
+
+ // Lock the drawable state to pressed until we return to Launcher
+ if (mPressedIcon != null) {
+ mPressedIcon.lockDrawableState();
+ }
+ mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
+ mLauncher.getStats().recordLaunch(appInfo.intent);
+ } else if (v instanceof PagedViewWidget) {
+ // Let the user know that they have to long press to add a widget
+ if (mWidgetInstructionToast != null) {
+ mWidgetInstructionToast.cancel();
+ }
+ mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
+ Toast.LENGTH_SHORT);
+ mWidgetInstructionToast.show();
+
+ // Create a little animation to show that the widget can move
+ float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
+ final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
+ AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
+ ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
+ tyuAnim.setDuration(125);
+ ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
+ tydAnim.setDuration(100);
+ bounce.play(tyuAnim).before(tydAnim);
+ bounce.setInterpolator(new AccelerateInterpolator());
+ bounce.start();
+ }
+ }
+
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ return FocusHelper.handleAppsCustomizeKeyEvent(v, keyCode, event);
+ }
+
+ /*
+ * PagedViewWithDraggableItems implementation
+ */
+ @Override
+ protected void determineDraggingStart(android.view.MotionEvent ev) {
+ // Disable dragging by pulling an app down for now.
+ }
+
+ private void beginDraggingApplication(View v) {
+ mLauncher.getWorkspace().onDragStartedWithItem(v);
+ mLauncher.getWorkspace().beginDragShared(v, this);
+ }
+
+ Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
+ Bundle options = null;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, mTmpRect);
+ Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(mLauncher,
+ info.componentName, null);
+
+ float density = getResources().getDisplayMetrics().density;
+ int xPaddingDips = (int) ((padding.left + padding.right) / density);
+ int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
+
+ options = new Bundle();
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
+ mTmpRect.left - xPaddingDips);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
+ mTmpRect.top - yPaddingDips);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
+ mTmpRect.right - xPaddingDips);
+ options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
+ mTmpRect.bottom - yPaddingDips);
+ }
+ return options;
+ }
+
+ private void preloadWidget(final PendingAddWidgetInfo info) {
+ final AppWidgetProviderInfo pInfo = info.info;
+ final Bundle options = getDefaultOptionsForWidget(mLauncher, info);
+
+ if (pInfo.configure != null) {
+ info.bindOptions = options;
+ return;
+ }
+
+ mWidgetCleanupState = WIDGET_PRELOAD_PENDING;
+ mBindWidgetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
+ // Options will be null for platforms with JB or lower, so this serves as an
+ // SDK level check.
+ if (options == null) {
+ if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
+ mWidgetLoadingId, info.componentName)) {
+ mWidgetCleanupState = WIDGET_BOUND;
+ }
+ } else {
+ if (AppWidgetManager.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
+ mWidgetLoadingId, info.componentName, options)) {
+ mWidgetCleanupState = WIDGET_BOUND;
+ }
+ }
+ }
+ };
+ post(mBindWidgetRunnable);
+
+ mInflateWidgetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mWidgetCleanupState != WIDGET_BOUND) {
+ return;
+ }
+ AppWidgetHostView hostView = mLauncher.
+ getAppWidgetHost().createView(getContext(), mWidgetLoadingId, pInfo);
+ info.boundWidget = hostView;
+ mWidgetCleanupState = WIDGET_INFLATED;
+ hostView.setVisibility(INVISIBLE);
+ int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info.spanX,
+ info.spanY, info, false);
+
+ // We want the first widget layout to be the correct size. This will be important
+ // for width size reporting to the AppWidgetManager.
+ DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
+ unScaledSize[1]);
+ lp.x = lp.y = 0;
+ lp.customPosition = true;
+ hostView.setLayoutParams(lp);
+ mLauncher.getDragLayer().addView(hostView);
+ }
+ };
+ post(mInflateWidgetRunnable);
+ }
+
+ @Override
+ public void onShortPress(View v) {
+ // We are anticipating a long press, and we use this time to load bind and instantiate
+ // the widget. This will need to be cleaned up if it turns out no long press occurs.
+ if (mCreateWidgetInfo != null) {
+ // Just in case the cleanup process wasn't properly executed. This shouldn't happen.
+ cleanupWidgetPreloading(false);
+ }
+ mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
+ preloadWidget(mCreateWidgetInfo);
+ }
+
+ private void cleanupWidgetPreloading(boolean widgetWasAdded) {
+ if (!widgetWasAdded) {
+ // If the widget was not added, we may need to do further cleanup.
+ PendingAddWidgetInfo info = mCreateWidgetInfo;
+ mCreateWidgetInfo = null;
+
+ if (mWidgetCleanupState == WIDGET_PRELOAD_PENDING) {
+ // We never did any preloading, so just remove pending callbacks to do so
+ removeCallbacks(mBindWidgetRunnable);
+ removeCallbacks(mInflateWidgetRunnable);
+ } else if (mWidgetCleanupState == WIDGET_BOUND) {
+ // Delete the widget id which was allocated
+ if (mWidgetLoadingId != -1) {
+ mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+ }
+
+ // We never got around to inflating the widget, so remove the callback to do so.
+ removeCallbacks(mInflateWidgetRunnable);
+ } else if (mWidgetCleanupState == WIDGET_INFLATED) {
+ // Delete the widget id which was allocated
+ if (mWidgetLoadingId != -1) {
+ mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
+ }
+
+ // The widget was inflated and added to the DragLayer -- remove it.
+ AppWidgetHostView widget = info.boundWidget;
+ mLauncher.getDragLayer().removeView(widget);
+ }
+ }
+ mWidgetCleanupState = WIDGET_NO_CLEANUP_REQUIRED;
+ mWidgetLoadingId = -1;
+ mCreateWidgetInfo = null;
+ PagedViewWidget.resetShortPressTarget();
+ }
+
+ @Override
+ public void cleanUpShortPress(View v) {
+ if (!mDraggingWidget) {
+ cleanupWidgetPreloading(false);
+ }
+ }
+
+ private boolean beginDraggingWidget(View v) {
+ mDraggingWidget = true;
+ // Get the widget preview as the drag representation
+ ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
+ PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
+
+ // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
+ // we abort the drag.
+ if (image.getDrawable() == null) {
+ mDraggingWidget = false;
+ return false;
+ }
+
+ // Compose the drag image
+ Bitmap preview;
+ Bitmap outline;
+ float scale = 1f;
+ Point previewPadding = null;
+
+ if (createItemInfo instanceof PendingAddWidgetInfo) {
+ // This can happen in some weird cases involving multi-touch. We can't start dragging
+ // the widget if this is null, so we break out.
+ if (mCreateWidgetInfo == null) {
+ return false;
+ }
+
+ PendingAddWidgetInfo createWidgetInfo = mCreateWidgetInfo;
+ createItemInfo = createWidgetInfo;
+ int spanX = createItemInfo.spanX;
+ int spanY = createItemInfo.spanY;
+ int[] size = mLauncher.getWorkspace().estimateItemSize(spanX, spanY,
+ createWidgetInfo, true);
+
+ FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
+ float minScale = 1.25f;
+ int maxWidth, maxHeight;
+ maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
+ maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]);
+
+ int[] previewSizeBeforeScale = new int[1];
+
+ preview = mWidgetPreviewLoader.generateWidgetPreview(createWidgetInfo.componentName,
+ createWidgetInfo.previewImage, createWidgetInfo.icon, spanX, spanY,
+ maxWidth, maxHeight, null, previewSizeBeforeScale);
+
+ // Compare the size of the drag preview to the preview in the AppsCustomize tray
+ int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
+ mWidgetPreviewLoader.maxWidthForWidgetPreview(spanX));
+ scale = previewWidthInAppsCustomize / (float) preview.getWidth();
+
+ // The bitmap in the AppsCustomize tray is always the the same size, so there
+ // might be extra pixels around the preview itself - this accounts for that
+ if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) {
+ int padding =
+ (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2;
+ previewPadding = new Point(padding, 0);
+ }
+ } else {
+ PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
+ Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo);
+ preview = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+ icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+ mCanvas.setBitmap(preview);
+ mCanvas.save();
+ WidgetPreviewLoader.renderDrawableToBitmap(icon, preview, 0, 0,
+ icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ mCanvas.restore();
+ mCanvas.setBitmap(null);
+ createItemInfo.spanX = createItemInfo.spanY = 1;
+ }
+
+ // Don't clip alpha values for the drag outline if we're using the default widget preview
+ boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
+ (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
+
+ // Save the preview for the outline generation, then dim the preview
+ outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
+ false);
+
+ // Start the drag
+ mLauncher.lockScreenOrientation();
+ mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
+ mDragController.startDrag(image, preview, this, createItemInfo,
+ DragController.DRAG_ACTION_COPY, previewPadding, scale);
+ outline.recycle();
+ preview.recycle();
+ return true;
+ }
+
+ @Override
+ protected boolean beginDragging(final View v) {
+ if (!super.beginDragging(v)) return false;
+
+ if (v instanceof PagedViewIcon) {
+ beginDraggingApplication(v);
+ } else if (v instanceof PagedViewWidget) {
+ if (!beginDraggingWidget(v)) {
+ return false;
+ }
+ }
+
+ // We delay entering spring-loaded mode slightly to make sure the UI
+ // thready is free of any work.
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ // We don't enter spring-loaded mode if the drag has been cancelled
+ if (mLauncher.getDragController().isDragging()) {
+ // Reset the alpha on the dragged icon before we drag
+ resetDrawableState();
+
+ // Go into spring loaded mode (must happen before we startDrag())
+ mLauncher.enterSpringLoadedDragMode();
+ }
+ }
+ }, 150);
+
+ return true;
+ }
+
+ /**
+ * 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))) {
+ // Exit spring loaded mode if we have not successfully dropped or have not handled the
+ // drop in Workspace
+ mLauncher.exitSpringLoadedDragMode();
+ }
+ mLauncher.unlockScreenOrientation(false);
+ }
+
+ @Override
+ public View getContent() {
+ return null;
+ }
+
+ @Override
+ public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+ mInTransition = true;
+ if (toWorkspace) {
+ cancelAllTasks();
+ }
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ }
+
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ mInTransition = false;
+ for (AsyncTaskPageData d : mDeferredSyncWidgetPageItems) {
+ onSyncWidgetPageItems(d);
+ }
+ mDeferredSyncWidgetPageItems.clear();
+ for (Runnable r : mDeferredPrepareLoadWidgetPreviewsTasks) {
+ r.run();
+ }
+ mDeferredPrepareLoadWidgetPreviewsTasks.clear();
+ mForceDrawAllChildrenNextFrame = !toWorkspace;
+ }
+
+ @Override
+ public void onDropCompleted(View target, 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;
+ }
+ cleanupWidgetPreloading(success);
+ mDraggingWidget = false;
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // We just dismiss the drag when we fling, so cleanup here
+ endDragging(null, true, true);
+ cleanupWidgetPreloading(false);
+ mDraggingWidget = false;
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ cancelAllTasks();
+ }
+
+ public void clearAllWidgetPages() {
+ cancelAllTasks();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View v = getPageAt(i);
+ if (v instanceof PagedViewGridLayout) {
+ ((PagedViewGridLayout) v).removeAllViewsOnPage();
+ mDirtyPageContent.set(i, true);
+ }
+ }
+ }
+
+ private void cancelAllTasks() {
+ // Clean up all the async tasks
+ Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+ while (iter.hasNext()) {
+ AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+ task.cancel(false);
+ iter.remove();
+ mDirtyPageContent.set(task.page, true);
+
+ // We've already preallocated the views for the data to load into, so clear them as well
+ View v = getPageAt(task.page);
+ if (v instanceof PagedViewGridLayout) {
+ ((PagedViewGridLayout) v).removeAllViewsOnPage();
+ }
+ }
+ mDeferredSyncWidgetPageItems.clear();
+ mDeferredPrepareLoadWidgetPreviewsTasks.clear();
+ }
+
+ public void setContentType(ContentType type) {
+ int page = getCurrentPage();
+ if (mContentType != type) {
+ page = 0;
+ }
+ mContentType = type;
+ invalidatePageData(page, true);
+ }
+
+ public ContentType getContentType() {
+ return mContentType;
+ }
+
+ protected void snapToPage(int whichPage, int delta, int duration) {
+ super.snapToPage(whichPage, delta, duration);
+
+ // Update the thread priorities given the direction lookahead
+ Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+ while (iter.hasNext()) {
+ AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+ int pageIndex = task.page;
+ if ((mNextPage > mCurrentPage && pageIndex >= mCurrentPage) ||
+ (mNextPage < mCurrentPage && pageIndex <= mCurrentPage)) {
+ task.setThreadPriority(getThreadPriorityForPage(pageIndex));
+ } else {
+ task.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
+ }
+ }
+ }
+
+ /*
+ * Apps PagedView implementation
+ */
+ private void setVisibilityOnChildren(ViewGroup layout, int visibility) {
+ int childCount = layout.getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ layout.getChildAt(i).setVisibility(visibility);
+ }
+ }
+ private void setupPage(AppsCustomizeCellLayout layout) {
+ layout.setGridSize(mCellCountX, mCellCountY);
+
+ // Note: We force a measure here to get around the fact that when we do layout calculations
+ // immediately after syncing, we don't have a proper width. That said, we already know the
+ // expected page width, so we can actually optimize by hiding all the TextView-based
+ // children that are expensive to measure, and let that happen naturally later.
+ setVisibilityOnChildren(layout, View.GONE);
+ int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
+ int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
+ layout.setMinimumWidth(getPageContentWidth());
+ layout.measure(widthSpec, heightSpec);
+ setVisibilityOnChildren(layout, View.VISIBLE);
+ }
+
+ public void syncAppsPageItems(int page, boolean immediate) {
+ // ensure that we have the right number of items on the pages
+ final boolean isRtl = isLayoutRtl();
+ int numCells = mCellCountX * mCellCountY;
+ int startIndex = page * numCells;
+ int endIndex = Math.min(startIndex + numCells, mApps.size());
+ AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);
+
+ layout.removeAllViewsOnPage();
+ ArrayList<Object> items = new ArrayList<Object>();
+ ArrayList<Bitmap> images = new ArrayList<Bitmap>();
+ for (int i = startIndex; i < endIndex; ++i) {
+ AppInfo info = mApps.get(i);
+ PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
+ R.layout.apps_customize_application, layout, false);
+ icon.applyFromApplicationInfo(info, true, this);
+ icon.setOnClickListener(this);
+ icon.setOnLongClickListener(this);
+ icon.setOnTouchListener(this);
+ icon.setOnKeyListener(this);
+
+ int index = i - startIndex;
+ int x = index % mCellCountX;
+ int y = index / mCellCountX;
+ if (isRtl) {
+ x = mCellCountX - x - 1;
+ }
+ layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);
+
+ items.add(info);
+ images.add(info.iconBitmap);
+ }
+
+ enableHwLayersOnVisiblePages();
+ }
+
+ /**
+ * A helper to return the priority for loading of the specified widget page.
+ */
+ private int getWidgetPageLoadPriority(int page) {
+ // If we are snapping to another page, use that index as the target page index
+ int toPage = mCurrentPage;
+ if (mNextPage > -1) {
+ toPage = mNextPage;
+ }
+
+ // We use the distance from the target page as an initial guess of priority, but if there
+ // are no pages of higher priority than the page specified, then bump up the priority of
+ // the specified page.
+ Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+ int minPageDiff = Integer.MAX_VALUE;
+ while (iter.hasNext()) {
+ AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+ minPageDiff = Math.abs(task.page - toPage);
+ }
+
+ int rawPageDiff = Math.abs(page - toPage);
+ return rawPageDiff - Math.min(rawPageDiff, minPageDiff);
+ }
+ /**
+ * Return the appropriate thread priority for loading for a given page (we give the current
+ * page much higher priority)
+ */
+ private int getThreadPriorityForPage(int page) {
+ // TODO-APPS_CUSTOMIZE: detect number of cores and set thread priorities accordingly below
+ int pageDiff = getWidgetPageLoadPriority(page);
+ if (pageDiff <= 0) {
+ return Process.THREAD_PRIORITY_LESS_FAVORABLE;
+ } else if (pageDiff <= 1) {
+ return Process.THREAD_PRIORITY_LOWEST;
+ } else {
+ return Process.THREAD_PRIORITY_LOWEST;
+ }
+ }
+ private int getSleepForPage(int page) {
+ int pageDiff = getWidgetPageLoadPriority(page);
+ return Math.max(0, pageDiff * sPageSleepDelay);
+ }
+ /**
+ * Creates and executes a new AsyncTask to load a page of widget previews.
+ */
+ private void prepareLoadWidgetPreviewsTask(int page, ArrayList<Object> widgets,
+ int cellWidth, int cellHeight, int cellCountX) {
+
+ // Prune all tasks that are no longer needed
+ Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+ while (iter.hasNext()) {
+ AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+ int taskPage = task.page;
+ if (taskPage < getAssociatedLowerPageBound(mCurrentPage) ||
+ taskPage > getAssociatedUpperPageBound(mCurrentPage)) {
+ task.cancel(false);
+ iter.remove();
+ } else {
+ task.setThreadPriority(getThreadPriorityForPage(taskPage));
+ }
+ }
+
+ // We introduce a slight delay to order the loading of side pages so that we don't thrash
+ final int sleepMs = getSleepForPage(page);
+ AsyncTaskPageData pageData = new AsyncTaskPageData(page, widgets, cellWidth, cellHeight,
+ new AsyncTaskCallback() {
+ @Override
+ public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
+ try {
+ try {
+ Thread.sleep(sleepMs);
+ } catch (Exception e) {}
+ loadWidgetPreviewsInBackground(task, data);
+ } finally {
+ if (task.isCancelled()) {
+ data.cleanup(true);
+ }
+ }
+ }
+ },
+ new AsyncTaskCallback() {
+ @Override
+ public void run(AppsCustomizeAsyncTask task, AsyncTaskPageData data) {
+ mRunningTasks.remove(task);
+ if (task.isCancelled()) return;
+ // do cleanup inside onSyncWidgetPageItems
+ onSyncWidgetPageItems(data);
+ }
+ }, mWidgetPreviewLoader);
+
+ // Ensure that the task is appropriately prioritized and runs in parallel
+ AppsCustomizeAsyncTask t = new AppsCustomizeAsyncTask(page,
+ AsyncTaskPageData.Type.LoadWidgetPreviewData);
+ t.setThreadPriority(getThreadPriorityForPage(page));
+ t.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pageData);
+ mRunningTasks.add(t);
+ }
+
+ /*
+ * Widgets PagedView implementation
+ */
+ private void setupPage(PagedViewGridLayout layout) {
+ // Note: We force a measure here to get around the fact that when we do layout calculations
+ // immediately after syncing, we don't have a proper width.
+ int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST);
+ int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST);
+ layout.setMinimumWidth(getPageContentWidth());
+ layout.measure(widthSpec, heightSpec);
+ }
+
+ public void syncWidgetPageItems(final int page, final boolean immediate) {
+ int numItemsPerPage = mWidgetCountX * mWidgetCountY;
+
+ // Calculate the dimensions of each cell we are giving to each widget
+ final ArrayList<Object> items = new ArrayList<Object>();
+ int contentWidth = mContentWidth;
+ final int cellWidth = ((contentWidth - mPageLayoutPaddingLeft - mPageLayoutPaddingRight
+ - ((mWidgetCountX - 1) * mWidgetWidthGap)) / mWidgetCountX);
+ int contentHeight = mContentHeight;
+ final int cellHeight = ((contentHeight - mPageLayoutPaddingTop - mPageLayoutPaddingBottom
+ - ((mWidgetCountY - 1) * mWidgetHeightGap)) / mWidgetCountY);
+
+ // Prepare the set of widgets to load previews for in the background
+ int offset = page * numItemsPerPage;
+ for (int i = offset; i < Math.min(offset + numItemsPerPage, mWidgets.size()); ++i) {
+ items.add(mWidgets.get(i));
+ }
+
+ // Prepopulate the pages with the other widget info, and fill in the previews later
+ final PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
+ layout.setColumnCount(layout.getCellCountX());
+ for (int i = 0; i < items.size(); ++i) {
+ Object rawInfo = items.get(i);
+ PendingAddItemInfo createItemInfo = null;
+ PagedViewWidget widget = (PagedViewWidget) mLayoutInflater.inflate(
+ R.layout.apps_customize_widget, layout, false);
+ if (rawInfo instanceof AppWidgetProviderInfo) {
+ // Fill in the widget information
+ AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo;
+ createItemInfo = new PendingAddWidgetInfo(info, null, null);
+
+ // Determine the widget spans and min resize spans.
+ int[] spanXY = Launcher.getSpanForWidget(mLauncher, info);
+ createItemInfo.spanX = spanXY[0];
+ createItemInfo.spanY = spanXY[1];
+ int[] minSpanXY = Launcher.getMinSpanForWidget(mLauncher, info);
+ createItemInfo.minSpanX = minSpanXY[0];
+ createItemInfo.minSpanY = minSpanXY[1];
+
+ widget.applyFromAppWidgetProviderInfo(info, -1, spanXY, mWidgetPreviewLoader);
+ widget.setTag(createItemInfo);
+ widget.setShortPressListener(this);
+ } else if (rawInfo instanceof ResolveInfo) {
+ // Fill in the shortcuts information
+ ResolveInfo info = (ResolveInfo) rawInfo;
+ createItemInfo = new PendingAddShortcutInfo(info.activityInfo);
+ createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+ createItemInfo.componentName = new ComponentName(info.activityInfo.packageName,
+ info.activityInfo.name);
+ widget.applyFromResolveInfo(mPackageManager, info, mWidgetPreviewLoader);
+ widget.setTag(createItemInfo);
+ }
+ widget.setOnClickListener(this);
+ widget.setOnLongClickListener(this);
+ widget.setOnTouchListener(this);
+ widget.setOnKeyListener(this);
+
+ // Layout each widget
+ int ix = i % mWidgetCountX;
+ int iy = i / mWidgetCountX;
+ GridLayout.LayoutParams lp = new GridLayout.LayoutParams(
+ GridLayout.spec(iy, GridLayout.START),
+ GridLayout.spec(ix, GridLayout.TOP));
+ lp.width = cellWidth;
+ lp.height = cellHeight;
+ lp.setGravity(Gravity.TOP | Gravity.START);
+ if (ix > 0) lp.leftMargin = mWidgetWidthGap;
+ if (iy > 0) lp.topMargin = mWidgetHeightGap;
+ layout.addView(widget, lp);
+ }
+
+ // wait until a call on onLayout to start loading, because
+ // PagedViewWidget.getPreviewSize() will return 0 if it hasn't been laid out
+ // TODO: can we do a measure/layout immediately?
+ layout.setOnLayoutListener(new Runnable() {
+ public void run() {
+ // Load the widget previews
+ int maxPreviewWidth = cellWidth;
+ int maxPreviewHeight = cellHeight;
+ if (layout.getChildCount() > 0) {
+ PagedViewWidget w = (PagedViewWidget) layout.getChildAt(0);
+ int[] maxSize = w.getPreviewSize();
+ maxPreviewWidth = maxSize[0];
+ maxPreviewHeight = maxSize[1];
+ }
+
+ mWidgetPreviewLoader.setPreviewSize(
+ maxPreviewWidth, maxPreviewHeight, mWidgetSpacingLayout);
+ if (immediate) {
+ AsyncTaskPageData data = new AsyncTaskPageData(page, items,
+ maxPreviewWidth, maxPreviewHeight, null, null, mWidgetPreviewLoader);
+ loadWidgetPreviewsInBackground(null, data);
+ onSyncWidgetPageItems(data);
+ } else {
+ if (mInTransition) {
+ mDeferredPrepareLoadWidgetPreviewsTasks.add(this);
+ } else {
+ prepareLoadWidgetPreviewsTask(page, items,
+ maxPreviewWidth, maxPreviewHeight, mWidgetCountX);
+ }
+ }
+ layout.setOnLayoutListener(null);
+ }
+ });
+ }
+ private void loadWidgetPreviewsInBackground(AppsCustomizeAsyncTask task,
+ AsyncTaskPageData data) {
+ // loadWidgetPreviewsInBackground can be called without a task to load a set of widget
+ // previews synchronously
+ if (task != null) {
+ // Ensure that this task starts running at the correct priority
+ task.syncThreadPriority();
+ }
+
+ // Load each of the widget/shortcut previews
+ ArrayList<Object> items = data.items;
+ ArrayList<Bitmap> images = data.generatedImages;
+ int count = items.size();
+ for (int i = 0; i < count; ++i) {
+ if (task != null) {
+ // Ensure we haven't been cancelled yet
+ if (task.isCancelled()) break;
+ // Before work on each item, ensure that this task is running at the correct
+ // priority
+ task.syncThreadPriority();
+ }
+
+ images.add(mWidgetPreviewLoader.getPreview(items.get(i)));
+ }
+ }
+
+ private void onSyncWidgetPageItems(AsyncTaskPageData data) {
+ if (mInTransition) {
+ mDeferredSyncWidgetPageItems.add(data);
+ return;
+ }
+ try {
+ int page = data.page;
+ PagedViewGridLayout layout = (PagedViewGridLayout) getPageAt(page);
+
+ ArrayList<Object> items = data.items;
+ int count = items.size();
+ for (int i = 0; i < count; ++i) {
+ PagedViewWidget widget = (PagedViewWidget) layout.getChildAt(i);
+ if (widget != null) {
+ Bitmap preview = data.generatedImages.get(i);
+ widget.applyPreview(new FastBitmapDrawable(preview), i);
+ }
+ }
+
+ enableHwLayersOnVisiblePages();
+
+ // Update all thread priorities
+ Iterator<AppsCustomizeAsyncTask> iter = mRunningTasks.iterator();
+ while (iter.hasNext()) {
+ AppsCustomizeAsyncTask task = (AppsCustomizeAsyncTask) iter.next();
+ int pageIndex = task.page;
+ task.setThreadPriority(getThreadPriorityForPage(pageIndex));
+ }
+ } finally {
+ data.cleanup(false);
+ }
+ }
+
+ @Override
+ public void syncPages() {
+ disablePagedViewAnimations();
+
+ removeAllViews();
+ cancelAllTasks();
+
+ Context context = getContext();
+ if (mContentType == ContentType.Applications) {
+ for (int i = 0; i < mNumAppsPages; ++i) {
+ AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context);
+ setupPage(layout);
+ addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ }
+ } else if (mContentType == ContentType.Widgets) {
+ for (int j = 0; j < mNumWidgetPages; ++j) {
+ PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX,
+ mWidgetCountY);
+ setupPage(layout);
+ addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ }
+ } else {
+ throw new RuntimeException("Invalid ContentType");
+ }
+
+ enablePagedViewAnimations();
+ }
+
+ @Override
+ public void syncPageItems(int page, boolean immediate) {
+ if (mContentType == ContentType.Widgets) {
+ syncWidgetPageItems(page, immediate);
+ } else {
+ syncAppsPageItems(page, immediate);
+ }
+ }
+
+ // We want our pages to be z-ordered such that the further a page is to the left, the higher
+ // it is in the z-order. This is important to insure touch events are handled correctly.
+ View getPageAt(int index) {
+ return getChildAt(indexToPage(index));
+ }
+
+ @Override
+ protected int indexToPage(int index) {
+ return getChildCount() - index - 1;
+ }
+
+ // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
+ @Override
+ protected void screenScrolled(int screenCenter) {
+ final boolean isRtl = isLayoutRtl();
+ super.screenScrolled(screenCenter);
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getPageAt(i);
+ if (v != null) {
+ float scrollProgress = getScrollProgress(screenCenter, v, i);
+
+ float interpolatedProgress;
+ float translationX;
+ float maxScrollProgress = Math.max(0, scrollProgress);
+ float minScrollProgress = Math.min(0, scrollProgress);
+
+ if (isRtl) {
+ translationX = maxScrollProgress * v.getMeasuredWidth();
+ interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(maxScrollProgress));
+ } else {
+ translationX = minScrollProgress * v.getMeasuredWidth();
+ interpolatedProgress = mZInterpolator.getInterpolation(Math.abs(minScrollProgress));
+ }
+ float scale = (1 - interpolatedProgress) +
+ interpolatedProgress * TRANSITION_SCALE_FACTOR;
+
+ float alpha;
+ if (isRtl && (scrollProgress > 0)) {
+ alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(maxScrollProgress));
+ } else if (!isRtl && (scrollProgress < 0)) {
+ alpha = mAlphaInterpolator.getInterpolation(1 - Math.abs(scrollProgress));
+ } else {
+ // On large screens we need to fade the page as it nears its leftmost position
+ alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
+ }
+
+ v.setCameraDistance(mDensity * CAMERA_DISTANCE);
+ int pageWidth = v.getMeasuredWidth();
+ int pageHeight = v.getMeasuredHeight();
+
+ if (PERFORM_OVERSCROLL_ROTATION) {
+ float xPivot = isRtl ? 1f - TRANSITION_PIVOT : TRANSITION_PIVOT;
+ boolean isOverscrollingFirstPage = isRtl ? scrollProgress > 0 : scrollProgress < 0;
+ boolean isOverscrollingLastPage = isRtl ? scrollProgress < 0 : scrollProgress > 0;
+
+ if (i == 0 && isOverscrollingFirstPage) {
+ // Overscroll to the left
+ v.setPivotX(xPivot * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ scale = 1.0f;
+ alpha = 1.0f;
+ // On the first page, we don't want the page to have any lateral motion
+ translationX = 0;
+ } else if (i == getChildCount() - 1 && isOverscrollingLastPage) {
+ // Overscroll to the right
+ v.setPivotX((1 - xPivot) * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ scale = 1.0f;
+ alpha = 1.0f;
+ // On the last page, we don't want the page to have any lateral motion.
+ translationX = 0;
+ } else {
+ v.setPivotY(pageHeight / 2.0f);
+ v.setPivotX(pageWidth / 2.0f);
+ v.setRotationY(0f);
+ }
+ }
+
+ v.setTranslationX(translationX);
+ v.setScaleX(scale);
+ v.setScaleY(scale);
+ v.setAlpha(alpha);
+
+ // If the view has 0 alpha, we set it to be invisible so as to prevent
+ // it from accepting touches
+ if (alpha == 0) {
+ v.setVisibility(INVISIBLE);
+ } else if (v.getVisibility() != VISIBLE) {
+ v.setVisibility(VISIBLE);
+ }
+ }
+ }
+
+ enableHwLayersOnVisiblePages();
+ }
+
+ private void enableHwLayersOnVisiblePages() {
+ final int screenCount = getChildCount();
+
+ getVisiblePages(mTempVisiblePagesRange);
+ int leftScreen = mTempVisiblePagesRange[0];
+ int rightScreen = mTempVisiblePagesRange[1];
+ int forceDrawScreen = -1;
+ if (leftScreen == rightScreen) {
+ // make sure we're caching at least two pages always
+ if (rightScreen < screenCount - 1) {
+ rightScreen++;
+ forceDrawScreen = rightScreen;
+ } else if (leftScreen > 0) {
+ leftScreen--;
+ forceDrawScreen = leftScreen;
+ }
+ } else {
+ forceDrawScreen = leftScreen + 1;
+ }
+
+ for (int i = 0; i < screenCount; i++) {
+ final View layout = (View) getPageAt(i);
+ if (!(leftScreen <= i && i <= rightScreen &&
+ (i == forceDrawScreen || shouldDrawChild(layout)))) {
+ layout.setLayerType(LAYER_TYPE_NONE, null);
+ }
+ }
+
+ for (int i = 0; i < screenCount; i++) {
+ final View layout = (View) getPageAt(i);
+
+ if (leftScreen <= i && i <= rightScreen &&
+ (i == forceDrawScreen || shouldDrawChild(layout))) {
+ if (layout.getLayerType() != LAYER_TYPE_HARDWARE) {
+ layout.setLayerType(LAYER_TYPE_HARDWARE, null);
+ }
+ }
+ }
+ }
+
+ protected void overScroll(float amount) {
+ acceleratedOverScroll(amount);
+ }
+
+ /**
+ * Used by the parent to get the content width to set the tab bar to
+ * @return
+ */
+ public int getPageContentWidth() {
+ return mContentWidth;
+ }
+
+ @Override
+ protected void onPageEndMoving() {
+ super.onPageEndMoving();
+ mForceDrawAllChildrenNextFrame = true;
+ // We reset the save index when we change pages so that it will be recalculated on next
+ // rotation
+ mSaveInstanceStateItemIndex = -1;
+ }
+
+ /*
+ * AllAppsView implementation
+ */
+ public void setup(Launcher launcher, DragController dragController) {
+ mLauncher = launcher;
+ mDragController = dragController;
+ }
+
+ /**
+ * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can
+ * appropriately determine when to invalidate the PagedView page data. In cases where the data
+ * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the
+ * next onMeasure() pass, which will trigger an invalidatePageData() itself.
+ */
+ private void invalidateOnDataChange() {
+ if (!isDataReady()) {
+ // The next layout pass will trigger data-ready if both widgets and apps are set, so
+ // request a layout to trigger the page data when ready.
+ requestLayout();
+ } else {
+ cancelAllTasks();
+ invalidatePageData();
+ }
+ }
+
+ public void setApps(ArrayList<AppInfo> list) {
+ if (!DISABLE_ALL_APPS) {
+ mApps = list;
+ Collections.sort(mApps, LauncherModel.getAppNameComparator());
+ updatePageCountsAndInvalidateData();
+ }
+ }
+ 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);
+ int index = Collections.binarySearch(mApps, info, LauncherModel.getAppNameComparator());
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ }
+ }
+ }
+ public void addApps(ArrayList<AppInfo> list) {
+ if (!DISABLE_ALL_APPS) {
+ addAppsWithoutInvalidate(list);
+ updatePageCountsAndInvalidateData();
+ }
+ }
+ 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;
+ }
+ 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);
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex > -1) {
+ mApps.remove(removeIndex);
+ }
+ }
+ }
+ public void removeApps(ArrayList<AppInfo> appInfos) {
+ if (!DISABLE_ALL_APPS) {
+ removeAppsWithoutInvalidate(appInfos);
+ updatePageCountsAndInvalidateData();
+ }
+ }
+ 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 (!DISABLE_ALL_APPS) {
+ removeAppsWithoutInvalidate(list);
+ addAppsWithoutInvalidate(list);
+ updatePageCountsAndInvalidateData();
+ }
+ }
+
+ public void reset() {
+ // If we have reset, then we should not continue to restore the previous state
+ mSaveInstanceStateItemIndex = -1;
+
+ AppsCustomizeTabHost tabHost = getTabHost();
+ String tag = tabHost.getCurrentTabTag();
+ if (tag != null) {
+ if (!tag.equals(tabHost.getTabTagForContentType(ContentType.Applications))) {
+ tabHost.setCurrentTabFromContent(ContentType.Applications);
+ }
+ }
+
+ if (mCurrentPage != 0) {
+ invalidatePageData(0);
+ }
+ }
+
+ private AppsCustomizeTabHost getTabHost() {
+ return (AppsCustomizeTabHost) mLauncher.findViewById(R.id.apps_customize_pane);
+ }
+
+ public void dumpState() {
+ // TODO: Dump information related to current list of Applications, Widgets, etc.
+ AppInfo.dumpApplicationInfoList(TAG, "mApps", mApps);
+ dumpAppWidgetProviderInfoList(TAG, "mWidgets", mWidgets);
+ }
+
+ private void dumpAppWidgetProviderInfoList(String tag, String label,
+ ArrayList<Object> list) {
+ Log.d(tag, label + " size=" + list.size());
+ for (Object i: list) {
+ if (i instanceof AppWidgetProviderInfo) {
+ AppWidgetProviderInfo info = (AppWidgetProviderInfo) i;
+ Log.d(tag, " label=\"" + info.label + "\" previewImage=" + info.previewImage
+ + " resizeMode=" + info.resizeMode + " configure=" + info.configure
+ + " initialLayout=" + info.initialLayout
+ + " minWidth=" + info.minWidth + " minHeight=" + info.minHeight);
+ } else if (i instanceof ResolveInfo) {
+ ResolveInfo info = (ResolveInfo) i;
+ Log.d(tag, " label=\"" + info.loadLabel(mPackageManager) + "\" icon="
+ + info.icon);
+ }
+ }
+ }
+
+ public void surrender() {
+ // TODO: If we are in the middle of any process (ie. for holographic outlines, etc) we
+ // should stop this now.
+
+ // Stop all background tasks
+ cancelAllTasks();
+ }
+
+ @Override
+ public void iconPressed(PagedViewIcon icon) {
+ // Reset the previously pressed icon and store a reference to the pressed icon so that
+ // we can reset it on return to Launcher (in Launcher.onResume())
+ if (mPressedIcon != null) {
+ mPressedIcon.resetDrawableState();
+ }
+ mPressedIcon = icon;
+ }
+
+ public void resetDrawableState() {
+ if (mPressedIcon != null) {
+ mPressedIcon.resetDrawableState();
+ mPressedIcon = null;
+ }
+ }
+
+ /*
+ * We load an extra page on each side to prevent flashes from scrolling and loading of the
+ * widget previews in the background with the AsyncTasks.
+ */
+ final static int sLookBehindPageCount = 2;
+ final static int sLookAheadPageCount = 2;
+ protected int getAssociatedLowerPageBound(int page) {
+ final int count = getChildCount();
+ int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
+ int windowMinIndex = Math.max(Math.min(page - sLookBehindPageCount, count - windowSize), 0);
+ return windowMinIndex;
+ }
+ protected int getAssociatedUpperPageBound(int page) {
+ final int count = getChildCount();
+ int windowSize = Math.min(count, sLookBehindPageCount + sLookAheadPageCount + 1);
+ int windowMaxIndex = Math.min(Math.max(page + sLookAheadPageCount, windowSize - 1),
+ count - 1);
+ return windowMaxIndex;
+ }
+
+ protected String getCurrentPageDescription() {
+ int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ int stringId = R.string.default_scroll_format;
+ int count = 0;
+
+ if (mContentType == ContentType.Applications) {
+ stringId = R.string.apps_customize_apps_scroll_format;
+ count = mNumAppsPages;
+ } else if (mContentType == ContentType.Widgets) {
+ stringId = R.string.apps_customize_widgets_scroll_format;
+ count = mNumWidgetPages;
+ } else {
+ throw new RuntimeException("Invalid ContentType");
+ }
+
+ return String.format(getContext().getString(stringId), page + 1, count);
+ }
+}
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
new file mode 100644
index 0000000..bfcf92a
--- /dev/null
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2011 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TabHost;
+import android.widget.TabWidget;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable,
+ TabHost.OnTabChangeListener, Insettable {
+ static final String LOG_TAG = "AppsCustomizeTabHost";
+
+ private static final String APPS_TAB_TAG = "APPS";
+ private static final String WIDGETS_TAB_TAG = "WIDGETS";
+
+ private final LayoutInflater mLayoutInflater;
+ private ViewGroup mTabs;
+ private ViewGroup mTabsContainer;
+ private AppsCustomizePagedView mAppsCustomizePane;
+ private FrameLayout mAnimationBuffer;
+ private LinearLayout mContent;
+
+ private boolean mInTransition;
+ private boolean mTransitioningToWorkspace;
+ private boolean mResetAfterTransition;
+ private Runnable mRelayoutAndMakeVisible;
+ private final Rect mInsets = new Rect();
+
+ public AppsCustomizeTabHost(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ mRelayoutAndMakeVisible = new Runnable() {
+ public void run() {
+ mTabs.requestLayout();
+ mTabsContainer.setAlpha(1f);
+ }
+ };
+ }
+
+ /**
+ * Convenience methods to select specific tabs. We want to set the content type immediately
+ * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view
+ * reflects the new content (but doesn't do the animation and logic associated with changing
+ * tabs manually).
+ */
+ void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) {
+ setOnTabChangedListener(null);
+ onTabChangedStart();
+ onTabChangedEnd(type);
+ setCurrentTabByTag(getTabTagForContentType(type));
+ setOnTabChangedListener(this);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ FrameLayout.LayoutParams flp = (LayoutParams) mContent.getLayoutParams();
+ flp.topMargin = insets.top;
+ flp.bottomMargin = insets.bottom;
+ flp.leftMargin = insets.left;
+ flp.rightMargin = insets.right;
+ mContent.setLayoutParams(flp);
+ }
+
+ /**
+ * Setup the tab host and create all necessary tabs.
+ */
+ @Override
+ protected void onFinishInflate() {
+ // Setup the tab host
+ setup();
+
+ final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container);
+ final TabWidget tabs = getTabWidget();
+ final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView)
+ findViewById(R.id.apps_customize_pane_content);
+ mTabs = tabs;
+ mTabsContainer = tabsContainer;
+ mAppsCustomizePane = appsCustomizePane;
+ mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer);
+ mContent = (LinearLayout) findViewById(R.id.apps_customize_content);
+ if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException();
+
+ // Configure the tabs content factory to return the same paged view (that we change the
+ // content filter on)
+ TabContentFactory contentFactory = new TabContentFactory() {
+ public View createTabContent(String tag) {
+ return appsCustomizePane;
+ }
+ };
+
+ // Create the tabs
+ TextView tabView;
+ String label;
+ label = getContext().getString(R.string.all_apps_button_label);
+ tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false);
+ tabView.setText(label);
+ tabView.setContentDescription(label);
+ addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory));
+ label = getContext().getString(R.string.widgets_tab_label);
+ tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false);
+ tabView.setText(label);
+ tabView.setContentDescription(label);
+ addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory));
+ setOnTabChangedListener(this);
+
+ // Setup the key listener to jump between the last tab view and the market icon
+ AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener();
+ View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1);
+ lastTab.setOnKeyListener(keyListener);
+ View shopButton = findViewById(R.id.market_button);
+ shopButton.setOnKeyListener(keyListener);
+
+ // Hide the tab bar until we measure
+ mTabsContainer.setAlpha(0f);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // Set the width of the tab list to the content width
+ if (remeasureTabWidth) {
+ int contentWidth = mAppsCustomizePane.getPageContentWidth();
+ if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) {
+ // Set the width and show the tab bar
+ mTabs.getLayoutParams().width = contentWidth;
+ mRelayoutAndMakeVisible.run();
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // If we are mid transitioning to the workspace, then intercept touch events here so we
+ // can ignore them, otherwise we just let all apps handle the touch events.
+ if (mInTransition && mTransitioningToWorkspace) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(ev);
+ };
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Allow touch events to fall through to the workspace if we are transitioning there
+ if (mInTransition && mTransitioningToWorkspace) {
+ return super.onTouchEvent(event);
+ }
+
+ // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall
+ // through to the workspace and trigger showWorkspace()
+ if (event.getY() < mAppsCustomizePane.getBottom()) {
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ private void onTabChangedStart() {
+ }
+
+ private void reloadCurrentPage() {
+ mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
+ mAppsCustomizePane.requestFocus();
+ }
+
+ private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) {
+ int bgAlpha = (int) (255 * (getResources().getInteger(
+ R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f));
+ setBackgroundColor(Color.argb(bgAlpha, 0, 0, 0));
+ mAppsCustomizePane.setContentType(type);
+ }
+
+ @Override
+ public void onTabChanged(String tabId) {
+ final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId);
+
+ // Animate the changing of the tab content by fading pages in and out
+ final Resources res = getResources();
+ final int duration = res.getInteger(R.integer.config_tabTransitionDuration);
+
+ // We post a runnable here because there is a delay while the first page is loading and
+ // the feedback from having changed the tab almost feels better than having it stick
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mAppsCustomizePane.getMeasuredWidth() <= 0 ||
+ mAppsCustomizePane.getMeasuredHeight() <= 0) {
+ reloadCurrentPage();
+ return;
+ }
+
+ // Take the visible pages and re-parent them temporarily to mAnimatorBuffer
+ // and then cross fade to the new pages
+ int[] visiblePageRange = new int[2];
+ mAppsCustomizePane.getVisiblePages(visiblePageRange);
+ if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) {
+ // If we can't get the visible page ranges, then just skip the animation
+ reloadCurrentPage();
+ return;
+ }
+ ArrayList<View> visiblePages = new ArrayList<View>();
+ for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) {
+ visiblePages.add(mAppsCustomizePane.getPageAt(i));
+ }
+
+ // We want the pages to be rendered in exactly the same way as they were when
+ // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer
+ // to be exactly the same as mAppsCustomizePane, and below, set the left/top
+ // parameters to be correct for each of the pages
+ mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0);
+
+ // mAppsCustomizePane renders its children in reverse order, so
+ // add the pages to mAnimationBuffer in reverse order to match that behavior
+ for (int i = visiblePages.size() - 1; i >= 0; i--) {
+ View child = visiblePages.get(i);
+ if (child instanceof AppsCustomizeCellLayout) {
+ ((AppsCustomizeCellLayout) child).resetChildrenOnKeyListeners();
+ } else if (child instanceof PagedViewGridLayout) {
+ ((PagedViewGridLayout) child).resetChildrenOnKeyListeners();
+ }
+ PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false);
+ mAppsCustomizePane.removeView(child);
+ PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true);
+ mAnimationBuffer.setAlpha(1f);
+ mAnimationBuffer.setVisibility(View.VISIBLE);
+ LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(),
+ child.getMeasuredHeight());
+ p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0);
+ mAnimationBuffer.addView(child, p);
+ }
+
+ // Toggle the new content
+ onTabChangedStart();
+ onTabChangedEnd(type);
+
+ // Animate the transition
+ ObjectAnimator outAnim = LauncherAnimUtils.ofFloat(mAnimationBuffer, "alpha", 0f);
+ outAnim.addListener(new AnimatorListenerAdapter() {
+ private void clearAnimationBuffer() {
+ mAnimationBuffer.setVisibility(View.GONE);
+ PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(false);
+ mAnimationBuffer.removeAllViews();
+ PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(true);
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ clearAnimationBuffer();
+ }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ clearAnimationBuffer();
+ }
+ });
+ ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f);
+ inAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ reloadCurrentPage();
+ }
+ });
+
+ final AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet();
+ animSet.playTogether(outAnim, inAnim);
+ animSet.setDuration(duration);
+ animSet.start();
+ }
+ });
+ }
+
+ public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) {
+ setOnTabChangedListener(null);
+ setCurrentTabByTag(getTabTagForContentType(type));
+ setOnTabChangedListener(this);
+ }
+
+ /**
+ * Returns the content type for the specified tab tag.
+ */
+ public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) {
+ if (tag.equals(APPS_TAB_TAG)) {
+ return AppsCustomizePagedView.ContentType.Applications;
+ } else if (tag.equals(WIDGETS_TAB_TAG)) {
+ return AppsCustomizePagedView.ContentType.Widgets;
+ }
+ return AppsCustomizePagedView.ContentType.Applications;
+ }
+
+ /**
+ * Returns the tab tag for a given content type.
+ */
+ public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) {
+ if (type == AppsCustomizePagedView.ContentType.Applications) {
+ return APPS_TAB_TAG;
+ } else if (type == AppsCustomizePagedView.ContentType.Widgets) {
+ return WIDGETS_TAB_TAG;
+ }
+ return APPS_TAB_TAG;
+ }
+
+ /**
+ * Disable focus on anything under this view in the hierarchy if we are not visible.
+ */
+ @Override
+ public int getDescendantFocusability() {
+ if (getVisibility() != View.VISIBLE) {
+ return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+ }
+ return super.getDescendantFocusability();
+ }
+
+ void reset() {
+ if (mInTransition) {
+ // Defer to after the transition to reset
+ mResetAfterTransition = true;
+ } else {
+ // Reset immediately
+ mAppsCustomizePane.reset();
+ }
+ }
+
+ private void enableAndBuildHardwareLayer() {
+ // isHardwareAccelerated() checks if we're attached to a window and if that
+ // window is HW accelerated-- we were sometimes not attached to a window
+ // and buildLayer was throwing an IllegalStateException
+ if (isHardwareAccelerated()) {
+ // Turn on hardware layers for performance
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+
+ // force building the layer, so you don't get a blip early in an animation
+ // when the layer is created layer
+ buildLayer();
+ }
+ }
+
+ @Override
+ public View getContent() {
+ return mContent;
+ }
+
+ /* LauncherTransitionable overrides */
+ @Override
+ public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+ mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace);
+ mInTransition = true;
+ mTransitioningToWorkspace = toWorkspace;
+
+ if (toWorkspace) {
+ // Going from All Apps -> Workspace
+ setVisibilityOfSiblingsWithLowerZOrder(VISIBLE);
+ } else {
+ // Going from Workspace -> All Apps
+ mContent.setVisibility(VISIBLE);
+
+ // Make sure the current page is loaded (we start loading the side pages after the
+ // transition to prevent slowing down the animation)
+ mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true);
+ }
+
+ if (mResetAfterTransition) {
+ mAppsCustomizePane.reset();
+ mResetAfterTransition = false;
+ }
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ if (animated) {
+ enableAndBuildHardwareLayer();
+ }
+
+ // Dismiss the workspace cling
+ l.dismissWorkspaceCling(null);
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ // Do nothing
+ }
+
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace);
+ mInTransition = false;
+ if (animated) {
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+
+ if (!toWorkspace) {
+ // Show the all apps cling (if not already shown)
+ mAppsCustomizePane.showAllAppsCling();
+ // Make sure adjacent pages are loaded (we wait until after the transition to
+ // prevent slowing down the animation)
+ mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
+
+ // Going from Workspace -> All Apps
+ // NOTE: We should do this at the end since we check visibility state in some of the
+ // cling initialization/dismiss code above.
+ setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE);
+ }
+ }
+
+ private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) {
+ ViewGroup parent = (ViewGroup) getParent();
+ if (parent == null) return;
+
+ View overviewPanel = ((Launcher) getContext()).getOverviewPanel();
+ final int count = parent.getChildCount();
+ if (!isChildrenDrawingOrderEnabled()) {
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child == this) {
+ break;
+ } else {
+ if (child.getVisibility() == GONE || child == overviewPanel) {
+ continue;
+ }
+ child.setVisibility(visibility);
+ }
+ }
+ } else {
+ throw new RuntimeException("Failed; can't get z-order of views");
+ }
+ }
+
+ public void onWindowVisible() {
+ if (getVisibility() == VISIBLE) {
+ mContent.setVisibility(VISIBLE);
+ // We unload the widget previews when the UI is hidden, so need to reload pages
+ // Load the current page synchronously, and the neighboring pages asynchronously
+ mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true);
+ mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
+ }
+ }
+
+ public void onTrimMemory() {
+ mContent.setVisibility(GONE);
+ // Clear the widget pages of all their subviews - this will trigger the widget previews
+ // to delete their bitmaps
+ mAppsCustomizePane.clearAllWidgetPages();
+ }
+
+ boolean isTransitioning() {
+ return mInTransition;
+ }
+}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
new file mode 100644
index 0000000..287bb50
--- /dev/null
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+/**
+ * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
+ * because we want to make the bubble taller than the text and TextView's clip is
+ * too aggressive.
+ */
+public class BubbleTextView extends TextView {
+ static final float SHADOW_LARGE_RADIUS = 4.0f;
+ static final float SHADOW_SMALL_RADIUS = 1.75f;
+ static final float SHADOW_Y_OFFSET = 2.0f;
+ static final int SHADOW_LARGE_COLOUR = 0xDD000000;
+ static final int SHADOW_SMALL_COLOUR = 0xCC000000;
+ static final float PADDING_H = 8.0f;
+ static final float PADDING_V = 3.0f;
+
+ private int mPrevAlpha = -1;
+
+ private HolographicOutlineHelper mOutlineHelper;
+ private final Canvas mTempCanvas = new Canvas();
+ private final Rect mTempRect = new Rect();
+ private boolean mDidInvalidateForPressedState;
+ private Bitmap mPressedOrFocusedBackground;
+ private int mFocusedOutlineColor;
+ private int mFocusedGlowColor;
+ private int mPressedOutlineColor;
+ private int mPressedGlowColor;
+
+ private int mTextColor;
+ private boolean mShadowsEnabled = true;
+ private boolean mIsTextVisible;
+
+ private boolean mBackgroundSizeChanged;
+ private Drawable mBackground;
+
+ private boolean mStayPressed;
+ private CheckLongPressHelper mLongPressHelper;
+
+ public BubbleTextView(Context context) {
+ super(context);
+ init();
+ }
+
+ public BubbleTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ 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_SP, grid.iconTextSize);
+ setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
+ }
+
+ private void init() {
+ mLongPressHelper = new CheckLongPressHelper(this);
+ mBackground = getBackground();
+
+ mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
+
+ final Resources res = getContext().getResources();
+ mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor =
+ res.getColor(R.color.outline_color);
+
+ setShadowLayer(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, SHADOW_LARGE_COLOUR);
+ }
+
+ public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
+ Bitmap b = info.getIcon(iconCache);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ setCompoundDrawablesWithIntrinsicBounds(null,
+ new FastBitmapDrawable(b),
+ null, null);
+ setCompoundDrawablePadding((int) ((grid.folderIconSizePx - grid.iconSizePx) / 2f));
+ setText(info.title);
+ setTag(info);
+ }
+
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) {
+ mBackgroundSizeChanged = true;
+ }
+ return super.setFrame(left, top, right, bottom);
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mBackground || super.verifyDrawable(who);
+ }
+
+ @Override
+ public void setTag(Object tag) {
+ if (tag != null) {
+ LauncherModel.checkItemInfo((ItemInfo) tag);
+ }
+ super.setTag(tag);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ if (isPressed()) {
+ // In this case, we have already created the pressed outline on ACTION_DOWN,
+ // so we just need to do an invalidate to trigger draw
+ if (!mDidInvalidateForPressedState) {
+ setCellLayoutPressedOrFocusedIcon();
+ }
+ } else {
+ // Otherwise, either clear the pressed/focused background, or create a background
+ // for the focused state
+ final boolean backgroundEmptyBefore = mPressedOrFocusedBackground == null;
+ if (!mStayPressed) {
+ mPressedOrFocusedBackground = null;
+ }
+ if (isFocused()) {
+ if (getLayout() == null) {
+ // In some cases, we get focus before we have been layed out. Set the
+ // background to null so that it will get created when the view is drawn.
+ mPressedOrFocusedBackground = null;
+ } else {
+ mPressedOrFocusedBackground = createGlowingOutline(
+ mTempCanvas, mFocusedGlowColor, mFocusedOutlineColor);
+ }
+ mStayPressed = false;
+ setCellLayoutPressedOrFocusedIcon();
+ }
+ final boolean backgroundEmptyNow = mPressedOrFocusedBackground == null;
+ if (!backgroundEmptyBefore && backgroundEmptyNow) {
+ setCellLayoutPressedOrFocusedIcon();
+ }
+ }
+
+ Drawable d = mBackground;
+ if (d != null && d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ super.drawableStateChanged();
+ }
+
+ /**
+ * Draw this BubbleTextView into the given Canvas.
+ *
+ * @param destCanvas the canvas to draw on
+ * @param padding the horizontal and vertical padding to use when drawing
+ */
+ private void drawWithPadding(Canvas destCanvas, int padding) {
+ final Rect clipRect = mTempRect;
+ getDrawingRect(clipRect);
+
+ // adjust the clip rect so that we don't include the text label
+ clipRect.bottom =
+ getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + getLayout().getLineTop(0);
+
+ // Draw the View into the bitmap.
+ // The translate of scrollX and scrollY is necessary when drawing TextViews, because
+ // they set scrollX and scrollY to large values to achieve centered text
+ destCanvas.save();
+ destCanvas.scale(getScaleX(), getScaleY(),
+ (getWidth() + padding) / 2, (getHeight() + padding) / 2);
+ destCanvas.translate(-getScrollX() + padding / 2, -getScrollY() + padding / 2);
+ destCanvas.clipRect(clipRect, Op.REPLACE);
+ draw(destCanvas);
+ destCanvas.restore();
+ }
+
+ /**
+ * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createGlowingOutline(Canvas canvas, int outlineColor, int glowColor) {
+ final int padding = mOutlineHelper.mMaxOuterBlurRadius;
+ final Bitmap b = Bitmap.createBitmap(
+ getWidth() + padding, getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ drawWithPadding(canvas, padding);
+ mOutlineHelper.applyExtraThickExpensiveOutlineWithBlur(b, canvas, glowColor, outlineColor);
+ canvas.setBitmap(null);
+
+ return b;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Call the superclass onTouchEvent first, because sometimes it changes the state to
+ // isPressed() on an ACTION_UP
+ boolean result = super.onTouchEvent(event);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ // So that the pressed outline is visible immediately when isPressed() is true,
+ // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
+ // to create it)
+ if (mPressedOrFocusedBackground == null) {
+ mPressedOrFocusedBackground = createGlowingOutline(
+ mTempCanvas, mPressedGlowColor, mPressedOutlineColor);
+ }
+ // Invalidate so the pressed state is visible, or set a flag so we know that we
+ // have to call invalidate as soon as the state is "pressed"
+ if (isPressed()) {
+ mDidInvalidateForPressedState = true;
+ setCellLayoutPressedOrFocusedIcon();
+ } else {
+ mDidInvalidateForPressedState = false;
+ }
+
+ mLongPressHelper.postCheckForLongPress();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // If we've touched down and up on an item, and it's still not "pressed", then
+ // destroy the pressed outline
+ if (!isPressed()) {
+ mPressedOrFocusedBackground = null;
+ }
+
+ mLongPressHelper.cancelLongPress();
+ break;
+ }
+ return result;
+ }
+
+ void setStayPressed(boolean stayPressed) {
+ mStayPressed = stayPressed;
+ if (!stayPressed) {
+ mPressedOrFocusedBackground = null;
+ }
+ setCellLayoutPressedOrFocusedIcon();
+ }
+
+ void setCellLayoutPressedOrFocusedIcon() {
+ if (getParent() instanceof ShortcutAndWidgetContainer) {
+ ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) getParent();
+ if (parent != null) {
+ CellLayout layout = (CellLayout) parent.getParent();
+ layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null);
+ }
+ }
+ }
+
+ void clearPressedOrFocusedBackground() {
+ mPressedOrFocusedBackground = null;
+ setCellLayoutPressedOrFocusedIcon();
+ }
+
+ Bitmap getPressedOrFocusedBackground() {
+ return mPressedOrFocusedBackground;
+ }
+
+ int getPressedOrFocusedBackgroundPadding() {
+ return mOutlineHelper.mMaxOuterBlurRadius / 2;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (!mShadowsEnabled) {
+ super.draw(canvas);
+ return;
+ }
+
+ final Drawable background = mBackground;
+ if (background != null) {
+ final int scrollX = getScrollX();
+ final int scrollY = getScrollY();
+
+ if (mBackgroundSizeChanged) {
+ background.setBounds(0, 0, getRight() - getLeft(), getBottom() - getTop());
+ mBackgroundSizeChanged = false;
+ }
+
+ if ((scrollX | scrollY) == 0) {
+ background.draw(canvas);
+ } else {
+ canvas.translate(scrollX, scrollY);
+ background.draw(canvas);
+ canvas.translate(-scrollX, -scrollY);
+ }
+ }
+
+ // 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(SHADOW_LARGE_RADIUS, 0.0f, SHADOW_Y_OFFSET, 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(SHADOW_SMALL_RADIUS, 0.0f, 0.0f, SHADOW_SMALL_COLOUR);
+ super.draw(canvas);
+ canvas.restore();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mBackground != null) mBackground.setCallback(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mBackground != null) mBackground.setCallback(null);
+ }
+
+ @Override
+ public void setTextColor(int color) {
+ mTextColor = color;
+ super.setTextColor(color);
+ }
+
+ public void setShadowsEnabled(boolean enabled) {
+ mShadowsEnabled = enabled;
+ getPaint().clearShadowLayer();
+ invalidate();
+ }
+
+ public void setTextVisibility(boolean visible) {
+ Resources res = getResources();
+ if (visible) {
+ super.setTextColor(mTextColor);
+ } else {
+ super.setTextColor(res.getColor(android.R.color.transparent));
+ }
+ mIsTextVisible = visible;
+ }
+
+ public boolean isTextVisible() {
+ return mIsTextVisible;
+ }
+
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ if (mPrevAlpha != alpha) {
+ mPrevAlpha = alpha;
+ super.onSetAlpha(alpha);
+ }
+ return true;
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ mLongPressHelper.cancelLongPress();
+ }
+}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
new file mode 100644
index 0000000..d51ae46
--- /dev/null
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -0,0 +1,165 @@
+/*
+ * 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.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+
+/**
+ * Implements a DropTarget.
+ */
+public class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener {
+
+ protected final int mTransitionDuration;
+
+ protected Launcher mLauncher;
+ private int mBottomDragPadding;
+ protected TextView mText;
+ protected SearchDropTargetBar mSearchDropTargetBar;
+
+ /** Whether this drop target is active for the current drag */
+ protected boolean mActive;
+
+ /** The paint applied to the drag view on hover */
+ protected int mHoverColor = 0;
+
+ public ButtonDropTarget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ButtonDropTarget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ Resources r = getResources();
+ mTransitionDuration = r.getInteger(R.integer.config_dropTargetBgTransitionDuration);
+ mBottomDragPadding = r.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
+ }
+
+ void setLauncher(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ public boolean acceptDrop(DragObject d) {
+ return false;
+ }
+
+ public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) {
+ mSearchDropTargetBar = searchDropTargetBar;
+ }
+
+ protected Drawable getCurrentDrawable() {
+ Drawable[] drawables = getCompoundDrawablesRelative();
+ for (int i = 0; i < drawables.length; ++i) {
+ if (drawables[i] != null) {
+ return drawables[i];
+ }
+ }
+ return null;
+ }
+
+ public void onDrop(DragObject d) {
+ }
+
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ // Do nothing
+ }
+
+ public void onDragEnter(DragObject d) {
+ d.dragView.setColor(mHoverColor);
+ }
+
+ public void onDragOver(DragObject d) {
+ // Do nothing
+ }
+
+ public void onDragExit(DragObject d) {
+ d.dragView.setColor(0);
+ }
+
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ // Do nothing
+ }
+
+ public boolean isDropEnabled() {
+ return mActive;
+ }
+
+ public void onDragEnd() {
+ // Do nothing
+ }
+
+ @Override
+ public void getHitRectRelativeToDragLayer(android.graphics.Rect outRect) {
+ super.getHitRect(outRect);
+ outRect.bottom += mBottomDragPadding;
+
+ int[] coords = new int[2];
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, coords);
+ outRect.offsetTo(coords[0], coords[1]);
+ }
+
+ private boolean isRtl() {
+ return (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ Rect getIconRect(int viewWidth, int viewHeight, int drawableWidth, int drawableHeight) {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+
+ // Find the rect to animate to (the view is center aligned)
+ Rect to = new Rect();
+ dragLayer.getViewRectRelativeToSelf(this, to);
+
+ final int width = drawableWidth;
+ final int height = drawableHeight;
+
+ final int left;
+ final int right;
+
+ if (isRtl()) {
+ right = to.right - getPaddingRight();
+ left = right - width;
+ } else {
+ left = to.left + getPaddingLeft();
+ right = left + width;
+ }
+
+ final int top = to.top + (getMeasuredHeight() - height) / 2;
+ final int bottom = top + height;
+
+ to.set(left, top, right, bottom);
+
+ // Center the destination rect about the trash icon
+ final int xOffset = (int) -(viewWidth - width) / 2;
+ final int yOffset = (int) -(viewHeight - height) / 2;
+ to.offset(xOffset, yOffset);
+
+ return to;
+ }
+
+ public void getLocationInDragLayer(int[] loc) {
+ mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
+ }
+}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
new file mode 100644
index 0000000..dafb79f
--- /dev/null
+++ b/src/com/android/launcher3/CellLayout.java
@@ -0,0 +1,3321 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LayoutAnimationController;
+
+import com.android.launcher3.R;
+import com.android.launcher3.FolderIcon.FolderRingAnimator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Stack;
+
+public class CellLayout extends ViewGroup {
+ static final String TAG = "CellLayout";
+
+ private Launcher mLauncher;
+ private int mCellWidth;
+ private int mCellHeight;
+ private int mFixedCellWidth;
+ private int mFixedCellHeight;
+
+ private int mCountX;
+ private int mCountY;
+
+ private int mOriginalWidthGap;
+ private int mOriginalHeightGap;
+ private int mWidthGap;
+ private int mHeightGap;
+ private int mMaxGap;
+ private boolean mScrollingTransformsDirty = false;
+
+ private final Rect mRect = new Rect();
+ private final CellInfo mCellInfo = new CellInfo();
+
+ // These are temporary variables to prevent having to allocate a new object just to
+ // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+ private final int[] mTmpXY = new int[2];
+ private final int[] mTmpPoint = new int[2];
+ int[] mTempLocation = new int[2];
+
+ boolean[][] mOccupied;
+ boolean[][] mTmpOccupied;
+ private boolean mLastDownOnOccupiedCell = false;
+
+ private OnTouchListener mInterceptTouchListener;
+
+ private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
+ private int[] mFolderLeaveBehindCell = {-1, -1};
+
+ private float FOREGROUND_ALPHA_DAMPER = 0.65f;
+ private int mForegroundAlpha = 0;
+ private float mBackgroundAlpha;
+ private float mBackgroundAlphaMultiplier = 1.0f;
+
+ private Drawable mNormalBackground;
+ private Drawable mActiveGlowBackground;
+ private Drawable mOverScrollForegroundDrawable;
+ private Drawable mOverScrollLeft;
+ private Drawable mOverScrollRight;
+ private Rect mBackgroundRect;
+ private Rect mForegroundRect;
+ private int mForegroundPadding;
+
+ // These values allow a fixed measurement to be set on the CellLayout.
+ private int mFixedWidth = -1;
+ private int mFixedHeight = -1;
+
+ // If we're actively dragging something over this screen, mIsDragOverlapping is true
+ private boolean mIsDragOverlapping = false;
+ boolean mUseActiveGlowBackground = false;
+
+ // These arrays are used to implement the drag visualization on x-large screens.
+ // They are used as circular arrays, indexed by mDragOutlineCurrent.
+ private Rect[] mDragOutlines = new Rect[4];
+ private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
+ private InterruptibleInOutAnimator[] mDragOutlineAnims =
+ new InterruptibleInOutAnimator[mDragOutlines.length];
+
+ // Used as an index into the above 3 arrays; indicates which is the most current value.
+ private int mDragOutlineCurrent = 0;
+ private final Paint mDragOutlinePaint = new Paint();
+
+ private BubbleTextView mPressedOrFocusedIcon;
+
+ private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
+ HashMap<CellLayout.LayoutParams, Animator>();
+ private HashMap<View, ReorderHintAnimation>
+ mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
+
+ private boolean mItemPlacementDirty = false;
+
+ // When a drag operation is in progress, holds the nearest cell to the touch point
+ private final int[] mDragCell = new int[2];
+
+ private boolean mDragging = false;
+
+ private TimeInterpolator mEaseOutInterpolator;
+ private ShortcutAndWidgetContainer mShortcutsAndWidgets;
+
+ private boolean mIsHotseat = false;
+ private float mHotseatScale = 1f;
+
+ public static final int MODE_DRAG_OVER = 0;
+ public static final int MODE_ON_DROP = 1;
+ public static final int MODE_ON_DROP_EXTERNAL = 2;
+ public static final int MODE_ACCEPT_DROP = 3;
+ private static final boolean DESTRUCTIVE_REORDER = false;
+ private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
+
+ static final int LANDSCAPE = 0;
+ static final int PORTRAIT = 1;
+
+ private static final float REORDER_HINT_MAGNITUDE = 0.12f;
+ private static final int REORDER_ANIMATION_DURATION = 150;
+ private float mReorderHintAnimationMagnitude;
+
+ private ArrayList<View> mIntersectingViews = new ArrayList<View>();
+ private Rect mOccupiedRect = new Rect();
+ private int[] mDirectionVector = new int[2];
+ int[] mPreviousReorderDirection = new int[2];
+ private static final int INVALID_DIRECTION = -100;
+ private DropTarget.DragEnforcer mDragEnforcer;
+
+ private Rect mTempRect = new Rect();
+
+ private final static PorterDuffXfermode sAddBlendMode =
+ new PorterDuffXfermode(PorterDuff.Mode.ADD);
+ private final static Paint sPaint = new Paint();
+
+ public CellLayout(Context context) {
+ this(context, null);
+ }
+
+ public CellLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CellLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mDragEnforcer = new DropTarget.DragEnforcer(context);
+
+ // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
+ // the user where a dragged item will land when dropped.
+ setWillNotDraw(false);
+ setClipToPadding(false);
+ mLauncher = (Launcher) context;
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
+
+ mCellWidth = mCellHeight = -1;
+ mFixedCellHeight = mFixedCellHeight = -1;
+ mWidthGap = mOriginalWidthGap = 0;
+ mHeightGap = mOriginalHeightGap = 0;
+ mMaxGap = Integer.MAX_VALUE;
+ mCountX = (int) grid.numColumns;
+ mCountY = (int) grid.numRows;
+ mOccupied = new boolean[mCountX][mCountY];
+ mTmpOccupied = new boolean[mCountX][mCountY];
+ mPreviousReorderDirection[0] = INVALID_DIRECTION;
+ mPreviousReorderDirection[1] = INVALID_DIRECTION;
+
+ a.recycle();
+
+ setAlwaysDrawnWithCacheEnabled(false);
+
+ final Resources res = getResources();
+ mHotseatScale = (float) grid.hotseatIconSize / grid.iconSize;
+
+ mNormalBackground = res.getDrawable(R.drawable.screenpanel);
+ mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
+
+ mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
+ mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
+ mForegroundPadding =
+ res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
+
+ mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
+ grid.iconSizePx);
+
+ mNormalBackground.setFilterBitmap(true);
+ mActiveGlowBackground.setFilterBitmap(true);
+
+ // Initialize the data structures used for the drag visualization.
+ mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
+ mDragCell[0] = mDragCell[1] = -1;
+ for (int i = 0; i < mDragOutlines.length; i++) {
+ mDragOutlines[i] = new Rect(-1, -1, -1, -1);
+ }
+
+ // When dragging things around the home screens, we show a green outline of
+ // where the item will land. The outlines gradually fade out, leaving a trail
+ // behind the drag path.
+ // Set up all the animations that are used to implement this fading.
+ final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
+ final float fromAlphaValue = 0;
+ final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
+
+ Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
+
+ for (int i = 0; i < mDragOutlineAnims.length; i++) {
+ final InterruptibleInOutAnimator anim =
+ new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
+ anim.getAnimator().setInterpolator(mEaseOutInterpolator);
+ final int thisIndex = i;
+ anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final Bitmap outline = (Bitmap)anim.getTag();
+
+ // If an animation is started and then stopped very quickly, we can still
+ // get spurious updates we've cleared the tag. Guard against this.
+ if (outline == null) {
+ @SuppressWarnings("all") // suppress dead code warning
+ final boolean debug = false;
+ if (debug) {
+ Object val = animation.getAnimatedValue();
+ Log.d(TAG, "anim " + thisIndex + " update: " + val +
+ ", isStopped " + anim.isStopped());
+ }
+ // Try to prevent it from continuing to run
+ animation.cancel();
+ } else {
+ mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
+ CellLayout.this.invalidate(mDragOutlines[thisIndex]);
+ }
+ }
+ });
+ // The animation holds a reference to the drag outline bitmap as long is it's
+ // running. This way the bitmap can be GCed when the animations are complete.
+ anim.getAnimator().addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
+ anim.setTag(null);
+ }
+ }
+ });
+ mDragOutlineAnims[i] = anim;
+ }
+
+ mBackgroundRect = new Rect();
+ mForegroundRect = new Rect();
+
+ mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+ mCountX, mCountY);
+
+ addView(mShortcutsAndWidgets);
+ }
+
+ public void enableHardwareLayer(boolean hasLayer) {
+ mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
+ }
+
+ public void buildHardwareLayer() {
+ mShortcutsAndWidgets.buildLayer();
+ }
+
+ public float getChildrenScale() {
+ return mIsHotseat ? mHotseatScale : 1.0f;
+ }
+
+ public void setCellDimensions(int width, int height) {
+ mFixedCellWidth = mCellWidth = width;
+ mFixedCellHeight = mCellHeight = height;
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+ mCountX, mCountY);
+ }
+
+ public void setGridSize(int x, int y) {
+ mCountX = x;
+ mCountY = y;
+ mOccupied = new boolean[mCountX][mCountY];
+ mTmpOccupied = new boolean[mCountX][mCountY];
+ mTempRectStack.clear();
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+ mCountX, mCountY);
+ requestLayout();
+ }
+
+ // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
+ public void setInvertIfRtl(boolean invert) {
+ mShortcutsAndWidgets.setInvertIfRtl(invert);
+ }
+
+ private void invalidateBubbleTextView(BubbleTextView icon) {
+ final int padding = icon.getPressedOrFocusedBackgroundPadding();
+ invalidate(icon.getLeft() + getPaddingLeft() - padding,
+ icon.getTop() + getPaddingTop() - padding,
+ icon.getRight() + getPaddingLeft() + padding,
+ icon.getBottom() + getPaddingTop() + padding);
+ }
+
+ void setOverScrollAmount(float r, boolean left) {
+ if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
+ mOverScrollForegroundDrawable = mOverScrollLeft;
+ } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
+ mOverScrollForegroundDrawable = mOverScrollRight;
+ }
+
+ r *= FOREGROUND_ALPHA_DAMPER;
+ mForegroundAlpha = (int) Math.round((r * 255));
+ mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
+ invalidate();
+ }
+
+ void setPressedOrFocusedIcon(BubbleTextView icon) {
+ // We draw the pressed or focused BubbleTextView's background in CellLayout because it
+ // requires an expanded clip rect (due to the glow's blur radius)
+ BubbleTextView oldIcon = mPressedOrFocusedIcon;
+ mPressedOrFocusedIcon = icon;
+ if (oldIcon != null) {
+ invalidateBubbleTextView(oldIcon);
+ }
+ if (mPressedOrFocusedIcon != null) {
+ invalidateBubbleTextView(mPressedOrFocusedIcon);
+ }
+ }
+
+ void setIsDragOverlapping(boolean isDragOverlapping) {
+ if (mIsDragOverlapping != isDragOverlapping) {
+ mIsDragOverlapping = isDragOverlapping;
+ setUseActiveGlowBackground(mIsDragOverlapping);
+ invalidate();
+ }
+ }
+
+ void setUseActiveGlowBackground(boolean use) {
+ mUseActiveGlowBackground = use;
+ }
+
+ boolean getIsDragOverlapping() {
+ return mIsDragOverlapping;
+ }
+
+ protected void setOverscrollTransformsDirty(boolean dirty) {
+ mScrollingTransformsDirty = dirty;
+ }
+
+ protected void resetOverscrollTransforms() {
+ if (mScrollingTransformsDirty) {
+ setOverscrollTransformsDirty(false);
+ setTranslationX(0);
+ setRotationY(0);
+ // It doesn't matter if we pass true or false here, the important thing is that we
+ // pass 0, which results in the overscroll drawable not being drawn any more.
+ setOverScrollAmount(0, false);
+ setPivotX(getMeasuredWidth() / 2);
+ setPivotY(getMeasuredHeight() / 2);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
+ // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
+ // When we're small, we are either drawn normally or in the "accepts drops" state (during
+ // a drag). However, we also drag the mini hover background *over* one of those two
+ // backgrounds
+ if (mBackgroundAlpha > 0.0f) {
+ Drawable bg;
+
+ if (mUseActiveGlowBackground) {
+ // In the mini case, we draw the active_glow bg *over* the active background
+ bg = mActiveGlowBackground;
+ } else {
+ bg = mNormalBackground;
+ }
+
+ bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
+ bg.setBounds(mBackgroundRect);
+ bg.draw(canvas);
+ }
+
+ final Paint paint = mDragOutlinePaint;
+ for (int i = 0; i < mDragOutlines.length; i++) {
+ final float alpha = mDragOutlineAlphas[i];
+ if (alpha > 0) {
+ final Rect r = mDragOutlines[i];
+ mTempRect.set(r);
+ Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
+ final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
+ paint.setAlpha((int)(alpha + .5f));
+ canvas.drawBitmap(b, null, mTempRect, paint);
+ }
+ }
+
+ // We draw the pressed or focused BubbleTextView's background in CellLayout because it
+ // requires an expanded clip rect (due to the glow's blur radius)
+ if (mPressedOrFocusedIcon != null) {
+ final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
+ final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
+ if (b != null) {
+ int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
+ (mCountX * mCellWidth);
+ int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
+ int top = getPaddingTop();
+ canvas.drawBitmap(b,
+ mPressedOrFocusedIcon.getLeft() + left - padding,
+ mPressedOrFocusedIcon.getTop() + top - padding,
+ null);
+ }
+ }
+
+ if (DEBUG_VISUALIZE_OCCUPIED) {
+ int[] pt = new int[2];
+ ColorDrawable cd = new ColorDrawable(Color.RED);
+ cd.setBounds(0, 0, mCellWidth, mCellHeight);
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ if (mOccupied[i][j]) {
+ cellToPoint(i, j, pt);
+ canvas.save();
+ canvas.translate(pt[0], pt[1]);
+ cd.draw(canvas);
+ canvas.restore();
+ }
+ }
+ }
+ }
+
+ int previewOffset = FolderRingAnimator.sPreviewSize;
+
+ // The folder outer / inner ring image(s)
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ for (int i = 0; i < mFolderOuterRings.size(); i++) {
+ FolderRingAnimator fra = mFolderOuterRings.get(i);
+
+ Drawable d;
+ int width, height;
+ cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
+ View child = getChildAt(fra.mCellX, fra.mCellY);
+
+ if (child != null) {
+ int centerX = mTempLocation[0] + mCellWidth / 2;
+ int centerY = mTempLocation[1] + previewOffset / 2 +
+ child.getPaddingTop() + grid.folderBackgroundOffset;
+
+ // Draw outer ring, if it exists
+ if (FolderIcon.HAS_OUTER_RING) {
+ d = FolderRingAnimator.sSharedOuterRingDrawable;
+ width = (int) (fra.getOuterRingSize() * getChildrenScale());
+ height = width;
+ canvas.save();
+ canvas.translate(centerX - width / 2, centerY - height / 2);
+ d.setBounds(0, 0, width, height);
+ d.draw(canvas);
+ canvas.restore();
+ }
+
+ // Draw inner ring
+ d = FolderRingAnimator.sSharedInnerRingDrawable;
+ width = (int) (fra.getInnerRingSize() * getChildrenScale());
+ height = width;
+ canvas.save();
+ canvas.translate(centerX - width / 2, centerY - width / 2);
+ d.setBounds(0, 0, width, height);
+ d.draw(canvas);
+ canvas.restore();
+ }
+ }
+
+ if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
+ Drawable d = FolderIcon.sSharedFolderLeaveBehind;
+ int width = d.getIntrinsicWidth();
+ int height = d.getIntrinsicHeight();
+
+ cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
+ View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
+ if (child != null) {
+ int centerX = mTempLocation[0] + mCellWidth / 2;
+ int centerY = mTempLocation[1] + previewOffset / 2 +
+ child.getPaddingTop() + grid.folderBackgroundOffset;
+
+ canvas.save();
+ canvas.translate(centerX - width / 2, centerY - width / 2);
+ d.setBounds(0, 0, width, height);
+ d.draw(canvas);
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (mForegroundAlpha > 0) {
+ mOverScrollForegroundDrawable.setBounds(mForegroundRect);
+ mOverScrollForegroundDrawable.draw(canvas);
+ }
+ }
+
+ public void showFolderAccept(FolderRingAnimator fra) {
+ mFolderOuterRings.add(fra);
+ }
+
+ public void hideFolderAccept(FolderRingAnimator fra) {
+ if (mFolderOuterRings.contains(fra)) {
+ mFolderOuterRings.remove(fra);
+ }
+ invalidate();
+ }
+
+ public void setFolderLeaveBehindCell(int x, int y) {
+ mFolderLeaveBehindCell[0] = x;
+ mFolderLeaveBehindCell[1] = y;
+ invalidate();
+ }
+
+ public void clearFolderLeaveBehind() {
+ mFolderLeaveBehindCell[0] = -1;
+ mFolderLeaveBehindCell[1] = -1;
+ invalidate();
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ public void restoreInstanceState(SparseArray<Parcelable> states) {
+ dispatchRestoreInstanceState(states);
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ // Cancel long press for all children
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.cancelLongPress();
+ }
+ }
+
+ public void setOnInterceptTouchListener(View.OnTouchListener listener) {
+ mInterceptTouchListener = listener;
+ }
+
+ int getCountX() {
+ return mCountX;
+ }
+
+ int getCountY() {
+ return mCountY;
+ }
+
+ public void setIsHotseat(boolean isHotseat) {
+ mIsHotseat = isHotseat;
+ mShortcutsAndWidgets.setIsHotseat(isHotseat);
+ }
+
+ public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
+ boolean markCells) {
+ final LayoutParams lp = params;
+
+ // Hotseat icons - remove text
+ if (child instanceof BubbleTextView) {
+ BubbleTextView bubbleChild = (BubbleTextView) child;
+ bubbleChild.setTextVisibility(!mIsHotseat);
+ }
+
+ child.setScaleX(getChildrenScale());
+ child.setScaleY(getChildrenScale());
+
+ // Generate an id for each view, this assumes we have at most 256x256 cells
+ // per workspace screen
+ if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
+ // If the horizontal or vertical span is set to -1, it is taken to
+ // mean that it spans the extent of the CellLayout
+ if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
+ if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
+
+ child.setId(childId);
+
+ mShortcutsAndWidgets.addView(child, index, lp);
+
+ if (markCells) markCellsAsOccupiedForView(child);
+
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void removeAllViews() {
+ clearOccupiedCells();
+ mShortcutsAndWidgets.removeAllViews();
+ }
+
+ @Override
+ public void removeAllViewsInLayout() {
+ if (mShortcutsAndWidgets.getChildCount() > 0) {
+ clearOccupiedCells();
+ mShortcutsAndWidgets.removeAllViewsInLayout();
+ }
+ }
+
+ public void removeViewWithoutMarkingCells(View view) {
+ mShortcutsAndWidgets.removeView(view);
+ }
+
+ @Override
+ public void removeView(View view) {
+ markCellsAsUnoccupiedForView(view);
+ mShortcutsAndWidgets.removeView(view);
+ }
+
+ @Override
+ public void removeViewAt(int index) {
+ markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
+ mShortcutsAndWidgets.removeViewAt(index);
+ }
+
+ @Override
+ public void removeViewInLayout(View view) {
+ markCellsAsUnoccupiedForView(view);
+ mShortcutsAndWidgets.removeViewInLayout(view);
+ }
+
+ @Override
+ public void removeViews(int start, int count) {
+ for (int i = start; i < start + count; i++) {
+ markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
+ }
+ mShortcutsAndWidgets.removeViews(start, count);
+ }
+
+ @Override
+ public void removeViewsInLayout(int start, int count) {
+ for (int i = start; i < start + count; i++) {
+ markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
+ }
+ mShortcutsAndWidgets.removeViewsInLayout(start, count);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (getParent() instanceof Workspace) {
+ Workspace workspace = (Workspace) getParent();
+ mCellInfo.screenId = workspace.getIdForScreen(this);
+ }
+ }
+
+ public void setTagToCellInfoForPoint(int touchX, int touchY) {
+ final CellInfo cellInfo = mCellInfo;
+ Rect frame = mRect;
+ final int x = touchX + getScrollX();
+ final int y = touchY + getScrollY();
+ final int count = mShortcutsAndWidgets.getChildCount();
+
+ boolean found = false;
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = mShortcutsAndWidgets.getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
+ lp.isLockedToGrid) {
+ child.getHitRect(frame);
+
+ float scale = child.getScaleX();
+ frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
+ child.getBottom());
+ // The child hit rect is relative to the CellLayoutChildren parent, so we need to
+ // offset that by this CellLayout's padding to test an (x,y) point that is relative
+ // to this view.
+ frame.offset(getPaddingLeft(), getPaddingTop());
+ frame.inset((int) (frame.width() * (1f - scale) / 2),
+ (int) (frame.height() * (1f - scale) / 2));
+
+ if (frame.contains(x, y)) {
+ cellInfo.cell = child;
+ cellInfo.cellX = lp.cellX;
+ cellInfo.cellY = lp.cellY;
+ cellInfo.spanX = lp.cellHSpan;
+ cellInfo.spanY = lp.cellVSpan;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ mLastDownOnOccupiedCell = found;
+
+ if (!found) {
+ final int cellXY[] = mTmpXY;
+ pointToCellExact(x, y, cellXY);
+
+ cellInfo.cell = null;
+ cellInfo.cellX = cellXY[0];
+ cellInfo.cellY = cellXY[1];
+ cellInfo.spanX = 1;
+ cellInfo.spanY = 1;
+ }
+ setTag(cellInfo);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // First we clear the tag to ensure that on every touch down we start with a fresh slate,
+ // even in the case where we return early. Not clearing here was causing bugs whereby on
+ // long-press we'd end up picking up an item from a previous drag operation.
+ final int action = ev.getAction();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ clearTagCellInfo();
+ }
+
+ if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
+ return true;
+ }
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
+ }
+
+ return false;
+ }
+
+ private void clearTagCellInfo() {
+ final CellInfo cellInfo = mCellInfo;
+ cellInfo.cell = null;
+ cellInfo.cellX = -1;
+ cellInfo.cellY = -1;
+ cellInfo.spanX = 0;
+ cellInfo.spanY = 0;
+ setTag(cellInfo);
+ }
+
+ public CellInfo getTag() {
+ return (CellInfo) super.getTag();
+ }
+
+ /**
+ * Given a point, return the cell that strictly encloses that point
+ * @param x X coordinate of the point
+ * @param y Y coordinate of the point
+ * @param result Array of 2 ints to hold the x and y coordinate of the cell
+ */
+ void pointToCellExact(int x, int y, int[] result) {
+ final int hStartPadding = getPaddingLeft();
+ final int vStartPadding = getPaddingTop();
+
+ result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
+ result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
+
+ final int xAxis = mCountX;
+ final int yAxis = mCountY;
+
+ if (result[0] < 0) result[0] = 0;
+ if (result[0] >= xAxis) result[0] = xAxis - 1;
+ if (result[1] < 0) result[1] = 0;
+ if (result[1] >= yAxis) result[1] = yAxis - 1;
+ }
+
+ /**
+ * Given a point, return the cell that most closely encloses that point
+ * @param x X coordinate of the point
+ * @param y Y coordinate of the point
+ * @param result Array of 2 ints to hold the x and y coordinate of the cell
+ */
+ void pointToCellRounded(int x, int y, int[] result) {
+ pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
+ }
+
+ /**
+ * Given a cell coordinate, return the point that represents the upper left corner of that cell
+ *
+ * @param cellX X coordinate of the cell
+ * @param cellY Y coordinate of the cell
+ *
+ * @param result Array of 2 ints to hold the x and y coordinate of the point
+ */
+ void cellToPoint(int cellX, int cellY, int[] result) {
+ final int hStartPadding = getPaddingLeft();
+ final int vStartPadding = getPaddingTop();
+
+ result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
+ result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
+ }
+
+ /**
+ * Given a cell coordinate, return the point that represents the center of the cell
+ *
+ * @param cellX X coordinate of the cell
+ * @param cellY Y coordinate of the cell
+ *
+ * @param result Array of 2 ints to hold the x and y coordinate of the point
+ */
+ void cellToCenterPoint(int cellX, int cellY, int[] result) {
+ regionToCenterPoint(cellX, cellY, 1, 1, result);
+ }
+
+ /**
+ * Given a cell coordinate and span return the point that represents the center of the regio
+ *
+ * @param cellX X coordinate of the cell
+ * @param cellY Y coordinate of the cell
+ *
+ * @param result Array of 2 ints to hold the x and y coordinate of the point
+ */
+ void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
+ final int hStartPadding = getPaddingLeft();
+ final int vStartPadding = getPaddingTop();
+ result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
+ (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
+ result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
+ (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
+ }
+
+ /**
+ * Given a cell coordinate and span fills out a corresponding pixel rect
+ *
+ * @param cellX X coordinate of the cell
+ * @param cellY Y coordinate of the cell
+ * @param result Rect in which to write the result
+ */
+ void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
+ final int hStartPadding = getPaddingLeft();
+ final int vStartPadding = getPaddingTop();
+ final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
+ final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
+ result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
+ top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
+ }
+
+ public float getDistanceFromCell(float x, float y, int[] cell) {
+ cellToCenterPoint(cell[0], cell[1], mTmpPoint);
+ float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
+ Math.pow(y - mTmpPoint[1], 2));
+ return distance;
+ }
+
+ int getCellWidth() {
+ return mCellWidth;
+ }
+
+ int getCellHeight() {
+ return mCellHeight;
+ }
+
+ int getWidthGap() {
+ return mWidthGap;
+ }
+
+ int getHeightGap() {
+ return mHeightGap;
+ }
+
+ Rect getContentRect(Rect r) {
+ if (r == null) {
+ r = new Rect();
+ }
+ int left = getPaddingLeft();
+ int top = getPaddingTop();
+ int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
+ int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
+ r.set(left, top, right, bottom);
+ return r;
+ }
+
+ /** Return a rect that has the cellWidth/cellHeight (left, top), and
+ * widthGap/heightGap (right, bottom) */
+ static void getMetrics(Rect metrics, int paddedMeasureWidth,
+ int paddedMeasureHeight, int countX, int countY) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX),
+ grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0);
+ }
+
+ public void setFixedSize(int width, int height) {
+ mFixedWidth = width;
+ mFixedHeight = height;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
+ int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
+ if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
+ int cw = grid.calculateCellWidth(childWidthSize, mCountX);
+ int ch = grid.calculateCellHeight(childHeightSize, mCountY);
+ if (cw != mCellWidth || ch != mCellHeight) {
+ mCellWidth = cw;
+ mCellHeight = ch;
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
+ mHeightGap, mCountX, mCountY);
+ }
+ }
+
+ int newWidth = childWidthSize;
+ int newHeight = childHeightSize;
+ if (mFixedWidth > 0 && mFixedHeight > 0) {
+ newWidth = mFixedWidth;
+ newHeight = mFixedHeight;
+ } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+ }
+
+ int numWidthGaps = mCountX - 1;
+ int numHeightGaps = mCountY - 1;
+
+ if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
+ int hSpace = childWidthSize;
+ int vSpace = childHeightSize;
+ int hFreeSpace = hSpace - (mCountX * mCellWidth);
+ int vFreeSpace = vSpace - (mCountY * mCellHeight);
+ mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
+ mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
+ mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
+ mHeightGap, mCountX, mCountY);
+ } else {
+ mWidthGap = mOriginalWidthGap;
+ mHeightGap = mOriginalHeightGap;
+ }
+ int count = getChildCount();
+ int maxWidth = 0;
+ int maxHeight = 0;
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
+ MeasureSpec.EXACTLY);
+ int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
+ MeasureSpec.EXACTLY);
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+ maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+ }
+ if (mFixedWidth > 0 && mFixedHeight > 0) {
+ setMeasuredDimension(maxWidth, maxHeight);
+ } else {
+ setMeasuredDimension(widthSize, heightSize);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
+ (mCountX * mCellWidth);
+ int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
+ int top = getPaddingTop();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ child.layout(left, top,
+ left + r - l,
+ top + b - t);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ // Expand the background drawing bounds by the padding baked into the background drawable
+ Rect padding = new Rect();
+ mNormalBackground.getPadding(padding);
+ mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
+
+ mForegroundRect.set(mForegroundPadding, mForegroundPadding,
+ w - mForegroundPadding, h - mForegroundPadding);
+ }
+
+ @Override
+ protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+ mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
+ }
+
+ @Override
+ protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+ mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
+ }
+
+ public float getBackgroundAlpha() {
+ return mBackgroundAlpha;
+ }
+
+ public void setBackgroundAlphaMultiplier(float multiplier) {
+ if (mBackgroundAlphaMultiplier != multiplier) {
+ mBackgroundAlphaMultiplier = multiplier;
+ invalidate();
+ }
+ }
+
+ public float getBackgroundAlphaMultiplier() {
+ return mBackgroundAlphaMultiplier;
+ }
+
+ public void setBackgroundAlpha(float alpha) {
+ if (mBackgroundAlpha != alpha) {
+ mBackgroundAlpha = alpha;
+ invalidate();
+ }
+ }
+
+ public void setShortcutAndWidgetAlpha(float alpha) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ getChildAt(i).setAlpha(alpha);
+ }
+ }
+
+ public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
+ if (getChildCount() > 0) {
+ return (ShortcutAndWidgetContainer) getChildAt(0);
+ }
+ return null;
+ }
+
+ public View getChildAt(int x, int y) {
+ return mShortcutsAndWidgets.getChildAt(x, y);
+ }
+
+ public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
+ int delay, boolean permanent, boolean adjustOccupied) {
+ ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
+ boolean[][] occupied = mOccupied;
+ if (!permanent) {
+ occupied = mTmpOccupied;
+ }
+
+ if (clc.indexOfChild(child) != -1) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final ItemInfo info = (ItemInfo) child.getTag();
+
+ // We cancel any existing animations
+ if (mReorderAnimators.containsKey(lp)) {
+ mReorderAnimators.get(lp).cancel();
+ mReorderAnimators.remove(lp);
+ }
+
+ final int oldX = lp.x;
+ final int oldY = lp.y;
+ if (adjustOccupied) {
+ occupied[lp.cellX][lp.cellY] = false;
+ occupied[cellX][cellY] = true;
+ }
+ lp.isLockedToGrid = true;
+ if (permanent) {
+ lp.cellX = info.cellX = cellX;
+ lp.cellY = info.cellY = cellY;
+ } else {
+ lp.tmpCellX = cellX;
+ lp.tmpCellY = cellY;
+ }
+ clc.setupLp(lp);
+ lp.isLockedToGrid = false;
+ final int newX = lp.x;
+ final int newY = lp.y;
+
+ lp.x = oldX;
+ lp.y = oldY;
+
+ // Exit early if we're not actually moving the view
+ if (oldX == newX && oldY == newY) {
+ lp.isLockedToGrid = true;
+ return true;
+ }
+
+ ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+ va.setDuration(duration);
+ mReorderAnimators.put(lp, va);
+
+ va.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float r = ((Float) animation.getAnimatedValue()).floatValue();
+ lp.x = (int) ((1 - r) * oldX + r * newX);
+ lp.y = (int) ((1 - r) * oldY + r * newY);
+ child.requestLayout();
+ }
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ boolean cancelled = false;
+ public void onAnimationEnd(Animator animation) {
+ // If the animation was cancelled, it means that another animation
+ // has interrupted this one, and we don't want to lock the item into
+ // place just yet.
+ if (!cancelled) {
+ lp.isLockedToGrid = true;
+ child.requestLayout();
+ }
+ if (mReorderAnimators.containsKey(lp)) {
+ mReorderAnimators.remove(lp);
+ }
+ }
+ public void onAnimationCancel(Animator animation) {
+ cancelled = true;
+ }
+ });
+ va.setStartDelay(delay);
+ va.start();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Estimate where the top left cell of the dragged item will land if it is dropped.
+ *
+ * @param originX The X value of the top left corner of the item
+ * @param originY The Y value of the top left corner of the item
+ * @param spanX The number of horizontal cells that the item spans
+ * @param spanY The number of vertical cells that the item spans
+ * @param result The estimated drop cell X and Y.
+ */
+ void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
+ final int countX = mCountX;
+ final int countY = mCountY;
+
+ // pointToCellRounded takes the top left of a cell but will pad that with
+ // cellWidth/2 and cellHeight/2 when finding the matching cell
+ pointToCellRounded(originX, originY, result);
+
+ // If the item isn't fully on this screen, snap to the edges
+ int rightOverhang = result[0] + spanX - countX;
+ if (rightOverhang > 0) {
+ result[0] -= rightOverhang; // Snap to right
+ }
+ result[0] = Math.max(0, result[0]); // Snap to left
+ int bottomOverhang = result[1] + spanY - countY;
+ if (bottomOverhang > 0) {
+ result[1] -= bottomOverhang; // Snap to bottom
+ }
+ result[1] = Math.max(0, result[1]); // Snap to top
+ }
+
+ void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
+ int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
+ final int oldDragCellX = mDragCell[0];
+ final int oldDragCellY = mDragCell[1];
+
+ if (dragOutline == null && v == null) {
+ return;
+ }
+
+ if (cellX != oldDragCellX || cellY != oldDragCellY) {
+ mDragCell[0] = cellX;
+ mDragCell[1] = cellY;
+ // Find the top left corner of the rect the object will occupy
+ final int[] topLeft = mTmpPoint;
+ cellToPoint(cellX, cellY, topLeft);
+
+ int left = topLeft[0];
+ int top = topLeft[1];
+
+ if (v != null && dragOffset == null) {
+ // When drawing the drag outline, it did not account for margin offsets
+ // added by the view's parent.
+ MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
+ left += lp.leftMargin;
+ top += lp.topMargin;
+
+ // Offsets due to the size difference between the View and the dragOutline.
+ // There is a size difference to account for the outer blur, which may lie
+ // outside the bounds of the view.
+ top += (v.getHeight() - dragOutline.getHeight()) / 2;
+ // We center about the x axis
+ left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+ - dragOutline.getWidth()) / 2;
+ } else {
+ if (dragOffset != null && dragRegion != null) {
+ // Center the drag region *horizontally* in the cell and apply a drag
+ // outline offset
+ left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+ - dragRegion.width()) / 2;
+ int cHeight = getShortcutsAndWidgets().getCellContentHeight();
+ int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
+ top += dragOffset.y + cellPaddingY;
+ } else {
+ // Center the drag outline in the cell
+ left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
+ - dragOutline.getWidth()) / 2;
+ top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
+ - dragOutline.getHeight()) / 2;
+ }
+ }
+ final int oldIndex = mDragOutlineCurrent;
+ mDragOutlineAnims[oldIndex].animateOut();
+ mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
+ Rect r = mDragOutlines[mDragOutlineCurrent];
+ r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
+ if (resize) {
+ cellToRect(cellX, cellY, spanX, spanY, r);
+ }
+
+ mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
+ mDragOutlineAnims[mDragOutlineCurrent].animateIn();
+ }
+ }
+
+ public void clearDragOutlines() {
+ final int oldIndex = mDragOutlineCurrent;
+ mDragOutlineAnims[oldIndex].animateOut();
+ mDragCell[0] = mDragCell[1] = -1;
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
+ int[] result) {
+ return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param minSpanX The minimum horizontal span required
+ * @param minSpanY The minimum vertical span required
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
+ int spanY, int[] result, int[] resultSpan) {
+ return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
+ result, resultSpan);
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param ignoreOccupied If true, the result can be an occupied cell
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
+ boolean ignoreOccupied, int[] result) {
+ return findNearestArea(pixelX, pixelY, spanX, spanY,
+ spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
+ }
+
+ private final Stack<Rect> mTempRectStack = new Stack<Rect>();
+ private void lazyInitTempRectStack() {
+ if (mTempRectStack.isEmpty()) {
+ for (int i = 0; i < mCountX * mCountY; i++) {
+ mTempRectStack.push(new Rect());
+ }
+ }
+ }
+
+ private void recycleTempRects(Stack<Rect> used) {
+ while (!used.isEmpty()) {
+ mTempRectStack.push(used.pop());
+ }
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param minSpanX The minimum horizontal span required
+ * @param minSpanY The minimum vertical span required
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param ignoreOccupied If true, the result can be an occupied cell
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+ View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
+ boolean[][] occupied) {
+ lazyInitTempRectStack();
+ // mark space take by ignoreView as available (method checks if ignoreView is null)
+ markCellsAsUnoccupiedForView(ignoreView, occupied);
+
+ // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
+ // to the center of the item, but we are searching based on the top-left cell, so
+ // we translate the point over to correspond to the top-left.
+ pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
+ pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
+
+ // Keep track of best-scoring drop area
+ final int[] bestXY = result != null ? result : new int[2];
+ double bestDistance = Double.MAX_VALUE;
+ final Rect bestRect = new Rect(-1, -1, -1, -1);
+ final Stack<Rect> validRegions = new Stack<Rect>();
+
+ final int countX = mCountX;
+ final int countY = mCountY;
+
+ if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
+ spanX < minSpanX || spanY < minSpanY) {
+ return bestXY;
+ }
+
+ for (int y = 0; y < countY - (minSpanY - 1); y++) {
+ inner:
+ for (int x = 0; x < countX - (minSpanX - 1); x++) {
+ int ySize = -1;
+ int xSize = -1;
+ if (ignoreOccupied) {
+ // First, let's see if this thing fits anywhere
+ for (int i = 0; i < minSpanX; i++) {
+ for (int j = 0; j < minSpanY; j++) {
+ if (occupied[x + i][y + j]) {
+ continue inner;
+ }
+ }
+ }
+ xSize = minSpanX;
+ ySize = minSpanY;
+
+ // We know that the item will fit at _some_ acceptable size, now let's see
+ // how big we can make it. We'll alternate between incrementing x and y spans
+ // until we hit a limit.
+ boolean incX = true;
+ boolean hitMaxX = xSize >= spanX;
+ boolean hitMaxY = ySize >= spanY;
+ while (!(hitMaxX && hitMaxY)) {
+ if (incX && !hitMaxX) {
+ for (int j = 0; j < ySize; j++) {
+ if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
+ // We can't move out horizontally
+ hitMaxX = true;
+ }
+ }
+ if (!hitMaxX) {
+ xSize++;
+ }
+ } else if (!hitMaxY) {
+ for (int i = 0; i < xSize; i++) {
+ if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
+ // We can't move out vertically
+ hitMaxY = true;
+ }
+ }
+ if (!hitMaxY) {
+ ySize++;
+ }
+ }
+ hitMaxX |= xSize >= spanX;
+ hitMaxY |= ySize >= spanY;
+ incX = !incX;
+ }
+ incX = true;
+ hitMaxX = xSize >= spanX;
+ hitMaxY = ySize >= spanY;
+ }
+ final int[] cellXY = mTmpXY;
+ cellToCenterPoint(x, y, cellXY);
+
+ // We verify that the current rect is not a sub-rect of any of our previous
+ // candidates. In this case, the current rect is disqualified in favour of the
+ // containing rect.
+ Rect currentRect = mTempRectStack.pop();
+ currentRect.set(x, y, x + xSize, y + ySize);
+ boolean contained = false;
+ for (Rect r : validRegions) {
+ if (r.contains(currentRect)) {
+ contained = true;
+ break;
+ }
+ }
+ validRegions.push(currentRect);
+ double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
+ + Math.pow(cellXY[1] - pixelY, 2));
+
+ if ((distance <= bestDistance && !contained) ||
+ currentRect.contains(bestRect)) {
+ bestDistance = distance;
+ bestXY[0] = x;
+ bestXY[1] = y;
+ if (resultSpan != null) {
+ resultSpan[0] = xSize;
+ resultSpan[1] = ySize;
+ }
+ bestRect.set(currentRect);
+ }
+ }
+ }
+ // re-mark space taken by ignoreView as occupied
+ markCellsAsOccupiedForView(ignoreView, occupied);
+
+ // Return -1, -1 if no suitable location found
+ if (bestDistance == Double.MAX_VALUE) {
+ bestXY[0] = -1;
+ bestXY[1] = -1;
+ }
+ recycleTempRects(validRegions);
+ return bestXY;
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location, and will also weigh in a suggested direction vector of the
+ * desired location. This method computers distance based on unit grid distances,
+ * not pixel distances.
+ *
+ * @param cellX The X cell nearest to which you want to search for a vacant area.
+ * @param cellY The Y cell nearest which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param direction The favored direction in which the views should move from x, y
+ * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
+ * matches exactly. Otherwise we find the best matching direction.
+ * @param occoupied The array which represents which cells in the CellLayout are occupied
+ * @param blockOccupied The array which represents which cells in the specified block (cellX,
+ * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
+ * @param result Array in which to place the result, or null (in which case a new array will
+ * be allocated)
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
+ boolean[][] occupied, boolean blockOccupied[][], int[] result) {
+ // Keep track of best-scoring drop area
+ final int[] bestXY = result != null ? result : new int[2];
+ float bestDistance = Float.MAX_VALUE;
+ int bestDirectionScore = Integer.MIN_VALUE;
+
+ final int countX = mCountX;
+ final int countY = mCountY;
+
+ for (int y = 0; y < countY - (spanY - 1); y++) {
+ inner:
+ for (int x = 0; x < countX - (spanX - 1); x++) {
+ // First, let's see if this thing fits anywhere
+ for (int i = 0; i < spanX; i++) {
+ for (int j = 0; j < spanY; j++) {
+ if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
+ continue inner;
+ }
+ }
+ }
+
+ float distance = (float)
+ Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
+ int[] curDirection = mTmpPoint;
+ computeDirectionVector(x - cellX, y - cellY, curDirection);
+ // The direction score is just the dot product of the two candidate direction
+ // and that passed in.
+ int curDirectionScore = direction[0] * curDirection[0] +
+ direction[1] * curDirection[1];
+ boolean exactDirectionOnly = false;
+ boolean directionMatches = direction[0] == curDirection[0] &&
+ direction[0] == curDirection[0];
+ if ((directionMatches || !exactDirectionOnly) &&
+ Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
+ bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
+ bestDistance = distance;
+ bestDirectionScore = curDirectionScore;
+ bestXY[0] = x;
+ bestXY[1] = y;
+ }
+ }
+ }
+
+ // Return -1, -1 if no suitable location found
+ if (bestDistance == Float.MAX_VALUE) {
+ bestXY[0] = -1;
+ bestXY[1] = -1;
+ }
+ return bestXY;
+ }
+
+ private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
+ int[] direction, ItemConfiguration currentState) {
+ CellAndSpan c = currentState.map.get(v);
+ boolean success = false;
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+ markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
+
+ findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
+
+ if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+ c.x = mTempLocation[0];
+ c.y = mTempLocation[1];
+ success = true;
+ }
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+ return success;
+ }
+
+ /**
+ * This helper class defines a cluster of views. It helps with defining complex edges
+ * of the cluster and determining how those edges interact with other views. The edges
+ * essentially define a fine-grained boundary around the cluster of views -- like a more
+ * precise version of a bounding box.
+ */
+ private class ViewCluster {
+ final static int LEFT = 0;
+ final static int TOP = 1;
+ final static int RIGHT = 2;
+ final static int BOTTOM = 3;
+
+ ArrayList<View> views;
+ ItemConfiguration config;
+ Rect boundingRect = new Rect();
+
+ int[] leftEdge = new int[mCountY];
+ int[] rightEdge = new int[mCountY];
+ int[] topEdge = new int[mCountX];
+ int[] bottomEdge = new int[mCountX];
+ boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
+
+ @SuppressWarnings("unchecked")
+ public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
+ this.views = (ArrayList<View>) views.clone();
+ this.config = config;
+ resetEdges();
+ }
+
+ void resetEdges() {
+ for (int i = 0; i < mCountX; i++) {
+ topEdge[i] = -1;
+ bottomEdge[i] = -1;
+ }
+ for (int i = 0; i < mCountY; i++) {
+ leftEdge[i] = -1;
+ rightEdge[i] = -1;
+ }
+ leftEdgeDirty = true;
+ rightEdgeDirty = true;
+ bottomEdgeDirty = true;
+ topEdgeDirty = true;
+ boundingRectDirty = true;
+ }
+
+ void computeEdge(int which, int[] edge) {
+ int count = views.size();
+ for (int i = 0; i < count; i++) {
+ CellAndSpan cs = config.map.get(views.get(i));
+ switch (which) {
+ case LEFT:
+ int left = cs.x;
+ for (int j = cs.y; j < cs.y + cs.spanY; j++) {
+ if (left < edge[j] || edge[j] < 0) {
+ edge[j] = left;
+ }
+ }
+ break;
+ case RIGHT:
+ int right = cs.x + cs.spanX;
+ for (int j = cs.y; j < cs.y + cs.spanY; j++) {
+ if (right > edge[j]) {
+ edge[j] = right;
+ }
+ }
+ break;
+ case TOP:
+ int top = cs.y;
+ for (int j = cs.x; j < cs.x + cs.spanX; j++) {
+ if (top < edge[j] || edge[j] < 0) {
+ edge[j] = top;
+ }
+ }
+ break;
+ case BOTTOM:
+ int bottom = cs.y + cs.spanY;
+ for (int j = cs.x; j < cs.x + cs.spanX; j++) {
+ if (bottom > edge[j]) {
+ edge[j] = bottom;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ boolean isViewTouchingEdge(View v, int whichEdge) {
+ CellAndSpan cs = config.map.get(v);
+
+ int[] edge = getEdge(whichEdge);
+
+ switch (whichEdge) {
+ case LEFT:
+ for (int i = cs.y; i < cs.y + cs.spanY; i++) {
+ if (edge[i] == cs.x + cs.spanX) {
+ return true;
+ }
+ }
+ break;
+ case RIGHT:
+ for (int i = cs.y; i < cs.y + cs.spanY; i++) {
+ if (edge[i] == cs.x) {
+ return true;
+ }
+ }
+ break;
+ case TOP:
+ for (int i = cs.x; i < cs.x + cs.spanX; i++) {
+ if (edge[i] == cs.y + cs.spanY) {
+ return true;
+ }
+ }
+ break;
+ case BOTTOM:
+ for (int i = cs.x; i < cs.x + cs.spanX; i++) {
+ if (edge[i] == cs.y) {
+ return true;
+ }
+ }
+ break;
+ }
+ return false;
+ }
+
+ void shift(int whichEdge, int delta) {
+ for (View v: views) {
+ CellAndSpan c = config.map.get(v);
+ switch (whichEdge) {
+ case LEFT:
+ c.x -= delta;
+ break;
+ case RIGHT:
+ c.x += delta;
+ break;
+ case TOP:
+ c.y -= delta;
+ break;
+ case BOTTOM:
+ default:
+ c.y += delta;
+ break;
+ }
+ }
+ resetEdges();
+ }
+
+ public void addView(View v) {
+ views.add(v);
+ resetEdges();
+ }
+
+ public Rect getBoundingRect() {
+ if (boundingRectDirty) {
+ boolean first = true;
+ for (View v: views) {
+ CellAndSpan c = config.map.get(v);
+ if (first) {
+ boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ first = false;
+ } else {
+ boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ }
+ }
+ }
+ return boundingRect;
+ }
+
+ public int[] getEdge(int which) {
+ switch (which) {
+ case LEFT:
+ return getLeftEdge();
+ case RIGHT:
+ return getRightEdge();
+ case TOP:
+ return getTopEdge();
+ case BOTTOM:
+ default:
+ return getBottomEdge();
+ }
+ }
+
+ public int[] getLeftEdge() {
+ if (leftEdgeDirty) {
+ computeEdge(LEFT, leftEdge);
+ }
+ return leftEdge;
+ }
+
+ public int[] getRightEdge() {
+ if (rightEdgeDirty) {
+ computeEdge(RIGHT, rightEdge);
+ }
+ return rightEdge;
+ }
+
+ public int[] getTopEdge() {
+ if (topEdgeDirty) {
+ computeEdge(TOP, topEdge);
+ }
+ return topEdge;
+ }
+
+ public int[] getBottomEdge() {
+ if (bottomEdgeDirty) {
+ computeEdge(BOTTOM, bottomEdge);
+ }
+ return bottomEdge;
+ }
+
+ PositionComparator comparator = new PositionComparator();
+ class PositionComparator implements Comparator<View> {
+ int whichEdge = 0;
+ public int compare(View left, View right) {
+ CellAndSpan l = config.map.get(left);
+ CellAndSpan r = config.map.get(right);
+ switch (whichEdge) {
+ case LEFT:
+ return (r.x + r.spanX) - (l.x + l.spanX);
+ case RIGHT:
+ return l.x - r.x;
+ case TOP:
+ return (r.y + r.spanY) - (l.y + l.spanY);
+ case BOTTOM:
+ default:
+ return l.y - r.y;
+ }
+ }
+ }
+
+ public void sortConfigurationForEdgePush(int edge) {
+ comparator.whichEdge = edge;
+ Collections.sort(config.sortedViews, comparator);
+ }
+ }
+
+ private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+ int[] direction, View dragView, ItemConfiguration currentState) {
+
+ ViewCluster cluster = new ViewCluster(views, currentState);
+ Rect clusterRect = cluster.getBoundingRect();
+ int whichEdge;
+ int pushDistance;
+ boolean fail = false;
+
+ // Determine the edge of the cluster that will be leading the push and how far
+ // the cluster must be shifted.
+ if (direction[0] < 0) {
+ whichEdge = ViewCluster.LEFT;
+ pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
+ } else if (direction[0] > 0) {
+ whichEdge = ViewCluster.RIGHT;
+ pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
+ } else if (direction[1] < 0) {
+ whichEdge = ViewCluster.TOP;
+ pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
+ } else {
+ whichEdge = ViewCluster.BOTTOM;
+ pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
+ }
+
+ // Break early for invalid push distance.
+ if (pushDistance <= 0) {
+ return false;
+ }
+
+ // Mark the occupied state as false for the group of views we want to move.
+ for (View v: views) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+ }
+
+ // We save the current configuration -- if we fail to find a solution we will revert
+ // to the initial state. The process of finding a solution modifies the configuration
+ // in place, hence the need for revert in the failure case.
+ currentState.save();
+
+ // The pushing algorithm is simplified by considering the views in the order in which
+ // they would be pushed by the cluster. For example, if the cluster is leading with its
+ // left edge, we consider sort the views by their right edge, from right to left.
+ cluster.sortConfigurationForEdgePush(whichEdge);
+
+ while (pushDistance > 0 && !fail) {
+ for (View v: currentState.sortedViews) {
+ // For each view that isn't in the cluster, we see if the leading edge of the
+ // cluster is contacting the edge of that view. If so, we add that view to the
+ // cluster.
+ if (!cluster.views.contains(v) && v != dragView) {
+ if (cluster.isViewTouchingEdge(v, whichEdge)) {
+ LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (!lp.canReorder) {
+ // The push solution includes the all apps button, this is not viable.
+ fail = true;
+ break;
+ }
+ cluster.addView(v);
+ CellAndSpan c = currentState.map.get(v);
+
+ // Adding view to cluster, mark it as not occupied.
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+ }
+ }
+ }
+ pushDistance--;
+
+ // The cluster has been completed, now we move the whole thing over in the appropriate
+ // direction.
+ cluster.shift(whichEdge, 1);
+ }
+
+ boolean foundSolution = false;
+ clusterRect = cluster.getBoundingRect();
+
+ // Due to the nature of the algorithm, the only check required to verify a valid solution
+ // is to ensure that completed shifted cluster lies completely within the cell layout.
+ if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
+ clusterRect.bottom <= mCountY) {
+ foundSolution = true;
+ } else {
+ currentState.restore();
+ }
+
+ // In either case, we set the occupied array as marked for the location of the views
+ for (View v: cluster.views) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+ }
+
+ return foundSolution;
+ }
+
+ private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
+ int[] direction, View dragView, ItemConfiguration currentState) {
+ if (views.size() == 0) return true;
+
+ boolean success = false;
+ Rect boundingRect = null;
+ // We construct a rect which represents the entire group of views passed in
+ for (View v: views) {
+ CellAndSpan c = currentState.map.get(v);
+ if (boundingRect == null) {
+ boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ } else {
+ boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ }
+ }
+
+ // Mark the occupied state as false for the group of views we want to move.
+ for (View v: views) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
+ }
+
+ boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
+ int top = boundingRect.top;
+ int left = boundingRect.left;
+ // We mark more precisely which parts of the bounding rect are truly occupied, allowing
+ // for interlocking.
+ for (View v: views) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
+ }
+
+ markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
+
+ findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
+ boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
+
+ // If we successfuly found a location by pushing the block of views, we commit it
+ if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
+ int deltaX = mTempLocation[0] - boundingRect.left;
+ int deltaY = mTempLocation[1] - boundingRect.top;
+ for (View v: views) {
+ CellAndSpan c = currentState.map.get(v);
+ c.x += deltaX;
+ c.y += deltaY;
+ }
+ success = true;
+ }
+
+ // In either case, we set the occupied array as marked for the location of the views
+ for (View v: views) {
+ CellAndSpan c = currentState.map.get(v);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+ }
+ return success;
+ }
+
+ private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
+ markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
+ }
+
+ // This method tries to find a reordering solution which satisfies the push mechanic by trying
+ // to push items in each of the cardinal directions, in an order based on the direction vector
+ // passed.
+ private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
+ int[] direction, View ignoreView, ItemConfiguration solution) {
+ if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
+ // If the direction vector has two non-zero components, we try pushing
+ // separately in each of the components.
+ int temp = direction[1];
+ direction[1] = 0;
+
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+ direction[1] = temp;
+ temp = direction[0];
+ direction[0] = 0;
+
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+ // Revert the direction
+ direction[0] = temp;
+
+ // Now we try pushing in each component of the opposite direction
+ direction[0] *= -1;
+ direction[1] *= -1;
+ temp = direction[1];
+ direction[1] = 0;
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+
+ direction[1] = temp;
+ temp = direction[0];
+ direction[0] = 0;
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+ // revert the direction
+ direction[0] = temp;
+ direction[0] *= -1;
+ direction[1] *= -1;
+
+ } else {
+ // If the direction vector has a single non-zero component, we push first in the
+ // direction of the vector
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+ // Then we try the opposite direction
+ direction[0] *= -1;
+ direction[1] *= -1;
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+ // Switch the direction back
+ direction[0] *= -1;
+ direction[1] *= -1;
+
+ // If we have failed to find a push solution with the above, then we try
+ // to find a solution by pushing along the perpendicular axis.
+
+ // Swap the components
+ int temp = direction[1];
+ direction[1] = direction[0];
+ direction[0] = temp;
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+
+ // Then we try the opposite direction
+ direction[0] *= -1;
+ direction[1] *= -1;
+ if (pushViewsToTempLocation(intersectingViews, occupied, direction,
+ ignoreView, solution)) {
+ return true;
+ }
+ // Switch the direction back
+ direction[0] *= -1;
+ direction[1] *= -1;
+
+ // Swap the components back
+ temp = direction[1];
+ direction[1] = direction[0];
+ direction[0] = temp;
+ }
+ return false;
+ }
+
+ private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
+ View ignoreView, ItemConfiguration solution) {
+ // Return early if get invalid cell positions
+ if (cellX < 0 || cellY < 0) return false;
+
+ mIntersectingViews.clear();
+ mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
+
+ // Mark the desired location of the view currently being dragged.
+ if (ignoreView != null) {
+ CellAndSpan c = solution.map.get(ignoreView);
+ if (c != null) {
+ c.x = cellX;
+ c.y = cellY;
+ }
+ }
+ Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+ Rect r1 = new Rect();
+ for (View child: solution.map.keySet()) {
+ if (child == ignoreView) continue;
+ CellAndSpan c = solution.map.get(child);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
+ if (Rect.intersects(r0, r1)) {
+ if (!lp.canReorder) {
+ return false;
+ }
+ mIntersectingViews.add(child);
+ }
+ }
+
+ // First we try to find a solution which respects the push mechanic. That is,
+ // we try to find a solution such that no displaced item travels through another item
+ // without also displacing that item.
+ if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
+ solution)) {
+ return true;
+ }
+
+ // Next we try moving the views as a block, but without requiring the push mechanic.
+ if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
+ solution)) {
+ return true;
+ }
+
+ // Ok, they couldn't move as a block, let's move them individually
+ for (View v : mIntersectingViews) {
+ if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
+ * the provided point and the provided cell
+ */
+ private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
+ double angle = Math.atan(((float) deltaY) / deltaX);
+
+ result[0] = 0;
+ result[1] = 0;
+ if (Math.abs(Math.cos(angle)) > 0.5f) {
+ result[0] = (int) Math.signum(deltaX);
+ }
+ if (Math.abs(Math.sin(angle)) > 0.5f) {
+ result[1] = (int) Math.signum(deltaY);
+ }
+ }
+
+ private void copyOccupiedArray(boolean[][] occupied) {
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ occupied[i][j] = mOccupied[i][j];
+ }
+ }
+ }
+
+ ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
+ int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
+ // Copy the current state into the solution. This solution will be manipulated as necessary.
+ copyCurrentStateToSolution(solution, false);
+ // Copy the current occupied array into the temporary occupied array. This array will be
+ // manipulated as necessary to find a solution.
+ copyOccupiedArray(mTmpOccupied);
+
+ // We find the nearest cell into which we would place the dragged item, assuming there's
+ // nothing in its way.
+ int result[] = new int[2];
+ result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+
+ boolean success = false;
+ // First we try the exact nearest position of the item being dragged,
+ // we will then want to try to move this around to other neighbouring positions
+ success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
+ solution);
+
+ if (!success) {
+ // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
+ // x, then 1 in y etc.
+ if (spanX > minSpanX && (minSpanY == spanY || decX)) {
+ return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
+ dragView, false, solution);
+ } else if (spanY > minSpanY) {
+ return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
+ dragView, true, solution);
+ }
+ solution.isSolution = false;
+ } else {
+ solution.isSolution = true;
+ solution.dragViewX = result[0];
+ solution.dragViewY = result[1];
+ solution.dragViewSpanX = spanX;
+ solution.dragViewSpanY = spanY;
+ }
+ return solution;
+ }
+
+ private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ CellAndSpan c;
+ if (temp) {
+ c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
+ } else {
+ c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
+ }
+ solution.add(child, c);
+ }
+ }
+
+ private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ mTmpOccupied[i][j] = false;
+ }
+ }
+
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ CellAndSpan c = solution.map.get(child);
+ if (c != null) {
+ lp.tmpCellX = c.x;
+ lp.tmpCellY = c.y;
+ lp.cellHSpan = c.spanX;
+ lp.cellVSpan = c.spanY;
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
+ }
+ }
+ markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
+ solution.dragViewSpanY, mTmpOccupied, true);
+ }
+
+ private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
+ commitDragView) {
+
+ boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ occupied[i][j] = false;
+ }
+ }
+
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ CellAndSpan c = solution.map.get(child);
+ if (c != null) {
+ animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
+ DESTRUCTIVE_REORDER, false);
+ markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
+ }
+ }
+ if (commitDragView) {
+ markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
+ solution.dragViewSpanY, occupied, true);
+ }
+ }
+
+ // This method starts or changes the reorder hint animations
+ private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ CellAndSpan c = solution.map.get(child);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (c != null) {
+ ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
+ c.x, c.y, c.spanX, c.spanY);
+ rha.animate();
+ }
+ }
+ }
+
+ // Class which represents the reorder hint animations. These animations show that an item is
+ // in a temporary state, and hint at where the item will return to.
+ class ReorderHintAnimation {
+ View child;
+ float finalDeltaX;
+ float finalDeltaY;
+ float initDeltaX;
+ float initDeltaY;
+ float finalScale;
+ float initScale;
+ private static final int DURATION = 300;
+ Animator a;
+
+ public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
+ int spanX, int spanY) {
+ regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
+ final int x0 = mTmpPoint[0];
+ final int y0 = mTmpPoint[1];
+ regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
+ final int x1 = mTmpPoint[0];
+ final int y1 = mTmpPoint[1];
+ final int dX = x1 - x0;
+ final int dY = y1 - y0;
+ finalDeltaX = 0;
+ finalDeltaY = 0;
+ if (dX == dY && dX == 0) {
+ } else {
+ if (dY == 0) {
+ finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
+ } else if (dX == 0) {
+ finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
+ } else {
+ double angle = Math.atan( (float) (dY) / dX);
+ finalDeltaX = (int) (- Math.signum(dX) *
+ Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
+ finalDeltaY = (int) (- Math.signum(dY) *
+ Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
+ }
+ }
+ initDeltaX = child.getTranslationX();
+ initDeltaY = child.getTranslationY();
+ finalScale = getChildrenScale() - 4.0f / child.getWidth();
+ initScale = child.getScaleX();
+ this.child = child;
+ }
+
+ void animate() {
+ if (mShakeAnimators.containsKey(child)) {
+ ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
+ oldAnimation.cancel();
+ mShakeAnimators.remove(child);
+ if (finalDeltaX == 0 && finalDeltaY == 0) {
+ completeAnimationImmediately();
+ return;
+ }
+ }
+ if (finalDeltaX == 0 && finalDeltaY == 0) {
+ return;
+ }
+ ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
+ a = va;
+ va.setRepeatMode(ValueAnimator.REVERSE);
+ va.setRepeatCount(ValueAnimator.INFINITE);
+ va.setDuration(DURATION);
+ va.setStartDelay((int) (Math.random() * 60));
+ va.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float r = ((Float) animation.getAnimatedValue()).floatValue();
+ float x = r * finalDeltaX + (1 - r) * initDeltaX;
+ float y = r * finalDeltaY + (1 - r) * initDeltaY;
+ child.setTranslationX(x);
+ child.setTranslationY(y);
+ float s = r * finalScale + (1 - r) * initScale;
+ child.setScaleX(s);
+ child.setScaleY(s);
+ }
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationRepeat(Animator animation) {
+ // We make sure to end only after a full period
+ initDeltaX = 0;
+ initDeltaY = 0;
+ initScale = getChildrenScale();
+ }
+ });
+ mShakeAnimators.put(child, this);
+ va.start();
+ }
+
+ private void cancel() {
+ if (a != null) {
+ a.cancel();
+ }
+ }
+
+ private void completeAnimationImmediately() {
+ if (a != null) {
+ a.cancel();
+ }
+
+ AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
+ a = s;
+ s.playTogether(
+ LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
+ LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
+ LauncherAnimUtils.ofFloat(child, "translationX", 0f),
+ LauncherAnimUtils.ofFloat(child, "translationY", 0f)
+ );
+ s.setDuration(REORDER_ANIMATION_DURATION);
+ s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
+ s.start();
+ }
+ }
+
+ private void completeAndClearReorderHintAnimations() {
+ for (ReorderHintAnimation a: mShakeAnimators.values()) {
+ a.completeAnimationImmediately();
+ }
+ mShakeAnimators.clear();
+ }
+
+ private void commitTempPlacement() {
+ for (int i = 0; i < mCountX; i++) {
+ for (int j = 0; j < mCountY; j++) {
+ mOccupied[i][j] = mTmpOccupied[i][j];
+ }
+ }
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ ItemInfo info = (ItemInfo) child.getTag();
+ // We do a null check here because the item info can be null in the case of the
+ // AllApps button in the hotseat.
+ if (info != null) {
+ if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
+ info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
+ info.requiresDbUpdate = true;
+ }
+ info.cellX = lp.cellX = lp.tmpCellX;
+ info.cellY = lp.cellY = lp.tmpCellY;
+ info.spanX = lp.cellHSpan;
+ info.spanY = lp.cellVSpan;
+ }
+ }
+ mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
+ }
+
+ public void setUseTempCoords(boolean useTempCoords) {
+ int childCount = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
+ lp.useTmpCoords = useTempCoords;
+ }
+ }
+
+ ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
+ int spanX, int spanY, View dragView, ItemConfiguration solution) {
+ int[] result = new int[2];
+ int[] resultSpan = new int[2];
+ findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
+ resultSpan);
+ if (result[0] >= 0 && result[1] >= 0) {
+ copyCurrentStateToSolution(solution, false);
+ solution.dragViewX = result[0];
+ solution.dragViewY = result[1];
+ solution.dragViewSpanX = resultSpan[0];
+ solution.dragViewSpanY = resultSpan[1];
+ solution.isSolution = true;
+ } else {
+ solution.isSolution = false;
+ }
+ return solution;
+ }
+
+ public void prepareChildForDrag(View child) {
+ markCellsAsUnoccupiedForView(child);
+ }
+
+ /* This seems like it should be obvious and straight-forward, but when the direction vector
+ needs to match with the notion of the dragView pushing other views, we have to employ
+ a slightly more subtle notion of the direction vector. The question is what two points is
+ the vector between? The center of the dragView and its desired destination? Not quite, as
+ this doesn't necessarily coincide with the interaction of the dragView and items occupying
+ those cells. Instead we use some heuristics to often lock the vector to up, down, left
+ or right, which helps make pushing feel right.
+ */
+ private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
+ int spanY, View dragView, int[] resultDirection) {
+ int[] targetDestination = new int[2];
+
+ findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
+ Rect dragRect = new Rect();
+ regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
+ dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
+
+ Rect dropRegionRect = new Rect();
+ getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
+ dragView, dropRegionRect, mIntersectingViews);
+
+ int dropRegionSpanX = dropRegionRect.width();
+ int dropRegionSpanY = dropRegionRect.height();
+
+ regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
+ dropRegionRect.height(), dropRegionRect);
+
+ int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
+ int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
+
+ if (dropRegionSpanX == mCountX || spanX == mCountX) {
+ deltaX = 0;
+ }
+ if (dropRegionSpanY == mCountY || spanY == mCountY) {
+ deltaY = 0;
+ }
+
+ if (deltaX == 0 && deltaY == 0) {
+ // No idea what to do, give a random direction.
+ resultDirection[0] = 1;
+ resultDirection[1] = 0;
+ } else {
+ computeDirectionVector(deltaX, deltaY, resultDirection);
+ }
+ }
+
+ // For a given cell and span, fetch the set of views intersecting the region.
+ private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
+ View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
+ if (boundingRect != null) {
+ boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
+ }
+ intersectingViews.clear();
+ Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
+ Rect r1 = new Rect();
+ final int count = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ if (child == dragView) continue;
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
+ if (Rect.intersects(r0, r1)) {
+ mIntersectingViews.add(child);
+ if (boundingRect != null) {
+ boundingRect.union(r1);
+ }
+ }
+ }
+ }
+
+ boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
+ View dragView, int[] result) {
+ result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+ getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
+ mIntersectingViews);
+ return !mIntersectingViews.isEmpty();
+ }
+
+ void revertTempState() {
+ if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
+ final int count = mShortcutsAndWidgets.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = mShortcutsAndWidgets.getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
+ lp.tmpCellX = lp.cellX;
+ lp.tmpCellY = lp.cellY;
+ animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
+ 0, false, false);
+ }
+ }
+ completeAndClearReorderHintAnimations();
+ setItemPlacementDirty(false);
+ }
+
+ boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
+ View dragView, int[] direction, boolean commit) {
+ int[] pixelXY = new int[2];
+ regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
+
+ // First we determine if things have moved enough to cause a different layout
+ ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
+ spanX, spanY, direction, dragView, true, new ItemConfiguration());
+
+ setUseTempCoords(true);
+ if (swapSolution != null && swapSolution.isSolution) {
+ // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
+ // committing anything or animating anything as we just want to determine if a solution
+ // exists
+ copySolutionToTempState(swapSolution, dragView);
+ setItemPlacementDirty(true);
+ animateItemsToSolution(swapSolution, dragView, commit);
+
+ if (commit) {
+ commitTempPlacement();
+ completeAndClearReorderHintAnimations();
+ setItemPlacementDirty(false);
+ } else {
+ beginOrAdjustHintAnimations(swapSolution, dragView,
+ REORDER_ANIMATION_DURATION);
+ }
+ mShortcutsAndWidgets.requestLayout();
+ }
+ return swapSolution.isSolution;
+ }
+
+ int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
+ View dragView, int[] result, int resultSpan[], int mode) {
+ // First we determine if things have moved enough to cause a different layout
+ result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
+
+ if (resultSpan == null) {
+ resultSpan = new int[2];
+ }
+
+ // When we are checking drop validity or actually dropping, we don't recompute the
+ // direction vector, since we want the solution to match the preview, and it's possible
+ // that the exact position of the item has changed to result in a new reordering outcome.
+ if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
+ && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
+ mDirectionVector[0] = mPreviousReorderDirection[0];
+ mDirectionVector[1] = mPreviousReorderDirection[1];
+ // We reset this vector after drop
+ if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
+ mPreviousReorderDirection[0] = INVALID_DIRECTION;
+ mPreviousReorderDirection[1] = INVALID_DIRECTION;
+ }
+ } else {
+ getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
+ mPreviousReorderDirection[0] = mDirectionVector[0];
+ mPreviousReorderDirection[1] = mDirectionVector[1];
+ }
+
+ ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
+ spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
+
+ // We attempt the approach which doesn't shuffle views at all
+ ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
+ minSpanY, spanX, spanY, dragView, new ItemConfiguration());
+
+ ItemConfiguration finalSolution = null;
+ if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
+ finalSolution = swapSolution;
+ } else if (noShuffleSolution.isSolution) {
+ finalSolution = noShuffleSolution;
+ }
+
+ boolean foundSolution = true;
+ if (!DESTRUCTIVE_REORDER) {
+ setUseTempCoords(true);
+ }
+
+ if (finalSolution != null) {
+ result[0] = finalSolution.dragViewX;
+ result[1] = finalSolution.dragViewY;
+ resultSpan[0] = finalSolution.dragViewSpanX;
+ resultSpan[1] = finalSolution.dragViewSpanY;
+
+ // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
+ // committing anything or animating anything as we just want to determine if a solution
+ // exists
+ if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
+ if (!DESTRUCTIVE_REORDER) {
+ copySolutionToTempState(finalSolution, dragView);
+ }
+ setItemPlacementDirty(true);
+ animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
+
+ if (!DESTRUCTIVE_REORDER &&
+ (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
+ commitTempPlacement();
+ completeAndClearReorderHintAnimations();
+ setItemPlacementDirty(false);
+ } else {
+ beginOrAdjustHintAnimations(finalSolution, dragView,
+ REORDER_ANIMATION_DURATION);
+ }
+ }
+ } else {
+ foundSolution = false;
+ result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
+ }
+
+ if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
+ setUseTempCoords(false);
+ }
+
+ mShortcutsAndWidgets.requestLayout();
+ return result;
+ }
+
+ void setItemPlacementDirty(boolean dirty) {
+ mItemPlacementDirty = dirty;
+ }
+ boolean isItemPlacementDirty() {
+ return mItemPlacementDirty;
+ }
+
+ private class ItemConfiguration {
+ HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
+ private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
+ ArrayList<View> sortedViews = new ArrayList<View>();
+ boolean isSolution = false;
+ int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
+
+ void save() {
+ // Copy current state into savedMap
+ for (View v: map.keySet()) {
+ map.get(v).copy(savedMap.get(v));
+ }
+ }
+
+ void restore() {
+ // Restore current state from savedMap
+ for (View v: savedMap.keySet()) {
+ savedMap.get(v).copy(map.get(v));
+ }
+ }
+
+ void add(View v, CellAndSpan cs) {
+ map.put(v, cs);
+ savedMap.put(v, new CellAndSpan());
+ sortedViews.add(v);
+ }
+
+ int area() {
+ return dragViewSpanX * dragViewSpanY;
+ }
+ }
+
+ private class CellAndSpan {
+ int x, y;
+ int spanX, spanY;
+
+ public CellAndSpan() {
+ }
+
+ public void copy(CellAndSpan copy) {
+ copy.x = x;
+ copy.y = y;
+ copy.spanX = spanX;
+ copy.spanY = spanY;
+ }
+
+ public CellAndSpan(int x, int y, int spanX, int spanY) {
+ this.x = x;
+ this.y = y;
+ this.spanX = spanX;
+ this.spanY = spanY;
+ }
+
+ public String toString() {
+ return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
+ }
+
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param ignoreView Considers space occupied by this view as unoccupied
+ * @param result Previously returned value to possibly recycle.
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestVacantArea(
+ int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
+ return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
+ }
+
+ /**
+ * Find a vacant area that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param minSpanX The minimum horizontal span required
+ * @param minSpanY The minimum vertical span required
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param ignoreView Considers space occupied by this view as unoccupied
+ * @param result Previously returned value to possibly recycle.
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
+ int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
+ return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
+ result, resultSpan, mOccupied);
+ }
+
+ /**
+ * Find a starting cell position that will fit the given bounds nearest the requested
+ * cell location. Uses Euclidean distance to score multiple vacant areas.
+ *
+ * @param pixelX The X location at which you want to search for a vacant area.
+ * @param pixelY The Y location at which you want to search for a vacant area.
+ * @param spanX Horizontal span of the object.
+ * @param spanY Vertical span of the object.
+ * @param ignoreView Considers space occupied by this view as unoccupied
+ * @param result Previously returned value to possibly recycle.
+ * @return The X, Y cell of a vacant area that can contain this object,
+ * nearest the requested location.
+ */
+ int[] findNearestArea(
+ int pixelX, int pixelY, int spanX, int spanY, int[] result) {
+ return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
+ }
+
+ boolean existsEmptyCell() {
+ return findCellForSpan(null, 1, 1);
+ }
+
+ /**
+ * Finds the upper-left coordinate of the first rectangle in the grid that can
+ * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
+ * then this method will only return coordinates for rectangles that contain the cell
+ * (intersectX, intersectY)
+ *
+ * @param cellXY The array that will contain the position of a vacant cell if such a cell
+ * can be found.
+ * @param spanX The horizontal span of the cell we want to find.
+ * @param spanY The vertical span of the cell we want to find.
+ *
+ * @return True if a vacant cell of the specified dimension was found, false otherwise.
+ */
+ boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
+ return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
+ }
+
+ /**
+ * Like above, but ignores any cells occupied by the item "ignoreView"
+ *
+ * @param cellXY The array that will contain the position of a vacant cell if such a cell
+ * can be found.
+ * @param spanX The horizontal span of the cell we want to find.
+ * @param spanY The vertical span of the cell we want to find.
+ * @param ignoreView The home screen item we should treat as not occupying any space
+ * @return
+ */
+ boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
+ return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
+ ignoreView, mOccupied);
+ }
+
+ /**
+ * Like above, but if intersectX and intersectY are not -1, then this method will try to
+ * return coordinates for rectangles that contain the cell [intersectX, intersectY]
+ *
+ * @param spanX The horizontal span of the cell we want to find.
+ * @param spanY The vertical span of the cell we want to find.
+ * @param ignoreView The home screen item we should treat as not occupying any space
+ * @param intersectX The X coordinate of the cell that we should try to overlap
+ * @param intersectX The Y coordinate of the cell that we should try to overlap
+ *
+ * @return True if a vacant cell of the specified dimension was found, false otherwise.
+ */
+ boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
+ int intersectX, int intersectY) {
+ return findCellForSpanThatIntersectsIgnoring(
+ cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
+ }
+
+ /**
+ * The superset of the above two methods
+ */
+ boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
+ int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
+ // mark space take by ignoreView as available (method checks if ignoreView is null)
+ markCellsAsUnoccupiedForView(ignoreView, occupied);
+
+ boolean foundCell = false;
+ while (true) {
+ int startX = 0;
+ if (intersectX >= 0) {
+ startX = Math.max(startX, intersectX - (spanX - 1));
+ }
+ int endX = mCountX - (spanX - 1);
+ if (intersectX >= 0) {
+ endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
+ }
+ int startY = 0;
+ if (intersectY >= 0) {
+ startY = Math.max(startY, intersectY - (spanY - 1));
+ }
+ int endY = mCountY - (spanY - 1);
+ if (intersectY >= 0) {
+ endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
+ }
+
+ for (int y = startY; y < endY && !foundCell; y++) {
+ inner:
+ for (int x = startX; x < endX; x++) {
+ for (int i = 0; i < spanX; i++) {
+ for (int j = 0; j < spanY; j++) {
+ if (occupied[x + i][y + j]) {
+ // small optimization: we can skip to after the column we just found
+ // an occupied cell
+ x += i;
+ continue inner;
+ }
+ }
+ }
+ if (cellXY != null) {
+ cellXY[0] = x;
+ cellXY[1] = y;
+ }
+ foundCell = true;
+ break;
+ }
+ }
+ if (intersectX == -1 && intersectY == -1) {
+ break;
+ } else {
+ // if we failed to find anything, try again but without any requirements of
+ // intersecting
+ intersectX = -1;
+ intersectY = -1;
+ continue;
+ }
+ }
+
+ // re-mark space taken by ignoreView as occupied
+ markCellsAsOccupiedForView(ignoreView, occupied);
+ return foundCell;
+ }
+
+ /**
+ * A drag event has begun over this layout.
+ * It may have begun over this layout (in which case onDragChild is called first),
+ * or it may have begun on another layout.
+ */
+ void onDragEnter() {
+ mDragEnforcer.onDragEnter();
+ mDragging = true;
+ }
+
+ /**
+ * Called when drag has left this CellLayout or has been completed (successfully or not)
+ */
+ void onDragExit() {
+ mDragEnforcer.onDragExit();
+ // This can actually be called when we aren't in a drag, e.g. when adding a new
+ // item to this layout via the customize drawer.
+ // Guard against that case.
+ if (mDragging) {
+ mDragging = false;
+ }
+
+ // Invalidate the drag data
+ mDragCell[0] = mDragCell[1] = -1;
+ mDragOutlineAnims[mDragOutlineCurrent].animateOut();
+ mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
+ revertTempState();
+ setIsDragOverlapping(false);
+ }
+
+ /**
+ * Mark a child as having been dropped.
+ * At the beginning of the drag operation, the child may have been on another
+ * screen, but it is re-parented before this method is called.
+ *
+ * @param child The child that is being dropped
+ */
+ void onDropChild(View child) {
+ if (child != null) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.dropped = true;
+ child.requestLayout();
+ }
+ }
+
+ /**
+ * Computes a bounding rectangle for a range of cells
+ *
+ * @param cellX X coordinate of upper left corner expressed as a cell position
+ * @param cellY Y coordinate of upper left corner expressed as a cell position
+ * @param cellHSpan Width in cells
+ * @param cellVSpan Height in cells
+ * @param resultRect Rect into which to put the results
+ */
+ public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
+ final int cellWidth = mCellWidth;
+ final int cellHeight = mCellHeight;
+ final int widthGap = mWidthGap;
+ final int heightGap = mHeightGap;
+
+ final int hStartPadding = getPaddingLeft();
+ final int vStartPadding = getPaddingTop();
+
+ int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
+ int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
+
+ int x = hStartPadding + cellX * (cellWidth + widthGap);
+ int y = vStartPadding + cellY * (cellHeight + heightGap);
+
+ resultRect.set(x, y, x + width, y + height);
+ }
+
+ /**
+ * Computes the required horizontal and vertical cell spans to always
+ * fit the given rectangle.
+ *
+ * @param width Width in pixels
+ * @param height Height in pixels
+ * @param result An array of length 2 in which to store the result (may be null).
+ */
+ public static int[] rectToCell(int width, int height, int[] result) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
+ CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+
+ // Always assume we're working with the smallest span to make sure we
+ // reserve enough space in both orientations.
+ int parentWidth = grid.calculateCellWidth(grid.widthPx
+ - padding.left - padding.right, (int) grid.numColumns);
+ int parentHeight = grid.calculateCellHeight(grid.heightPx
+ - padding.top - padding.bottom, (int) grid.numRows);
+ int smallerSize = Math.min(parentWidth, parentHeight);
+
+ // Always round up to next largest cell
+ int spanX = (int) Math.ceil(width / (float) smallerSize);
+ int spanY = (int) Math.ceil(height / (float) smallerSize);
+
+ if (result == null) {
+ return new int[] { spanX, spanY };
+ }
+ result[0] = spanX;
+ result[1] = spanY;
+ return result;
+ }
+
+ public int[] cellSpansToSize(int hSpans, int vSpans) {
+ int[] size = new int[2];
+ size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
+ size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
+ return size;
+ }
+
+ /**
+ * Calculate the grid spans needed to fit given item
+ */
+ public void calculateSpans(ItemInfo info) {
+ final int minWidth;
+ final int minHeight;
+
+ if (info instanceof LauncherAppWidgetInfo) {
+ minWidth = ((LauncherAppWidgetInfo) info).minWidth;
+ minHeight = ((LauncherAppWidgetInfo) info).minHeight;
+ } else if (info instanceof PendingAddWidgetInfo) {
+ minWidth = ((PendingAddWidgetInfo) info).minWidth;
+ minHeight = ((PendingAddWidgetInfo) info).minHeight;
+ } else {
+ // It's not a widget, so it must be 1x1
+ info.spanX = info.spanY = 1;
+ return;
+ }
+ int[] spans = rectToCell(minWidth, minHeight, null);
+ info.spanX = spans[0];
+ info.spanY = spans[1];
+ }
+
+ /**
+ * Find the first vacant cell, if there is one.
+ *
+ * @param vacant Holds the x and y coordinate of the vacant cell
+ * @param spanX Horizontal cell span.
+ * @param spanY Vertical cell span.
+ *
+ * @return True if a vacant cell was found
+ */
+ public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
+
+ return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
+ }
+
+ static boolean findVacantCell(int[] vacant, int spanX, int spanY,
+ int xCount, int yCount, boolean[][] occupied) {
+
+ for (int y = 0; y < yCount; y++) {
+ for (int x = 0; x < xCount; x++) {
+ boolean available = !occupied[x][y];
+out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
+ for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
+ available = available && !occupied[i][j];
+ if (!available) break out;
+ }
+ }
+
+ if (available) {
+ vacant[0] = x;
+ vacant[1] = y;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void clearOccupiedCells() {
+ for (int x = 0; x < mCountX; x++) {
+ for (int y = 0; y < mCountY; y++) {
+ mOccupied[x][y] = false;
+ }
+ }
+ }
+
+ public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
+ markCellsAsUnoccupiedForView(view);
+ markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
+ }
+
+ public void markCellsAsOccupiedForView(View view) {
+ markCellsAsOccupiedForView(view, mOccupied);
+ }
+ public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
+ if (view == null || view.getParent() != mShortcutsAndWidgets) return;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
+ }
+
+ public void markCellsAsUnoccupiedForView(View view) {
+ markCellsAsUnoccupiedForView(view, mOccupied);
+ }
+ public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
+ if (view == null || view.getParent() != mShortcutsAndWidgets) return;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
+ }
+
+ private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
+ boolean value) {
+ if (cellX < 0 || cellY < 0) return;
+ for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
+ for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
+ occupied[x][y] = value;
+ }
+ }
+ }
+
+ public int getDesiredWidth() {
+ return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
+ (Math.max((mCountX - 1), 0) * mWidthGap);
+ }
+
+ public int getDesiredHeight() {
+ return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
+ (Math.max((mCountY - 1), 0) * mHeightGap);
+ }
+
+ public boolean isOccupied(int x, int y) {
+ if (x < mCountX && y < mCountY) {
+ return mOccupied[x][y];
+ } else {
+ throw new RuntimeException("Position exceeds the bound of this CellLayout");
+ }
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new CellLayout.LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof CellLayout.LayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new CellLayout.LayoutParams(p);
+ }
+
+ public static class CellLayoutAnimationController extends LayoutAnimationController {
+ public CellLayoutAnimationController(Animation animation, float delay) {
+ super(animation, delay);
+ }
+
+ @Override
+ protected long getDelayForView(View view) {
+ return (int) (Math.random() * 150);
+ }
+ }
+
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ /**
+ * Horizontal location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellX;
+
+ /**
+ * Vertical location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellY;
+
+ /**
+ * Temporary horizontal location of the item in the grid during reorder
+ */
+ public int tmpCellX;
+
+ /**
+ * Temporary vertical location of the item in the grid during reorder
+ */
+ public int tmpCellY;
+
+ /**
+ * Indicates that the temporary coordinates should be used to layout the items
+ */
+ public boolean useTmpCoords;
+
+ /**
+ * Number of cells spanned horizontally by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellHSpan;
+
+ /**
+ * Number of cells spanned vertically by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellVSpan;
+
+ /**
+ * Indicates whether the item will set its x, y, width and height parameters freely,
+ * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
+ */
+ public boolean isLockedToGrid = true;
+
+ /**
+ * Indicates that this item should use the full extents of its parent.
+ */
+ public boolean isFullscreen = false;
+
+ /**
+ * Indicates whether this item can be reordered. Always true except in the case of the
+ * the AllApps button.
+ */
+ public boolean canReorder = true;
+
+ // X coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int x;
+ // Y coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int y;
+
+ boolean dropped;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ this.cellX = source.cellX;
+ this.cellY = source.cellY;
+ this.cellHSpan = source.cellHSpan;
+ this.cellVSpan = source.cellVSpan;
+ }
+
+ public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ this.cellX = cellX;
+ this.cellY = cellY;
+ this.cellHSpan = cellHSpan;
+ this.cellVSpan = cellVSpan;
+ }
+
+ public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
+ boolean invertHorizontally, int colCount) {
+ if (isLockedToGrid) {
+ final int myCellHSpan = cellHSpan;
+ final int myCellVSpan = cellVSpan;
+ int myCellX = useTmpCoords ? tmpCellX : cellX;
+ int myCellY = useTmpCoords ? tmpCellY : cellY;
+
+ if (invertHorizontally) {
+ myCellX = colCount - myCellX - cellHSpan;
+ }
+
+ width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
+ leftMargin - rightMargin;
+ height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
+ topMargin - bottomMargin;
+ x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
+ y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
+ }
+ }
+
+ public String toString() {
+ return "(" + this.cellX + ", " + this.cellY + ")";
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public int getY() {
+ return y;
+ }
+ }
+
+ // This class stores info for two purposes:
+ // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
+ // its spanX, spanY, and the screen it is on
+ // 2. When long clicking on an empty cell in a CellLayout, we save information about the
+ // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
+ // the CellLayout that was long clicked
+ static final class CellInfo {
+ View cell;
+ int cellX = -1;
+ int cellY = -1;
+ int spanX;
+ int spanY;
+ long screenId;
+ long container;
+
+ @Override
+ public String toString() {
+ return "Cell[view=" + (cell == null ? "null" : cell.getClass())
+ + ", x=" + cellX + ", y=" + cellY + "]";
+ }
+ }
+
+ public boolean lastDownOnOccupiedCell() {
+ return mLastDownOnOccupiedCell;
+ }
+}
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
new file mode 100644
index 0000000..8114979
--- /dev/null
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 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.view.View;
+
+public class CheckLongPressHelper {
+ private View mView;
+ private boolean mHasPerformedLongPress;
+ private CheckForLongPress mPendingCheckForLongPress;
+
+ class CheckForLongPress implements Runnable {
+ public void run() {
+ if ((mView.getParent() != null) && mView.hasWindowFocus()
+ && !mHasPerformedLongPress) {
+ if (mView.performLongClick()) {
+ mView.setPressed(false);
+ mHasPerformedLongPress = true;
+ }
+ }
+ }
+ }
+
+ public CheckLongPressHelper(View v) {
+ mView = v;
+ }
+
+ public void postCheckForLongPress() {
+ mHasPerformedLongPress = false;
+
+ if (mPendingCheckForLongPress == null) {
+ mPendingCheckForLongPress = new CheckForLongPress();
+ }
+ mView.postDelayed(mPendingCheckForLongPress,
+ LauncherAppState.getInstance().getLongPressTimeout());
+ }
+
+ public void cancelLongPress() {
+ mHasPerformedLongPress = false;
+ if (mPendingCheckForLongPress != null) {
+ mView.removeCallbacks(mPendingCheckForLongPress);
+ mPendingCheckForLongPress = null;
+ }
+ }
+
+ public boolean hasPerformedLongPress() {
+ return mHasPerformedLongPress;
+ }
+}
diff --git a/src/com/android/launcher3/CheckableFrameLayout.java b/src/com/android/launcher3/CheckableFrameLayout.java
new file mode 100644
index 0000000..5b7d824
--- /dev/null
+++ b/src/com/android/launcher3/CheckableFrameLayout.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 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.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.FrameLayout;
+
+public class CheckableFrameLayout extends FrameLayout implements Checkable {
+ private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
+ boolean mChecked;
+
+ public CheckableFrameLayout(Context context) {
+ super(context);
+ }
+
+ public CheckableFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CheckableFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ public void setChecked(boolean checked) {
+ if (checked != mChecked) {
+ mChecked = checked;
+ refreshDrawableState();
+ }
+ }
+
+ public void toggle() {
+ setChecked(!mChecked);
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+}
diff --git a/src/com/android/launcher3/Cling.java b/src/com/android/launcher3/Cling.java
new file mode 100644
index 0000000..338b722
--- /dev/null
+++ b/src/com/android/launcher3/Cling.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2011 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+public class Cling extends FrameLayout implements Insettable, View.OnClickListener,
+ View.OnLongClickListener, View.OnTouchListener {
+
+ static final String FIRST_RUN_CLING_DISMISSED_KEY = "cling_gel.first_run.dismissed";
+ static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
+ static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed";
+
+ private static String FIRST_RUN_PORTRAIT = "first_run_portrait";
+ private static String FIRST_RUN_LANDSCAPE = "first_run_landscape";
+
+ private static String WORKSPACE_PORTRAIT = "workspace_portrait";
+ private static String WORKSPACE_LANDSCAPE = "workspace_landscape";
+ private static String WORKSPACE_LARGE = "workspace_large";
+ private static String WORKSPACE_CUSTOM = "workspace_custom";
+
+ private static String FOLDER_PORTRAIT = "folder_portrait";
+ private static String FOLDER_LANDSCAPE = "folder_landscape";
+ private static String FOLDER_LARGE = "folder_large";
+
+ private static float FIRST_RUN_CIRCLE_BUFFER_DPS = 60;
+ private static float WORKSPACE_INNER_CIRCLE_RADIUS_DPS = 50;
+ private static float WORKSPACE_OUTER_CIRCLE_RADIUS_DPS = 60;
+ private static float WORKSPACE_CIRCLE_Y_OFFSET_DPS = 30;
+
+ private Launcher mLauncher;
+ private boolean mIsInitialized;
+ private String mDrawIdentifier;
+ private Drawable mBackground;
+
+ private int[] mTouchDownPt = new int[2];
+
+ private Drawable mFocusedHotseatApp;
+ private ComponentName mFocusedHotseatAppComponent;
+ private Rect mFocusedHotseatAppBounds;
+
+ private Paint mErasePaint;
+ private Paint mBubblePaint;
+ private Paint mDotPaint;
+
+ private View mScrimView;
+ private int mBackgroundColor;
+
+ private final Rect mInsets = new Rect();
+
+ public Cling(Context context) {
+ this(context, null, 0);
+ }
+
+ public Cling(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public Cling(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Cling, defStyle, 0);
+ mDrawIdentifier = a.getString(R.styleable.Cling_drawIdentifier);
+ a.recycle();
+
+ setClickable(true);
+
+ }
+
+ void init(Launcher l, View scrim) {
+ if (!mIsInitialized) {
+ mLauncher = l;
+ mScrimView = scrim;
+ mBackgroundColor = 0xdd000000;
+ setOnLongClickListener(this);
+ setOnClickListener(this);
+ setOnTouchListener(this);
+
+ mErasePaint = new Paint();
+ mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+ mErasePaint.setColor(0xFFFFFF);
+ mErasePaint.setAlpha(0);
+ mErasePaint.setAntiAlias(true);
+
+ int circleColor = getResources().getColor(
+ R.color.first_run_cling_circle_background_color);
+ mBubblePaint = new Paint();
+ mBubblePaint.setColor(circleColor);
+ mBubblePaint.setAntiAlias(true);
+
+ mDotPaint = new Paint();
+ mDotPaint.setColor(0x72BBED);
+ mDotPaint.setAntiAlias(true);
+
+ mIsInitialized = true;
+ }
+ }
+
+ void setFocusedHotseatApp(int drawableId, int appRank, ComponentName cn, String title,
+ String description) {
+ // Get the app to draw
+ Resources r = getResources();
+ int appIconId = drawableId;
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat != null && appIconId > -1 && appRank > -1 && !title.isEmpty() &&
+ !description.isEmpty()) {
+ // Set the app bounds
+ int x = hotseat.getCellXFromOrder(appRank);
+ int y = hotseat.getCellYFromOrder(appRank);
+ Rect pos = hotseat.getCellCoordinates(x, y);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mFocusedHotseatApp = getResources().getDrawable(appIconId);
+ mFocusedHotseatAppComponent = cn;
+ mFocusedHotseatAppBounds = new Rect(pos.left, pos.top,
+ pos.left + Utilities.sIconTextureWidth,
+ pos.top + Utilities.sIconTextureHeight);
+ Utilities.scaleRectAboutCenter(mFocusedHotseatAppBounds,
+ (grid.hotseatIconSize / grid.iconSize));
+
+ // Set the title
+ TextView v = (TextView) findViewById(R.id.focused_hotseat_app_title);
+ if (v != null) {
+ v.setText(title);
+ }
+
+ // Set the description
+ v = (TextView) findViewById(R.id.focused_hotseat_app_description);
+ if (v != null) {
+ v.setText(description);
+ }
+
+ // Show the bubble
+ View bubble = findViewById(R.id.focused_hotseat_app_bubble);
+ bubble.setVisibility(View.VISIBLE);
+ }
+ }
+
+ void show(boolean animate, int duration) {
+ setVisibility(View.VISIBLE);
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+ mDrawIdentifier.equals(WORKSPACE_LARGE) ||
+ mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
+ View content = getContent();
+ content.setAlpha(0f);
+ content.animate()
+ .alpha(1f)
+ .setDuration(duration)
+ .setListener(null)
+ .start();
+ setAlpha(1f);
+ } else {
+ if (animate) {
+ buildLayer();
+ setAlpha(0f);
+ animate()
+ .alpha(1f)
+ .setInterpolator(new AccelerateInterpolator())
+ .setDuration(duration)
+ .setListener(null)
+ .start();
+ } else {
+ setAlpha(1f);
+ }
+ }
+
+ // Show the scrim if necessary
+ if (mScrimView != null) {
+ mScrimView.setVisibility(View.VISIBLE);
+ mScrimView.setAlpha(0f);
+ mScrimView.animate()
+ .alpha(1f)
+ .setDuration(duration)
+ .setListener(null)
+ .start();
+ }
+
+ setFocusableInTouchMode(true);
+ post(new Runnable() {
+ public void run() {
+ setFocusable(true);
+ requestFocus();
+ }
+ });
+ }
+
+ void hide(final int duration, final Runnable postCb) {
+ if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
+ mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
+ View content = getContent();
+ content.animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ // We are about to trigger the workspace cling, so don't do anything else
+ setVisibility(View.GONE);
+ postCb.run();
+ };
+ })
+ .start();
+ } else {
+ animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ // We are about to trigger the workspace cling, so don't do anything else
+ setVisibility(View.GONE);
+ postCb.run();
+ };
+ })
+ .start();
+ }
+
+ // Show the scrim if necessary
+ if (mScrimView != null) {
+ mScrimView.animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ mScrimView.setVisibility(View.GONE);
+ };
+ })
+ .start();
+ }
+ }
+
+ void cleanup() {
+ mBackground = null;
+ mIsInitialized = false;
+ }
+
+ void bringScrimToFront() {
+ if (mScrimView != null) {
+ mScrimView.bringToFront();
+ }
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ }
+
+ View getContent() {
+ return findViewById(R.id.content);
+ }
+
+ String getDrawIdentifier() {
+ return mDrawIdentifier;
+ }
+
+ @Override
+ public View focusSearch(int direction) {
+ return this.focusSearch(this, direction);
+ }
+
+ @Override
+ public View focusSearch(View focused, int direction) {
+ return FocusFinder.getInstance().findNextFocus(this, focused, direction);
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ return (mDrawIdentifier.equals(WORKSPACE_PORTRAIT)
+ || mDrawIdentifier.equals(WORKSPACE_LANDSCAPE)
+ || mDrawIdentifier.equals(WORKSPACE_LARGE)
+ || mDrawIdentifier.equals(WORKSPACE_CUSTOM));
+ }
+
+ @Override
+ public boolean onTouchEvent(android.view.MotionEvent event) {
+ if (mDrawIdentifier.equals(FOLDER_PORTRAIT) ||
+ mDrawIdentifier.equals(FOLDER_LANDSCAPE) ||
+ mDrawIdentifier.equals(FOLDER_LARGE)) {
+ Folder f = mLauncher.getWorkspace().getOpenFolder();
+ if (f != null) {
+ Rect r = new Rect();
+ f.getHitRect(r);
+ if (r.contains((int) event.getX(), (int) event.getY())) {
+ return false;
+ }
+ }
+ }
+ return super.onTouchEvent(event);
+ };
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchDownPt[0] = (int) ev.getX();
+ mTouchDownPt[1] = (int) ev.getY();
+ }
+ return false;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+ mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+ if (mFocusedHotseatAppBounds != null &&
+ mFocusedHotseatAppBounds.contains(mTouchDownPt[0], mTouchDownPt[1])) {
+ // Launch the activity that is being highlighted
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(mFocusedHotseatAppComponent);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ mLauncher.startActivity(intent, null);
+ mLauncher.dismissWorkspaceCling(this);
+ }
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+ mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+ mLauncher.dismissWorkspaceCling(null);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mIsInitialized) {
+ canvas.save();
+
+ // Get the background override if there is one
+ if (mBackground == null) {
+ if (mDrawIdentifier.equals(WORKSPACE_CUSTOM)) {
+ mBackground = getResources().getDrawable(R.drawable.bg_cling5);
+ }
+ }
+ // Draw the background
+ Bitmap eraseBg = null;
+ Canvas eraseCanvas = null;
+ if (mScrimView != null) {
+ // Skip drawing the background
+ mScrimView.setBackgroundColor(mBackgroundColor);
+ } else if (mBackground != null) {
+ mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ mBackground.draw(canvas);
+ } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+ mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+ // Initialize the draw buffer (to allow punching through)
+ eraseBg = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
+ Bitmap.Config.ARGB_8888);
+ eraseCanvas = new Canvas(eraseBg);
+ eraseCanvas.drawColor(mBackgroundColor);
+ } else {
+ canvas.drawColor(mBackgroundColor);
+ }
+
+ // Draw everything else
+ DisplayMetrics metrics = new DisplayMetrics();
+ mLauncher.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ float alpha = getAlpha();
+ View content = getContent();
+ if (content != null) {
+ alpha *= content.getAlpha();
+ }
+ if (mDrawIdentifier.equals(FIRST_RUN_PORTRAIT) ||
+ mDrawIdentifier.equals(FIRST_RUN_LANDSCAPE)) {
+ // Draw the circle
+ View bubbleContent = findViewById(R.id.bubble_content);
+ Rect bubbleRect = new Rect();
+ bubbleContent.getGlobalVisibleRect(bubbleRect);
+ mBubblePaint.setAlpha((int) (255 * alpha));
+ float buffer = DynamicGrid.pxFromDp(FIRST_RUN_CIRCLE_BUFFER_DPS, metrics);
+ canvas.drawCircle(metrics.widthPixels / 2,
+ bubbleRect.centerY(),
+ (bubbleContent.getMeasuredWidth() + buffer) / 2,
+ mBubblePaint);
+ } else if (mDrawIdentifier.equals(WORKSPACE_PORTRAIT) ||
+ mDrawIdentifier.equals(WORKSPACE_LANDSCAPE) ||
+ mDrawIdentifier.equals(WORKSPACE_LARGE)) {
+ int offset = DynamicGrid.pxFromDp(WORKSPACE_CIRCLE_Y_OFFSET_DPS, metrics);
+ mErasePaint.setAlpha((int) (128));
+ eraseCanvas.drawCircle(metrics.widthPixels / 2,
+ metrics.heightPixels / 2 - offset,
+ DynamicGrid.pxFromDp(WORKSPACE_OUTER_CIRCLE_RADIUS_DPS, metrics),
+ mErasePaint);
+ mErasePaint.setAlpha(0);
+ eraseCanvas.drawCircle(metrics.widthPixels / 2,
+ metrics.heightPixels / 2 - offset,
+ DynamicGrid.pxFromDp(WORKSPACE_INNER_CIRCLE_RADIUS_DPS, metrics),
+ mErasePaint);
+ canvas.drawBitmap(eraseBg, 0, 0, null);
+ eraseCanvas.setBitmap(null);
+ eraseBg = null;
+
+ // Draw the focused hotseat app icon
+ if (mFocusedHotseatAppBounds != null && mFocusedHotseatApp != null) {
+ mFocusedHotseatApp.setBounds(mFocusedHotseatAppBounds.left,
+ mFocusedHotseatAppBounds.top, mFocusedHotseatAppBounds.right,
+ mFocusedHotseatAppBounds.bottom);
+ mFocusedHotseatApp.setAlpha((int) (255 * alpha));
+ mFocusedHotseatApp.draw(canvas);
+ }
+ }
+
+ canvas.restore();
+ }
+
+ // Draw the rest of the cling
+ super.dispatchDraw(canvas);
+ };
+}
diff --git a/src/com/android/launcher3/CropView.java b/src/com/android/launcher3/CropView.java
new file mode 100644
index 0000000..9224e3b
--- /dev/null
+++ b/src/com/android/launcher3/CropView.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2013 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.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import com.android.photos.views.TiledImageRenderer.TileSource;
+import com.android.photos.views.TiledImageView;
+
+public class CropView extends TiledImageView implements OnScaleGestureListener {
+
+ private ScaleGestureDetector mScaleGestureDetector;
+ private long mTouchDownTime;
+ private float mFirstX, mFirstY;
+ private float mLastX, mLastY;
+ private float mCenterX, mCenterY;
+ private float mMinScale;
+ private boolean mTouchEnabled = true;
+ private RectF mTempEdges = new RectF();
+ private float[] mTempPoint = new float[] { 0, 0 };
+ private float[] mTempCoef = new float[] { 0, 0 };
+ private float[] mTempAdjustment = new float[] { 0, 0 };
+ private float[] mTempImageDims = new float[] { 0, 0 };
+ private float[] mTempRendererCenter = new float[] { 0, 0 };
+ TouchCallback mTouchCallback;
+ Matrix mRotateMatrix;
+ Matrix mInverseRotateMatrix;
+
+ public interface TouchCallback {
+ void onTouchDown();
+ void onTap();
+ void onTouchUp();
+ }
+
+ public CropView(Context context) {
+ this(context, null);
+ }
+
+ public CropView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ mRotateMatrix = new Matrix();
+ mInverseRotateMatrix = new Matrix();
+ }
+
+ private float[] getImageDims() {
+ final float imageWidth = mRenderer.source.getImageWidth();
+ final float imageHeight = mRenderer.source.getImageHeight();
+ float[] imageDims = mTempImageDims;
+ imageDims[0] = imageWidth;
+ imageDims[1] = imageHeight;
+ mRotateMatrix.mapPoints(imageDims);
+ imageDims[0] = Math.abs(imageDims[0]);
+ imageDims[1] = Math.abs(imageDims[1]);
+ return imageDims;
+ }
+
+ private void getEdgesHelper(RectF edgesOut) {
+ final float width = getWidth();
+ final float height = getHeight();
+ final float[] imageDims = getImageDims();
+ final float imageWidth = imageDims[0];
+ final float imageHeight = imageDims[1];
+
+ float initialCenterX = mRenderer.source.getImageWidth() / 2f;
+ float initialCenterY = mRenderer.source.getImageHeight() / 2f;
+
+ float[] rendererCenter = mTempRendererCenter;
+ rendererCenter[0] = mCenterX - initialCenterX;
+ rendererCenter[1] = mCenterY - initialCenterY;
+ mRotateMatrix.mapPoints(rendererCenter);
+ rendererCenter[0] += imageWidth / 2;
+ rendererCenter[1] += imageHeight / 2;
+
+ final float scale = mRenderer.scale;
+ float centerX = (width / 2f - rendererCenter[0] + (imageWidth - width) / 2f)
+ * scale + width / 2f;
+ float centerY = (height / 2f - rendererCenter[1] + (imageHeight - height) / 2f)
+ * scale + height / 2f;
+ float leftEdge = centerX - imageWidth / 2f * scale;
+ float rightEdge = centerX + imageWidth / 2f * scale;
+ float topEdge = centerY - imageHeight / 2f * scale;
+ float bottomEdge = centerY + imageHeight / 2f * scale;
+
+ edgesOut.left = leftEdge;
+ edgesOut.right = rightEdge;
+ edgesOut.top = topEdge;
+ edgesOut.bottom = bottomEdge;
+ }
+
+ public int getImageRotation() {
+ return mRenderer.rotation;
+ }
+
+ public RectF getCrop() {
+ final RectF edges = mTempEdges;
+ getEdgesHelper(edges);
+ final float scale = mRenderer.scale;
+
+ float cropLeft = -edges.left / scale;
+ float cropTop = -edges.top / scale;
+ float cropRight = cropLeft + getWidth() / scale;
+ float cropBottom = cropTop + getHeight() / scale;
+
+ return new RectF(cropLeft, cropTop, cropRight, cropBottom);
+ }
+
+ public Point getSourceDimensions() {
+ return new Point(mRenderer.source.getImageWidth(), mRenderer.source.getImageHeight());
+ }
+
+ public void setTileSource(TileSource source, Runnable isReadyCallback) {
+ super.setTileSource(source, isReadyCallback);
+ mCenterX = mRenderer.centerX;
+ mCenterY = mRenderer.centerY;
+ mRotateMatrix.reset();
+ mRotateMatrix.setRotate(mRenderer.rotation);
+ mInverseRotateMatrix.reset();
+ mInverseRotateMatrix.setRotate(-mRenderer.rotation);
+ updateMinScale(getWidth(), getHeight(), source, true);
+ }
+
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ updateMinScale(w, h, mRenderer.source, false);
+ }
+
+ public void setScale(float scale) {
+ synchronized (mLock) {
+ mRenderer.scale = scale;
+ }
+ }
+
+ private void updateMinScale(int w, int h, TileSource source, boolean resetScale) {
+ synchronized (mLock) {
+ if (resetScale) {
+ mRenderer.scale = 1;
+ }
+ if (source != null) {
+ final float[] imageDims = getImageDims();
+ final float imageWidth = imageDims[0];
+ final float imageHeight = imageDims[1];
+ mMinScale = Math.max(w / imageWidth, h / imageHeight);
+ mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
+ }
+ }
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ // Don't need the lock because this will only fire inside of
+ // onTouchEvent
+ mRenderer.scale *= detector.getScaleFactor();
+ mRenderer.scale = Math.max(mMinScale, mRenderer.scale);
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ }
+
+ public void moveToLeft() {
+ if (getWidth() == 0 || getHeight() == 0) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ moveToLeft();
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+ final RectF edges = mTempEdges;
+ getEdgesHelper(edges);
+ final float scale = mRenderer.scale;
+ mCenterX += Math.ceil(edges.left / scale);
+ updateCenter();
+ }
+
+ private void updateCenter() {
+ mRenderer.centerX = Math.round(mCenterX);
+ mRenderer.centerY = Math.round(mCenterY);
+ }
+
+ public void setTouchEnabled(boolean enabled) {
+ mTouchEnabled = enabled;
+ }
+
+ public void setTouchCallback(TouchCallback cb) {
+ mTouchCallback = cb;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = event.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i)
+ continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
+ }
+ final int div = pointerUp ? count - 1 : count;
+ float x = sumX / div;
+ float y = sumY / div;
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ mFirstX = x;
+ mFirstY = y;
+ mTouchDownTime = System.currentTimeMillis();
+ if (mTouchCallback != null) {
+ mTouchCallback.onTouchDown();
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ ViewConfiguration config = ViewConfiguration.get(getContext());
+
+ float squaredDist = (mFirstX - x) * (mFirstX - x) + (mFirstY - y) * (mFirstY - y);
+ float slop = config.getScaledTouchSlop() * config.getScaledTouchSlop();
+ long now = System.currentTimeMillis();
+ if (mTouchCallback != null) {
+ // only do this if it's a small movement
+ if (squaredDist < slop &&
+ now < mTouchDownTime + ViewConfiguration.getTapTimeout()) {
+ mTouchCallback.onTap();
+ }
+ mTouchCallback.onTouchUp();
+ }
+ }
+
+ if (!mTouchEnabled) {
+ return true;
+ }
+
+ synchronized (mLock) {
+ mScaleGestureDetector.onTouchEvent(event);
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ float[] point = mTempPoint;
+ point[0] = (mLastX - x) / mRenderer.scale;
+ point[1] = (mLastY - y) / mRenderer.scale;
+ mInverseRotateMatrix.mapPoints(point);
+ mCenterX += point[0];
+ mCenterY += point[1];
+ updateCenter();
+ invalidate();
+ break;
+ }
+ if (mRenderer.source != null) {
+ // Adjust position so that the wallpaper covers the entire area
+ // of the screen
+ final RectF edges = mTempEdges;
+ getEdgesHelper(edges);
+ final float scale = mRenderer.scale;
+
+ float[] coef = mTempCoef;
+ coef[0] = 1;
+ coef[1] = 1;
+ mRotateMatrix.mapPoints(coef);
+ float[] adjustment = mTempAdjustment;
+ mTempAdjustment[0] = 0;
+ mTempAdjustment[1] = 0;
+ if (edges.left > 0) {
+ adjustment[0] = edges.left / scale;
+ } else if (edges.right < getWidth()) {
+ adjustment[0] = (edges.right - getWidth()) / scale;
+ }
+ if (edges.top > 0) {
+ adjustment[1] = FloatMath.ceil(edges.top / scale);
+ } else if (edges.bottom < getHeight()) {
+ adjustment[1] = (edges.bottom - getHeight()) / scale;
+ }
+ for (int dim = 0; dim <= 1; dim++) {
+ if (coef[dim] > 0) adjustment[dim] = FloatMath.ceil(adjustment[dim]);
+ }
+
+ mInverseRotateMatrix.mapPoints(adjustment);
+ mCenterX += adjustment[0];
+ mCenterY += adjustment[1];
+ updateCenter();
+ }
+ }
+
+ mLastX = x;
+ mLastY = y;
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java
new file mode 100644
index 0000000..92ecf96
--- /dev/null
+++ b/src/com/android/launcher3/DeferredHandler.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.util.Pair;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+/**
+ * Queue of things to run on a looper thread. Items posted with {@link #post} will not
+ * be actually enqued on the handler until after the last one has run, to keep from
+ * starving the thread.
+ *
+ * This class is fifo.
+ */
+public class DeferredHandler {
+ private LinkedList<Pair<Runnable, Integer>> mQueue = new LinkedList<Pair<Runnable, Integer>>();
+ private MessageQueue mMessageQueue = Looper.myQueue();
+ private Impl mHandler = new Impl();
+
+ private class Impl extends Handler implements MessageQueue.IdleHandler {
+ public void handleMessage(Message msg) {
+ Pair<Runnable, Integer> p;
+ Runnable r;
+ synchronized (mQueue) {
+ if (mQueue.size() == 0) {
+ return;
+ }
+ p = mQueue.removeFirst();
+ r = p.first;
+ }
+ r.run();
+ synchronized (mQueue) {
+ scheduleNextLocked();
+ }
+ }
+
+ public boolean queueIdle() {
+ handleMessage(null);
+ return false;
+ }
+ }
+
+ private class IdleRunnable implements Runnable {
+ Runnable mRunnable;
+
+ IdleRunnable(Runnable r) {
+ mRunnable = r;
+ }
+
+ public void run() {
+ mRunnable.run();
+ }
+ }
+
+ public DeferredHandler() {
+ }
+
+ /** Schedule runnable to run after everything that's on the queue right now. */
+ public void post(Runnable runnable) {
+ post(runnable, 0);
+ }
+ public void post(Runnable runnable, int type) {
+ synchronized (mQueue) {
+ mQueue.add(new Pair<Runnable, Integer>(runnable, type));
+ if (mQueue.size() == 1) {
+ scheduleNextLocked();
+ }
+ }
+ }
+
+ /** Schedule runnable to run when the queue goes idle. */
+ public void postIdle(final Runnable runnable) {
+ postIdle(runnable, 0);
+ }
+ public void postIdle(final Runnable runnable, int type) {
+ post(new IdleRunnable(runnable), type);
+ }
+
+ public void cancelRunnable(Runnable runnable) {
+ synchronized (mQueue) {
+ while (mQueue.remove(runnable)) { }
+ }
+ }
+ public void cancelAllRunnablesOfType(int type) {
+ synchronized (mQueue) {
+ ListIterator<Pair<Runnable, Integer>> iter = mQueue.listIterator();
+ Pair<Runnable, Integer> p;
+ while (iter.hasNext()) {
+ p = iter.next();
+ if (p.second == type) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ public void cancel() {
+ synchronized (mQueue) {
+ mQueue.clear();
+ }
+ }
+
+ /** Runs all queued Runnables from the calling thread. */
+ public void flush() {
+ LinkedList<Pair<Runnable, Integer>> queue = new LinkedList<Pair<Runnable, Integer>>();
+ synchronized (mQueue) {
+ queue.addAll(mQueue);
+ mQueue.clear();
+ }
+ for (Pair<Runnable, Integer> p : queue) {
+ p.first.run();
+ }
+ }
+
+ void scheduleNextLocked() {
+ if (mQueue.size() > 0) {
+ Pair<Runnable, Integer> p = mQueue.getFirst();
+ Runnable peek = p.first;
+ if (peek instanceof IdleRunnable) {
+ mMessageQueue.addIdleHandler(mHandler);
+ } else {
+ mHandler.sendEmptyMessage(1);
+ }
+ }
+ }
+}
+
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
new file mode 100644
index 0000000..4023daf
--- /dev/null
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2011 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.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.TransitionDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.List;
+import java.util.Set;
+
+public class DeleteDropTarget extends ButtonDropTarget {
+ private static int DELETE_ANIMATION_DURATION = 285;
+ private static int FLING_DELETE_ANIMATION_DURATION = 350;
+ private static float FLING_TO_DELETE_FRICTION = 0.035f;
+ private static int MODE_FLING_DELETE_TO_TRASH = 0;
+ private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
+
+ private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
+
+ private ColorStateList mOriginalTextColor;
+ private TransitionDrawable mUninstallDrawable;
+ private TransitionDrawable mRemoveDrawable;
+ private TransitionDrawable mCurrentDrawable;
+
+ private boolean mWaitingForUninstall = false;
+
+ public DeleteDropTarget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ // Get the drawable
+ mOriginalTextColor = getTextColors();
+
+ // Get the hover color
+ Resources r = getResources();
+ mHoverColor = r.getColor(R.color.delete_target_hover_tint);
+ mUninstallDrawable = (TransitionDrawable)
+ r.getDrawable(R.drawable.uninstall_target_selector);
+ mRemoveDrawable = (TransitionDrawable) r.getDrawable(R.drawable.remove_target_selector);
+
+ mRemoveDrawable.setCrossFadeEnabled(true);
+ mUninstallDrawable.setCrossFadeEnabled(true);
+
+ // The current drawable is set to either the remove drawable or the uninstall drawable
+ // and is initially set to the remove drawable, as set in the layout xml.
+ mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
+
+ // Remove the text in the Phone UI in landscape
+ int orientation = getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (!LauncherAppState.getInstance().isScreenLarge()) {
+ setText("");
+ }
+ }
+ }
+
+ private boolean isAllAppsApplication(DragSource source, Object info) {
+ return (source instanceof AppsCustomizePagedView) && (info instanceof AppInfo);
+ }
+ private boolean isAllAppsWidget(DragSource source, Object info) {
+ if (source instanceof AppsCustomizePagedView) {
+ if (info instanceof PendingAddItemInfo) {
+ PendingAddItemInfo addInfo = (PendingAddItemInfo) info;
+ switch (addInfo.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ private boolean isDragSourceWorkspaceOrFolder(DragObject d) {
+ return (d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder);
+ }
+ private boolean isWorkspaceOrFolderApplication(DragObject d) {
+ return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof ShortcutInfo);
+ }
+ private boolean isWorkspaceOrFolderWidget(DragObject d) {
+ return isDragSourceWorkspaceOrFolder(d) && (d.dragInfo instanceof LauncherAppWidgetInfo);
+ }
+ private boolean isWorkspaceFolder(DragObject d) {
+ return (d.dragSource instanceof Workspace) && (d.dragInfo instanceof FolderInfo);
+ }
+
+ private void setHoverColor() {
+ mCurrentDrawable.startTransition(mTransitionDuration);
+ setTextColor(mHoverColor);
+ }
+ private void resetHoverColor() {
+ mCurrentDrawable.resetTransition();
+ setTextColor(mOriginalTextColor);
+ }
+
+ @Override
+ public boolean acceptDrop(DragObject d) {
+ return willAcceptDrop(d.dragInfo);
+ }
+
+ public static boolean willAcceptDrop(Object info) {
+ if (info instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) info;
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
+ item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ return true;
+ }
+
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ return true;
+ }
+
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
+ item instanceof AppInfo) {
+ AppInfo appInfo = (AppInfo) info;
+ return (appInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
+ }
+
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
+ item instanceof ShortcutInfo) {
+ if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ ShortcutInfo shortcutInfo = (ShortcutInfo) info;
+ return (shortcutInfo.flags & AppInfo.DOWNLOADED_FLAG) != 0;
+ } else {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ boolean isVisible = true;
+ boolean useUninstallLabel = !AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ isAllAppsApplication(source, info);
+
+ // If we are dragging an application from AppsCustomize, only show the control if we can
+ // delete the app (it was downloaded), and rename the string to "uninstall" in such a case.
+ // Hide the delete target if it is a widget from AppsCustomize.
+ if (!willAcceptDrop(info) || isAllAppsWidget(source, info)) {
+ isVisible = false;
+ }
+
+ if (useUninstallLabel) {
+ setCompoundDrawablesRelativeWithIntrinsicBounds(mUninstallDrawable, null, null, null);
+ } else {
+ setCompoundDrawablesRelativeWithIntrinsicBounds(mRemoveDrawable, null, null, null);
+ }
+ mCurrentDrawable = (TransitionDrawable) getCurrentDrawable();
+
+ mActive = isVisible;
+ resetHoverColor();
+ ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ if (getText().length() > 0) {
+ setText(useUninstallLabel ? R.string.delete_target_uninstall_label
+ : R.string.delete_target_label);
+ }
+ }
+
+ @Override
+ public void onDragEnd() {
+ super.onDragEnd();
+ mActive = false;
+ }
+
+ public void onDragEnter(DragObject d) {
+ super.onDragEnter(d);
+
+ setHoverColor();
+ }
+
+ public void onDragExit(DragObject d) {
+ super.onDragExit(d);
+
+ if (!d.dragComplete) {
+ resetHoverColor();
+ } else {
+ // Restore the hover color if we are deleting
+ d.dragView.setColor(mHoverColor);
+ }
+ }
+
+ private void animateToTrashAndCompleteDrop(final DragObject d) {
+ final DragLayer dragLayer = mLauncher.getDragLayer();
+ final Rect from = new Rect();
+ dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+ final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+ mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight());
+ final float scale = (float) to.width() / from.width();
+
+ mSearchDropTargetBar.deferOnDragEnd();
+ deferCompleteDropIfUninstalling(d);
+
+ Runnable onAnimationEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ completeDrop(d);
+ mSearchDropTargetBar.onDragEnd();
+ mLauncher.exitSpringLoadedDragMode();
+ }
+ };
+ dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
+ DELETE_ANIMATION_DURATION, new DecelerateInterpolator(2),
+ new LinearInterpolator(), onAnimationEndRunnable,
+ DragLayer.ANIMATION_END_DISAPPEAR, null);
+ }
+
+ private void deferCompleteDropIfUninstalling(DragObject d) {
+ mWaitingForUninstall = false;
+ if (isUninstallFromWorkspace(d)) {
+ if (d.dragSource instanceof Folder) {
+ ((Folder) d.dragSource).deferCompleteDropAfterUninstallActivity();
+ } else if (d.dragSource instanceof Workspace) {
+ ((Workspace) d.dragSource).deferCompleteDropAfterUninstallActivity();
+ }
+ mWaitingForUninstall = true;
+ }
+ }
+
+ private boolean isUninstallFromWorkspace(DragObject d) {
+ if (AppsCustomizePagedView.DISABLE_ALL_APPS && isWorkspaceOrFolderApplication(d)) {
+ ShortcutInfo shortcut = (ShortcutInfo) d.dragInfo;
+ if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
+ Set<String> categories = shortcut.intent.getCategories();
+ boolean includesLauncherCategory = false;
+ if (categories != null) {
+ for (String category : categories) {
+ if (category.equals(Intent.CATEGORY_LAUNCHER)) {
+ includesLauncherCategory = true;
+ break;
+ }
+ }
+ }
+ return includesLauncherCategory;
+ }
+ }
+ return false;
+ }
+
+ private void completeDrop(DragObject d) {
+ ItemInfo item = (ItemInfo) d.dragInfo;
+ boolean wasWaitingForUninstall = mWaitingForUninstall;
+ mWaitingForUninstall = false;
+ if (isAllAppsApplication(d.dragSource, item)) {
+ // Uninstall the application if it is being dragged from AppsCustomize
+ AppInfo appInfo = (AppInfo) item;
+ mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags);
+ } else if (isUninstallFromWorkspace(d)) {
+ ShortcutInfo shortcut = (ShortcutInfo) item;
+ if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
+ final ComponentName componentName = shortcut.intent.getComponent();
+ final DragSource dragSource = d.dragSource;
+ int flags = AppInfo.initFlags(
+ ShortcutInfo.getPackageInfo(getContext(), componentName.getPackageName()));
+ mWaitingForUninstall =
+ mLauncher.startApplicationUninstallActivity(componentName, flags);
+ if (mWaitingForUninstall) {
+ final Runnable checkIfUninstallWasSuccess = new Runnable() {
+ @Override
+ public void run() {
+ mWaitingForUninstall = false;
+ String packageName = componentName.getPackageName();
+ List<ResolveInfo> activities =
+ AllAppsList.findActivitiesForPackage(getContext(), packageName);
+ boolean uninstallSuccessful = activities.size() == 0;
+ if (dragSource instanceof Folder) {
+ ((Folder) dragSource).
+ onUninstallActivityReturned(uninstallSuccessful);
+ } else if (dragSource instanceof Workspace) {
+ ((Workspace) dragSource).
+ onUninstallActivityReturned(uninstallSuccessful);
+ }
+ }
+ };
+ mLauncher.addOnResumeCallback(checkIfUninstallWasSuccess);
+ }
+ }
+ } else if (isWorkspaceOrFolderApplication(d)) {
+ LauncherModel.deleteItemFromDatabase(mLauncher, item);
+ } else if (isWorkspaceFolder(d)) {
+ // Remove the folder from the workspace and delete the contents from launcher model
+ FolderInfo folderInfo = (FolderInfo) item;
+ mLauncher.removeFolder(folderInfo);
+ LauncherModel.deleteFolderContentsFromDatabase(mLauncher, folderInfo);
+ } else if (isWorkspaceOrFolderWidget(d)) {
+ // Remove the widget from the workspace
+ mLauncher.removeAppWidget((LauncherAppWidgetInfo) item);
+ LauncherModel.deleteItemFromDatabase(mLauncher, item);
+
+ final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item;
+ final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost();
+ if (appWidgetHost != null) {
+ // Deleting an app widget ID is a void call but writes to disk before returning
+ // to the caller...
+ new Thread("deleteAppWidgetId") {
+ public void run() {
+ appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId);
+ }
+ }.start();
+ }
+ }
+ if (wasWaitingForUninstall && !mWaitingForUninstall) {
+ if (d.dragSource instanceof Folder) {
+ ((Folder) d.dragSource).onUninstallActivityReturned(false);
+ } else if (d.dragSource instanceof Workspace) {
+ ((Workspace) d.dragSource).onUninstallActivityReturned(false);
+ }
+ }
+ }
+
+ public void onDrop(DragObject d) {
+ animateToTrashAndCompleteDrop(d);
+ }
+
+ /**
+ * Creates an animation from the current drag view to the delete trash icon.
+ */
+ private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer,
+ DragObject d, PointF vel, ViewConfiguration config) {
+ final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+ mCurrentDrawable.getIntrinsicWidth(), mCurrentDrawable.getIntrinsicHeight());
+ final Rect from = new Rect();
+ dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+
+ // Calculate how far along the velocity vector we should put the intermediate point on
+ // the bezier curve
+ float velocity = Math.abs(vel.length());
+ float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f));
+ int offsetY = (int) (-from.top * vp);
+ int offsetX = (int) (offsetY / (vel.y / vel.x));
+ final float y2 = from.top + offsetY; // intermediate t/l
+ final float x2 = from.left + offsetX;
+ final float x1 = from.left; // drag view t/l
+ final float y1 = from.top;
+ final float x3 = to.left; // delete target t/l
+ final float y3 = to.top;
+
+ final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() {
+ @Override
+ public float getInterpolation(float t) {
+ return t * t * t * t * t * t * t * t;
+ }
+ };
+ return new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final DragView dragView = (DragView) dragLayer.getAnimatedView();
+ float t = ((Float) animation.getAnimatedValue()).floatValue();
+ float tp = scaleAlphaInterpolator.getInterpolation(t);
+ float initialScale = dragView.getInitialScale();
+ float finalAlpha = 0.5f;
+ float scale = dragView.getScaleX();
+ float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f;
+ float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f;
+ float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) +
+ (t * t) * x3;
+ float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) +
+ (t * t) * y3;
+
+ dragView.setTranslationX(x);
+ dragView.setTranslationY(y);
+ dragView.setScaleX(initialScale * (1f - tp));
+ dragView.setScaleY(initialScale * (1f - tp));
+ dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp));
+ }
+ };
+ }
+
+ /**
+ * Creates an animation from the current drag view along its current velocity vector.
+ * For this animation, the alpha runs for a fixed duration and we update the position
+ * progressively.
+ */
+ private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
+ private DragLayer mDragLayer;
+ private PointF mVelocity;
+ private Rect mFrom;
+ private long mPrevTime;
+ private boolean mHasOffsetForScale;
+ private float mFriction;
+
+ private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+
+ public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
+ long startTime, float friction) {
+ mDragLayer = dragLayer;
+ mVelocity = vel;
+ mFrom = from;
+ mPrevTime = startTime;
+ mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction);
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final DragView dragView = (DragView) mDragLayer.getAnimatedView();
+ float t = ((Float) animation.getAnimatedValue()).floatValue();
+ long curTime = AnimationUtils.currentAnimationTimeMillis();
+
+ if (!mHasOffsetForScale) {
+ mHasOffsetForScale = true;
+ float scale = dragView.getScaleX();
+ float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f;
+ float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f;
+
+ mFrom.left += xOffset;
+ mFrom.top += yOffset;
+ }
+
+ mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
+ mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
+
+ dragView.setTranslationX(mFrom.left);
+ dragView.setTranslationY(mFrom.top);
+ dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
+
+ mVelocity.x *= mFriction;
+ mVelocity.y *= mFriction;
+ mPrevTime = curTime;
+ }
+ };
+ private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer,
+ DragObject d, PointF vel, final long startTime, final int duration,
+ ViewConfiguration config) {
+ final Rect from = new Rect();
+ dragLayer.getViewRectRelativeToSelf(d.dragView, from);
+
+ return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime,
+ FLING_TO_DELETE_FRICTION);
+ }
+
+ public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
+ final boolean isAllApps = d.dragSource instanceof AppsCustomizePagedView;
+
+ // Don't highlight the icon as it's animating
+ d.dragView.setColor(0);
+ d.dragView.updateInitialScaleToCurrentScale();
+ // Don't highlight the target if we are flinging from AllApps
+ if (isAllApps) {
+ resetHoverColor();
+ }
+
+ if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
+ // Defer animating out the drop target if we are animating to it
+ mSearchDropTargetBar.deferOnDragEnd();
+ mSearchDropTargetBar.finishAnimations();
+ }
+
+ final ViewConfiguration config = ViewConfiguration.get(mLauncher);
+ final DragLayer dragLayer = mLauncher.getDragLayer();
+ final int duration = FLING_DELETE_ANIMATION_DURATION;
+ final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+ // NOTE: Because it takes time for the first frame of animation to actually be
+ // called and we expect the animation to be a continuation of the fling, we have
+ // to account for the time that has elapsed since the fling finished. And since
+ // we don't have a startDelay, we will always get call to update when we call
+ // start() (which we want to ignore).
+ final TimeInterpolator tInterpolator = new TimeInterpolator() {
+ private int mCount = -1;
+ private float mOffset = 0f;
+
+ @Override
+ public float getInterpolation(float t) {
+ if (mCount < 0) {
+ mCount++;
+ } else if (mCount == 0) {
+ mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+ startTime) / duration);
+ mCount++;
+ }
+ return Math.min(1f, mOffset + t);
+ }
+ };
+ AnimatorUpdateListener updateCb = null;
+ if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
+ updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config);
+ } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) {
+ updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
+ duration, config);
+ }
+ deferCompleteDropIfUninstalling(d);
+
+ Runnable onAnimationEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
+ // itself, otherwise, complete the drop to initiate the deletion process
+ if (!isAllApps) {
+ mLauncher.exitSpringLoadedDragMode();
+ completeDrop(d);
+ }
+ mLauncher.getDragController().onDeferredEndFling(d);
+ }
+ };
+ dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
+ DragLayer.ANIMATION_END_DISAPPEAR, null);
+ }
+}
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
new file mode 100644
index 0000000..5b5c35c
--- /dev/null
+++ b/src/com/android/launcher3/DragController.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+
+/**
+ * Class for initiating a drag within a view or across multiple views.
+ */
+public class DragController {
+ private static final String TAG = "Launcher.DragController";
+
+ /** Indicates the drag is a move. */
+ public static int DRAG_ACTION_MOVE = 0;
+
+ /** Indicates the drag is a copy. */
+ public static int DRAG_ACTION_COPY = 1;
+
+ private static final int SCROLL_DELAY = 500;
+ private static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
+
+ private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
+
+ private static final int SCROLL_OUTSIDE_ZONE = 0;
+ private static final int SCROLL_WAITING_IN_ZONE = 1;
+
+ static final int SCROLL_NONE = -1;
+ static final int SCROLL_LEFT = 0;
+ static final int SCROLL_RIGHT = 1;
+
+ private static final float MAX_FLING_DEGREES = 35f;
+
+ private Launcher mLauncher;
+ private Handler mHandler;
+
+ // temporaries to avoid gc thrash
+ private Rect mRectTemp = new Rect();
+ private final int[] mCoordinatesTemp = new int[2];
+
+ /** Whether or not we're dragging. */
+ private boolean mDragging;
+
+ /** X coordinate of the down event. */
+ private int mMotionDownX;
+
+ /** Y coordinate of the down event. */
+ private int mMotionDownY;
+
+ /** the area at the edge of the screen that makes the workspace go left
+ * or right while you're dragging.
+ */
+ private int mScrollZone;
+
+ private DropTarget.DragObject mDragObject;
+
+ /** Who can receive drop events */
+ private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
+ private ArrayList<DragListener> mListeners = new ArrayList<DragListener>();
+ private DropTarget mFlingToDeleteDropTarget;
+
+ /** The window token used as the parent for the DragView. */
+ private IBinder mWindowToken;
+
+ /** The view that will be scrolled when dragging to the left and right edges of the screen. */
+ private View mScrollView;
+
+ private View mMoveTarget;
+
+ private DragScroller mDragScroller;
+ private int mScrollState = SCROLL_OUTSIDE_ZONE;
+ private ScrollRunnable mScrollRunnable = new ScrollRunnable();
+
+ private DropTarget mLastDropTarget;
+
+ private InputMethodManager mInputMethodManager;
+
+ private int mLastTouch[] = new int[2];
+ private long mLastTouchUpTime = -1;
+ private int mDistanceSinceScroll = 0;
+
+ private int mTmpPoint[] = new int[2];
+ private Rect mDragLayerRect = new Rect();
+
+ protected int mFlingToDeleteThresholdVelocity;
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * Interface to receive notifications when a drag starts or stops
+ */
+ interface DragListener {
+ /**
+ * A drag has begun
+ *
+ * @param source An object representing where the drag originated
+ * @param info The data associated with the object that is being dragged
+ * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE}
+ * or {@link DragController#DRAG_ACTION_COPY}
+ */
+ void onDragStart(DragSource source, Object info, int dragAction);
+
+ /**
+ * The drag has ended
+ */
+ void onDragEnd();
+ }
+
+ /**
+ * Used to create a new DragLayer from XML.
+ *
+ * @param context The application's context.
+ */
+ public DragController(Launcher launcher) {
+ Resources r = launcher.getResources();
+ mLauncher = launcher;
+ mHandler = new Handler();
+ mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
+ mVelocityTracker = VelocityTracker.obtain();
+
+ float density = r.getDisplayMetrics().density;
+ mFlingToDeleteThresholdVelocity =
+ (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density);
+ }
+
+ public boolean dragging() {
+ return mDragging;
+ }
+
+ /**
+ * Starts a drag.
+ *
+ * @param v The view that is being dragged
+ * @param bmp The bitmap that represents the view being dragged
+ * @param source An object representing where the drag originated
+ * @param dragInfo The data associated with the object that is being dragged
+ * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
+ * {@link #DRAG_ACTION_COPY}
+ * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
+ * Makes dragging feel more precise, e.g. you can clip out a transparent border
+ */
+ public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction,
+ Point extraPadding, float initialDragViewScale) {
+ int[] loc = mCoordinatesTemp;
+ mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
+ int viewExtraPaddingLeft = extraPadding != null ? extraPadding.x : 0;
+ int viewExtraPaddingTop = extraPadding != null ? extraPadding.y : 0;
+ int dragLayerX = loc[0] + v.getPaddingLeft() + viewExtraPaddingLeft +
+ (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
+ int dragLayerY = loc[1] + v.getPaddingTop() + viewExtraPaddingTop +
+ (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
+
+ startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
+ null, initialDragViewScale);
+
+ if (dragAction == DRAG_ACTION_MOVE) {
+ v.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Starts a drag.
+ *
+ * @param b The bitmap to display as the drag image. It will be re-scaled to the
+ * enlarged size.
+ * @param dragLayerX The x position in the DragLayer of the left-top of the bitmap.
+ * @param dragLayerY The y position in the DragLayer of the left-top of the bitmap.
+ * @param source An object representing where the drag originated
+ * @param dragInfo The data associated with the object that is being dragged
+ * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
+ * {@link #DRAG_ACTION_COPY}
+ * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
+ * Makes dragging feel more precise, e.g. you can clip out a transparent border
+ */
+ public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
+ DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
+ float initialDragViewScale) {
+ if (PROFILE_DRAWING_DURING_DRAG) {
+ android.os.Debug.startMethodTracing("Launcher");
+ }
+
+ // Hide soft keyboard, if visible
+ if (mInputMethodManager == null) {
+ mInputMethodManager = (InputMethodManager)
+ mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
+ }
+ mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
+
+ for (DragListener listener : mListeners) {
+ listener.onDragStart(source, dragInfo, dragAction);
+ }
+
+ final int registrationX = mMotionDownX - dragLayerX;
+ final int registrationY = mMotionDownY - dragLayerY;
+
+ final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
+ final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
+
+ mDragging = true;
+
+ mDragObject = new DropTarget.DragObject();
+
+ mDragObject.dragComplete = false;
+ mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
+ mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+ mDragObject.dragSource = source;
+ mDragObject.dragInfo = dragInfo;
+
+ final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
+ registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
+
+ if (dragOffset != null) {
+ dragView.setDragVisualizeOffset(new Point(dragOffset));
+ }
+ if (dragRegion != null) {
+ dragView.setDragRegion(new Rect(dragRegion));
+ }
+
+ mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ dragView.show(mMotionDownX, mMotionDownY);
+ handleMoveEvent(mMotionDownX, mMotionDownY);
+ }
+
+ /**
+ * Draw the view into a bitmap.
+ */
+ Bitmap getViewBitmap(View v) {
+ v.clearFocus();
+ v.setPressed(false);
+
+ boolean willNotCache = v.willNotCacheDrawing();
+ v.setWillNotCacheDrawing(false);
+
+ // Reset the drawing cache background color to fully transparent
+ // for the duration of this operation
+ int color = v.getDrawingCacheBackgroundColor();
+ v.setDrawingCacheBackgroundColor(0);
+ float alpha = v.getAlpha();
+ v.setAlpha(1.0f);
+
+ if (color != 0) {
+ v.destroyDrawingCache();
+ }
+ v.buildDrawingCache();
+ Bitmap cacheBitmap = v.getDrawingCache();
+ if (cacheBitmap == null) {
+ Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException());
+ return null;
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
+
+ // Restore the view
+ v.destroyDrawingCache();
+ v.setAlpha(alpha);
+ v.setWillNotCacheDrawing(willNotCache);
+ v.setDrawingCacheBackgroundColor(color);
+
+ return bitmap;
+ }
+
+ /**
+ * Call this from a drag source view like this:
+ *
+ * <pre>
+ * @Override
+ * public boolean dispatchKeyEvent(KeyEvent event) {
+ * return mDragController.dispatchKeyEvent(this, event)
+ * || super.dispatchKeyEvent(event);
+ * </pre>
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mDragging;
+ }
+
+ public boolean isDragging() {
+ return mDragging;
+ }
+
+ /**
+ * Stop dragging without dropping.
+ */
+ public void cancelDrag() {
+ if (mDragging) {
+ if (mLastDropTarget != null) {
+ mLastDropTarget.onDragExit(mDragObject);
+ }
+ mDragObject.deferDragViewCleanupPostAnimation = false;
+ mDragObject.cancelled = true;
+ mDragObject.dragComplete = true;
+ mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+ }
+ endDrag();
+ }
+ public void onAppsRemoved(ArrayList<AppInfo> appInfos, Context context) {
+ // Cancel the current drag if we are removing an app that we are dragging
+ if (mDragObject != null) {
+ Object rawDragInfo = mDragObject.dragInfo;
+ if (rawDragInfo instanceof ShortcutInfo) {
+ ShortcutInfo dragInfo = (ShortcutInfo) rawDragInfo;
+ for (AppInfo info : appInfos) {
+ // Added null checks to prevent NPE we've seen in the wild
+ if (dragInfo != null &&
+ dragInfo.intent != null) {
+ boolean isSameComponent =
+ dragInfo.intent.getComponent().equals(info.componentName);
+ if (isSameComponent) {
+ cancelDrag();
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void endDrag() {
+ if (mDragging) {
+ mDragging = false;
+ clearScrollRunnable();
+ boolean isDeferred = false;
+ if (mDragObject.dragView != null) {
+ isDeferred = mDragObject.deferDragViewCleanupPostAnimation;
+ if (!isDeferred) {
+ mDragObject.dragView.remove();
+ }
+ mDragObject.dragView = null;
+ }
+
+ // Only end the drag if we are not deferred
+ if (!isDeferred) {
+ for (DragListener listener : mListeners) {
+ listener.onDragEnd();
+ }
+ }
+ }
+
+ releaseVelocityTracker();
+ }
+
+ /**
+ * This only gets called as a result of drag view cleanup being deferred in endDrag();
+ */
+ void onDeferredEndDrag(DragView dragView) {
+ dragView.remove();
+
+ if (mDragObject.deferDragViewCleanupPostAnimation) {
+ // If we skipped calling onDragEnd() before, do it now
+ for (DragListener listener : mListeners) {
+ listener.onDragEnd();
+ }
+ }
+ }
+
+ void onDeferredEndFling(DropTarget.DragObject d) {
+ d.dragSource.onFlingToDeleteCompleted();
+ }
+
+ /**
+ * Clamps the position to the drag layer bounds.
+ */
+ private int[] getClampedDragLayerPos(float x, float y) {
+ mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
+ mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
+ mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
+ return mTmpPoint;
+ }
+
+ long getLastGestureUpTime() {
+ if (mDragging) {
+ return System.currentTimeMillis();
+ } else {
+ return mLastTouchUpTime;
+ }
+ }
+
+ void resetLastGestureUpTime() {
+ mLastTouchUpTime = -1;
+ }
+
+ /**
+ * Call this from a drag source view.
+ */
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ @SuppressWarnings("all") // suppress dead code warning
+ final boolean debug = false;
+ if (debug) {
+ Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
+ + mDragging);
+ }
+
+ // Update the velocity tracker
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ final int action = ev.getAction();
+ final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+ final int dragLayerX = dragLayerPos[0];
+ final int dragLayerY = dragLayerPos[1];
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ break;
+ case MotionEvent.ACTION_DOWN:
+ // Remember location of down touch
+ mMotionDownX = dragLayerX;
+ mMotionDownY = dragLayerY;
+ mLastDropTarget = null;
+ break;
+ case MotionEvent.ACTION_UP:
+ mLastTouchUpTime = System.currentTimeMillis();
+ if (mDragging) {
+ PointF vec = isFlingingToDelete(mDragObject.dragSource);
+ if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
+ vec = null;
+ }
+ if (vec != null) {
+ dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+ } else {
+ drop(dragLayerX, dragLayerY);
+ }
+ }
+ endDrag();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ cancelDrag();
+ break;
+ }
+
+ return mDragging;
+ }
+
+ /**
+ * Sets the view that should handle move events.
+ */
+ void setMoveTarget(View view) {
+ mMoveTarget = view;
+ }
+
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ return mMoveTarget != null && mMoveTarget.dispatchUnhandledMove(focused, direction);
+ }
+
+ private void clearScrollRunnable() {
+ mHandler.removeCallbacks(mScrollRunnable);
+ if (mScrollState == SCROLL_WAITING_IN_ZONE) {
+ mScrollState = SCROLL_OUTSIDE_ZONE;
+ mScrollRunnable.setDirection(SCROLL_RIGHT);
+ mDragScroller.onExitScrollArea();
+ mLauncher.getDragLayer().onExitScrollArea();
+ }
+ }
+
+ private void handleMoveEvent(int x, int y) {
+ mDragObject.dragView.move(x, y);
+
+ // Drop on someone?
+ final int[] coordinates = mCoordinatesTemp;
+ DropTarget dropTarget = findDropTarget(x, y, coordinates);
+ mDragObject.x = coordinates[0];
+ mDragObject.y = coordinates[1];
+ checkTouchMove(dropTarget);
+
+ // Check if we are hovering over the scroll areas
+ mDistanceSinceScroll +=
+ Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
+ mLastTouch[0] = x;
+ mLastTouch[1] = y;
+ checkScrollState(x, y);
+ }
+
+ public void forceTouchMove() {
+ int[] dummyCoordinates = mCoordinatesTemp;
+ DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
+ mDragObject.x = dummyCoordinates[0];
+ mDragObject.y = dummyCoordinates[1];
+ checkTouchMove(dropTarget);
+ }
+
+ private void checkTouchMove(DropTarget dropTarget) {
+ if (dropTarget != null) {
+ if (mLastDropTarget != dropTarget) {
+ if (mLastDropTarget != null) {
+ mLastDropTarget.onDragExit(mDragObject);
+ }
+ dropTarget.onDragEnter(mDragObject);
+ }
+ dropTarget.onDragOver(mDragObject);
+ } else {
+ if (mLastDropTarget != null) {
+ mLastDropTarget.onDragExit(mDragObject);
+ }
+ }
+ mLastDropTarget = dropTarget;
+ }
+
+ private void checkScrollState(int x, int y) {
+ final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
+ final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
+ final DragLayer dragLayer = mLauncher.getDragLayer();
+ final boolean isRtl = (dragLayer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ final int forwardDirection = isRtl ? SCROLL_RIGHT : SCROLL_LEFT;
+ final int backwardsDirection = isRtl ? SCROLL_LEFT : SCROLL_RIGHT;
+
+ if (x < mScrollZone) {
+ if (mScrollState == SCROLL_OUTSIDE_ZONE) {
+ mScrollState = SCROLL_WAITING_IN_ZONE;
+ if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
+ dragLayer.onEnterScrollArea(forwardDirection);
+ mScrollRunnable.setDirection(forwardDirection);
+ mHandler.postDelayed(mScrollRunnable, delay);
+ }
+ }
+ } else if (x > mScrollView.getWidth() - mScrollZone) {
+ if (mScrollState == SCROLL_OUTSIDE_ZONE) {
+ mScrollState = SCROLL_WAITING_IN_ZONE;
+ if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
+ dragLayer.onEnterScrollArea(backwardsDirection);
+ mScrollRunnable.setDirection(backwardsDirection);
+ mHandler.postDelayed(mScrollRunnable, delay);
+ }
+ }
+ } else {
+ clearScrollRunnable();
+ }
+ }
+
+ /**
+ * Call this from a drag source view.
+ */
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mDragging) {
+ return false;
+ }
+
+ // Update the velocity tracker
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ final int action = ev.getAction();
+ final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+ final int dragLayerX = dragLayerPos[0];
+ final int dragLayerY = dragLayerPos[1];
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ // Remember where the motion event started
+ mMotionDownX = dragLayerX;
+ mMotionDownY = dragLayerY;
+
+ if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
+ mScrollState = SCROLL_WAITING_IN_ZONE;
+ mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+ } else {
+ mScrollState = SCROLL_OUTSIDE_ZONE;
+ }
+ handleMoveEvent(dragLayerX, dragLayerY);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ handleMoveEvent(dragLayerX, dragLayerY);
+ break;
+ case MotionEvent.ACTION_UP:
+ // Ensure that we've processed a move event at the current pointer location.
+ handleMoveEvent(dragLayerX, dragLayerY);
+ mHandler.removeCallbacks(mScrollRunnable);
+
+ if (mDragging) {
+ PointF vec = isFlingingToDelete(mDragObject.dragSource);
+ if (!DeleteDropTarget.willAcceptDrop(mDragObject.dragInfo)) {
+ vec = null;
+ }
+ if (vec != null) {
+ dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+ } else {
+ drop(dragLayerX, dragLayerY);
+ }
+ }
+ endDrag();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ mHandler.removeCallbacks(mScrollRunnable);
+ cancelDrag();
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines whether the user flung the current item to delete it.
+ *
+ * @return the vector at which the item was flung, or null if no fling was detected.
+ */
+ private PointF isFlingingToDelete(DragSource source) {
+ if (mFlingToDeleteDropTarget == null) return null;
+ if (!source.supportsFlingToDelete()) return null;
+
+ ViewConfiguration config = ViewConfiguration.get(mLauncher);
+ mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+
+ if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+ // Do a quick dot product test to ensure that we are flinging upwards
+ PointF vel = new PointF(mVelocityTracker.getXVelocity(),
+ mVelocityTracker.getYVelocity());
+ PointF upVec = new PointF(0f, -1f);
+ float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
+ (vel.length() * upVec.length()));
+ if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+ return vel;
+ }
+ }
+ return null;
+ }
+
+ private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
+ final int[] coordinates = mCoordinatesTemp;
+
+ mDragObject.x = coordinates[0];
+ mDragObject.y = coordinates[1];
+
+ // Clean up dragging on the target if it's not the current fling delete target otherwise,
+ // start dragging to it.
+ if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
+ mLastDropTarget.onDragExit(mDragObject);
+ }
+
+ // Drop onto the fling-to-delete target
+ boolean accepted = false;
+ mFlingToDeleteDropTarget.onDragEnter(mDragObject);
+ // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
+ // "drop"
+ mDragObject.dragComplete = true;
+ mFlingToDeleteDropTarget.onDragExit(mDragObject);
+ if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
+ mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y,
+ vel);
+ accepted = true;
+ }
+ mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
+ accepted);
+ }
+
+ private void drop(float x, float y) {
+ final int[] coordinates = mCoordinatesTemp;
+ final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
+
+ mDragObject.x = coordinates[0];
+ mDragObject.y = coordinates[1];
+ boolean accepted = false;
+ if (dropTarget != null) {
+ mDragObject.dragComplete = true;
+ dropTarget.onDragExit(mDragObject);
+ if (dropTarget.acceptDrop(mDragObject)) {
+ dropTarget.onDrop(mDragObject);
+ accepted = true;
+ }
+ }
+ mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
+ }
+
+ private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
+ final Rect r = mRectTemp;
+
+ final ArrayList<DropTarget> dropTargets = mDropTargets;
+ final int count = dropTargets.size();
+ for (int i=count-1; i>=0; i--) {
+ DropTarget target = dropTargets.get(i);
+ if (!target.isDropEnabled())
+ continue;
+
+ target.getHitRectRelativeToDragLayer(r);
+
+ mDragObject.x = x;
+ mDragObject.y = y;
+ if (r.contains(x, y)) {
+
+ dropCoordinates[0] = x;
+ dropCoordinates[1] = y;
+ mLauncher.getDragLayer().mapCoordInSelfToDescendent((View) target, dropCoordinates);
+
+ return target;
+ }
+ }
+ return null;
+ }
+
+ public void setDragScoller(DragScroller scroller) {
+ mDragScroller = scroller;
+ }
+
+ public void setWindowToken(IBinder token) {
+ mWindowToken = token;
+ }
+
+ /**
+ * Sets the drag listner which will be notified when a drag starts or ends.
+ */
+ public void addDragListener(DragListener l) {
+ mListeners.add(l);
+ }
+
+ /**
+ * Remove a previously installed drag listener.
+ */
+ public void removeDragListener(DragListener l) {
+ mListeners.remove(l);
+ }
+
+ /**
+ * Add a DropTarget to the list of potential places to receive drop events.
+ */
+ public void addDropTarget(DropTarget target) {
+ mDropTargets.add(target);
+ }
+
+ /**
+ * Don't send drop events to <em>target</em> any more.
+ */
+ public void removeDropTarget(DropTarget target) {
+ mDropTargets.remove(target);
+ }
+
+ /**
+ * Sets the current fling-to-delete drop target.
+ */
+ public void setFlingToDeleteDropTarget(DropTarget target) {
+ mFlingToDeleteDropTarget = target;
+ }
+
+ private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * Set which view scrolls for touch events near the edge of the screen.
+ */
+ public void setScrollView(View v) {
+ mScrollView = v;
+ }
+
+ DragView getDragView() {
+ return mDragObject.dragView;
+ }
+
+ private class ScrollRunnable implements Runnable {
+ private int mDirection;
+
+ ScrollRunnable() {
+ }
+
+ public void run() {
+ if (mDragScroller != null) {
+ if (mDirection == SCROLL_LEFT) {
+ mDragScroller.scrollLeft();
+ } else {
+ mDragScroller.scrollRight();
+ }
+ mScrollState = SCROLL_OUTSIDE_ZONE;
+ mDistanceSinceScroll = 0;
+ mDragScroller.onExitScrollArea();
+ mLauncher.getDragLayer().onExitScrollArea();
+
+ if (isDragging()) {
+ // Check the scroll again so that we can requeue the scroller if necessary
+ checkScrollState(mLastTouch[0], mLastTouch[1]);
+ }
+ }
+ }
+
+ void setDirection(int direction) {
+ mDirection = direction;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
new file mode 100644
index 0000000..89f8275
--- /dev/null
+++ b/src/com/android/launcher3/DragLayer.java
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.*;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * A ViewGroup that coordinates dragging across its descendants
+ */
+public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChangeListener {
+ private DragController mDragController;
+ private int[] mTmpXY = new int[2];
+
+ private int mXDown, mYDown;
+ private Launcher mLauncher;
+
+ // Variables relating to resizing widgets
+ private final ArrayList<AppWidgetResizeFrame> mResizeFrames =
+ new ArrayList<AppWidgetResizeFrame>();
+ private AppWidgetResizeFrame mCurrentResizeFrame;
+
+ // Variables relating to animation of views after drop
+ private ValueAnimator mDropAnim = null;
+ private ValueAnimator mFadeOutAnim = null;
+ private TimeInterpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+ private DragView mDropView = null;
+ private int mAnchorViewInitialScrollX = 0;
+ private View mAnchorView = null;
+
+ private boolean mHoverPointClosesFolder = false;
+ private Rect mHitRect = new Rect();
+ private int mWorkspaceIndex = -1;
+ private int mQsbIndex = -1;
+ public static final int ANIMATION_END_DISAPPEAR = 0;
+ public static final int ANIMATION_END_FADE_OUT = 1;
+ public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
+
+ private TouchCompleteListener mTouchCompleteListener;
+
+ private final Rect mInsets = new Rect();
+
+ /**
+ * Used to create a new DragLayer from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ */
+ public DragLayer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // Disable multitouch across the workspace/all apps/customize tray
+ setMotionEventSplittingEnabled(false);
+ setChildrenDrawingOrderEnabled(true);
+ setOnHierarchyChangeListener(this);
+
+ mLeftHoverDrawable = getResources().getDrawable(R.drawable.page_hover_left_holo);
+ mRightHoverDrawable = getResources().getDrawable(R.drawable.page_hover_right_holo);
+ }
+
+ public void setup(Launcher launcher, DragController controller) {
+ mLauncher = launcher;
+ mDragController = controller;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mDragController.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ final int n = getChildCount();
+ for (int i = 0; i < n; i++) {
+ final View child = getChildAt(i);
+ final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
+ if (child instanceof Insettable) {
+ ((Insettable)child).setInsets(insets);
+ } else {
+ flp.topMargin += (insets.top - mInsets.top);
+ flp.leftMargin += (insets.left - mInsets.left);
+ flp.rightMargin += (insets.right - mInsets.right);
+ flp.bottomMargin += (insets.bottom - mInsets.bottom);
+ }
+ child.setLayoutParams(flp);
+ }
+ mInsets.set(insets);
+ return true; // I'll take it from here
+ }
+
+ private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) {
+ getDescendantRectRelativeToSelf(folder.getEditTextRegion(), mHitRect);
+ if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isEventOverFolder(Folder folder, MotionEvent ev) {
+ getDescendantRectRelativeToSelf(folder, mHitRect);
+ if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
+ Rect hitRect = new Rect();
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+
+ for (AppWidgetResizeFrame child: mResizeFrames) {
+ child.getHitRect(hitRect);
+ if (hitRect.contains(x, y)) {
+ if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
+ mCurrentResizeFrame = child;
+ mXDown = x;
+ mYDown = y;
+ requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+ }
+ }
+
+ Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+ if (currentFolder != null && !mLauncher.isFolderClingVisible() && intercept) {
+ if (currentFolder.isEditingName()) {
+ if (!isEventOverFolderTextRegion(currentFolder, ev)) {
+ currentFolder.dismissEditingName();
+ return true;
+ }
+ }
+
+ getDescendantRectRelativeToSelf(currentFolder, hitRect);
+ if (!isEventOverFolder(currentFolder, ev)) {
+ mLauncher.closeFolder();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ int action = ev.getAction();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ if (handleTouchDown(ev, true)) {
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ if (mTouchCompleteListener != null) {
+ mTouchCompleteListener.onTouchComplete();
+ }
+ mTouchCompleteListener = null;
+ }
+ clearAllResizeFrames();
+ return mDragController.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onInterceptHoverEvent(MotionEvent ev) {
+ if (mLauncher == null || mLauncher.getWorkspace() == null) {
+ return false;
+ }
+ Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+ if (currentFolder == null) {
+ return false;
+ } else {
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (accessibilityManager.isTouchExplorationEnabled()) {
+ final int action = ev.getAction();
+ boolean isOverFolder;
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ isOverFolder = isEventOverFolder(currentFolder, ev);
+ if (!isOverFolder) {
+ sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
+ mHoverPointClosesFolder = true;
+ return true;
+ } else if (isOverFolder) {
+ mHoverPointClosesFolder = false;
+ } else {
+ return true;
+ }
+ case MotionEvent.ACTION_HOVER_MOVE:
+ isOverFolder = isEventOverFolder(currentFolder, ev);
+ if (!isOverFolder && !mHoverPointClosesFolder) {
+ sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
+ mHoverPointClosesFolder = true;
+ return true;
+ } else if (isOverFolder) {
+ mHoverPointClosesFolder = false;
+ } else {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void sendTapOutsideFolderAccessibilityEvent(boolean isEditingName) {
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (accessibilityManager.isEnabled()) {
+ int stringId = isEditingName ? R.string.folder_tap_to_rename : R.string.folder_tap_to_close;
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ onInitializeAccessibilityEvent(event);
+ event.getText().add(getContext().getString(stringId));
+ accessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+
+ @Override
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+ if (currentFolder != null) {
+ if (child == currentFolder) {
+ return super.onRequestSendAccessibilityEvent(child, event);
+ }
+ // Skip propagating onRequestSendAccessibilityEvent all for other children
+ // when a folder is open
+ return false;
+ }
+ return super.onRequestSendAccessibilityEvent(child, event);
+ }
+
+ @Override
+ public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
+ Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+ if (currentFolder != null) {
+ // Only add the folder as a child for accessibility when it is open
+ childrenForAccessibility.add(currentFolder);
+ } else {
+ super.addChildrenForAccessibility(childrenForAccessibility);
+ }
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent ev) {
+ // If we've received this, we've already done the necessary handling
+ // in onInterceptHoverEvent. Return true to consume the event.
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean handled = false;
+ int action = ev.getAction();
+
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ if (handleTouchDown(ev, false)) {
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ if (mTouchCompleteListener != null) {
+ mTouchCompleteListener.onTouchComplete();
+ }
+ mTouchCompleteListener = null;
+ }
+
+ if (mCurrentResizeFrame != null) {
+ handled = true;
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
+ mCurrentResizeFrame.onTouchUp();
+ mCurrentResizeFrame = null;
+ }
+ }
+ if (handled) return true;
+ return mDragController.onTouchEvent(ev);
+ }
+
+ /**
+ * Determine the rect of the descendant in this DragLayer's coordinates
+ *
+ * @param descendant The descendant whose coordinates we want to find.
+ * @param r The rect into which to place the results.
+ * @return The factor by which this descendant is scaled relative to this DragLayer.
+ */
+ public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
+ mTmpXY[0] = 0;
+ mTmpXY[1] = 0;
+ float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
+
+ r.set(mTmpXY[0], mTmpXY[1],
+ (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
+ (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
+ return scale;
+ }
+
+ public float getLocationInDragLayer(View child, int[] loc) {
+ loc[0] = 0;
+ loc[1] = 0;
+ return getDescendantCoordRelativeToSelf(child, loc);
+ }
+
+ public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
+ return getDescendantCoordRelativeToSelf(descendant, coord, false);
+ }
+
+ /**
+ * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's
+ * coordinates.
+ *
+ * @param descendant The descendant to which the passed coordinate is relative.
+ * @param coord The coordinate that we want mapped.
+ * @param includeRootScroll Whether or not to account for the scroll of the root descendant:
+ * sometimes this is relevant as in a child's coordinates within the root descendant.
+ * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+ * this scale factor is assumed to be equal in X and Y, and so if at any point this
+ * assumption fails, we will need to return a pair of scale factors.
+ */
+ public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
+ boolean includeRootScroll) {
+ return Utilities.getDescendantCoordRelativeToParent(descendant, this,
+ coord, includeRootScroll);
+ }
+
+ /**
+ * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
+ */
+ public float mapCoordInSelfToDescendent(View descendant, int[] coord) {
+ return Utilities.mapCoordInSelfToDescendent(descendant, this, coord);
+ }
+
+ public void getViewRectRelativeToSelf(View v, Rect r) {
+ int[] loc = new int[2];
+ getLocationInWindow(loc);
+ int x = loc[0];
+ int y = loc[1];
+
+ v.getLocationInWindow(loc);
+ int vX = loc[0];
+ int vY = loc[1];
+
+ int left = vX - x;
+ int top = vY - y;
+ r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ return mDragController.dispatchUnhandledMove(focused, direction);
+ }
+
+ public static class LayoutParams extends FrameLayout.LayoutParams {
+ public int x, y;
+ public boolean customPosition = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public int getY() {
+ return y;
+ }
+ }
+
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams();
+ if (flp instanceof LayoutParams) {
+ final LayoutParams lp = (LayoutParams) flp;
+ if (lp.customPosition) {
+ child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height);
+ }
+ }
+ }
+ }
+
+ public void clearAllResizeFrames() {
+ if (mResizeFrames.size() > 0) {
+ for (AppWidgetResizeFrame frame: mResizeFrames) {
+ frame.commitResize();
+ removeView(frame);
+ }
+ mResizeFrames.clear();
+ }
+ }
+
+ public boolean hasResizeFrames() {
+ return mResizeFrames.size() > 0;
+ }
+
+ public boolean isWidgetBeingResized() {
+ return mCurrentResizeFrame != null;
+ }
+
+ public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
+ CellLayout cellLayout) {
+ AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
+ widget, cellLayout, this);
+
+ LayoutParams lp = new LayoutParams(-1, -1);
+ lp.customPosition = true;
+
+ addView(resizeFrame, lp);
+ mResizeFrames.add(resizeFrame);
+
+ resizeFrame.snapToWidget(false);
+ }
+
+ public void animateViewIntoPosition(DragView dragView, final View child) {
+ animateViewIntoPosition(dragView, child, null);
+ }
+
+ public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
+ float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
+ int duration) {
+ Rect r = new Rect();
+ getViewRectRelativeToSelf(dragView, r);
+ final int fromX = r.left;
+ final int fromY = r.top;
+
+ animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
+ onFinishRunnable, animationEndStyle, duration, null);
+ }
+
+ public void animateViewIntoPosition(DragView dragView, final View child,
+ final Runnable onFinishAnimationRunnable) {
+ animateViewIntoPosition(dragView, child, -1, onFinishAnimationRunnable, null);
+ }
+
+ public void animateViewIntoPosition(DragView dragView, final View child, int duration,
+ final Runnable onFinishAnimationRunnable, View anchorView) {
+ ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ parentChildren.measureChild(child);
+
+ Rect r = new Rect();
+ getViewRectRelativeToSelf(dragView, r);
+
+ int coord[] = new int[2];
+ float childScale = child.getScaleX();
+ coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
+ coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
+
+ // Since the child hasn't necessarily been laid out, we force the lp to be updated with
+ // the correct coordinates (above) and use these to determine the final location
+ float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
+ // We need to account for the scale of the child itself, as the above only accounts for
+ // for the scale in parents.
+ scale *= childScale;
+ int toX = coord[0];
+ int toY = coord[1];
+ if (child instanceof TextView) {
+ TextView tv = (TextView) child;
+
+ // The child may be scaled (always about the center of the view) so to account for it,
+ // we have to offset the position by the scaled size. Once we do that, we can center
+ // the drag view about the scaled child view.
+ toY += Math.round(scale * tv.getPaddingTop());
+ toY -= dragView.getMeasuredHeight() * (1 - scale) / 2;
+ toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
+ } else if (child instanceof FolderIcon) {
+ // Account for holographic blur padding on the drag view
+ toY += Math.round(scale * (child.getPaddingTop() - dragView.getDragRegionTop()));
+ toY -= scale * Workspace.DRAG_BITMAP_PADDING / 2;
+ toY -= (1 - scale) * dragView.getMeasuredHeight() / 2;
+ // Center in the x coordinate about the target's drawable
+ toX -= (dragView.getMeasuredWidth() - Math.round(scale * child.getMeasuredWidth())) / 2;
+ } else {
+ toY -= (Math.round(scale * (dragView.getHeight() - child.getMeasuredHeight()))) / 2;
+ toX -= (Math.round(scale * (dragView.getMeasuredWidth()
+ - child.getMeasuredWidth()))) / 2;
+ }
+
+ final int fromX = r.left;
+ final int fromY = r.top;
+ child.setVisibility(INVISIBLE);
+ Runnable onCompleteRunnable = new Runnable() {
+ public void run() {
+ child.setVisibility(VISIBLE);
+ if (onFinishAnimationRunnable != null) {
+ onFinishAnimationRunnable.run();
+ }
+ }
+ };
+ animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, scale, scale,
+ onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
+ }
+
+ public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
+ final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
+ float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
+ int animationEndStyle, int duration, View anchorView) {
+ Rect from = new Rect(fromX, fromY, fromX +
+ view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
+ Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
+ animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
+ null, null, onCompleteRunnable, animationEndStyle, anchorView);
+ }
+
+ /**
+ * This method animates a view at the end of a drag and drop animation.
+ *
+ * @param view The view to be animated. This view is drawn directly into DragLayer, and so
+ * doesn't need to be a child of DragLayer.
+ * @param from The initial location of the view. Only the left and top parameters are used.
+ * @param to The final location of the view. Only the left and top parameters are used. This
+ * location doesn't account for scaling, and so should be centered about the desired
+ * final location (including scaling).
+ * @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
+ * @param finalScale The final scale of the view. The view is scaled about its center.
+ * @param duration The duration of the animation.
+ * @param motionInterpolator The interpolator to use for the location of the view.
+ * @param alphaInterpolator The interpolator to use for the alpha of the view.
+ * @param onCompleteRunnable Optional runnable to run on animation completion.
+ * @param fadeOut Whether or not to fade out the view once the animation completes. If true,
+ * the runnable will execute after the view is faded out.
+ * @param anchorView If not null, this represents the view which the animated view stays
+ * anchored to in case scrolling is currently taking place. Note: currently this is
+ * only used for the X dimension for the case of the workspace.
+ */
+ public void animateView(final DragView view, final Rect from, final Rect to,
+ final float finalAlpha, final float initScaleX, final float initScaleY,
+ final float finalScaleX, final float finalScaleY, int duration,
+ final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
+ final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
+
+ // Calculate the duration of the animation based on the object's distance
+ final float dist = (float) Math.sqrt(Math.pow(to.left - from.left, 2) +
+ Math.pow(to.top - from.top, 2));
+ final Resources res = getResources();
+ final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
+
+ // If duration < 0, this is a cue to compute the duration based on the distance
+ if (duration < 0) {
+ duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
+ if (dist < maxDist) {
+ duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
+ }
+ duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
+ }
+
+ // Fall back to cubic ease out interpolator for the animation if none is specified
+ TimeInterpolator interpolator = null;
+ if (alphaInterpolator == null || motionInterpolator == null) {
+ interpolator = mCubicEaseOutInterpolator;
+ }
+
+ // Animate the view
+ final float initAlpha = view.getAlpha();
+ final float dropViewScale = view.getScaleX();
+ AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float percent = (Float) animation.getAnimatedValue();
+ final int width = view.getMeasuredWidth();
+ final int height = view.getMeasuredHeight();
+
+ float alphaPercent = alphaInterpolator == null ? percent :
+ alphaInterpolator.getInterpolation(percent);
+ float motionPercent = motionInterpolator == null ? percent :
+ motionInterpolator.getInterpolation(percent);
+
+ float initialScaleX = initScaleX * dropViewScale;
+ float initialScaleY = initScaleY * dropViewScale;
+ float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
+ float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
+ float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
+
+ float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
+ float fromTop = from.top + (initialScaleY - 1f) * height / 2;
+
+ int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
+ int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
+
+ int xPos = x - mDropView.getScrollX() + (mAnchorView != null
+ ? (mAnchorViewInitialScrollX - mAnchorView.getScrollX()) : 0);
+ int yPos = y - mDropView.getScrollY();
+
+ mDropView.setTranslationX(xPos);
+ mDropView.setTranslationY(yPos);
+ mDropView.setScaleX(scaleX);
+ mDropView.setScaleY(scaleY);
+ mDropView.setAlpha(alpha);
+ }
+ };
+ animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
+ anchorView);
+ }
+
+ public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
+ TimeInterpolator interpolator, final Runnable onCompleteRunnable,
+ final int animationEndStyle, View anchorView) {
+ // Clean up the previous animations
+ if (mDropAnim != null) mDropAnim.cancel();
+ if (mFadeOutAnim != null) mFadeOutAnim.cancel();
+
+ // Show the drop view if it was previously hidden
+ mDropView = view;
+ mDropView.cancelAnimation();
+ mDropView.resetLayoutParams();
+
+ // Set the anchor view if the page is scrolling
+ if (anchorView != null) {
+ mAnchorViewInitialScrollX = anchorView.getScrollX();
+ }
+ mAnchorView = anchorView;
+
+ // Create and start the animation
+ mDropAnim = new ValueAnimator();
+ mDropAnim.setInterpolator(interpolator);
+ mDropAnim.setDuration(duration);
+ mDropAnim.setFloatValues(0f, 1f);
+ mDropAnim.addUpdateListener(updateCb);
+ mDropAnim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ switch (animationEndStyle) {
+ case ANIMATION_END_DISAPPEAR:
+ clearAnimatedView();
+ break;
+ case ANIMATION_END_FADE_OUT:
+ fadeOutDragView();
+ break;
+ case ANIMATION_END_REMAIN_VISIBLE:
+ break;
+ }
+ }
+ });
+ mDropAnim.start();
+ }
+
+ public void clearAnimatedView() {
+ if (mDropAnim != null) {
+ mDropAnim.cancel();
+ }
+ if (mDropView != null) {
+ mDragController.onDeferredEndDrag(mDropView);
+ }
+ mDropView = null;
+ invalidate();
+ }
+
+ public View getAnimatedView() {
+ return mDropView;
+ }
+
+ private void fadeOutDragView() {
+ mFadeOutAnim = new ValueAnimator();
+ mFadeOutAnim.setDuration(150);
+ mFadeOutAnim.setFloatValues(0f, 1f);
+ mFadeOutAnim.removeAllUpdateListeners();
+ mFadeOutAnim.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float percent = (Float) animation.getAnimatedValue();
+
+ float alpha = 1 - percent;
+ mDropView.setAlpha(alpha);
+ }
+ });
+ mFadeOutAnim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ if (mDropView != null) {
+ mDragController.onDeferredEndDrag(mDropView);
+ }
+ mDropView = null;
+ invalidate();
+ }
+ });
+ mFadeOutAnim.start();
+ }
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ updateChildIndices();
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ updateChildIndices();
+ }
+
+ private void updateChildIndices() {
+ if (mLauncher != null) {
+ mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace());
+ mQsbIndex = indexOfChild(mLauncher.getSearchBar());
+ }
+ }
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int i) {
+ // TODO: We have turned off this custom drawing order because it now effects touch
+ // dispatch order. We need to sort that issue out and then decide how to go about this.
+ if (true || LauncherAppState.isScreenLandscape(getContext()) ||
+ mWorkspaceIndex == -1 || mQsbIndex == -1 ||
+ mLauncher.getWorkspace().isDrawingBackgroundGradient()) {
+ return i;
+ }
+
+ // This ensures that the workspace is drawn above the hotseat and qsb,
+ // except when the workspace is drawing a background gradient, in which
+ // case we want the workspace to stay behind these elements.
+ if (i == mQsbIndex) {
+ return mWorkspaceIndex;
+ } else if (i == mWorkspaceIndex) {
+ return mQsbIndex;
+ } else {
+ return i;
+ }
+ }
+
+ private boolean mInScrollArea;
+ private Drawable mLeftHoverDrawable;
+ private Drawable mRightHoverDrawable;
+
+ void onEnterScrollArea(int direction) {
+ mInScrollArea = true;
+ invalidate();
+ }
+
+ void onExitScrollArea() {
+ mInScrollArea = false;
+ invalidate();
+ }
+
+ /**
+ * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
+ */
+ private boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (mInScrollArea && !LauncherAppState.getInstance().isScreenLarge()) {
+ Workspace workspace = mLauncher.getWorkspace();
+ int width = getMeasuredWidth();
+ Rect childRect = new Rect();
+ getDescendantRectRelativeToSelf(workspace.getChildAt(0), childRect);
+
+ int page = workspace.getNextPage();
+ final boolean isRtl = isLayoutRtl();
+ CellLayout leftPage = (CellLayout) workspace.getChildAt(isRtl ? page + 1 : page - 1);
+ CellLayout rightPage = (CellLayout) workspace.getChildAt(isRtl ? page - 1 : page + 1);
+
+ if (leftPage != null && leftPage.getIsDragOverlapping()) {
+ mLeftHoverDrawable.setBounds(0, childRect.top,
+ mLeftHoverDrawable.getIntrinsicWidth(), childRect.bottom);
+ mLeftHoverDrawable.draw(canvas);
+ } else if (rightPage != null && rightPage.getIsDragOverlapping()) {
+ mRightHoverDrawable.setBounds(width - mRightHoverDrawable.getIntrinsicWidth(),
+ childRect.top, width, childRect.bottom);
+ mRightHoverDrawable.draw(canvas);
+ }
+ }
+ }
+
+ public void setTouchCompleteListener(TouchCompleteListener listener) {
+ mTouchCompleteListener = listener;
+ }
+
+ public interface TouchCompleteListener {
+ public void onTouchComplete();
+ }
+}
diff --git a/src/com/android/launcher3/DragScroller.java b/src/com/android/launcher3/DragScroller.java
new file mode 100644
index 0000000..e261f15
--- /dev/null
+++ b/src/com/android/launcher3/DragScroller.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+/**
+ * Handles scrolling while dragging
+ *
+ */
+public interface DragScroller {
+ void scrollLeft();
+ void scrollRight();
+
+ /**
+ * The touch point has entered the scroll area; a scroll is imminent.
+ * This event will only occur while a drag is active.
+ *
+ * @param direction The scroll direction
+ */
+ boolean onEnterScrollArea(int x, int y, int direction);
+
+ /**
+ * The touch point has left the scroll area.
+ * NOTE: This may not be called, if a drop occurs inside the scroll area.
+ */
+ boolean onExitScrollArea();
+}
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
new file mode 100644
index 0000000..2ef99ae
--- /dev/null
+++ b/src/com/android/launcher3/DragSource.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.view.View;
+
+import com.android.launcher3.DropTarget.DragObject;
+
+/**
+ * Interface defining an object that can originate a drag.
+ *
+ */
+public interface DragSource {
+ /**
+ * @return whether items dragged from this source supports
+ */
+ boolean supportsFlingToDelete();
+
+ /**
+ * A callback specifically made back to the source after an item from this source has been flung
+ * to be deleted on a DropTarget. In such a situation, this method will be called after
+ * onDropCompleted, and more importantly, after the fling animation has completed.
+ */
+ void onFlingToDeleteCompleted();
+
+ /**
+ * A callback made back to the source after an item from this source has been dropped on a
+ * DropTarget.
+ */
+ void onDropCompleted(View target, DragObject d, boolean isFlingToDelete, boolean success);
+}
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
new file mode 100644
index 0000000..686cf62
--- /dev/null
+++ b/src/com/android/launcher3/DragView.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.launcher3;
+
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.R;
+
+public class DragView extends View {
+ private static float sDragAlpha = 1f;
+
+ private Bitmap mBitmap;
+ private Bitmap mCrossFadeBitmap;
+ private Paint mPaint;
+ private int mRegistrationX;
+ private int mRegistrationY;
+
+ private Point mDragVisualizeOffset = null;
+ private Rect mDragRegion = null;
+ private DragLayer mDragLayer = null;
+ private boolean mHasDrawn = false;
+ private float mCrossFadeProgress = 0f;
+
+ ValueAnimator mAnim;
+ private float mOffsetX = 0.0f;
+ private float mOffsetY = 0.0f;
+ private float mInitialScale = 1f;
+
+ /**
+ * Construct the drag view.
+ * <p>
+ * The registration point is the point inside our view that the touch events should
+ * be centered upon.
+ *
+ * @param launcher The Launcher instance
+ * @param bitmap The view that we're dragging around. We scale it up when we draw it.
+ * @param registrationX The x coordinate of the registration point.
+ * @param registrationY The y coordinate of the registration point.
+ */
+ public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
+ int left, int top, int width, int height, final float initialScale) {
+ super(launcher);
+ mDragLayer = launcher.getDragLayer();
+ mInitialScale = initialScale;
+
+ final Resources res = getResources();
+ final float offsetX = res.getDimensionPixelSize(R.dimen.dragViewOffsetX);
+ final float offsetY = res.getDimensionPixelSize(R.dimen.dragViewOffsetY);
+ final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale);
+ final float scale = (width + scaleDps) / width;
+
+ // Set the initial scale to avoid any jumps
+ setScaleX(initialScale);
+ setScaleY(initialScale);
+
+ // Animate the view into the correct position
+ mAnim = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+ mAnim.setDuration(150);
+ mAnim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float value = (Float) animation.getAnimatedValue();
+
+ final int deltaX = (int) ((value * offsetX) - mOffsetX);
+ final int deltaY = (int) ((value * offsetY) - mOffsetY);
+
+ mOffsetX += deltaX;
+ mOffsetY += deltaY;
+ setScaleX(initialScale + (value * (scale - initialScale)));
+ setScaleY(initialScale + (value * (scale - initialScale)));
+ if (sDragAlpha != 1f) {
+ setAlpha(sDragAlpha * value + (1f - value));
+ }
+
+ if (getParent() == null) {
+ animation.cancel();
+ } else {
+ setTranslationX(getTranslationX() + deltaX);
+ setTranslationY(getTranslationY() + deltaY);
+ }
+ }
+ });
+
+ mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height);
+ setDragRegion(new Rect(0, 0, width, height));
+
+ // The point in our scaled bitmap that the touch events are located
+ mRegistrationX = registrationX;
+ mRegistrationY = registrationY;
+
+ // Force a measure, because Workspace uses getMeasuredHeight() before the layout pass
+ int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ measure(ms, ms);
+ mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ }
+
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+
+ public int getDragRegionLeft() {
+ return mDragRegion.left;
+ }
+
+ public int getDragRegionTop() {
+ return mDragRegion.top;
+ }
+
+ public int getDragRegionWidth() {
+ return mDragRegion.width();
+ }
+
+ public int getDragRegionHeight() {
+ return mDragRegion.height();
+ }
+
+ public void setDragVisualizeOffset(Point p) {
+ mDragVisualizeOffset = p;
+ }
+
+ public Point getDragVisualizeOffset() {
+ return mDragVisualizeOffset;
+ }
+
+ public void setDragRegion(Rect r) {
+ mDragRegion = r;
+ }
+
+ public Rect getDragRegion() {
+ return mDragRegion;
+ }
+
+ public float getInitialScale() {
+ return mInitialScale;
+ }
+
+ public void updateInitialScaleToCurrentScale() {
+ mInitialScale = getScaleX();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ @SuppressWarnings("all") // suppress dead code warning
+ final boolean debug = false;
+ if (debug) {
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(0x66ffffff);
+ canvas.drawRect(0, 0, getWidth(), getHeight(), p);
+ }
+
+ mHasDrawn = true;
+ boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+ if (crossFade) {
+ int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
+ mPaint.setAlpha(alpha);
+ }
+ canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+ if (crossFade) {
+ mPaint.setAlpha((int) (255 * mCrossFadeProgress));
+ canvas.save();
+ float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
+ float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+ canvas.scale(sX, sY);
+ canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+ canvas.restore();
+ }
+ }
+
+ public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
+ mCrossFadeBitmap = crossFadeBitmap;
+ }
+
+ public void crossFade(int duration) {
+ ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1f);
+ va.setDuration(duration);
+ va.setInterpolator(new DecelerateInterpolator(1.5f));
+ va.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mCrossFadeProgress = animation.getAnimatedFraction();
+ }
+ });
+ va.start();
+ }
+
+ public void setColor(int color) {
+ if (mPaint == null) {
+ mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ }
+ if (color != 0) {
+ mPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+ } else {
+ mPaint.setColorFilter(null);
+ }
+ invalidate();
+ }
+
+ public boolean hasDrawn() {
+ return mHasDrawn;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ mPaint.setAlpha((int) (255 * alpha));
+ invalidate();
+ }
+
+ /**
+ * Create a window containing this view and show it.
+ *
+ * @param windowToken obtained from v.getWindowToken() from one of your views
+ * @param touchX the x coordinate the user touched in DragLayer coordinates
+ * @param touchY the y coordinate the user touched in DragLayer coordinates
+ */
+ public void show(int touchX, int touchY) {
+ mDragLayer.addView(this);
+
+ // Start the pick-up animation
+ DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
+ lp.width = mBitmap.getWidth();
+ lp.height = mBitmap.getHeight();
+ lp.customPosition = true;
+ setLayoutParams(lp);
+ setTranslationX(touchX - mRegistrationX);
+ setTranslationY(touchY - mRegistrationY);
+ // Post the animation to skip other expensive work happening on the first frame
+ post(new Runnable() {
+ public void run() {
+ mAnim.start();
+ }
+ });
+ }
+
+ public void cancelAnimation() {
+ if (mAnim != null && mAnim.isRunning()) {
+ mAnim.cancel();
+ }
+ }
+
+ public void resetLayoutParams() {
+ mOffsetX = mOffsetY = 0;
+ requestLayout();
+ }
+
+ /**
+ * Move the window containing this view.
+ *
+ * @param touchX the x coordinate the user touched in DragLayer coordinates
+ * @param touchY the y coordinate the user touched in DragLayer coordinates
+ */
+ void move(int touchX, int touchY) {
+ setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
+ setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
+ }
+
+ void remove() {
+ if (getParent() != null) {
+ mDragLayer.removeView(DragView.this);
+ }
+ }
+}
+
diff --git a/src/com/android/launcher3/DrawableStateProxyView.java b/src/com/android/launcher3/DrawableStateProxyView.java
new file mode 100644
index 0000000..0758de1
--- /dev/null
+++ b/src/com/android/launcher3/DrawableStateProxyView.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 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.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.R;
+
+public class DrawableStateProxyView extends LinearLayout {
+
+ private View mView;
+ private int mViewId;
+
+ public DrawableStateProxyView(Context context) {
+ this(context, null);
+ }
+
+ public DrawableStateProxyView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+
+ public DrawableStateProxyView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawableStateProxyView,
+ defStyle, 0);
+ mViewId = a.getResourceId(R.styleable.DrawableStateProxyView_sourceViewId, -1);
+ a.recycle();
+
+ setFocusable(false);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ if (mView == null) {
+ View parent = (View) getParent();
+ mView = parent.findViewById(mViewId);
+ }
+ if (mView != null) {
+ mView.setPressed(isPressed());
+ mView.setHovered(isHovered());
+ }
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ return false;
+ }
+}
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
new file mode 100644
index 0000000..64f0ac8
--- /dev/null
+++ b/src/com/android/launcher3/DropTarget.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.Log;
+
+/**
+ * Interface defining an object that can receive a drag.
+ *
+ */
+public interface DropTarget {
+
+ public static final String TAG = "DropTarget";
+
+ class DragObject {
+ public int x = -1;
+ public int y = -1;
+
+ /** X offset from the upper-left corner of the cell to where we touched. */
+ public int xOffset = -1;
+
+ /** Y offset from the upper-left corner of the cell to where we touched. */
+ public int yOffset = -1;
+
+ /** This indicates whether a drag is in final stages, either drop or cancel. It
+ * differentiates onDragExit, since this is called when the drag is ending, above
+ * the current drag target, or when the drag moves off the current drag object.
+ */
+ public boolean dragComplete = false;
+
+ /** The view that moves around while you drag. */
+ public DragView dragView = null;
+
+ /** The data associated with the object being dragged */
+ public Object dragInfo = null;
+
+ /** Where the drag originated */
+ public DragSource dragSource = null;
+
+ /** Post drag animation runnable */
+ public Runnable postAnimationRunnable = null;
+
+ /** Indicates that the drag operation was cancelled */
+ public boolean cancelled = false;
+
+ /** Defers removing the DragView from the DragLayer until after the drop animation. */
+ public boolean deferDragViewCleanupPostAnimation = true;
+
+ public DragObject() {
+ }
+ }
+
+ public static class DragEnforcer implements DragController.DragListener {
+ int dragParity = 0;
+
+ public DragEnforcer(Context context) {
+ Launcher launcher = (Launcher) context;
+ launcher.getDragController().addDragListener(this);
+ }
+
+ void onDragEnter() {
+ dragParity++;
+ if (dragParity != 1) {
+ Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity);
+ }
+ }
+
+ void onDragExit() {
+ dragParity--;
+ if (dragParity != 0) {
+ Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity);
+ }
+ }
+
+ @Override
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ if (dragParity != 0) {
+ Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity);
+ }
+ }
+
+ @Override
+ public void onDragEnd() {
+ if (dragParity != 0) {
+ Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity);
+ }
+ }
+ }
+
+ /**
+ * Used to temporarily disable certain drop targets
+ *
+ * @return boolean specifying whether this drop target is currently enabled
+ */
+ boolean isDropEnabled();
+
+ /**
+ * Handle an object being dropped on the DropTarget
+ *
+ * @param source DragSource where the drag started
+ * @param x X coordinate of the drop location
+ * @param y Y coordinate of the drop location
+ * @param xOffset Horizontal offset with the object being dragged where the original
+ * touch happened
+ * @param yOffset Vertical offset with the object being dragged where the original
+ * touch happened
+ * @param dragView The DragView that's being dragged around on screen.
+ * @param dragInfo Data associated with the object being dragged
+ *
+ */
+ void onDrop(DragObject dragObject);
+
+ void onDragEnter(DragObject dragObject);
+
+ void onDragOver(DragObject dragObject);
+
+ void onDragExit(DragObject dragObject);
+
+ /**
+ * Handle an object being dropped as a result of flinging to delete and will be called in place
+ * of onDrop(). (This is only called on objects that are set as the DragController's
+ * fling-to-delete target.
+ */
+ void onFlingToDelete(DragObject dragObject, int x, int y, PointF vec);
+
+ /**
+ * Check if a drop action can occur at, or near, the requested location.
+ * This will be called just before onDrop.
+ *
+ * @param source DragSource where the drag started
+ * @param x X coordinate of the drop location
+ * @param y Y coordinate of the drop location
+ * @param xOffset Horizontal offset with the object being dragged where the
+ * original touch happened
+ * @param yOffset Vertical offset with the object being dragged where the
+ * original touch happened
+ * @param dragView The DragView that's being dragged around on screen.
+ * @param dragInfo Data associated with the object being dragged
+ * @return True if the drop will be accepted, false otherwise.
+ */
+ boolean acceptDrop(DragObject dragObject);
+
+ // These methods are implemented in Views
+ void getHitRectRelativeToDragLayer(Rect outRect);
+ void getLocationInDragLayer(int[] loc);
+ int getLeft();
+ int getTop();
+}
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
new file mode 100644
index 0000000..4776c86
--- /dev/null
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+
+class DeviceProfileQuery {
+ float widthDps;
+ float heightDps;
+ float value;
+ PointF dimens;
+
+ DeviceProfileQuery(float w, float h, float v) {
+ widthDps = w;
+ heightDps = h;
+ value = v;
+ dimens = new PointF(w, h);
+ }
+}
+
+class DeviceProfile {
+ String name;
+ float minWidthDps;
+ float minHeightDps;
+ float numRows;
+ float numColumns;
+ float iconSize;
+ float iconTextSize;
+ float numHotseatIcons;
+ float hotseatIconSize;
+
+ boolean isLandscape;
+ boolean isTablet;
+ boolean isLargeTablet;
+ boolean transposeLayoutWithOrientation;
+
+ int desiredWorkspaceLeftRightMarginPx;
+ int edgeMarginPx;
+ Rect defaultWidgetPadding;
+
+ int widthPx;
+ int heightPx;
+ int availableWidthPx;
+ int availableHeightPx;
+ int iconSizePx;
+ int iconTextSizePx;
+ int cellWidthPx;
+ int cellHeightPx;
+ int folderBackgroundOffset;
+ int folderIconSizePx;
+ int folderCellWidthPx;
+ int folderCellHeightPx;
+ int hotseatCellWidthPx;
+ int hotseatCellHeightPx;
+ int hotseatIconSizePx;
+ int hotseatBarHeightPx;
+ int hotseatAllAppsRank;
+ int allAppsNumRows;
+ int allAppsNumCols;
+ int searchBarSpaceWidthPx;
+ int searchBarSpaceMaxWidthPx;
+ int searchBarSpaceHeightPx;
+ int searchBarHeightPx;
+ int pageIndicatorHeightPx;
+
+ DeviceProfile(String n, float w, float h, float r, float c,
+ float is, float its, float hs, float his) {
+ // Ensure that we have an odd number of hotseat items (since we need to place all apps)
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
+ throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
+ }
+
+ name = n;
+ minWidthDps = w;
+ minHeightDps = h;
+ numRows = r;
+ numColumns = c;
+ iconSize = is;
+ iconTextSize = its;
+ numHotseatIcons = hs;
+ hotseatIconSize = his;
+ }
+
+ DeviceProfile(Context context,
+ ArrayList<DeviceProfile> profiles,
+ float minWidth, float minHeight,
+ int wPx, int hPx,
+ int awPx, int ahPx,
+ Resources resources) {
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ ArrayList<DeviceProfileQuery> points =
+ new ArrayList<DeviceProfileQuery>();
+ transposeLayoutWithOrientation =
+ resources.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ minWidthDps = minWidth;
+ minHeightDps = minHeight;
+
+ ComponentName cn = new ComponentName(context.getPackageName(),
+ this.getClass().getName());
+ defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
+ edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
+ desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
+ pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
+
+ // Interpolate the rows
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
+ }
+ numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ // Interpolate the columns
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
+ }
+ numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ // Interpolate the icon size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
+ }
+ iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+ iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
+
+ // Interpolate the icon text size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
+ }
+ iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+ iconTextSizePx = DynamicGrid.pxFromSp(iconTextSize, dm);
+
+ // Interpolate the hotseat size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
+ }
+ numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+ // Interpolate the hotseat icon size
+ points.clear();
+ for (DeviceProfile p : profiles) {
+ points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
+ }
+ // Hotseat
+ hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
+ hotseatIconSizePx = DynamicGrid.pxFromDp(hotseatIconSize, dm);
+ hotseatAllAppsRank = (int) (numColumns / 2);
+
+ // Calculate other vars based on Configuration
+ updateFromConfiguration(resources, wPx, hPx, awPx, ahPx);
+
+ // Search Bar
+ searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
+ searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
+ searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
+ searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
+
+ // Calculate the actual text height
+ Paint textPaint = new Paint();
+ textPaint.setTextSize(iconTextSizePx);
+ FontMetrics fm = textPaint.getFontMetrics();
+ cellWidthPx = iconSizePx;
+ cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
+
+ // At this point, if the cells do not fit into the available height, then we need
+ // to shrink the icon size
+ /*
+ Rect padding = getWorkspacePadding(isLandscape ?
+ CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ int h = (int) (numRows * cellHeightPx) + padding.top + padding.bottom;
+ if (h > availableHeightPx) {
+ float delta = h - availableHeightPx;
+ int deltaPx = (int) Math.ceil(delta / numRows);
+ iconSizePx -= deltaPx;
+ iconSize = DynamicGrid.dpiFromPx(iconSizePx, dm);
+ cellWidthPx = iconSizePx;
+ cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
+ }
+ */
+
+ // Hotseat
+ hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
+ hotseatCellWidthPx = iconSizePx;
+ hotseatCellHeightPx = iconSizePx;
+
+ // Folder
+ folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
+ folderCellHeightPx = cellHeightPx + (int) ((3f/2f) * edgeMarginPx);
+ folderBackgroundOffset = -edgeMarginPx;
+ folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
+ }
+
+ void updateFromConfiguration(Resources resources, int wPx, int hPx,
+ int awPx, int ahPx) {
+ isLandscape = (resources.getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE);
+ isTablet = resources.getBoolean(R.bool.is_tablet);
+ isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
+ widthPx = wPx;
+ heightPx = hPx;
+ availableWidthPx = awPx;
+ availableHeightPx = ahPx;
+
+ Rect padding = getWorkspacePadding(isLandscape ?
+ CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ int pageIndicatorOffset =
+ resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
+ if (isLandscape) {
+ allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
+ (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
+ } else {
+ allAppsNumRows = (int) numRows + 1;
+ }
+ allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
+ (iconSizePx + 2 * edgeMarginPx);
+ }
+
+ private float dist(PointF p0, PointF p1) {
+ return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
+ (p1.y-p0.y)*(p1.y-p0.y));
+ }
+
+ private float weight(PointF a, PointF b,
+ float pow) {
+ float d = dist(a, b);
+ if (d == 0f) {
+ return Float.POSITIVE_INFINITY;
+ }
+ return (float) (1f / Math.pow(d, pow));
+ }
+
+ private float invDistWeightedInterpolate(float width, float height,
+ ArrayList<DeviceProfileQuery> points) {
+ float sum = 0;
+ float weights = 0;
+ float pow = 5;
+ float kNearestNeighbors = 3;
+ final PointF xy = new PointF(width, height);
+
+ ArrayList<DeviceProfileQuery> pointsByNearness = points;
+ Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
+ public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
+ return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
+ }
+ });
+
+ for (int i = 0; i < pointsByNearness.size(); ++i) {
+ DeviceProfileQuery p = pointsByNearness.get(i);
+ if (i < kNearestNeighbors) {
+ float w = weight(xy, p.dimens, pow);
+ if (w == Float.POSITIVE_INFINITY) {
+ return p.value;
+ }
+ weights += w;
+ }
+ }
+
+ for (int i = 0; i < pointsByNearness.size(); ++i) {
+ DeviceProfileQuery p = pointsByNearness.get(i);
+ if (i < kNearestNeighbors) {
+ float w = weight(xy, p.dimens, pow);
+ sum += w * p.value / weights;
+ }
+ }
+
+ return sum;
+ }
+
+ Rect getWorkspacePadding(int orientation) {
+ Rect padding = new Rect();
+ if (orientation == CellLayout.LANDSCAPE &&
+ transposeLayoutWithOrientation) {
+ // Pad the left and right of the workspace with search/hotseat bar sizes
+ padding.set(searchBarSpaceHeightPx, edgeMarginPx,
+ hotseatBarHeightPx, edgeMarginPx);
+ } else {
+ if (isTablet()) {
+ // Pad the left and right of the workspace to ensure consistent spacing
+ // between all icons
+ int width = (orientation == CellLayout.LANDSCAPE)
+ ? Math.max(widthPx, heightPx)
+ : Math.min(widthPx, heightPx);
+ // XXX: If the icon size changes across orientations, we will have to take
+ // that into account here too.
+ int gap = (int) ((width - 2 * edgeMarginPx -
+ (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+ padding.set(edgeMarginPx + gap,
+ searchBarSpaceHeightPx,
+ edgeMarginPx + gap,
+ hotseatBarHeightPx + pageIndicatorHeightPx);
+ } else {
+ // Pad the top and bottom of the workspace with search/hotseat bar sizes
+ padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
+ searchBarSpaceHeightPx,
+ desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
+ hotseatBarHeightPx + pageIndicatorHeightPx);
+ }
+ }
+ return padding;
+ }
+
+ // The rect returned will be extended to below the system ui that covers the workspace
+ Rect getHotseatRect() {
+ if (isVerticalBarLayout()) {
+ return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
+ Integer.MAX_VALUE, availableHeightPx);
+ } else {
+ return new Rect(0, availableHeightPx - hotseatBarHeightPx,
+ availableWidthPx, Integer.MAX_VALUE);
+ }
+ }
+
+ int calculateCellWidth(int width, int countX) {
+ return width / countX;
+ }
+ int calculateCellHeight(int height, int countY) {
+ return height / countY;
+ }
+
+ boolean isPhone() {
+ return !isTablet && !isLargeTablet;
+ }
+ boolean isTablet() {
+ return isTablet;
+ }
+ boolean isLargeTablet() {
+ return isLargeTablet;
+ }
+
+ boolean isVerticalBarLayout() {
+ return isLandscape && transposeLayoutWithOrientation;
+ }
+
+ public void layout(Launcher launcher) {
+ FrameLayout.LayoutParams lp;
+ Resources res = launcher.getResources();
+ boolean hasVerticalBarLayout = isVerticalBarLayout();
+
+ // Layout the search bar space
+ View searchBar = launcher.getSearchBar();
+ lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
+ if (hasVerticalBarLayout) {
+ // Vertical search bar
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ lp.width = searchBarSpaceHeightPx;
+ lp.height = LayoutParams.MATCH_PARENT;
+ searchBar.setPadding(
+ 0, 2 * edgeMarginPx, 0,
+ 2 * edgeMarginPx);
+ } else {
+ // Horizontal search bar
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.width = searchBarSpaceWidthPx;
+ lp.height = searchBarSpaceHeightPx;
+ searchBar.setPadding(
+ 2 * edgeMarginPx,
+ 2 * edgeMarginPx,
+ 2 * edgeMarginPx, 0);
+ }
+ searchBar.setLayoutParams(lp);
+
+ // Layout the search bar
+ View qsbBar = launcher.getQsbBar();
+ LayoutParams vglp = qsbBar.getLayoutParams();
+ vglp.width = LayoutParams.MATCH_PARENT;
+ vglp.height = LayoutParams.MATCH_PARENT;
+ qsbBar.setLayoutParams(vglp);
+
+ // Layout the voice proxy
+ View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
+ if (voiceButtonProxy != null) {
+ if (hasVerticalBarLayout) {
+ // TODO: MOVE THIS INTO SEARCH BAR MEASURE
+ } else {
+ lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
+ lp.gravity = Gravity.TOP | Gravity.END;
+ lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
+ 2 * iconSizePx;
+ lp.height = searchBarSpaceHeightPx;
+ }
+ }
+
+ // Layout the workspace
+ View workspace = launcher.findViewById(R.id.workspace);
+ lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
+ lp.gravity = Gravity.CENTER;
+ Rect padding = getWorkspacePadding(isLandscape
+ ? CellLayout.LANDSCAPE
+ : CellLayout.PORTRAIT);
+ workspace.setPadding(padding.left, padding.top,
+ padding.right, padding.bottom);
+ workspace.setLayoutParams(lp);
+
+ // Layout the hotseat
+ View hotseat = launcher.findViewById(R.id.hotseat);
+ lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
+ if (hasVerticalBarLayout) {
+ // Vertical hotseat
+ lp.gravity = Gravity.RIGHT;
+ lp.width = hotseatBarHeightPx;
+ lp.height = LayoutParams.MATCH_PARENT;
+ hotseat.setPadding(0, 2 * edgeMarginPx,
+ 2 * edgeMarginPx, 2 * edgeMarginPx);
+ } else if (isTablet()) {
+ // Pad the hotseat with the grid gap calculated above
+ int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
+ (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
+ int gridWidth = (int) ((numColumns * cellWidthPx) +
+ ((numColumns - 1) * gridGap));
+ int hotseatGap = (int) Math.max(0,
+ (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
+ / (numHotseatIcons - 1));
+ lp.gravity = Gravity.BOTTOM;
+ lp.width = LayoutParams.MATCH_PARENT;
+ lp.height = hotseatBarHeightPx;
+ hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
+ 2 * edgeMarginPx + gridGap + hotseatGap,
+ 2 * edgeMarginPx);
+ } else {
+ // For phones, layout the hotseat without any bottom margin
+ // to ensure that we have space for the folders
+ lp.gravity = Gravity.BOTTOM;
+ lp.width = LayoutParams.MATCH_PARENT;
+ lp.height = hotseatBarHeightPx;
+ hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
+ 2 * edgeMarginPx, 0);
+ }
+ hotseat.setLayoutParams(lp);
+
+ // Layout the page indicators
+ View pageIndicator = launcher.findViewById(R.id.page_indicator);
+ if (pageIndicator != null) {
+ if (hasVerticalBarLayout) {
+ // Hide the page indicators when we have vertical search/hotseat
+ pageIndicator.setVisibility(View.GONE);
+ } else {
+ // Put the page indicators above the hotseat
+ lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
+ lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ lp.width = LayoutParams.WRAP_CONTENT;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ lp.bottomMargin = hotseatBarHeightPx;
+ pageIndicator.setLayoutParams(lp);
+ }
+ }
+ }
+}
+
+public class DynamicGrid {
+ @SuppressWarnings("unused")
+ private static final String TAG = "DynamicGrid";
+
+ private DeviceProfile mProfile;
+ private float mMinWidth;
+ private float mMinHeight;
+
+ public static float dpiFromPx(int size, DisplayMetrics metrics){
+ float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+ return (size / densityRatio);
+ }
+ public static int pxFromDp(float size, DisplayMetrics metrics) {
+ return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ size, metrics));
+ }
+ public static int pxFromSp(float size, DisplayMetrics metrics) {
+ return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+ size, metrics));
+ }
+
+ public DynamicGrid(Context context, Resources resources,
+ int minWidthPx, int minHeightPx,
+ int widthPx, int heightPx,
+ int awPx, int ahPx) {
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ ArrayList<DeviceProfile> deviceProfiles =
+ new ArrayList<DeviceProfile>();
+ boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;
+ // Our phone profiles include the bar sizes in each orientation
+ deviceProfiles.add(new DeviceProfile("Super Short Stubby",
+ 255, 300, 2, 3, 48, 13, (hasAA ? 5 : 4), 48));
+ deviceProfiles.add(new DeviceProfile("Shorter Stubby",
+ 255, 400, 3, 3, 48, 13, (hasAA ? 5 : 4), 48));
+ deviceProfiles.add(new DeviceProfile("Short Stubby",
+ 275, 420, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
+ deviceProfiles.add(new DeviceProfile("Stubby",
+ 255, 450, 3, 4, 48, 13, (hasAA ? 5 : 4), 48));
+ deviceProfiles.add(new DeviceProfile("Nexus S",
+ 296, 491.33f, 4, 4, 48, 13, (hasAA ? 5 : 4), 48));
+ deviceProfiles.add(new DeviceProfile("Nexus 4",
+ 359, 518, 4, 4, 60, 13, (hasAA ? 5 : 4), 56));
+ // The tablet profile is odd in that the landscape orientation
+ // also includes the nav bar on the side
+ deviceProfiles.add(new DeviceProfile("Nexus 7",
+ 575, 904, 6, 6, 72, 14.4f, 7, 60));
+ // Larger tablet profiles always have system bars on the top & bottom
+ deviceProfiles.add(new DeviceProfile("Nexus 10",
+ 727, 1207, 5, 8, 80, 14.4f, 9, 64));
+ /*
+ deviceProfiles.add(new DeviceProfile("Nexus 7",
+ 600, 960, 5, 5, 72, 14.4f, 5, 60));
+ deviceProfiles.add(new DeviceProfile("Nexus 10",
+ 800, 1280, 5, 5, 80, 14.4f, (hasAA ? 7 : 6), 64));
+ */
+ deviceProfiles.add(new DeviceProfile("20-inch Tablet",
+ 1527, 2527, 7, 7, 100, 20, 7, 72));
+ mMinWidth = dpiFromPx(minWidthPx, dm);
+ mMinHeight = dpiFromPx(minHeightPx, dm);
+ mProfile = new DeviceProfile(context, deviceProfiles,
+ mMinWidth, mMinHeight,
+ widthPx, heightPx,
+ awPx, ahPx,
+ resources);
+ }
+
+ DeviceProfile getDeviceProfile() {
+ return mProfile;
+ }
+
+ public String toString() {
+ return "-------- DYNAMIC GRID ------- \n" +
+ "Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +
+ ", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +
+ " [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +
+ ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSize +
+ ", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +
+ ", hc: " + mProfile.numHotseatIcons + ", his: " + mProfile.hotseatIconSizePx + "]";
+ }
+}
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
new file mode 100644
index 0000000..14760c7
--- /dev/null
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+class FastBitmapDrawable extends Drawable {
+ private Bitmap mBitmap;
+ private int mAlpha;
+ private int mWidth;
+ private int mHeight;
+ private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+
+ FastBitmapDrawable(Bitmap b) {
+ mAlpha = 255;
+ mBitmap = b;
+ if (b != null) {
+ mWidth = mBitmap.getWidth();
+ mHeight = mBitmap.getHeight();
+ } else {
+ mWidth = mHeight = 0;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect r = getBounds();
+ // Draw the bitmap into the bounding rect
+ canvas.drawBitmap(mBitmap, null, r, mPaint);
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mPaint.setColorFilter(cf);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ mPaint.setAlpha(alpha);
+ }
+
+ public void setFilterBitmap(boolean filterBitmap) {
+ mPaint.setFilterBitmap(filterBitmap);
+ }
+
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public int getMinimumWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getMinimumHeight() {
+ return mHeight;
+ }
+
+ public void setBitmap(Bitmap b) {
+ mBitmap = b;
+ if (b != null) {
+ mWidth = mBitmap.getWidth();
+ mHeight = mBitmap.getHeight();
+ } else {
+ mWidth = mHeight = 0;
+ }
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+}
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
new file mode 100644
index 0000000..78fdadd
--- /dev/null
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2013 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+
+/*
+ * This is a helper class that listens to updates from the corresponding animation.
+ * For the first two frames, it adjusts the current play time of the animation to
+ * prevent jank at the beginning of the animation
+ */
+public class FirstFrameAnimatorHelper extends AnimatorListenerAdapter
+ implements ValueAnimator.AnimatorUpdateListener {
+ private static final boolean DEBUG = false;
+ private static final int MAX_DELAY = 1000;
+ private static final int IDEAL_FRAME_DURATION = 16;
+ private View mTarget;
+ private long mStartFrame;
+ private long mStartTime = -1;
+ private boolean mHandlingOnAnimationUpdate;
+ private boolean mAdjustedSecondFrameTime;
+
+ private static ViewTreeObserver.OnDrawListener sGlobalDrawListener;
+ private static long sGlobalFrameCounter;
+ private static boolean sVisible;
+
+ public FirstFrameAnimatorHelper(ValueAnimator animator, View target) {
+ mTarget = target;
+ animator.addUpdateListener(this);
+ }
+
+ public FirstFrameAnimatorHelper(ViewPropertyAnimator vpa, View target) {
+ mTarget = target;
+ vpa.setListener(this);
+ }
+
+ // only used for ViewPropertyAnimators
+ public void onAnimationStart(Animator animation) {
+ final ValueAnimator va = (ValueAnimator) animation;
+ va.addUpdateListener(FirstFrameAnimatorHelper.this);
+ onAnimationUpdate(va);
+ }
+
+ public static void setIsVisible(boolean visible) {
+ sVisible = visible;
+ }
+
+ public static void initializeDrawListener(View view) {
+ if (sGlobalDrawListener != null) {
+ view.getViewTreeObserver().removeOnDrawListener(sGlobalDrawListener);
+ }
+ sGlobalDrawListener = new ViewTreeObserver.OnDrawListener() {
+ private long mTime = System.currentTimeMillis();
+ public void onDraw() {
+ sGlobalFrameCounter++;
+ if (DEBUG) {
+ long newTime = System.currentTimeMillis();
+ Log.d("FirstFrameAnimatorHelper", "TICK " + (newTime - mTime));
+ mTime = newTime;
+ }
+ }
+ };
+ view.getViewTreeObserver().addOnDrawListener(sGlobalDrawListener);
+ sVisible = true;
+ }
+
+ public void onAnimationUpdate(final ValueAnimator animation) {
+ final long currentTime = System.currentTimeMillis();
+ if (mStartTime == -1) {
+ mStartFrame = sGlobalFrameCounter;
+ mStartTime = currentTime;
+ }
+
+ if (!mHandlingOnAnimationUpdate &&
+ sVisible &&
+ // If the current play time exceeds the duration, the animation
+ // will get finished, even if we call setCurrentPlayTime -- therefore
+ // don't adjust the animation in that case
+ animation.getCurrentPlayTime() < animation.getDuration()) {
+ mHandlingOnAnimationUpdate = true;
+ long frameNum = sGlobalFrameCounter - mStartFrame;
+ // If we haven't drawn our first frame, reset the time to t = 0
+ // (give up after MAX_DELAY ms of waiting though - might happen, for example, if we
+ // are no longer in the foreground and no frames are being rendered ever)
+ if (frameNum == 0 && currentTime < mStartTime + MAX_DELAY) {
+ // The first frame on animations doesn't always trigger an invalidate...
+ // force an invalidate here to make sure the animation continues to advance
+ mTarget.getRootView().invalidate();
+ animation.setCurrentPlayTime(0);
+
+ // For the second frame, if the first frame took more than 16ms,
+ // adjust the start time and pretend it took only 16ms anyway. This
+ // prevents a large jump in the animation due to an expensive first frame
+ } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+ !mAdjustedSecondFrameTime &&
+ currentTime > mStartTime + IDEAL_FRAME_DURATION) {
+ animation.setCurrentPlayTime(IDEAL_FRAME_DURATION);
+ mAdjustedSecondFrameTime = true;
+ } else {
+ if (frameNum > 1) {
+ mTarget.post(new Runnable() {
+ public void run() {
+ animation.removeUpdateListener(FirstFrameAnimatorHelper.this);
+ }
+ });
+ }
+ if (DEBUG) print(animation);
+ }
+ mHandlingOnAnimationUpdate = false;
+ } else {
+ if (DEBUG) print(animation);
+ }
+ }
+
+ public void print(ValueAnimator animation) {
+ float flatFraction = animation.getCurrentPlayTime() / (float) animation.getDuration();
+ Log.d("FirstFrameAnimatorHelper", sGlobalFrameCounter +
+ "(" + (sGlobalFrameCounter - mStartFrame) + ") " + mTarget + " dirty? " +
+ mTarget.isDirty() + " " + flatFraction + " " + this + " " + animation);
+ }
+}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
new file mode 100644
index 0000000..4600985
--- /dev/null
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -0,0 +1,898 @@
+/*
+ * Copyright (C) 2011 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.res.Configuration;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ScrollView;
+import android.widget.TabHost;
+import android.widget.TabWidget;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * A keyboard listener we set on all the workspace icons.
+ */
+class IconKeyEventListener implements View.OnKeyListener {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ return FocusHelper.handleIconKeyEvent(v, keyCode, event);
+ }
+}
+
+/**
+ * A keyboard listener we set on all the workspace icons.
+ */
+class FolderKeyEventListener implements View.OnKeyListener {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ return FocusHelper.handleFolderKeyEvent(v, keyCode, event);
+ }
+}
+
+/**
+ * A keyboard listener we set on all the hotseat buttons.
+ */
+class HotseatIconKeyEventListener implements View.OnKeyListener {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ final Configuration configuration = v.getResources().getConfiguration();
+ return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
+ }
+}
+
+/**
+ * A keyboard listener we set on the last tab button in AppsCustomize to jump to then
+ * market icon and vice versa.
+ */
+class AppsCustomizeTabKeyEventListener implements View.OnKeyListener {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event);
+ }
+}
+
+public class FocusHelper {
+ /**
+ * Private helper to get the parent TabHost in the view hiearchy.
+ */
+ private static TabHost findTabHostParent(View v) {
+ ViewParent p = v.getParent();
+ while (p != null && !(p instanceof TabHost)) {
+ p = p.getParent();
+ }
+ return (TabHost) p;
+ }
+
+ /**
+ * Handles key events in a AppsCustomize tab between the last tab view and the shop button.
+ */
+ static boolean handleAppsCustomizeTabKeyEvent(View v, int keyCode, KeyEvent e) {
+ final TabHost tabHost = findTabHostParent(v);
+ final ViewGroup contents = tabHost.getTabContentView();
+ final View shop = tabHost.findViewById(R.id.market_button);
+
+ final int action = e.getAction();
+ final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+ boolean wasHandled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (handleKeyEvent) {
+ // Select the shop button if we aren't on it
+ if (v != shop) {
+ shop.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (handleKeyEvent) {
+ // Select the content view (down is handled by the tab key handler otherwise)
+ if (v == shop) {
+ contents.requestFocus();
+ wasHandled = true;
+ }
+ }
+ break;
+ default: break;
+ }
+ return wasHandled;
+ }
+
+ /**
+ * Returns the Viewgroup containing page contents for the page at the index specified.
+ */
+ private static ViewGroup getAppsCustomizePage(ViewGroup container, int index) {
+ ViewGroup page = (ViewGroup) ((PagedView) container).getPageAt(index);
+ if (page instanceof CellLayout) {
+ // There are two layers, a PagedViewCellLayout and PagedViewCellLayoutChildren
+ page = ((CellLayout) page).getShortcutsAndWidgets();
+ }
+ return page;
+ }
+
+ /**
+ * Handles key events in a PageViewExtendedLayout containing PagedViewWidgets.
+ */
+ static boolean handlePagedViewGridLayoutWidgetKeyEvent(PagedViewWidget w, int keyCode,
+ KeyEvent e) {
+
+ final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
+ final PagedView container = (PagedView) parent.getParent();
+ final TabHost tabHost = findTabHostParent(container);
+ final TabWidget tabs = tabHost.getTabWidget();
+ final int widgetIndex = parent.indexOfChild(w);
+ final int widgetCount = parent.getChildCount();
+ final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
+ final int pageCount = container.getChildCount();
+ final int cellCountX = parent.getCellCountX();
+ final int cellCountY = parent.getCellCountY();
+ final int x = widgetIndex % cellCountX;
+ final int y = widgetIndex / cellCountX;
+
+ final int action = e.getAction();
+ final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+ ViewGroup newParent = null;
+ // Now that we load items in the bg asynchronously, we can't just focus
+ // child siblings willy-nilly
+ View child = null;
+ boolean wasHandled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (handleKeyEvent) {
+ // Select the previous widget or the last widget on the previous page
+ if (widgetIndex > 0) {
+ parent.getChildAt(widgetIndex - 1).requestFocus();
+ } else {
+ if (pageIndex > 0) {
+ newParent = getAppsCustomizePage(container, pageIndex - 1);
+ if (newParent != null) {
+ child = newParent.getChildAt(newParent.getChildCount() - 1);
+ if (child != null) child.requestFocus();
+ }
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (handleKeyEvent) {
+ // Select the next widget or the first widget on the next page
+ if (widgetIndex < (widgetCount - 1)) {
+ parent.getChildAt(widgetIndex + 1).requestFocus();
+ } else {
+ if (pageIndex < (pageCount - 1)) {
+ newParent = getAppsCustomizePage(container, pageIndex + 1);
+ if (newParent != null) {
+ child = newParent.getChildAt(0);
+ if (child != null) child.requestFocus();
+ }
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (handleKeyEvent) {
+ // Select the closest icon in the previous row, otherwise select the tab bar
+ if (y > 0) {
+ int newWidgetIndex = ((y - 1) * cellCountX) + x;
+ child = parent.getChildAt(newWidgetIndex);
+ if (child != null) child.requestFocus();
+ } else {
+ tabs.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (handleKeyEvent) {
+ // Select the closest icon in the previous row, otherwise do nothing
+ if (y < (cellCountY - 1)) {
+ int newWidgetIndex = Math.min(widgetCount - 1, ((y + 1) * cellCountX) + x);
+ child = parent.getChildAt(newWidgetIndex);
+ if (child != null) child.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (handleKeyEvent) {
+ // Simulate a click on the widget
+ View.OnClickListener clickListener = (View.OnClickListener) container;
+ clickListener.onClick(w);
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_PAGE_UP:
+ if (handleKeyEvent) {
+ // Select the first item on the previous page, or the first item on this page
+ // if there is no previous page
+ if (pageIndex > 0) {
+ newParent = getAppsCustomizePage(container, pageIndex - 1);
+ if (newParent != null) {
+ child = newParent.getChildAt(0);
+ }
+ } else {
+ child = parent.getChildAt(0);
+ }
+ if (child != null) child.requestFocus();
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ if (handleKeyEvent) {
+ // Select the first item on the next page, or the last item on this page
+ // if there is no next page
+ if (pageIndex < (pageCount - 1)) {
+ newParent = getAppsCustomizePage(container, pageIndex + 1);
+ if (newParent != null) {
+ child = newParent.getChildAt(0);
+ }
+ } else {
+ child = parent.getChildAt(widgetCount - 1);
+ }
+ if (child != null) child.requestFocus();
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ if (handleKeyEvent) {
+ // Select the first item on this page
+ child = parent.getChildAt(0);
+ if (child != null) child.requestFocus();
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_END:
+ if (handleKeyEvent) {
+ // Select the last item on this page
+ parent.getChildAt(widgetCount - 1).requestFocus();
+ }
+ wasHandled = true;
+ break;
+ default: break;
+ }
+ return wasHandled;
+ }
+
+ /**
+ * Handles key events in a PageViewCellLayout containing PagedViewIcons.
+ */
+ static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
+ ViewGroup parentLayout;
+ ViewGroup itemContainer;
+ int countX;
+ int countY;
+ if (v.getParent() instanceof ShortcutAndWidgetContainer) {
+ itemContainer = (ViewGroup) v.getParent();
+ parentLayout = (ViewGroup) itemContainer.getParent();
+ countX = ((CellLayout) parentLayout).getCountX();
+ countY = ((CellLayout) parentLayout).getCountY();
+ } else {
+ itemContainer = parentLayout = (ViewGroup) v.getParent();
+ countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
+ countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
+ }
+
+ // Note we have an extra parent because of the
+ // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
+ final PagedView container = (PagedView) parentLayout.getParent();
+ final TabHost tabHost = findTabHostParent(container);
+ final TabWidget tabs = tabHost.getTabWidget();
+ final int iconIndex = itemContainer.indexOfChild(v);
+ final int itemCount = itemContainer.getChildCount();
+ final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
+ final int pageCount = container.getChildCount();
+
+ final int x = iconIndex % countX;
+ final int y = iconIndex / countX;
+
+ final int action = e.getAction();
+ final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+ ViewGroup newParent = null;
+ // Side pages do not always load synchronously, so check before focusing child siblings
+ // willy-nilly
+ View child = null;
+ boolean wasHandled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (handleKeyEvent) {
+ // Select the previous icon or the last icon on the previous page
+ if (iconIndex > 0) {
+ itemContainer.getChildAt(iconIndex - 1).requestFocus();
+ } else {
+ if (pageIndex > 0) {
+ newParent = getAppsCustomizePage(container, pageIndex - 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex - 1);
+ child = newParent.getChildAt(newParent.getChildCount() - 1);
+ if (child != null) child.requestFocus();
+ }
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (handleKeyEvent) {
+ // Select the next icon or the first icon on the next page
+ if (iconIndex < (itemCount - 1)) {
+ itemContainer.getChildAt(iconIndex + 1).requestFocus();
+ } else {
+ if (pageIndex < (pageCount - 1)) {
+ newParent = getAppsCustomizePage(container, pageIndex + 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex + 1);
+ child = newParent.getChildAt(0);
+ if (child != null) child.requestFocus();
+ }
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (handleKeyEvent) {
+ // Select the closest icon in the previous row, otherwise select the tab bar
+ if (y > 0) {
+ int newiconIndex = ((y - 1) * countX) + x;
+ itemContainer.getChildAt(newiconIndex).requestFocus();
+ } else {
+ tabs.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (handleKeyEvent) {
+ // Select the closest icon in the previous row, otherwise do nothing
+ if (y < (countY - 1)) {
+ int newiconIndex = Math.min(itemCount - 1, ((y + 1) * countX) + x);
+ itemContainer.getChildAt(newiconIndex).requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (handleKeyEvent) {
+ // Simulate a click on the icon
+ View.OnClickListener clickListener = (View.OnClickListener) container;
+ clickListener.onClick(v);
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_PAGE_UP:
+ if (handleKeyEvent) {
+ // Select the first icon on the previous page, or the first icon on this page
+ // if there is no previous page
+ if (pageIndex > 0) {
+ newParent = getAppsCustomizePage(container, pageIndex - 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex - 1);
+ child = newParent.getChildAt(0);
+ if (child != null) child.requestFocus();
+ }
+ } else {
+ itemContainer.getChildAt(0).requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ if (handleKeyEvent) {
+ // Select the first icon on the next page, or the last icon on this page
+ // if there is no next page
+ if (pageIndex < (pageCount - 1)) {
+ newParent = getAppsCustomizePage(container, pageIndex + 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex + 1);
+ child = newParent.getChildAt(0);
+ if (child != null) child.requestFocus();
+ }
+ } else {
+ itemContainer.getChildAt(itemCount - 1).requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ if (handleKeyEvent) {
+ // Select the first icon on this page
+ itemContainer.getChildAt(0).requestFocus();
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_END:
+ if (handleKeyEvent) {
+ // Select the last icon on this page
+ itemContainer.getChildAt(itemCount - 1).requestFocus();
+ }
+ wasHandled = true;
+ break;
+ default: break;
+ }
+ return wasHandled;
+ }
+
+ /**
+ * Handles key events in the tab widget.
+ */
+ static boolean handleTabKeyEvent(AccessibleTabView v, int keyCode, KeyEvent e) {
+ if (!LauncherAppState.getInstance().isScreenLarge()) return false;
+
+ final FocusOnlyTabWidget parent = (FocusOnlyTabWidget) v.getParent();
+ final TabHost tabHost = findTabHostParent(parent);
+ final ViewGroup contents = tabHost.getTabContentView();
+ final int tabCount = parent.getTabCount();
+ final int tabIndex = parent.getChildTabIndex(v);
+
+ final int action = e.getAction();
+ final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+ boolean wasHandled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (handleKeyEvent) {
+ // Select the previous tab
+ if (tabIndex > 0) {
+ parent.getChildTabViewAt(tabIndex - 1).requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (handleKeyEvent) {
+ // Select the next tab, or if the last tab has a focus right id, select that
+ if (tabIndex < (tabCount - 1)) {
+ parent.getChildTabViewAt(tabIndex + 1).requestFocus();
+ } else {
+ if (v.getNextFocusRightId() != View.NO_ID) {
+ tabHost.findViewById(v.getNextFocusRightId()).requestFocus();
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ // Do nothing
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (handleKeyEvent) {
+ // Select the content view
+ contents.requestFocus();
+ }
+ wasHandled = true;
+ break;
+ default: break;
+ }
+ return wasHandled;
+ }
+
+ /**
+ * Handles key events in the workspace hotseat (bottom of the screen).
+ */
+ static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
+ final ViewGroup parent = (ViewGroup) v.getParent();
+ final ViewGroup launcher = (ViewGroup) parent.getParent();
+ final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
+ final int buttonIndex = parent.indexOfChild(v);
+ final int buttonCount = parent.getChildCount();
+ final int pageIndex = workspace.getCurrentPage();
+
+ // NOTE: currently we don't special case for the phone UI in different
+ // orientations, even though the hotseat is on the side in landscape mode. This
+ // is to ensure that accessibility consistency is maintained across rotations.
+
+ final int action = e.getAction();
+ final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+ boolean wasHandled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (handleKeyEvent) {
+ // Select the previous button, otherwise snap to the previous page
+ if (buttonIndex > 0) {
+ parent.getChildAt(buttonIndex - 1).requestFocus();
+ } else {
+ workspace.snapToPage(pageIndex - 1);
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (handleKeyEvent) {
+ // Select the next button, otherwise snap to the next page
+ if (buttonIndex < (buttonCount - 1)) {
+ parent.getChildAt(buttonIndex + 1).requestFocus();
+ } else {
+ workspace.snapToPage(pageIndex + 1);
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (handleKeyEvent) {
+ // Select the first bubble text view in the current page of the workspace
+ final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
+ final ShortcutAndWidgetContainer children = layout.getShortcutsAndWidgets();
+ final View newIcon = getIconInDirection(layout, children, -1, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ workspace.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ // Do nothing
+ wasHandled = true;
+ break;
+ default: break;
+ }
+ return wasHandled;
+ }
+
+ /**
+ * Private helper method to get the CellLayoutChildren given a CellLayout index.
+ */
+ private static ShortcutAndWidgetContainer getCellLayoutChildrenForIndex(
+ ViewGroup container, int i) {
+ ViewGroup parent = (ViewGroup) container.getChildAt(i);
+ return (ShortcutAndWidgetContainer) parent.getChildAt(0);
+ }
+
+ /**
+ * Private helper method to sort all the CellLayout children in order of their (x,y) spatially
+ * from top left to bottom right.
+ */
+ private static ArrayList<View> getCellLayoutChildrenSortedSpatially(CellLayout layout,
+ ViewGroup parent) {
+ // First we order each the CellLayout children by their x,y coordinates
+ final int cellCountX = layout.getCountX();
+ final int count = parent.getChildCount();
+ ArrayList<View> views = new ArrayList<View>();
+ for (int j = 0; j < count; ++j) {
+ views.add(parent.getChildAt(j));
+ }
+ Collections.sort(views, new Comparator<View>() {
+ @Override
+ public int compare(View lhs, View rhs) {
+ CellLayout.LayoutParams llp = (CellLayout.LayoutParams) lhs.getLayoutParams();
+ CellLayout.LayoutParams rlp = (CellLayout.LayoutParams) rhs.getLayoutParams();
+ int lvIndex = (llp.cellY * cellCountX) + llp.cellX;
+ int rvIndex = (rlp.cellY * cellCountX) + rlp.cellX;
+ return lvIndex - rvIndex;
+ }
+ });
+ return views;
+ }
+ /**
+ * Private helper method to find the index of the next BubbleTextView or FolderIcon in the
+ * direction delta.
+ *
+ * @param delta either -1 or 1 depending on the direction we want to search
+ */
+ private static View findIndexOfIcon(ArrayList<View> views, int i, int delta) {
+ // Then we find the next BubbleTextView offset by delta from i
+ final int count = views.size();
+ int newI = i + delta;
+ while (0 <= newI && newI < count) {
+ View newV = views.get(newI);
+ if (newV instanceof BubbleTextView || newV instanceof FolderIcon) {
+ return newV;
+ }
+ newI += delta;
+ }
+ return null;
+ }
+ private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i,
+ int delta) {
+ final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
+ return findIndexOfIcon(views, i, delta);
+ }
+ private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v,
+ int delta) {
+ final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
+ return findIndexOfIcon(views, views.indexOf(v), delta);
+ }
+ /**
+ * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction
+ * delta on the next line.
+ *
+ * @param delta either -1 or 1 depending on the line and direction we want to search
+ */
+ private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v,
+ int lineDelta) {
+ final ArrayList<View> views = getCellLayoutChildrenSortedSpatially(layout, parent);
+ final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+ final int cellCountY = layout.getCountY();
+ final int row = lp.cellY;
+ final int newRow = row + lineDelta;
+ if (0 <= newRow && newRow < cellCountY) {
+ float closestDistance = Float.MAX_VALUE;
+ int closestIndex = -1;
+ int index = views.indexOf(v);
+ int endIndex = (lineDelta < 0) ? -1 : views.size();
+ while (index != endIndex) {
+ View newV = views.get(index);
+ CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams();
+ boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row);
+ if (satisfiesRow &&
+ (newV instanceof BubbleTextView || newV instanceof FolderIcon)) {
+ float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) +
+ Math.pow(tmpLp.cellY - lp.cellY, 2));
+ if (tmpDistance < closestDistance) {
+ closestIndex = index;
+ closestDistance = tmpDistance;
+ }
+ }
+ if (index <= endIndex) {
+ ++index;
+ } else {
+ --index;
+ }
+ }
+ if (closestIndex > -1) {
+ return views.get(closestIndex);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Handles key events in a Workspace containing.
+ */
+ static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) {
+ ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
+ final CellLayout layout = (CellLayout) parent.getParent();
+ final Workspace workspace = (Workspace) layout.getParent();
+ final ViewGroup launcher = (ViewGroup) workspace.getParent();
+ final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar);
+ final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat);
+ int pageIndex = workspace.indexOfChild(layout);
+ int pageCount = workspace.getChildCount();
+
+ final int action = e.getAction();
+ final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+ boolean wasHandled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (handleKeyEvent) {
+ // Select the previous icon or the last icon on the previous page if possible
+ View newIcon = getIconInDirection(layout, parent, v, -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ if (pageIndex > 0) {
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ newIcon = getIconInDirection(layout, parent,
+ parent.getChildCount(), -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ // Snap to the previous page
+ workspace.snapToPage(pageIndex - 1);
+ }
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (handleKeyEvent) {
+ // Select the next icon or the first icon on the next page if possible
+ View newIcon = getIconInDirection(layout, parent, v, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ if (pageIndex < (pageCount - 1)) {
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ newIcon = getIconInDirection(layout, parent, -1, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ // Snap to the next page
+ workspace.snapToPage(pageIndex + 1);
+ }
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (handleKeyEvent) {
+ // Select the closest icon in the previous line, otherwise select the tab bar
+ View newIcon = getClosestIconOnLine(layout, parent, v, -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ wasHandled = true;
+ } else {
+ tabs.requestFocus();
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (handleKeyEvent) {
+ // Select the closest icon in the next line, otherwise select the button bar
+ View newIcon = getClosestIconOnLine(layout, parent, v, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ wasHandled = true;
+ } else if (hotseat != null) {
+ hotseat.requestFocus();
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_PAGE_UP:
+ if (handleKeyEvent) {
+ // Select the first icon on the previous page or the first icon on this page
+ // if there is no previous page
+ if (pageIndex > 0) {
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ View newIcon = getIconInDirection(layout, parent, -1, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ // Snap to the previous page
+ workspace.snapToPage(pageIndex - 1);
+ }
+ } else {
+ View newIcon = getIconInDirection(layout, parent, -1, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ if (handleKeyEvent) {
+ // Select the first icon on the next page or the last icon on this page
+ // if there is no previous page
+ if (pageIndex < (pageCount - 1)) {
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ View newIcon = getIconInDirection(layout, parent, -1, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ // Snap to the next page
+ workspace.snapToPage(pageIndex + 1);
+ }
+ } else {
+ View newIcon = getIconInDirection(layout, parent,
+ parent.getChildCount(), -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ if (handleKeyEvent) {
+ // Select the first icon on this page
+ View newIcon = getIconInDirection(layout, parent, -1, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_END:
+ if (handleKeyEvent) {
+ // Select the last icon on this page
+ View newIcon = getIconInDirection(layout, parent,
+ parent.getChildCount(), -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ default: break;
+ }
+ return wasHandled;
+ }
+
+ /**
+ * Handles key events for items in a Folder.
+ */
+ static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) {
+ ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
+ final CellLayout layout = (CellLayout) parent.getParent();
+ final ScrollView scrollView = (ScrollView) layout.getParent();
+ final Folder folder = (Folder) scrollView.getParent();
+ View title = folder.mFolderName;
+
+ final int action = e.getAction();
+ final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
+ boolean wasHandled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (handleKeyEvent) {
+ // Select the previous icon
+ View newIcon = getIconInDirection(layout, parent, v, -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (handleKeyEvent) {
+ // Select the next icon
+ View newIcon = getIconInDirection(layout, parent, v, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ title.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (handleKeyEvent) {
+ // Select the closest icon in the previous line
+ View newIcon = getClosestIconOnLine(layout, parent, v, -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (handleKeyEvent) {
+ // Select the closest icon in the next line
+ View newIcon = getClosestIconOnLine(layout, parent, v, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ } else {
+ title.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ if (handleKeyEvent) {
+ // Select the first icon on this page
+ View newIcon = getIconInDirection(layout, parent, -1, 1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ case KeyEvent.KEYCODE_MOVE_END:
+ if (handleKeyEvent) {
+ // Select the last icon on this page
+ View newIcon = getIconInDirection(layout, parent,
+ parent.getChildCount(), -1);
+ if (newIcon != null) {
+ newIcon.requestFocus();
+ }
+ }
+ wasHandled = true;
+ break;
+ default: break;
+ }
+ return wasHandled;
+ }
+}
diff --git a/src/com/android/launcher3/FocusOnlyTabWidget.java b/src/com/android/launcher3/FocusOnlyTabWidget.java
new file mode 100644
index 0000000..08fc311
--- /dev/null
+++ b/src/com/android/launcher3/FocusOnlyTabWidget.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 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.util.AttributeSet;
+import android.view.View;
+import android.widget.TabWidget;
+
+public class FocusOnlyTabWidget extends TabWidget {
+ public FocusOnlyTabWidget(Context context) {
+ super(context);
+ }
+
+ public FocusOnlyTabWidget(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FocusOnlyTabWidget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public View getSelectedTab() {
+ final int count = getTabCount();
+ for (int i = 0; i < count; ++i) {
+ View v = getChildTabViewAt(i);
+ if (v.isSelected()) {
+ return v;
+ }
+ }
+ return null;
+ }
+
+ public int getChildTabIndex(View v) {
+ final int tabCount = getTabCount();
+ for (int i = 0; i < tabCount; ++i) {
+ if (getChildTabViewAt(i) == v) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void setCurrentTabToFocusedTab() {
+ View tab = null;
+ int index = -1;
+ final int count = getTabCount();
+ for (int i = 0; i < count; ++i) {
+ View v = getChildTabViewAt(i);
+ if (v.hasFocus()) {
+ tab = v;
+ index = i;
+ break;
+ }
+ }
+ if (index > -1) {
+ super.setCurrentTab(index);
+ super.onFocusChange(tab, true);
+ }
+ }
+ public void superOnFocusChange(View v, boolean hasFocus) {
+ super.onFocusChange(v, hasFocus);
+ }
+
+ @Override
+ public void onFocusChange(android.view.View v, boolean hasFocus) {
+ if (v == this && hasFocus && getTabCount() > 0) {
+ getSelectedTab().requestFocus();
+ return;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
new file mode 100644
index 0000000..5231aac
--- /dev/null
+++ b/src/com/android/launcher3/Folder.java
@@ -0,0 +1,1226 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.support.v4.widget.AutoScrollHelper;
+import android.text.InputType;
+import android.text.Selection;
+import android.text.Spannable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.android.launcher3.FolderInfo.FolderListener;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Represents a set of icons chosen by the user or generated by the system.
+ */
+public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
+ View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
+ View.OnFocusChangeListener {
+ private static final String TAG = "Launcher.Folder";
+
+ protected DragController mDragController;
+ protected Launcher mLauncher;
+ protected FolderInfo mInfo;
+
+ static final int STATE_NONE = -1;
+ static final int STATE_SMALL = 0;
+ static final int STATE_ANIMATING = 1;
+ static final int STATE_OPEN = 2;
+
+ private int mExpandDuration;
+ protected CellLayout mContent;
+ private ScrollView mScrollView;
+ private final LayoutInflater mInflater;
+ private final IconCache mIconCache;
+ private int mState = STATE_NONE;
+ private static final int REORDER_ANIMATION_DURATION = 230;
+ private static final int REORDER_DELAY = 250;
+ private static final int ON_EXIT_CLOSE_DELAY = 800;
+ private boolean mRearrangeOnClose = false;
+ private FolderIcon mFolderIcon;
+ private int mMaxCountX;
+ private int mMaxCountY;
+ private int mMaxNumItems;
+ private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+ private Drawable mIconDrawable;
+ boolean mItemsInvalidated = false;
+ private ShortcutInfo mCurrentDragInfo;
+ private View mCurrentDragView;
+ boolean mSuppressOnAdd = false;
+ private int[] mTargetCell = new int[2];
+ private int[] mPreviousTargetCell = new int[2];
+ private int[] mEmptyCell = new int[2];
+ private Alarm mReorderAlarm = new Alarm();
+ private Alarm mOnExitAlarm = new Alarm();
+ private int mFolderNameHeight;
+ private Rect mTempRect = new Rect();
+ private boolean mDragInProgress = false;
+ private boolean mDeleteFolderOnDropCompleted = false;
+ private boolean mSuppressFolderDeletion = false;
+ private boolean mItemAddedBackToSelfViaIcon = false;
+ FolderEditText mFolderName;
+ private float mFolderIconPivotX;
+ private float mFolderIconPivotY;
+
+ private boolean mIsEditingName = false;
+ private InputMethodManager mInputMethodManager;
+
+ private static String sDefaultFolderName;
+ private static String sHintText;
+
+ private int DRAG_MODE_NONE = 0;
+ private int DRAG_MODE_REORDER = 1;
+ private int mDragMode = DRAG_MODE_NONE;
+
+ private boolean mDestroyed;
+
+ private AutoScrollHelper mAutoScrollHelper;
+
+ private Runnable mDeferredAction;
+ private boolean mDeferDropAfterUninstall;
+ private boolean mUninstallSuccessful;
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attribtues set containing the Workspace's customization values.
+ */
+ public Folder(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ setAlwaysDrawnWithCacheEnabled(false);
+ mInflater = LayoutInflater.from(context);
+ mIconCache = app.getIconCache();
+
+ Resources res = getResources();
+ mMaxCountX = (int) grid.numColumns;
+ mMaxCountY = (int) grid.numRows;
+ mMaxNumItems = mMaxCountX * mMaxCountY;
+
+ mInputMethodManager = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration);
+
+ if (sDefaultFolderName == null) {
+ sDefaultFolderName = res.getString(R.string.folder_name);
+ }
+ if (sHintText == null) {
+ sHintText = res.getString(R.string.folder_hint_text);
+ }
+ mLauncher = (Launcher) context;
+ // We need this view to be focusable in touch mode so that when text editing of the folder
+ // name is complete, we have something to focus on, thus hiding the cursor and giving
+ // reliable behvior when clicking the text field (since it will always gain focus on click).
+ setFocusableInTouchMode(true);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mScrollView = (ScrollView) findViewById(R.id.scroll_view);
+ mContent = (CellLayout) findViewById(R.id.folder_content);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
+ mContent.setGridSize(0, 0);
+ mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+ mContent.setInvertIfRtl(true);
+ mFolderName = (FolderEditText) findViewById(R.id.folder_name);
+ mFolderName.setFolder(this);
+ mFolderName.setOnFocusChangeListener(this);
+
+ // We find out how tall the text view wants to be (it is set to wrap_content), so that
+ // we can allocate the appropriate amount of space for it.
+ int measureSpec = MeasureSpec.UNSPECIFIED;
+ mFolderName.measure(measureSpec, measureSpec);
+ mFolderNameHeight = mFolderName.getMeasuredHeight();
+
+ // We disable action mode for now since it messes up the view on phones
+ mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
+ mFolderName.setOnEditorActionListener(this);
+ mFolderName.setSelectAllOnFocus(true);
+ mFolderName.setInputType(mFolderName.getInputType() |
+ InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+ mAutoScrollHelper = new FolderAutoScrollHelper(mScrollView);
+ }
+
+ private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
+
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ public void onDestroyActionMode(ActionMode mode) {
+ }
+
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+ };
+
+ public void onClick(View v) {
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ mLauncher.onClick(v);
+ }
+ }
+
+ public boolean onLongClick(View v) {
+ // Return if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return true;
+
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ ShortcutInfo item = (ShortcutInfo) tag;
+ if (!v.isInTouchMode()) {
+ return false;
+ }
+
+ mLauncher.dismissFolderCling(null);
+
+ mLauncher.getWorkspace().onDragStartedWithItem(v);
+ mLauncher.getWorkspace().beginDragShared(v, this);
+ mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
+
+ mCurrentDragInfo = item;
+ mEmptyCell[0] = item.cellX;
+ mEmptyCell[1] = item.cellY;
+ mCurrentDragView = v;
+
+ mContent.removeView(mCurrentDragView);
+ mInfo.remove(mCurrentDragInfo);
+ mDragInProgress = true;
+ mItemAddedBackToSelfViaIcon = false;
+ }
+ return true;
+ }
+
+ public boolean isEditingName() {
+ return mIsEditingName;
+ }
+
+ public void startEditingFolderName() {
+ mFolderName.setHint("");
+ mIsEditingName = true;
+ }
+
+ public void dismissEditingName() {
+ mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ doneEditingFolderName(true);
+ }
+
+ public void doneEditingFolderName(boolean commit) {
+ mFolderName.setHint(sHintText);
+ // Convert to a string here to ensure that no other state associated with the text field
+ // gets saved.
+ String newTitle = mFolderName.getText().toString();
+ mInfo.setTitle(newTitle);
+ LauncherModel.updateItemInDatabase(mLauncher, mInfo);
+
+ if (commit) {
+ sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ String.format(getContext().getString(R.string.folder_renamed), newTitle));
+ }
+ // In order to clear the focus from the text field, we set the focus on ourself. This
+ // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
+ requestFocus();
+
+ Selection.setSelection((Spannable) mFolderName.getText(), 0, 0);
+ mIsEditingName = false;
+ }
+
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ dismissEditingName();
+ return true;
+ }
+ return false;
+ }
+
+ public View getEditTextRegion() {
+ return mFolderName;
+ }
+
+ public Drawable getDragDrawable() {
+ return mIconDrawable;
+ }
+
+ /**
+ * We need to handle touch events to prevent them from falling through to the workspace below.
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return true;
+ }
+
+ public void setDragController(DragController dragController) {
+ mDragController = dragController;
+ }
+
+ void setFolderIcon(FolderIcon icon) {
+ mFolderIcon = icon;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ // When the folder gets focus, we don't want to announce the list of items.
+ return true;
+ }
+
+ /**
+ * @return the FolderInfo object associated with this folder
+ */
+ FolderInfo getInfo() {
+ return mInfo;
+ }
+
+ private class GridComparator implements Comparator<ShortcutInfo> {
+ int mNumCols;
+ public GridComparator(int numCols) {
+ mNumCols = numCols;
+ }
+
+ @Override
+ public int compare(ShortcutInfo lhs, ShortcutInfo rhs) {
+ int lhIndex = lhs.cellY * mNumCols + lhs.cellX;
+ int rhIndex = rhs.cellY * mNumCols + rhs.cellX;
+ return (lhIndex - rhIndex);
+ }
+ }
+
+ private void placeInReadingOrder(ArrayList<ShortcutInfo> items) {
+ int maxX = 0;
+ int count = items.size();
+ for (int i = 0; i < count; i++) {
+ ShortcutInfo item = items.get(i);
+ if (item.cellX > maxX) {
+ maxX = item.cellX;
+ }
+ }
+
+ GridComparator gridComparator = new GridComparator(maxX + 1);
+ Collections.sort(items, gridComparator);
+ final int countX = mContent.getCountX();
+ for (int i = 0; i < count; i++) {
+ int x = i % countX;
+ int y = i / countX;
+ ShortcutInfo item = items.get(i);
+ item.cellX = x;
+ item.cellY = y;
+ }
+ }
+
+ void bind(FolderInfo info) {
+ mInfo = info;
+ ArrayList<ShortcutInfo> children = info.contents;
+ ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
+ setupContentForNumItems(children.size());
+ placeInReadingOrder(children);
+ int count = 0;
+ for (int i = 0; i < children.size(); i++) {
+ ShortcutInfo child = (ShortcutInfo) children.get(i);
+ if (!createAndAddShortcut(child)) {
+ overflow.add(child);
+ } else {
+ count++;
+ }
+ }
+
+ // We rearrange the items in case there are any empty gaps
+ setupContentForNumItems(count);
+
+ // If our folder has too many items we prune them from the list. This is an issue
+ // when upgrading from the old Folders implementation which could contain an unlimited
+ // number of items.
+ for (ShortcutInfo item: overflow) {
+ mInfo.remove(item);
+ LauncherModel.deleteItemFromDatabase(mLauncher, item);
+ }
+
+ mItemsInvalidated = true;
+ updateTextViewFocus();
+ mInfo.addListener(this);
+
+ if (!sDefaultFolderName.contentEquals(mInfo.title)) {
+ mFolderName.setText(mInfo.title);
+ } else {
+ mFolderName.setText("");
+ }
+ updateItemLocationsInDatabase();
+ }
+
+ /**
+ * Creates a new UserFolder, inflated from R.layout.user_folder.
+ *
+ * @param context The application's context.
+ *
+ * @return A new UserFolder.
+ */
+ static Folder fromXml(Context context) {
+ return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
+ }
+
+ /**
+ * This method is intended to make the UserFolder to be visually identical in size and position
+ * to its associated FolderIcon. This allows for a seamless transition into the expanded state.
+ */
+ private void positionAndSizeAsIcon() {
+ if (!(getParent() instanceof DragLayer)) return;
+ setScaleX(0.8f);
+ setScaleY(0.8f);
+ setAlpha(0f);
+ mState = STATE_SMALL;
+ }
+
+ public void animateOpen() {
+ positionAndSizeAsIcon();
+
+ if (!(getParent() instanceof DragLayer)) return;
+ centerAboutIcon();
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+ final ObjectAnimator oa =
+ LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
+
+ oa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ String.format(getContext().getString(R.string.folder_opened),
+ mContent.getCountX(), mContent.getCountY()));
+ mState = STATE_ANIMATING;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mState = STATE_OPEN;
+ setLayerType(LAYER_TYPE_NONE, null);
+ Cling cling = mLauncher.showFirstRunFoldersCling();
+ if (cling != null) {
+ cling.bringScrimToFront();
+ bringToFront();
+ cling.bringToFront();
+ }
+ setFocusOnFirstChild();
+ }
+ });
+ oa.setDuration(mExpandDuration);
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ oa.start();
+ }
+
+ private void sendCustomAccessibilityEvent(int type, String text) {
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (accessibilityManager.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(type);
+ onInitializeAccessibilityEvent(event);
+ event.getText().add(text);
+ accessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+
+ private void setFocusOnFirstChild() {
+ View firstChild = mContent.getChildAt(0, 0);
+ if (firstChild != null) {
+ firstChild.requestFocus();
+ }
+ }
+
+ public void animateClosed() {
+ if (!(getParent() instanceof DragLayer)) return;
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f);
+ final ObjectAnimator oa =
+ LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY);
+
+ oa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onCloseComplete();
+ setLayerType(LAYER_TYPE_NONE, null);
+ mState = STATE_SMALL;
+ }
+ @Override
+ public void onAnimationStart(Animator animation) {
+ sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ getContext().getString(R.string.folder_closed));
+ mState = STATE_ANIMATING;
+ }
+ });
+ oa.setDuration(mExpandDuration);
+ setLayerType(LAYER_TYPE_HARDWARE, null);
+ oa.start();
+ }
+
+ public boolean acceptDrop(DragObject d) {
+ final ItemInfo item = (ItemInfo) d.dragInfo;
+ final int itemType = item.itemType;
+ return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+ itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+ !isFull());
+ }
+
+ protected boolean findAndSetEmptyCells(ShortcutInfo item) {
+ int[] emptyCell = new int[2];
+ if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
+ item.cellX = emptyCell[0];
+ item.cellY = emptyCell[1];
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected boolean createAndAddShortcut(ShortcutInfo item) {
+ final BubbleTextView textView =
+ (BubbleTextView) mInflater.inflate(R.layout.application, this, false);
+ textView.setCompoundDrawablesWithIntrinsicBounds(null,
+ new FastBitmapDrawable(item.getIcon(mIconCache)), null, null);
+ textView.setText(item.title);
+ textView.setTag(item);
+ textView.setTextColor(getResources().getColor(R.color.folder_items_text_color));
+ textView.setShadowsEnabled(false);
+
+ textView.setOnClickListener(this);
+ textView.setOnLongClickListener(this);
+
+ // We need to check here to verify that the given item's location isn't already occupied
+ // by another item.
+ if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
+ || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
+ // This shouldn't happen, log it.
+ Log.e(TAG, "Folder order not properly persisted during bind");
+ if (!findAndSetEmptyCells(item)) {
+ return false;
+ }
+ }
+
+ CellLayout.LayoutParams lp =
+ new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
+ boolean insert = false;
+ textView.setOnKeyListener(new FolderKeyEventListener());
+ mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
+ return true;
+ }
+
+ public void onDragEnter(DragObject d) {
+ mPreviousTargetCell[0] = -1;
+ mPreviousTargetCell[1] = -1;
+ mOnExitAlarm.cancelAlarm();
+ }
+
+ OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
+ public void onAlarm(Alarm alarm) {
+ realTimeReorder(mEmptyCell, mTargetCell);
+ }
+ };
+
+ boolean readingOrderGreaterThan(int[] v1, int[] v2) {
+ if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void realTimeReorder(int[] empty, int[] target) {
+ boolean wrap;
+ int startX;
+ int endX;
+ int startY;
+ int delay = 0;
+ float delayAmount = 30;
+ if (readingOrderGreaterThan(target, empty)) {
+ wrap = empty[0] >= mContent.getCountX() - 1;
+ startY = wrap ? empty[1] + 1 : empty[1];
+ for (int y = startY; y <= target[1]; y++) {
+ startX = y == empty[1] ? empty[0] + 1 : 0;
+ endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
+ for (int x = startX; x <= endX; x++) {
+ View v = mContent.getChildAt(x,y);
+ if (mContent.animateChildToPosition(v, empty[0], empty[1],
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
+ empty[0] = x;
+ empty[1] = y;
+ delay += delayAmount;
+ delayAmount *= 0.9;
+ }
+ }
+ }
+ } else {
+ wrap = empty[0] == 0;
+ startY = wrap ? empty[1] - 1 : empty[1];
+ for (int y = startY; y >= target[1]; y--) {
+ startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
+ endX = y > target[1] ? 0 : target[0];
+ for (int x = startX; x >= endX; x--) {
+ View v = mContent.getChildAt(x,y);
+ if (mContent.animateChildToPosition(v, empty[0], empty[1],
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
+ empty[0] = x;
+ empty[1] = y;
+ delay += delayAmount;
+ delayAmount *= 0.9;
+ }
+ }
+ }
+ }
+ }
+
+ public boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ }
+
+ public void onDragOver(DragObject d) {
+ final DragView dragView = d.dragView;
+ final int scrollOffset = mScrollView.getScrollY();
+ final float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, dragView, null);
+ r[0] -= getPaddingLeft();
+ r[1] -= getPaddingTop();
+
+ final long downTime = SystemClock.uptimeMillis();
+ final MotionEvent translatedEv = MotionEvent.obtain(
+ downTime, downTime, MotionEvent.ACTION_MOVE, d.x, d.y, 0);
+
+ if (!mAutoScrollHelper.isEnabled()) {
+ mAutoScrollHelper.setEnabled(true);
+ }
+
+ final boolean handled = mAutoScrollHelper.onTouch(this, translatedEv);
+ translatedEv.recycle();
+
+ if (handled) {
+ mReorderAlarm.cancelAlarm();
+ } else {
+ mTargetCell = mContent.findNearestArea(
+ (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
+ if (isLayoutRtl()) {
+ mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
+ }
+ if (mTargetCell[0] != mPreviousTargetCell[0]
+ || mTargetCell[1] != mPreviousTargetCell[1]) {
+ mReorderAlarm.cancelAlarm();
+ mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
+ mReorderAlarm.setAlarm(REORDER_DELAY);
+ mPreviousTargetCell[0] = mTargetCell[0];
+ mPreviousTargetCell[1] = mTargetCell[1];
+ mDragMode = DRAG_MODE_REORDER;
+ } else {
+ mDragMode = DRAG_MODE_NONE;
+ }
+ }
+ }
+
+ // This is used to compute the visual center of the dragView. The idea is that
+ // the visual center represents the user's interpretation of where the item is, and hence
+ // is the appropriate point to use when determining drop location.
+ private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
+ DragView dragView, float[] recycle) {
+ float res[];
+ if (recycle == null) {
+ res = new float[2];
+ } else {
+ res = recycle;
+ }
+
+ // These represent the visual top and left of drag view if a dragRect was provided.
+ // If a dragRect was not provided, then they correspond to the actual view left and
+ // top, as the dragRect is in that case taken to be the entire dragView.
+ // R.dimen.dragViewOffsetY.
+ int left = x - xOffset;
+ int top = y - yOffset;
+
+ // In order to find the visual center, we shift by half the dragRect
+ res[0] = left + dragView.getDragRegion().width() / 2;
+ res[1] = top + dragView.getDragRegion().height() / 2;
+
+ return res;
+ }
+
+ OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
+ public void onAlarm(Alarm alarm) {
+ completeDragExit();
+ }
+ };
+
+ public void completeDragExit() {
+ mLauncher.closeFolder();
+ mCurrentDragInfo = null;
+ mCurrentDragView = null;
+ mSuppressOnAdd = false;
+ mRearrangeOnClose = true;
+ }
+
+ public void onDragExit(DragObject d) {
+ // Exiting folder; stop the auto scroller.
+ mAutoScrollHelper.setEnabled(false);
+ // We only close the folder if this is a true drag exit, ie. not because
+ // a drop has occurred above the folder.
+ if (!d.dragComplete) {
+ mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
+ mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
+ }
+ mReorderAlarm.cancelAlarm();
+ mDragMode = DRAG_MODE_NONE;
+ }
+
+ public void onDropCompleted(final View target, final DragObject d,
+ final boolean isFlingToDelete, final boolean success) {
+ if (mDeferDropAfterUninstall) {
+ Log.d(TAG, "Deferred handling drop because waiting for uninstall.");
+ mDeferredAction = new Runnable() {
+ public void run() {
+ onDropCompleted(target, d, isFlingToDelete, success);
+ mDeferredAction = null;
+ }
+ };
+ return;
+ }
+
+ boolean beingCalledAfterUninstall = mDeferredAction != null;
+ boolean successfulDrop =
+ success && (!beingCalledAfterUninstall || mUninstallSuccessful);
+
+ if (successfulDrop) {
+ if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) {
+ replaceFolderWithFinalItem();
+ }
+ } else {
+ setupContentForNumItems(getItemCount());
+ // The drag failed, we need to return the item to the folder
+ mFolderIcon.onDrop(d);
+ }
+
+ if (target != this) {
+ if (mOnExitAlarm.alarmPending()) {
+ mOnExitAlarm.cancelAlarm();
+ if (!successfulDrop) {
+ mSuppressFolderDeletion = true;
+ }
+ completeDragExit();
+ }
+ }
+
+ mDeleteFolderOnDropCompleted = false;
+ mDragInProgress = false;
+ mItemAddedBackToSelfViaIcon = false;
+ mCurrentDragInfo = null;
+ mCurrentDragView = null;
+ mSuppressOnAdd = false;
+
+ // Reordering may have occured, and we need to save the new item locations. We do this once
+ // at the end to prevent unnecessary database operations.
+ updateItemLocationsInDatabaseBatch();
+ }
+
+ public void deferCompleteDropAfterUninstallActivity() {
+ mDeferDropAfterUninstall = true;
+ }
+
+ public void onUninstallActivityReturned(boolean success) {
+ mDeferDropAfterUninstall = false;
+ mUninstallSuccessful = success;
+ if (mDeferredAction != null) {
+ mDeferredAction.run();
+ }
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
+ }
+
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ // Do nothing
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // Do nothing
+ }
+
+ private void updateItemLocationsInDatabase() {
+ ArrayList<View> list = getItemsInReadingOrder();
+ for (int i = 0; i < list.size(); i++) {
+ View v = list.get(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ LauncherModel.moveItemInDatabase(mLauncher, info, mInfo.id, 0,
+ info.cellX, info.cellY);
+ }
+ }
+
+ private void updateItemLocationsInDatabaseBatch() {
+ ArrayList<View> list = getItemsInReadingOrder();
+ ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+ for (int i = 0; i < list.size(); i++) {
+ View v = list.get(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ items.add(info);
+ }
+
+ LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0);
+ }
+
+ public void addItemLocationsInDatabase() {
+ ArrayList<View> list = getItemsInReadingOrder();
+ for (int i = 0; i < list.size(); i++) {
+ View v = list.get(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ LauncherModel.addItemToDatabase(mLauncher, info, mInfo.id, 0,
+ info.cellX, info.cellY, false);
+ }
+ }
+
+ public void notifyDrop() {
+ if (mDragInProgress) {
+ mItemAddedBackToSelfViaIcon = true;
+ }
+ }
+
+ public boolean isDropEnabled() {
+ return true;
+ }
+
+ private void setupContentDimensions(int count) {
+ ArrayList<View> list = getItemsInReadingOrder();
+
+ int countX = mContent.getCountX();
+ int countY = mContent.getCountY();
+ boolean done = false;
+
+ while (!done) {
+ int oldCountX = countX;
+ int oldCountY = countY;
+ if (countX * countY < count) {
+ // Current grid is too small, expand it
+ if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
+ countX++;
+ } else if (countY < mMaxCountY) {
+ countY++;
+ }
+ if (countY == 0) countY++;
+ } else if ((countY - 1) * countX >= count && countY >= countX) {
+ countY = Math.max(0, countY - 1);
+ } else if ((countX - 1) * countY >= count) {
+ countX = Math.max(0, countX - 1);
+ }
+ done = countX == oldCountX && countY == oldCountY;
+ }
+ mContent.setGridSize(countX, countY);
+ arrangeChildren(list);
+ }
+
+ public boolean isFull() {
+ return getItemCount() >= mMaxNumItems;
+ }
+
+ private void centerAboutIcon() {
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+
+ DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
+ int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ int height = getFolderHeight();
+
+ float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2);
+ int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2);
+ int centeredLeft = centerX - width / 2;
+ int centeredTop = centerY - height / 2;
+ int currentPage = mLauncher.getWorkspace().getNextPage();
+ // In case the workspace is scrolling, we need to use the final scroll to compute
+ // the folders bounds.
+ mLauncher.getWorkspace().setFinalScrollForPageChange(currentPage);
+ // We first fetch the currently visible CellLayoutChildren
+ CellLayout currentLayout = (CellLayout) mLauncher.getWorkspace().getChildAt(currentPage);
+ ShortcutAndWidgetContainer boundingLayout = currentLayout.getShortcutsAndWidgets();
+ Rect bounds = new Rect();
+ parent.getDescendantRectRelativeToSelf(boundingLayout, bounds);
+ // We reset the workspaces scroll
+ mLauncher.getWorkspace().resetFinalScrollForPageChange(currentPage);
+
+ // We need to bound the folder to the currently visible CellLayoutChildren
+ int left = Math.min(Math.max(bounds.left, centeredLeft),
+ bounds.left + bounds.width() - width);
+ int top = Math.min(Math.max(bounds.top, centeredTop),
+ bounds.top + bounds.height() - height);
+ if (grid.isPhone() && (grid.availableWidthPx - width) < grid.iconSizePx) {
+ // Center the folder if it is full (on phones only)
+ left = (grid.availableWidthPx - width) / 2;
+ } else if (width >= bounds.width()) {
+ // If the folder doesn't fit within the bounds, center it about the desired bounds
+ left = bounds.left + (bounds.width() - width) / 2;
+ }
+ if (height >= bounds.height()) {
+ top = bounds.top + (bounds.height() - height) / 2;
+ }
+
+ int folderPivotX = width / 2 + (centeredLeft - left);
+ int folderPivotY = height / 2 + (centeredTop - top);
+ setPivotX(folderPivotX);
+ setPivotY(folderPivotY);
+ mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
+ (1.0f * folderPivotX / width));
+ mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
+ (1.0f * folderPivotY / height));
+
+ lp.width = width;
+ lp.height = height;
+ lp.x = left;
+ lp.y = top;
+ }
+
+ float getPivotXForIconAnimation() {
+ return mFolderIconPivotX;
+ }
+ float getPivotYForIconAnimation() {
+ return mFolderIconPivotY;
+ }
+
+ private void setupContentForNumItems(int count) {
+ setupContentDimensions(count);
+
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ if (lp == null) {
+ lp = new DragLayer.LayoutParams(0, 0);
+ lp.customPosition = true;
+ setLayoutParams(lp);
+ }
+ centerAboutIcon();
+ }
+
+ private int getContentAreaHeight() {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Rect workspacePadding = grid.getWorkspacePadding(grid.isLandscape ?
+ CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
+ int maxContentAreaHeight = grid.availableHeightPx -
+ 4 * grid.edgeMarginPx -
+ workspacePadding.top - workspacePadding.bottom -
+ getPaddingTop() - getPaddingBottom() -
+ mFolderNameHeight;
+ return Math.min(maxContentAreaHeight,
+ mContent.getDesiredHeight());
+ }
+
+ private int getFolderHeight() {
+ int height = getPaddingTop() + getPaddingBottom()
+ + getContentAreaHeight() + mFolderNameHeight;
+ return height;
+ }
+
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
+ int height = getFolderHeight();
+ int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(),
+ MeasureSpec.EXACTLY);
+ int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
+ MeasureSpec.EXACTLY);
+ mContent.setFixedSize(mContent.getDesiredWidth(), mContent.getDesiredHeight());
+ mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
+ mFolderName.measure(contentAreaWidthSpec,
+ MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
+ setMeasuredDimension(width, height);
+ }
+
+ private void arrangeChildren(ArrayList<View> list) {
+ int[] vacant = new int[2];
+ if (list == null) {
+ list = getItemsInReadingOrder();
+ }
+ mContent.removeAllViews();
+
+ for (int i = 0; i < list.size(); i++) {
+ View v = list.get(i);
+ mContent.getVacantCell(vacant, 1, 1);
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+ lp.cellX = vacant[0];
+ lp.cellY = vacant[1];
+ ItemInfo info = (ItemInfo) v.getTag();
+ if (info.cellX != vacant[0] || info.cellY != vacant[1]) {
+ info.cellX = vacant[0];
+ info.cellY = vacant[1];
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
+ info.cellX, info.cellY);
+ }
+ boolean insert = false;
+ mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
+ }
+ mItemsInvalidated = true;
+ }
+
+ public int getItemCount() {
+ return mContent.getShortcutsAndWidgets().getChildCount();
+ }
+
+ public View getItemAt(int index) {
+ return mContent.getShortcutsAndWidgets().getChildAt(index);
+ }
+
+ private void onCloseComplete() {
+ DragLayer parent = (DragLayer) getParent();
+ if (parent != null) {
+ parent.removeView(this);
+ }
+ mDragController.removeDropTarget((DropTarget) this);
+ clearFocus();
+ mFolderIcon.requestFocus();
+
+ if (mRearrangeOnClose) {
+ setupContentForNumItems(getItemCount());
+ mRearrangeOnClose = false;
+ }
+ if (getItemCount() <= 1) {
+ if (!mDragInProgress && !mSuppressFolderDeletion) {
+ replaceFolderWithFinalItem();
+ } else if (mDragInProgress) {
+ mDeleteFolderOnDropCompleted = true;
+ }
+ }
+ mSuppressFolderDeletion = false;
+ }
+
+ private void replaceFolderWithFinalItem() {
+ // Add the last remaining child to the workspace in place of the folder
+ Runnable onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screenId);
+
+ View child = null;
+ // Move the item from the folder to the workspace, in the position of the folder
+ if (getItemCount() == 1) {
+ ShortcutInfo finalItem = mInfo.contents.get(0);
+ child = mLauncher.createShortcut(R.layout.application, cellLayout,
+ finalItem);
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container,
+ mInfo.screenId, mInfo.cellX, mInfo.cellY);
+ }
+ if (getItemCount() <= 1) {
+ // Remove the folder
+ LauncherModel.deleteItemFromDatabase(mLauncher, mInfo);
+ cellLayout.removeView(mFolderIcon);
+ if (mFolderIcon instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) mFolderIcon);
+ }
+ mLauncher.removeFolder(mInfo);
+ }
+ // We add the child after removing the folder to prevent both from existing at
+ // the same time in the CellLayout. We need to add the new item with addInScreenFromBind()
+ // to ensure that hotseat items are placed correctly.
+ if (child != null) {
+ mLauncher.getWorkspace().addInScreenFromBind(child, mInfo.container, mInfo.screenId,
+ mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY);
+ }
+ }
+ };
+ View finalChild = getItemAt(0);
+ if (finalChild != null) {
+ mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
+ }
+ mDestroyed = true;
+ }
+
+ boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ // This method keeps track of the last item in the folder for the purposes
+ // of keyboard focus
+ private void updateTextViewFocus() {
+ View lastChild = getItemAt(getItemCount() - 1);
+ getItemAt(getItemCount() - 1);
+ if (lastChild != null) {
+ mFolderName.setNextFocusDownId(lastChild.getId());
+ mFolderName.setNextFocusRightId(lastChild.getId());
+ mFolderName.setNextFocusLeftId(lastChild.getId());
+ mFolderName.setNextFocusUpId(lastChild.getId());
+ }
+ }
+
+ public void onDrop(DragObject d) {
+ ShortcutInfo item;
+ if (d.dragInfo instanceof AppInfo) {
+ // Came from all apps -- make a copy
+ item = ((AppInfo) d.dragInfo).makeShortcut();
+ item.spanX = 1;
+ item.spanY = 1;
+ } else {
+ item = (ShortcutInfo) d.dragInfo;
+ }
+ // Dragged from self onto self, currently this is the only path possible, however
+ // we keep this as a distinct code path.
+ if (item == mCurrentDragInfo) {
+ ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
+ si.cellX = lp.cellX = mEmptyCell[0];
+ si.cellX = lp.cellY = mEmptyCell[1];
+ mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
+ if (d.dragView.hasDrawn()) {
+ mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView);
+ } else {
+ d.deferDragViewCleanupPostAnimation = false;
+ mCurrentDragView.setVisibility(VISIBLE);
+ }
+ mItemsInvalidated = true;
+ setupContentDimensions(getItemCount());
+ mSuppressOnAdd = true;
+ }
+ mInfo.add(item);
+ }
+
+ // This is used so the item doesn't immediately appear in the folder when added. In one case
+ // we need to create the illusion that the item isn't added back to the folder yet, to
+ // to correspond to the animation of the icon back into the folder. This is
+ public void hideItem(ShortcutInfo info) {
+ View v = getViewForInfo(info);
+ v.setVisibility(INVISIBLE);
+ }
+ public void showItem(ShortcutInfo info) {
+ View v = getViewForInfo(info);
+ v.setVisibility(VISIBLE);
+ }
+
+ public void onAdd(ShortcutInfo item) {
+ mItemsInvalidated = true;
+ // If the item was dropped onto this open folder, we have done the work associated
+ // with adding the item to the folder, as indicated by mSuppressOnAdd being set
+ if (mSuppressOnAdd) return;
+ if (!findAndSetEmptyCells(item)) {
+ // The current layout is full, can we expand it?
+ setupContentForNumItems(getItemCount() + 1);
+ findAndSetEmptyCells(item);
+ }
+ createAndAddShortcut(item);
+ LauncherModel.addOrMoveItemInDatabase(
+ mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
+ }
+
+ public void onRemove(ShortcutInfo item) {
+ mItemsInvalidated = true;
+ // If this item is being dragged from this open folder, we have already handled
+ // the work associated with removing the item, so we don't have to do anything here.
+ if (item == mCurrentDragInfo) return;
+ View v = getViewForInfo(item);
+ mContent.removeView(v);
+ if (mState == STATE_ANIMATING) {
+ mRearrangeOnClose = true;
+ } else {
+ setupContentForNumItems(getItemCount());
+ }
+ if (getItemCount() <= 1) {
+ replaceFolderWithFinalItem();
+ }
+ }
+
+ private View getViewForInfo(ShortcutInfo item) {
+ for (int j = 0; j < mContent.getCountY(); j++) {
+ for (int i = 0; i < mContent.getCountX(); i++) {
+ View v = mContent.getChildAt(i, j);
+ if (v.getTag() == item) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+
+ public void onItemsChanged() {
+ updateTextViewFocus();
+ }
+
+ public void onTitleChanged(CharSequence title) {
+ }
+
+ public ArrayList<View> getItemsInReadingOrder() {
+ if (mItemsInvalidated) {
+ mItemsInReadingOrder.clear();
+ for (int j = 0; j < mContent.getCountY(); j++) {
+ for (int i = 0; i < mContent.getCountX(); i++) {
+ View v = mContent.getChildAt(i, j);
+ if (v != null) {
+ mItemsInReadingOrder.add(v);
+ }
+ }
+ }
+ mItemsInvalidated = false;
+ }
+ return mItemsInReadingOrder;
+ }
+
+ public void getLocationInDragLayer(int[] loc) {
+ mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
+ }
+
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (v == mFolderName && hasFocus) {
+ startEditingFolderName();
+ }
+ }
+
+ @Override
+ public void getHitRectRelativeToDragLayer(Rect outRect) {
+ getHitRect(outRect);
+ }
+}
diff --git a/src/com/android/launcher3/FolderAutoScrollHelper.java b/src/com/android/launcher3/FolderAutoScrollHelper.java
new file mode 100644
index 0000000..40e8884
--- /dev/null
+++ b/src/com/android/launcher3/FolderAutoScrollHelper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 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.support.v4.widget.AutoScrollHelper;
+import android.widget.ScrollView;
+
+/**
+ * An implementation of {@link AutoScrollHelper} that knows how to scroll
+ * through a {@link Folder}.
+ */
+public class FolderAutoScrollHelper extends AutoScrollHelper {
+ private static final float MAX_SCROLL_VELOCITY = 1500f;
+
+ private final ScrollView mTarget;
+
+ public FolderAutoScrollHelper(ScrollView target) {
+ super(target);
+
+ mTarget = target;
+
+ setActivationDelay(0);
+ setEdgeType(EDGE_TYPE_INSIDE_EXTEND);
+ setExclusive(true);
+ setMaximumVelocity(MAX_SCROLL_VELOCITY, MAX_SCROLL_VELOCITY);
+ setRampDownDuration(0);
+ setRampUpDuration(0);
+ }
+
+ @Override
+ public void scrollTargetBy(int deltaX, int deltaY) {
+ mTarget.scrollBy(deltaX, deltaY);
+ }
+
+ @Override
+ public boolean canTargetScrollHorizontally(int direction) {
+ // List do not scroll horizontally.
+ return false;
+ }
+
+ @Override
+ public boolean canTargetScrollVertically(int direction) {
+ return mTarget.canScrollVertically(direction);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/FolderEditText.java b/src/com/android/launcher3/FolderEditText.java
new file mode 100644
index 0000000..c311008
--- /dev/null
+++ b/src/com/android/launcher3/FolderEditText.java
@@ -0,0 +1,36 @@
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+public class FolderEditText extends EditText {
+
+ private Folder mFolder;
+
+ public FolderEditText(Context context) {
+ super(context);
+ }
+
+ public FolderEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FolderEditText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setFolder(Folder folder) {
+ mFolder = folder;
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ // Catch the back button on the soft keyboard so that we can just close the activity
+ if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) {
+ mFolder.doneEditingFolderName(true);
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+}
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
new file mode 100644
index 0000000..7e1e350
--- /dev/null
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.FolderInfo.FolderListener;
+
+import java.util.ArrayList;
+
+/**
+ * An icon that can appear on in the workspace representing an {@link UserFolder}.
+ */
+public class FolderIcon extends LinearLayout implements FolderListener {
+ private Launcher mLauncher;
+ private Folder mFolder;
+ private FolderInfo mInfo;
+ private static boolean sStaticValuesDirty = true;
+
+ private CheckLongPressHelper mLongPressHelper;
+
+ // The number of icons to display in the
+ private static final int NUM_ITEMS_IN_PREVIEW = 3;
+ private static final int CONSUMPTION_ANIMATION_DURATION = 100;
+ private static final int DROP_IN_ANIMATION_DURATION = 400;
+ private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
+ private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
+
+ // The degree to which the inner ring grows when accepting drop
+ private static final float INNER_RING_GROWTH_FACTOR = 0.15f;
+
+ // The degree to which the outer ring is scaled in its natural state
+ private static final float OUTER_RING_GROWTH_FACTOR = 0.3f;
+
+ // The amount of vertical spread between items in the stack [0...1]
+ private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
+
+ // Flag as to whether or not to draw an outer ring. Currently none is designed.
+ public static final boolean HAS_OUTER_RING = true;
+
+ // The degree to which the item in the back of the stack is scaled [0...1]
+ // (0 means it's not scaled at all, 1 means it's scaled to nothing)
+ private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
+
+ public static Drawable sSharedFolderLeaveBehind = null;
+
+ private ImageView mPreviewBackground;
+ private BubbleTextView mFolderName;
+
+ FolderRingAnimator mFolderRingAnimator = null;
+
+ // These variables are all associated with the drawing of the preview; they are stored
+ // as member variables for shared usage and to avoid computation on each frame
+ private int mIntrinsicIconSize;
+ private float mBaselineIconScale;
+ private int mBaselineIconSize;
+ private int mAvailableSpaceInPreview;
+ private int mTotalWidth = -1;
+ private int mPreviewOffsetX;
+ private int mPreviewOffsetY;
+ private float mMaxPerspectiveShift;
+ boolean mAnimating = false;
+
+ private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+ private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
+ private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
+
+ public FolderIcon(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public FolderIcon(Context context) {
+ super(context);
+ init();
+ }
+
+ private void init() {
+ mLongPressHelper = new CheckLongPressHelper(this);
+ }
+
+ public boolean isDropEnabled() {
+ final ViewGroup cellLayoutChildren = (ViewGroup) getParent();
+ final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent();
+ final Workspace workspace = (Workspace) cellLayout.getParent();
+ return !workspace.isSmall();
+ }
+
+ static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
+ FolderInfo folderInfo, IconCache iconCache) {
+ @SuppressWarnings("all") // suppress dead code warning
+ final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
+ if (error) {
+ throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
+ "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
+ "is dependent on this");
+ }
+
+ FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
+ icon.setClipToPadding(false);
+ icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
+ icon.mFolderName.setText(folderInfo.title);
+ icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ // Offset the preview background to center this view accordingly
+ LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams();
+ lp.topMargin = grid.folderBackgroundOffset;
+ lp.width = grid.folderIconSizePx;
+ lp.height = grid.folderIconSizePx;
+
+ icon.setTag(folderInfo);
+ icon.setOnClickListener(launcher);
+ icon.mInfo = folderInfo;
+ icon.mLauncher = launcher;
+ icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
+ folderInfo.title));
+ Folder folder = Folder.fromXml(launcher);
+ folder.setDragController(launcher.getDragController());
+ folder.setFolderIcon(icon);
+ folder.bind(folderInfo);
+ icon.mFolder = folder;
+
+ icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
+ folderInfo.addListener(icon);
+
+ return icon;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ sStaticValuesDirty = true;
+ return super.onSaveInstanceState();
+ }
+
+ public static class FolderRingAnimator {
+ public int mCellX;
+ public int mCellY;
+ private CellLayout mCellLayout;
+ public float mOuterRingSize;
+ public float mInnerRingSize;
+ public FolderIcon mFolderIcon = null;
+ public static Drawable sSharedOuterRingDrawable = null;
+ public static Drawable sSharedInnerRingDrawable = null;
+ public static int sPreviewSize = -1;
+ public static int sPreviewPadding = -1;
+
+ private ValueAnimator mAcceptAnimator;
+ private ValueAnimator mNeutralAnimator;
+
+ public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
+ mFolderIcon = folderIcon;
+ Resources res = launcher.getResources();
+
+ // We need to reload the static values when configuration changes in case they are
+ // different in another configuration
+ if (sStaticValuesDirty) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread "
+ + Thread.currentThread());
+ }
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ sPreviewSize = grid.folderIconSizePx;
+ sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
+ sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
+ sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip_holo);
+ sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest);
+ sStaticValuesDirty = false;
+ }
+ }
+
+ public void animateToAcceptState() {
+ if (mNeutralAnimator != null) {
+ mNeutralAnimator.cancel();
+ }
+ mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
+ mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
+
+ final int previewSize = sPreviewSize;
+ mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float percent = (Float) animation.getAnimatedValue();
+ mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize;
+ mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize;
+ if (mCellLayout != null) {
+ mCellLayout.invalidate();
+ }
+ }
+ });
+ mAcceptAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mFolderIcon != null) {
+ mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE);
+ }
+ }
+ });
+ mAcceptAnimator.start();
+ }
+
+ public void animateToNaturalState() {
+ if (mAcceptAnimator != null) {
+ mAcceptAnimator.cancel();
+ }
+ mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
+ mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
+
+ final int previewSize = sPreviewSize;
+ mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float percent = (Float) animation.getAnimatedValue();
+ mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize;
+ mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize;
+ if (mCellLayout != null) {
+ mCellLayout.invalidate();
+ }
+ }
+ });
+ mNeutralAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCellLayout != null) {
+ mCellLayout.hideFolderAccept(FolderRingAnimator.this);
+ }
+ if (mFolderIcon != null) {
+ mFolderIcon.mPreviewBackground.setVisibility(VISIBLE);
+ }
+ }
+ });
+ mNeutralAnimator.start();
+ }
+
+ // Location is expressed in window coordinates
+ public void getCell(int[] loc) {
+ loc[0] = mCellX;
+ loc[1] = mCellY;
+ }
+
+ // Location is expressed in window coordinates
+ public void setCell(int x, int y) {
+ mCellX = x;
+ mCellY = y;
+ }
+
+ public void setCellLayout(CellLayout layout) {
+ mCellLayout = layout;
+ }
+
+ public float getOuterRingSize() {
+ return mOuterRingSize;
+ }
+
+ public float getInnerRingSize() {
+ return mInnerRingSize;
+ }
+ }
+
+ Folder getFolder() {
+ return mFolder;
+ }
+
+ FolderInfo getFolderInfo() {
+ return mInfo;
+ }
+
+ private boolean willAcceptItem(ItemInfo item) {
+ final int itemType = item.itemType;
+ return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+ itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
+ !mFolder.isFull() && item != mInfo && !mInfo.opened);
+ }
+
+ public boolean acceptDrop(Object dragInfo) {
+ final ItemInfo item = (ItemInfo) dragInfo;
+ return !mFolder.isDestroyed() && willAcceptItem(item);
+ }
+
+ public void addItem(ShortcutInfo item) {
+ mInfo.add(item);
+ }
+
+ public void onDragEnter(Object dragInfo) {
+ if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return;
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
+ CellLayout layout = (CellLayout) getParent().getParent();
+ mFolderRingAnimator.setCell(lp.cellX, lp.cellY);
+ mFolderRingAnimator.setCellLayout(layout);
+ mFolderRingAnimator.animateToAcceptState();
+ layout.showFolderAccept(mFolderRingAnimator);
+ }
+
+ public void onDragOver(Object dragInfo) {
+ }
+
+ public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
+ final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
+ float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
+
+ // These correspond two the drawable and view that the icon was dropped _onto_
+ Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
+ computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
+ destView.getMeasuredWidth());
+
+ // This will animate the first item from it's position as an icon into its
+ // position as the first item in the preview
+ animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
+ addItem(destInfo);
+
+ // This will animate the dragView (srcView) into the new folder
+ onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null);
+ }
+
+ public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
+ Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
+ computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
+ finalView.getMeasuredWidth());
+
+ // This will animate the first item from it's position as an icon into its
+ // position as the first item in the preview
+ animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true,
+ onCompleteRunnable);
+ }
+
+ public void onDragExit(Object dragInfo) {
+ onDragExit();
+ }
+
+ public void onDragExit() {
+ mFolderRingAnimator.animateToNaturalState();
+ }
+
+ private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
+ float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable,
+ DragObject d) {
+ item.cellX = -1;
+ item.cellY = -1;
+
+ // Typically, the animateView corresponds to the DragView; however, if this is being done
+ // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
+ // will not have a view to animate
+ if (animateView != null) {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ Rect from = new Rect();
+ dragLayer.getViewRectRelativeToSelf(animateView, from);
+ Rect to = finalRect;
+ if (to == null) {
+ to = new Rect();
+ Workspace workspace = mLauncher.getWorkspace();
+ // Set cellLayout and this to it's final state to compute final animation locations
+ workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
+ float scaleX = getScaleX();
+ float scaleY = getScaleY();
+ setScaleX(1.0f);
+ setScaleY(1.0f);
+ scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
+ // Finished computing final animation locations, restore current state
+ setScaleX(scaleX);
+ setScaleY(scaleY);
+ workspace.resetTransitionTransform((CellLayout) getParent().getParent());
+ }
+
+ int[] center = new int[2];
+ float scale = getLocalCenterForIndex(index, center);
+ center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
+ center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
+
+ to.offset(center[0] - animateView.getMeasuredWidth() / 2,
+ center[1] - animateView.getMeasuredHeight() / 2);
+
+ float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
+
+ float finalScale = scale * scaleRelativeToDragLayer;
+ dragLayer.animateView(animateView, from, to, finalAlpha,
+ 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
+ new DecelerateInterpolator(2), new AccelerateInterpolator(2),
+ postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
+ addItem(item);
+ mHiddenItems.add(item);
+ mFolder.hideItem(item);
+ postDelayed(new Runnable() {
+ public void run() {
+ mHiddenItems.remove(item);
+ mFolder.showItem(item);
+ invalidate();
+ }
+ }, DROP_IN_ANIMATION_DURATION);
+ } else {
+ addItem(item);
+ }
+ }
+
+ public void onDrop(DragObject d) {
+ ShortcutInfo item;
+ if (d.dragInfo instanceof AppInfo) {
+ // Came from all apps -- make a copy
+ item = ((AppInfo) d.dragInfo).makeShortcut();
+ } else {
+ item = (ShortcutInfo) d.dragInfo;
+ }
+ mFolder.notifyDrop();
+ onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d);
+ }
+
+ private void computePreviewDrawingParams(int drawableSize, int totalSize) {
+ if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ mIntrinsicIconSize = drawableSize;
+ mTotalWidth = totalSize;
+
+ final int previewSize = mPreviewBackground.getLayoutParams().height;
+ final int previewPadding = FolderRingAnimator.sPreviewPadding;
+
+ mAvailableSpaceInPreview = (previewSize - 2 * previewPadding);
+ // cos(45) = 0.707 + ~= 0.1) = 0.8f
+ int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
+
+ int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
+ mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
+
+ mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
+ mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
+
+ mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2;
+ mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset;
+ }
+ }
+
+ private void computePreviewDrawingParams(Drawable d) {
+ computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
+ }
+
+ class PreviewItemDrawingParams {
+ PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) {
+ this.transX = transX;
+ this.transY = transY;
+ this.scale = scale;
+ this.overlayAlpha = overlayAlpha;
+ }
+ float transX;
+ float transY;
+ float scale;
+ int overlayAlpha;
+ Drawable drawable;
+ }
+
+ private float getLocalCenterForIndex(int index, int[] center) {
+ mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams);
+
+ mParams.transX += mPreviewOffsetX;
+ mParams.transY += mPreviewOffsetY;
+ float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2;
+ float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2;
+
+ center[0] = (int) Math.round(offsetX);
+ center[1] = (int) Math.round(offsetY);
+ return mParams.scale;
+ }
+
+ private PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
+ PreviewItemDrawingParams params) {
+ index = NUM_ITEMS_IN_PREVIEW - index - 1;
+ float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
+ float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
+
+ float offset = (1 - r) * mMaxPerspectiveShift;
+ float scaledSize = scale * mBaselineIconSize;
+ float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
+
+ // We want to imagine our coordinates from the bottom left, growing up and to the
+ // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
+ float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop();
+ float transX = offset + scaleOffsetCorrection;
+ float totalScale = mBaselineIconScale * scale;
+ final int overlayAlpha = (int) (80 * (1 - r));
+
+ if (params == null) {
+ params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
+ } else {
+ params.transX = transX;
+ params.transY = transY;
+ params.scale = totalScale;
+ params.overlayAlpha = overlayAlpha;
+ }
+ return params;
+ }
+
+ private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
+ canvas.save();
+ canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY);
+ canvas.scale(params.scale, params.scale);
+ Drawable d = params.drawable;
+
+ if (d != null) {
+ d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
+ d.setFilterBitmap(true);
+ d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255),
+ PorterDuff.Mode.SRC_ATOP);
+ d.draw(canvas);
+ d.clearColorFilter();
+ d.setFilterBitmap(false);
+ }
+ canvas.restore();
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ if (mFolder == null) return;
+ if (mFolder.getItemCount() == 0 && !mAnimating) return;
+
+ ArrayList<View> items = mFolder.getItemsInReadingOrder();
+ Drawable d;
+ TextView v;
+
+ // Update our drawing parameters if necessary
+ if (mAnimating) {
+ computePreviewDrawingParams(mAnimParams.drawable);
+ } else {
+ v = (TextView) items.get(0);
+ d = v.getCompoundDrawables()[1];
+ computePreviewDrawingParams(d);
+ }
+
+ int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW);
+ if (!mAnimating) {
+ for (int i = nItemsInPreview - 1; i >= 0; i--) {
+ v = (TextView) items.get(i);
+ if (!mHiddenItems.contains(v.getTag())) {
+ d = v.getCompoundDrawables()[1];
+ mParams = computePreviewItemDrawingParams(i, mParams);
+ mParams.drawable = d;
+ drawPreviewItem(canvas, mParams);
+ }
+ }
+ } else {
+ drawPreviewItem(canvas, mAnimParams);
+ }
+ }
+
+ private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
+ final Runnable onCompleteRunnable) {
+ final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null);
+
+ final float scale0 = 1.0f;
+ final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2;
+ final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2 + getPaddingTop();
+ mAnimParams.drawable = d;
+
+ ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f);
+ va.addUpdateListener(new AnimatorUpdateListener(){
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float progress = (Float) animation.getAnimatedValue();
+ if (reverse) {
+ progress = 1 - progress;
+ mPreviewBackground.setAlpha(progress);
+ }
+
+ mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0);
+ mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0);
+ mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0);
+ invalidate();
+ }
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mAnimating = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimating = false;
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ }
+ });
+ va.setDuration(duration);
+ va.start();
+ }
+
+ public void setTextVisible(boolean visible) {
+ if (visible) {
+ mFolderName.setVisibility(VISIBLE);
+ } else {
+ mFolderName.setVisibility(INVISIBLE);
+ }
+ }
+
+ public boolean getTextVisible() {
+ return mFolderName.getVisibility() == VISIBLE;
+ }
+
+ public void onItemsChanged() {
+ invalidate();
+ requestLayout();
+ }
+
+ public void onAdd(ShortcutInfo item) {
+ invalidate();
+ requestLayout();
+ }
+
+ public void onRemove(ShortcutInfo item) {
+ invalidate();
+ requestLayout();
+ }
+
+ public void onTitleChanged(CharSequence title) {
+ mFolderName.setText(title.toString());
+ setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
+ title));
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Call the superclass onTouchEvent first, because sometimes it changes the state to
+ // isPressed() on an ACTION_UP
+ boolean result = super.onTouchEvent(event);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLongPressHelper.postCheckForLongPress();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mLongPressHelper.cancelLongPress();
+ break;
+ }
+ return result;
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ mLongPressHelper.cancelLongPress();
+ }
+}
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
new file mode 100644
index 0000000..bb5ae82
--- /dev/null
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import java.util.ArrayList;
+
+import android.content.ContentValues;
+
+/**
+ * Represents a folder containing shortcuts or apps.
+ */
+class FolderInfo extends ItemInfo {
+
+ /**
+ * Whether this folder has been opened
+ */
+ boolean opened;
+
+ /**
+ * The apps and shortcuts
+ */
+ ArrayList<ShortcutInfo> contents = new ArrayList<ShortcutInfo>();
+
+ ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
+
+ FolderInfo() {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+ }
+
+ /**
+ * Add an app or shortcut
+ *
+ * @param item
+ */
+ public void add(ShortcutInfo item) {
+ contents.add(item);
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).onAdd(item);
+ }
+ itemsChanged();
+ }
+
+ /**
+ * Remove an app or shortcut. Does not change the DB.
+ *
+ * @param item
+ */
+ public void remove(ShortcutInfo item) {
+ contents.remove(item);
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).onRemove(item);
+ }
+ itemsChanged();
+ }
+
+ public void setTitle(CharSequence title) {
+ this.title = title;
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).onTitleChanged(title);
+ }
+ }
+
+ @Override
+ void onAddToDatabase(ContentValues values) {
+ super.onAddToDatabase(values);
+ values.put(LauncherSettings.Favorites.TITLE, title.toString());
+ }
+
+ void addListener(FolderListener listener) {
+ listeners.add(listener);
+ }
+
+ void removeListener(FolderListener listener) {
+ if (listeners.contains(listener)) {
+ listeners.remove(listener);
+ }
+ }
+
+ void itemsChanged() {
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).onItemsChanged();
+ }
+ }
+
+ @Override
+ void unbind() {
+ super.unbind();
+ listeners.clear();
+ }
+
+ interface FolderListener {
+ public void onAdd(ShortcutInfo item);
+ public void onRemove(ShortcutInfo item);
+ public void onTitleChanged(CharSequence title);
+ public void onItemsChanged();
+ }
+
+ @Override
+ public String toString() {
+ return "FolderInfo(id=" + this.id + " type=" + this.itemType
+ + " container=" + this.container + " screen=" + screenId
+ + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
+ + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ }
+}
diff --git a/src/com/android/launcher3/HideFromAccessibilityHelper.java b/src/com/android/launcher3/HideFromAccessibilityHelper.java
new file mode 100644
index 0000000..75cbb1b
--- /dev/null
+++ b/src/com/android/launcher3/HideFromAccessibilityHelper.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2012 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.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.OnHierarchyChangeListener;
+
+import java.util.HashMap;
+
+public class HideFromAccessibilityHelper implements OnHierarchyChangeListener {
+ private HashMap<View, Integer> mPreviousValues;
+ boolean mHide;
+ boolean mOnlyAllApps;
+
+ public HideFromAccessibilityHelper() {
+ mPreviousValues = new HashMap<View, Integer>();
+ mHide = false;
+ }
+
+ public void setImportantForAccessibilityToNo(View v, boolean onlyAllApps) {
+ mOnlyAllApps = onlyAllApps;
+ setImportantForAccessibilityToNoHelper(v);
+ mHide = true;
+ }
+
+ private void setImportantForAccessibilityToNoHelper(View v) {
+ mPreviousValues.put(v, v.getImportantForAccessibility());
+ v.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+
+ // Call method on children recursively
+ if (v instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) v;
+ vg.setOnHierarchyChangeListener(this);
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ View child = vg.getChildAt(i);
+
+ if (includeView(child)) {
+ setImportantForAccessibilityToNoHelper(child);
+ }
+ }
+ }
+ }
+
+ public void restoreImportantForAccessibility(View v) {
+ if (mHide) {
+ restoreImportantForAccessibilityHelper(v);
+ }
+ mHide = false;
+ }
+
+ private void restoreImportantForAccessibilityHelper(View v) {
+ Integer important = mPreviousValues.get(v);
+ v.setImportantForAccessibility(important);
+ mPreviousValues.remove(v);
+
+ // Call method on children recursively
+ if (v instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) v;
+
+ // We assume if a class implements OnHierarchyChangeListener, it listens
+ // to changes to any of its children (happens to be the case in Launcher)
+ if (vg instanceof OnHierarchyChangeListener) {
+ vg.setOnHierarchyChangeListener((OnHierarchyChangeListener) vg);
+ } else {
+ vg.setOnHierarchyChangeListener(null);
+ }
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ View child = vg.getChildAt(i);
+ if (includeView(child)) {
+ restoreImportantForAccessibilityHelper(child);
+ }
+ }
+ }
+ }
+
+ public void onChildViewAdded(View parent, View child) {
+ if (mHide && includeView(child)) {
+ setImportantForAccessibilityToNoHelper(child);
+ }
+ }
+
+ public void onChildViewRemoved(View parent, View child) {
+ if (mHide && includeView(child)) {
+ restoreImportantForAccessibilityHelper(child);
+ }
+ }
+
+ private boolean includeView(View v) {
+ return !hasAncestorOfType(v, Cling.class) &&
+ (!mOnlyAllApps || hasAncestorOfType(v, AppsCustomizeTabHost.class));
+ }
+
+ private boolean hasAncestorOfType(View v, Class c) {
+ return v != null &&
+ (v.getClass().equals(c) ||
+ (v.getParent() instanceof ViewGroup &&
+ hasAncestorOfType((ViewGroup) v.getParent(), c)));
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/HolographicImageView.java b/src/com/android/launcher3/HolographicImageView.java
new file mode 100644
index 0000000..18ac092
--- /dev/null
+++ b/src/com/android/launcher3/HolographicImageView.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+
+public class HolographicImageView extends ImageView {
+
+ private final HolographicViewHelper mHolographicHelper;
+ private boolean mHotwordOn;
+ private boolean mIsPressed;
+ private boolean mIsFocused;
+
+ public HolographicImageView(Context context) {
+ this(context, null);
+ }
+
+ public HolographicImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public HolographicImageView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mHolographicHelper = new HolographicViewHelper(context);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HolographicLinearLayout,
+ defStyle, 0);
+ mHotwordOn = a.getBoolean(R.styleable.HolographicLinearLayout_stateHotwordOn, false);
+ a.recycle();
+
+ setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (isPressed() != mIsPressed) {
+ mIsPressed = isPressed();
+ refreshDrawableState();
+ }
+ return false;
+ }
+ });
+
+ setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (isFocused() != mIsFocused) {
+ mIsFocused = isFocused();
+ refreshDrawableState();
+ }
+ }
+ });
+ }
+
+ void invalidatePressedFocusedStates() {
+ mHolographicHelper.invalidatePressedFocusedStates(this);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ mHolographicHelper.generatePressedFocusedStates(this);
+ Drawable d = getDrawable();
+ if (d instanceof StateListDrawable) {
+ StateListDrawable sld = (StateListDrawable) d;
+ sld.setState(getDrawableState());
+ sld.invalidateSelf();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // One time call to generate the pressed/focused state -- must be called after
+ // measure/layout
+ mHolographicHelper.generatePressedFocusedStates(this);
+ }
+
+ private boolean isHotwordOn() {
+ return mHotwordOn;
+ }
+
+ public void setHotwordState(boolean on) {
+ if (on == mHotwordOn) {
+ return;
+ }
+ mHotwordOn = on;
+ refreshDrawableState();
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isHotwordOn()) {
+ mergeDrawableStates(drawableState, new int[] {R.attr.stateHotwordOn});
+ }
+ return drawableState;
+ }
+}
diff --git a/src/com/android/launcher3/HolographicLinearLayout.java b/src/com/android/launcher3/HolographicLinearLayout.java
new file mode 100644
index 0000000..5344a7e
--- /dev/null
+++ b/src/com/android/launcher3/HolographicLinearLayout.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2011 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.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+public class HolographicLinearLayout extends LinearLayout {
+ private final HolographicViewHelper mHolographicHelper;
+ private ImageView mImageView;
+ private int mImageViewId;
+
+ private boolean mHotwordOn;
+ private boolean mIsPressed;
+ private boolean mIsFocused;
+
+ public HolographicLinearLayout(Context context) {
+ this(context, null);
+ }
+
+ public HolographicLinearLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public HolographicLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HolographicLinearLayout,
+ defStyle, 0);
+ mImageViewId = a.getResourceId(R.styleable.HolographicLinearLayout_sourceImageViewId, -1);
+ mHotwordOn = a.getBoolean(R.styleable.HolographicLinearLayout_stateHotwordOn, false);
+ a.recycle();
+
+
+ setWillNotDraw(false);
+ mHolographicHelper = new HolographicViewHelper(context);
+
+ setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (isPressed() != mIsPressed) {
+ mIsPressed = isPressed();
+ refreshDrawableState();
+ }
+ return false;
+ }
+ });
+
+ setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (isFocused() != mIsFocused) {
+ mIsFocused = isFocused();
+ refreshDrawableState();
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ if (mImageView != null) {
+ mHolographicHelper.generatePressedFocusedStates(mImageView);
+ Drawable d = mImageView.getDrawable();
+ if (d instanceof StateListDrawable) {
+ StateListDrawable sld = (StateListDrawable) d;
+ sld.setState(getDrawableState());
+ sld.invalidateSelf();
+ }
+ }
+ }
+
+ void invalidatePressedFocusedStates() {
+ mHolographicHelper.invalidatePressedFocusedStates(mImageView);
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // One time call to generate the pressed/focused state -- must be called after
+ // measure/layout
+ if (mImageView == null) {
+ mImageView = (ImageView) findViewById(mImageViewId);
+ }
+ mHolographicHelper.generatePressedFocusedStates(mImageView);
+ }
+
+ private boolean isHotwordOn() {
+ return mHotwordOn;
+ }
+
+ public void setHotwordState(boolean on) {
+ if (on == mHotwordOn) {
+ return;
+ }
+ mHotwordOn = on;
+ refreshDrawableState();
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ if (isHotwordOn()) {
+ mergeDrawableStates(drawableState, new int[] {R.attr.stateHotwordOn});
+ }
+ return drawableState;
+ }
+}
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/HolographicOutlineHelper.java
new file mode 100644
index 0000000..d7b960a
--- /dev/null
+++ b/src/com/android/launcher3/HolographicOutlineHelper.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+
+public class HolographicOutlineHelper {
+ private final Paint mHolographicPaint = new Paint();
+ private final Paint mBlurPaint = new Paint();
+ private final Paint mErasePaint = new Paint();
+
+ public int mMaxOuterBlurRadius;
+ public int mMinOuterBlurRadius;
+
+ private BlurMaskFilter mExtraThickOuterBlurMaskFilter;
+ private BlurMaskFilter mThickOuterBlurMaskFilter;
+ private BlurMaskFilter mMediumOuterBlurMaskFilter;
+ private BlurMaskFilter mThinOuterBlurMaskFilter;
+ private BlurMaskFilter mThickInnerBlurMaskFilter;
+ private BlurMaskFilter mExtraThickInnerBlurMaskFilter;
+ private BlurMaskFilter mMediumInnerBlurMaskFilter;
+
+ private static final int THICK = 0;
+ private static final int MEDIUM = 1;
+ private static final int EXTRA_THICK = 2;
+
+ static HolographicOutlineHelper INSTANCE;
+
+ private HolographicOutlineHelper(Context context) {
+ final float scale = LauncherAppState.getInstance().getScreenDensity();
+
+ mMinOuterBlurRadius = (int) (scale * 1.0f);
+ mMaxOuterBlurRadius = (int) (scale * 12.0f);
+
+ mExtraThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 12.0f, BlurMaskFilter.Blur.OUTER);
+ mThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.OUTER);
+ mMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER);
+ mThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER);
+ mExtraThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.NORMAL);
+ mThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL);
+ mMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL);
+
+ mHolographicPaint.setFilterBitmap(true);
+ mHolographicPaint.setAntiAlias(true);
+ mBlurPaint.setFilterBitmap(true);
+ mBlurPaint.setAntiAlias(true);
+ mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ mErasePaint.setFilterBitmap(true);
+ mErasePaint.setAntiAlias(true);
+ }
+
+ public static HolographicOutlineHelper obtain(Context context) {
+ if (INSTANCE == null) {
+ INSTANCE = new HolographicOutlineHelper(context);
+ }
+ return INSTANCE;
+ }
+
+ /**
+ * Returns the interpolated holographic highlight alpha for the effect we want when scrolling
+ * pages.
+ */
+ public static float highlightAlphaInterpolator(float r) {
+ float maxAlpha = 0.6f;
+ return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f);
+ }
+
+ /**
+ * Returns the interpolated view alpha for the effect we want when scrolling pages.
+ */
+ public static float viewAlphaInterpolator(float r) {
+ final float pivot = 0.95f;
+ if (r < pivot) {
+ return (float) Math.pow(r / pivot, 1.5f);
+ } else {
+ return 1.0f;
+ }
+ }
+
+ /**
+ * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
+ * bitmap.
+ */
+ void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+ int outlineColor, int thickness) {
+ applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true,
+ thickness);
+ }
+ void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+ int outlineColor, boolean clipAlpha, int thickness) {
+
+ // We start by removing most of the alpha channel so as to ignore shadows, and
+ // other types of partial transparency when defining the shape of the object
+ if (clipAlpha) {
+ int[] srcBuffer = new int[srcDst.getWidth() * srcDst.getHeight()];
+ srcDst.getPixels(srcBuffer,
+ 0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight());
+ for (int i = 0; i < srcBuffer.length; i++) {
+ final int alpha = srcBuffer[i] >>> 24;
+ if (alpha < 188) {
+ srcBuffer[i] = 0;
+ }
+ }
+ srcDst.setPixels(srcBuffer,
+ 0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight());
+ }
+ Bitmap glowShape = srcDst.extractAlpha();
+
+ // calculate the outer blur first
+ BlurMaskFilter outerBlurMaskFilter;
+ switch (thickness) {
+ case EXTRA_THICK:
+ outerBlurMaskFilter = mExtraThickOuterBlurMaskFilter;
+ break;
+ case THICK:
+ outerBlurMaskFilter = mThickOuterBlurMaskFilter;
+ break;
+ case MEDIUM:
+ outerBlurMaskFilter = mMediumOuterBlurMaskFilter;
+ break;
+ default:
+ throw new RuntimeException("Invalid blur thickness");
+ }
+ mBlurPaint.setMaskFilter(outerBlurMaskFilter);
+ int[] outerBlurOffset = new int[2];
+ Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset);
+ if (thickness == EXTRA_THICK) {
+ mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
+ } else {
+ mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
+ }
+
+ int[] brightOutlineOffset = new int[2];
+ Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset);
+
+ // calculate the inner blur
+ srcDstCanvas.setBitmap(glowShape);
+ srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
+ BlurMaskFilter innerBlurMaskFilter;
+ switch (thickness) {
+ case EXTRA_THICK:
+ innerBlurMaskFilter = mExtraThickInnerBlurMaskFilter;
+ break;
+ case THICK:
+ innerBlurMaskFilter = mThickInnerBlurMaskFilter;
+ break;
+ case MEDIUM:
+ innerBlurMaskFilter = mMediumInnerBlurMaskFilter;
+ break;
+ default:
+ throw new RuntimeException("Invalid blur thickness");
+ }
+ mBlurPaint.setMaskFilter(innerBlurMaskFilter);
+ int[] thickInnerBlurOffset = new int[2];
+ Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset);
+
+ // mask out the inner blur
+ srcDstCanvas.setBitmap(thickInnerBlur);
+ srcDstCanvas.drawBitmap(glowShape, -thickInnerBlurOffset[0],
+ -thickInnerBlurOffset[1], mErasePaint);
+ srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(),
+ mErasePaint);
+ srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1],
+ mErasePaint);
+
+ // draw the inner and outer blur
+ srcDstCanvas.setBitmap(srcDst);
+ srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ mHolographicPaint.setColor(color);
+ srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
+ mHolographicPaint);
+ srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1],
+ mHolographicPaint);
+
+ // draw the bright outline
+ mHolographicPaint.setColor(outlineColor);
+ srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1],
+ mHolographicPaint);
+
+ // cleanup
+ srcDstCanvas.setBitmap(null);
+ brightOutline.recycle();
+ thickOuterBlur.recycle();
+ thickInnerBlur.recycle();
+ glowShape.recycle();
+ }
+
+ void applyExtraThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+ int outlineColor) {
+ applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, EXTRA_THICK);
+ }
+
+ void applyThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+ int outlineColor) {
+ applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, THICK);
+ }
+
+ void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+ int outlineColor, boolean clipAlpha) {
+ applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, clipAlpha,
+ MEDIUM);
+ }
+
+ void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
+ int outlineColor) {
+ applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, MEDIUM);
+ }
+
+}
diff --git a/src/com/android/launcher3/HolographicViewHelper.java b/src/com/android/launcher3/HolographicViewHelper.java
new file mode 100644
index 0000000..7ef0355
--- /dev/null
+++ b/src/com/android/launcher3/HolographicViewHelper.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2011 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.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.widget.ImageView;
+
+public class HolographicViewHelper {
+
+ private final Canvas mTempCanvas = new Canvas();
+
+ private boolean mStatesUpdated;
+ private int mHighlightColor, mHotwordColor;
+
+ public HolographicViewHelper(Context context) {
+ Resources res = context.getResources();
+ mHighlightColor = res.getColor(android.R.color.holo_blue_light);
+ mHotwordColor = res.getColor(android.R.color.holo_green_light);
+ }
+
+ /**
+ * Generate the pressed/focused states if necessary.
+ */
+ void generatePressedFocusedStates(ImageView v) {
+ if (!mStatesUpdated && v != null) {
+ mStatesUpdated = true;
+ Bitmap original = createOriginalImage(v, mTempCanvas);
+ Bitmap outline = createImageWithOverlay(v, mTempCanvas, mHighlightColor);
+ Bitmap hotword = createImageWithOverlay(v, mTempCanvas, mHotwordColor);
+ FastBitmapDrawable originalD = new FastBitmapDrawable(original);
+ FastBitmapDrawable outlineD = new FastBitmapDrawable(outline);
+ FastBitmapDrawable hotwordD = new FastBitmapDrawable(hotword);
+
+ StateListDrawable states = new StateListDrawable();
+
+ states.addState(new int[] {android.R.attr.state_pressed}, outlineD);
+ states.addState(new int[] {android.R.attr.state_focused}, outlineD);
+ states.addState(new int[] {R.attr.stateHotwordOn}, hotwordD);
+ states.addState(new int[] {}, originalD);
+ v.setImageDrawable(states);
+ }
+ }
+
+ /**
+ * Invalidates the pressed/focused states.
+ */
+ void invalidatePressedFocusedStates(ImageView v) {
+ mStatesUpdated = false;
+ if (v != null) {
+ v.invalidate();
+ }
+ }
+
+ /**
+ * Creates a copy of the original image.
+ */
+ private Bitmap createOriginalImage(ImageView v, Canvas canvas) {
+ final Drawable d = v.getDrawable();
+ final Bitmap b = Bitmap.createBitmap(
+ d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ canvas.save();
+ d.draw(canvas);
+ canvas.restore();
+ canvas.setBitmap(null);
+
+ return b;
+ }
+
+ /**
+ * Creates a new press state image which is the old image with a blue overlay.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createImageWithOverlay(ImageView v, Canvas canvas, int color) {
+ final Drawable d = v.getDrawable();
+ final Bitmap b = Bitmap.createBitmap(
+ d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ canvas.save();
+ d.draw(canvas);
+ canvas.restore();
+ canvas.drawColor(color, PorterDuff.Mode.SRC_IN);
+ canvas.setBitmap(null);
+
+ return b;
+ }
+}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
new file mode 100644
index 0000000..2aab64d
--- /dev/null
+++ b/src/com/android/launcher3/Hotseat.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2011 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.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class Hotseat extends FrameLayout {
+ private static final String TAG = "Hotseat";
+
+ private CellLayout mContent;
+
+ private Launcher mLauncher;
+
+ private int mAllAppsButtonRank;
+
+ private boolean mTransposeLayoutWithOrientation;
+ private boolean mIsLandscape;
+
+ public Hotseat(Context context) {
+ this(context, null);
+ }
+
+ public Hotseat(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public Hotseat(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ Resources r = context.getResources();
+ mTransposeLayoutWithOrientation =
+ r.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
+ mIsLandscape = context.getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ public void setup(Launcher launcher) {
+ mLauncher = launcher;
+ setOnKeyListener(new HotseatIconKeyEventListener());
+ }
+
+ CellLayout getLayout() {
+ return mContent;
+ }
+
+ /**
+ * Registers the specified listener on the cell layout of the hotseat.
+ */
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mContent.setOnLongClickListener(l);
+ }
+
+ private boolean hasVerticalHotseat() {
+ return (mIsLandscape && mTransposeLayoutWithOrientation);
+ }
+
+ /* Get the orientation invariant order of the item in the hotseat for persistence. */
+ int getOrderInHotseat(int x, int y) {
+ return hasVerticalHotseat() ? (mContent.getCountY() - y - 1) : x;
+ }
+ /* Get the orientation specific coordinates given an invariant order in the hotseat. */
+ int getCellXFromOrder(int rank) {
+ return hasVerticalHotseat() ? 0 : rank;
+ }
+ int getCellYFromOrder(int rank) {
+ return hasVerticalHotseat() ? (mContent.getCountY() - (rank + 1)) : 0;
+ }
+ public boolean isAllAppsButtonRank(int rank) {
+ if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ return false;
+ } else {
+ return rank == mAllAppsButtonRank;
+ }
+ }
+
+ /** This returns the coordinates of an app in a given cell, relative to the DragLayer */
+ Rect getCellCoordinates(int cellX, int cellY) {
+ Rect coords = new Rect();
+ mContent.cellToRect(cellX, cellY, 1, 1, coords);
+ int[] hotseatInParent = new int[2];
+ Utilities.getDescendantCoordRelativeToParent(this, mLauncher.getDragLayer(),
+ hotseatInParent, false);
+ coords.offset(hotseatInParent[0], hotseatInParent[1]);
+
+ // Center the icon
+ int cWidth = mContent.getShortcutsAndWidgets().getCellContentWidth();
+ int cHeight = mContent.getShortcutsAndWidgets().getCellContentHeight();
+ int cellPaddingX = (int) Math.max(0, ((coords.width() - cWidth) / 2f));
+ int cellPaddingY = (int) Math.max(0, ((coords.height() - cHeight) / 2f));
+ coords.offset(cellPaddingX, cellPaddingY);
+
+ return coords;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ mAllAppsButtonRank = grid.hotseatAllAppsRank;
+ mContent = (CellLayout) findViewById(R.id.layout);
+ if (grid.isLandscape && !grid.isLargeTablet()) {
+ mContent.setGridSize(1, (int) grid.numHotseatIcons);
+ } else {
+ mContent.setGridSize((int) grid.numHotseatIcons, 1);
+ }
+ mContent.setIsHotseat(true);
+
+ resetLayout();
+ }
+
+ void resetLayout() {
+ mContent.removeAllViewsInLayout();
+
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ // Add the Apps button
+ Context context = getContext();
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ TextView allAppsButton = (TextView)
+ inflater.inflate(R.layout.all_apps_button, mContent, false);
+ Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+ d.setBounds(0, 0, Utilities.sIconTextureWidth, Utilities.sIconTextureHeight);
+ allAppsButton.setCompoundDrawables(null, d, null, null);
+
+ allAppsButton.setContentDescription(context.getString(R.string.all_apps_button_label));
+ if (mLauncher != null) {
+ allAppsButton.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
+ }
+ allAppsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(android.view.View v) {
+ if (mLauncher != null) {
+ mLauncher.onClickAllAppsButton(v);
+ }
+ }
+ });
+
+ // Note: We do this to ensure that the hotseat is always laid out in the orientation of
+ // the hotseat in order regardless of which orientation they were added
+ int x = getCellXFromOrder(mAllAppsButtonRank);
+ int y = getCellYFromOrder(mAllAppsButtonRank);
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(x,y,1,1);
+ lp.canReorder = false;
+ mContent.addViewToCellLayout(allAppsButton, -1, 0, lp, true);
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // We don't want any clicks to go through to the hotseat unless the workspace is in
+ // the normal state.
+ if (mLauncher.getWorkspace().isSmall()) {
+ return true;
+ }
+ return false;
+ }
+
+ void addAllAppsFolder(IconCache iconCache,
+ ArrayList<AppInfo> allApps, ArrayList<ComponentName> onWorkspace,
+ Launcher launcher, Workspace workspace) {
+ if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ FolderInfo fi = new FolderInfo();
+
+ fi.cellX = getCellXFromOrder(mAllAppsButtonRank);
+ fi.cellY = getCellYFromOrder(mAllAppsButtonRank);
+ fi.spanX = 1;
+ fi.spanY = 1;
+ fi.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ fi.screenId = mAllAppsButtonRank;
+ fi.itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+ fi.title = "More Apps";
+ LauncherModel.addItemToDatabase(launcher, fi, fi.container, fi.screenId, fi.cellX,
+ fi.cellY, false);
+ FolderIcon folder = FolderIcon.fromXml(R.layout.folder_icon, launcher,
+ getLayout(), fi, iconCache);
+ workspace.addInScreen(folder, fi.container, fi.screenId, fi.cellX, fi.cellY,
+ fi.spanX, fi.spanY);
+
+ for (AppInfo info: allApps) {
+ ComponentName cn = info.intent.getComponent();
+ if (!onWorkspace.contains(cn)) {
+ Log.d(TAG, "Adding to 'more apps': " + info.intent);
+ ShortcutInfo si = info.makeShortcut();
+ fi.add(si);
+ }
+ }
+ }
+ }
+
+ void addAppsToAllAppsFolder(ArrayList<AppInfo> apps) {
+ if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ View v = mContent.getChildAt(getCellXFromOrder(mAllAppsButtonRank), getCellYFromOrder(mAllAppsButtonRank));
+ FolderIcon fi = null;
+
+ if (v instanceof FolderIcon) {
+ fi = (FolderIcon) v;
+ } else {
+ return;
+ }
+
+ FolderInfo info = fi.getFolderInfo();
+ for (AppInfo a: apps) {
+ ShortcutInfo si = a.makeShortcut();
+ info.add(si);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
new file mode 100644
index 0000000..1797826
--- /dev/null
+++ b/src/com/android/launcher3/IconCache.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+
+import java.util.HashMap;
+
+/**
+ * Cache of application icons. Icons can be made from any thread.
+ */
+public class IconCache {
+ @SuppressWarnings("unused")
+ private static final String TAG = "Launcher.IconCache";
+
+ private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
+
+ private static class CacheEntry {
+ public Bitmap icon;
+ public String title;
+ }
+
+ private final Bitmap mDefaultIcon;
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final HashMap<ComponentName, CacheEntry> mCache =
+ new HashMap<ComponentName, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
+ private int mIconDpi;
+
+ public IconCache(Context context) {
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mIconDpi = activityManager.getLauncherLargeIconDensity();
+
+ // need to set mIconDpi before getting default icon
+ mDefaultIcon = makeDefaultIcon();
+ }
+
+ public Drawable getFullResDefaultActivityIcon() {
+ return getFullResIcon(Resources.getSystem(),
+ android.R.mipmap.sym_def_app_icon);
+ }
+
+ public Drawable getFullResIcon(Resources resources, int iconId) {
+ Drawable d;
+ try {
+ d = resources.getDrawableForDensity(iconId, mIconDpi);
+ } catch (Resources.NotFoundException e) {
+ d = null;
+ }
+
+ return (d != null) ? d : getFullResDefaultActivityIcon();
+ }
+
+ public Drawable getFullResIcon(String packageName, int iconId) {
+ Resources resources;
+ try {
+ resources = mPackageManager.getResourcesForApplication(packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ resources = null;
+ }
+ if (resources != null) {
+ if (iconId != 0) {
+ return getFullResIcon(resources, iconId);
+ }
+ }
+ return getFullResDefaultActivityIcon();
+ }
+
+ public Drawable getFullResIcon(ResolveInfo info) {
+ return getFullResIcon(info.activityInfo);
+ }
+
+ public Drawable getFullResIcon(ActivityInfo info) {
+
+ Resources resources;
+ try {
+ resources = mPackageManager.getResourcesForApplication(
+ info.applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ resources = null;
+ }
+ if (resources != null) {
+ int iconId = info.getIconResource();
+ if (iconId != 0) {
+ return getFullResIcon(resources, iconId);
+ }
+ }
+ return getFullResDefaultActivityIcon();
+ }
+
+ private Bitmap makeDefaultIcon() {
+ Drawable d = getFullResDefaultActivityIcon();
+ Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
+ Math.max(d.getIntrinsicHeight(), 1),
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ d.setBounds(0, 0, b.getWidth(), b.getHeight());
+ d.draw(c);
+ c.setBitmap(null);
+ return b;
+ }
+
+ /**
+ * Remove any records for the supplied ComponentName.
+ */
+ public void remove(ComponentName componentName) {
+ synchronized (mCache) {
+ mCache.remove(componentName);
+ }
+ }
+
+ /**
+ * Empty out the cache.
+ */
+ public void flush() {
+ synchronized (mCache) {
+ mCache.clear();
+ }
+ }
+
+ /**
+ * Fill in "application" with the icon and label for "info."
+ */
+ public void getTitleAndIcon(AppInfo application, ResolveInfo info,
+ HashMap<Object, CharSequence> labelCache) {
+ synchronized (mCache) {
+ CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
+
+ application.title = entry.title;
+ application.iconBitmap = entry.icon;
+ }
+ }
+
+ public Bitmap getIcon(Intent intent) {
+ synchronized (mCache) {
+ final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
+ ComponentName component = intent.getComponent();
+
+ if (resolveInfo == null || component == null) {
+ return mDefaultIcon;
+ }
+
+ CacheEntry entry = cacheLocked(component, resolveInfo, null);
+ return entry.icon;
+ }
+ }
+
+ public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo,
+ HashMap<Object, CharSequence> labelCache) {
+ synchronized (mCache) {
+ if (resolveInfo == null || component == null) {
+ return null;
+ }
+
+ CacheEntry entry = cacheLocked(component, resolveInfo, labelCache);
+ return entry.icon;
+ }
+ }
+
+ public boolean isDefaultIcon(Bitmap icon) {
+ return mDefaultIcon == icon;
+ }
+
+ private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
+ HashMap<Object, CharSequence> labelCache) {
+ CacheEntry entry = mCache.get(componentName);
+ if (entry == null) {
+ entry = new CacheEntry();
+
+ mCache.put(componentName, entry);
+
+ ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
+ if (labelCache != null && labelCache.containsKey(key)) {
+ entry.title = labelCache.get(key).toString();
+ } else {
+ entry.title = info.loadLabel(mPackageManager).toString();
+ if (labelCache != null) {
+ labelCache.put(key, entry.title);
+ }
+ }
+ if (entry.title == null) {
+ entry.title = info.activityInfo.name;
+ }
+
+ entry.icon = Utilities.createIconBitmap(
+ getFullResIcon(info), mContext);
+ }
+ return entry;
+ }
+
+ public HashMap<ComponentName,Bitmap> getAllIcons() {
+ synchronized (mCache) {
+ HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
+ for (ComponentName cn : mCache.keySet()) {
+ final CacheEntry e = mCache.get(cn);
+ set.put(cn, e.icon);
+ }
+ return set;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
new file mode 100644
index 0000000..2ad43b6
--- /dev/null
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2011 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.ComponentName;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.drawable.TransitionDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class InfoDropTarget extends ButtonDropTarget {
+
+ private ColorStateList mOriginalTextColor;
+ private TransitionDrawable mDrawable;
+
+ public InfoDropTarget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public InfoDropTarget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mOriginalTextColor = getTextColors();
+
+ // Get the hover color
+ Resources r = getResources();
+ mHoverColor = r.getColor(R.color.info_target_hover_tint);
+ mDrawable = (TransitionDrawable) getCurrentDrawable();
+ if (null != mDrawable) {
+ mDrawable.setCrossFadeEnabled(true);
+ }
+
+ // Remove the text in the Phone UI in landscape
+ int orientation = getResources().getConfiguration().orientation;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (!LauncherAppState.getInstance().isScreenLarge()) {
+ setText("");
+ }
+ }
+ }
+
+ private boolean isFromAllApps(DragSource source) {
+ return (source instanceof AppsCustomizePagedView);
+ }
+
+ @Override
+ public boolean acceptDrop(DragObject d) {
+ // acceptDrop is called just before onDrop. We do the work here, rather than
+ // in onDrop, because it allows us to reject the drop (by returning false)
+ // so that the object being dragged isn't removed from the drag source.
+ ComponentName componentName = null;
+ if (d.dragInfo instanceof AppInfo) {
+ componentName = ((AppInfo) d.dragInfo).componentName;
+ } else if (d.dragInfo instanceof ShortcutInfo) {
+ componentName = ((ShortcutInfo) d.dragInfo).intent.getComponent();
+ } else if (d.dragInfo instanceof PendingAddItemInfo) {
+ componentName = ((PendingAddItemInfo) d.dragInfo).componentName;
+ }
+ if (componentName != null) {
+ mLauncher.startApplicationDetailsActivity(componentName);
+ }
+
+ // There is no post-drop animation, so clean up the DragView now
+ d.deferDragViewCleanupPostAnimation = false;
+ return false;
+ }
+
+ @Override
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ boolean isVisible = true;
+
+ // Hide this button unless we are dragging something from AllApps
+ if (!isFromAllApps(source)) {
+ isVisible = false;
+ }
+
+ mActive = isVisible;
+ mDrawable.resetTransition();
+ setTextColor(mOriginalTextColor);
+ ((ViewGroup) getParent()).setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onDragEnd() {
+ super.onDragEnd();
+ mActive = false;
+ }
+
+ public void onDragEnter(DragObject d) {
+ super.onDragEnter(d);
+
+ mDrawable.startTransition(mTransitionDuration);
+ setTextColor(mHoverColor);
+ }
+
+ public void onDragExit(DragObject d) {
+ super.onDragExit(d);
+
+ if (!d.dragComplete) {
+ mDrawable.resetTransition();
+ setTextColor(mOriginalTextColor);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Insettable.java b/src/com/android/launcher3/Insettable.java
new file mode 100644
index 0000000..1d2356c
--- /dev/null
+++ b/src/com/android/launcher3/Insettable.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 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.graphics.Rect;
+
+public interface Insettable {
+
+ void setInsets(Rect insets);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
new file mode 100644
index 0000000..821c15f
--- /dev/null
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Base64;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.json.*;
+
+public class InstallShortcutReceiver extends BroadcastReceiver {
+ public static final String ACTION_INSTALL_SHORTCUT =
+ "com.android.launcher.action.INSTALL_SHORTCUT";
+
+ public static final String DATA_INTENT_KEY = "intent.data";
+ public static final String LAUNCH_INTENT_KEY = "intent.launch";
+ public static final String NAME_KEY = "name";
+ public static final String ICON_KEY = "icon";
+ public static final String ICON_RESOURCE_NAME_KEY = "iconResource";
+ public static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
+ // The set of shortcuts that are pending install
+ public static final String APPS_PENDING_INSTALL = "apps_to_install";
+
+ public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
+ public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
+
+ private static final int INSTALL_SHORTCUT_SUCCESSFUL = 0;
+ private static final int INSTALL_SHORTCUT_IS_DUPLICATE = -1;
+
+ // A mime-type representing shortcut data
+ public static final String SHORTCUT_MIMETYPE =
+ "com.android.launcher3/shortcut";
+
+ private static Object sLock = new Object();
+
+ private static void addToStringSet(SharedPreferences sharedPrefs,
+ SharedPreferences.Editor editor, String key, String value) {
+ Set<String> strings = sharedPrefs.getStringSet(key, null);
+ if (strings == null) {
+ strings = new HashSet<String>(0);
+ } else {
+ strings = new HashSet<String>(strings);
+ }
+ strings.add(value);
+ editor.putStringSet(key, strings);
+ }
+
+ private static void addToInstallQueue(
+ SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) {
+ synchronized(sLock) {
+ try {
+ JSONStringer json = new JSONStringer()
+ .object()
+ .key(DATA_INTENT_KEY).value(info.data.toUri(0))
+ .key(LAUNCH_INTENT_KEY).value(info.launchIntent.toUri(0))
+ .key(NAME_KEY).value(info.name);
+ if (info.icon != null) {
+ byte[] iconByteArray = ItemInfo.flattenBitmap(info.icon);
+ json = json.key(ICON_KEY).value(
+ Base64.encodeToString(
+ iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+ }
+ if (info.iconResource != null) {
+ json = json.key(ICON_RESOURCE_NAME_KEY).value(info.iconResource.resourceName);
+ json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
+ .value(info.iconResource.packageName);
+ }
+ json = json.endObject();
+ SharedPreferences.Editor editor = sharedPrefs.edit();
+ addToStringSet(sharedPrefs, editor, APPS_PENDING_INSTALL, json.toString());
+ editor.commit();
+ } catch (org.json.JSONException e) {
+ Log.d("InstallShortcutReceiver", "Exception when adding shortcut: " + e);
+ }
+ }
+ }
+
+ public static void removeFromInstallQueue(SharedPreferences sharedPrefs,
+ ArrayList<String> packageNames) {
+ synchronized(sLock) {
+ Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+ if (strings != null) {
+ Set<String> newStrings = new HashSet<String>(strings);
+ for (String json : newStrings) {
+ try {
+ JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ Intent launchIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
+ String pn = launchIntent.getPackage();
+ if (pn == null) {
+ pn = launchIntent.getComponent().getPackageName();
+ }
+ if (packageNames.contains(pn)) {
+ newStrings.remove(json);
+ }
+ } catch (org.json.JSONException e) {
+ Log.d("InstallShortcutReceiver", "Exception reading shortcut to remove: " + e);
+ } catch (java.net.URISyntaxException e) {
+ Log.d("InstallShortcutReceiver", "Exception reading shortcut to remove: " + e);
+ }
+ }
+ sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL,
+ new HashSet<String>(newStrings)).commit();
+ }
+ }
+ }
+
+ private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue(
+ SharedPreferences sharedPrefs) {
+ synchronized(sLock) {
+ Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null);
+ if (strings == null) {
+ return new ArrayList<PendingInstallShortcutInfo>();
+ }
+ ArrayList<PendingInstallShortcutInfo> infos =
+ new ArrayList<PendingInstallShortcutInfo>();
+ for (String json : strings) {
+ try {
+ JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ Intent data = Intent.parseUri(object.getString(DATA_INTENT_KEY), 0);
+ Intent launchIntent =
+ Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0);
+ String name = object.getString(NAME_KEY);
+ String iconBase64 = object.optString(ICON_KEY);
+ String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY);
+ String iconResourcePackageName =
+ object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
+ if (iconBase64 != null && !iconBase64.isEmpty()) {
+ byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
+ Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
+ data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
+ } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
+ Intent.ShortcutIconResource iconResource =
+ new Intent.ShortcutIconResource();
+ iconResource.resourceName = iconResourceName;
+ iconResource.packageName = iconResourcePackageName;
+ data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
+ }
+ data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
+ PendingInstallShortcutInfo info =
+ new PendingInstallShortcutInfo(data, name, launchIntent);
+ infos.add(info);
+ } catch (org.json.JSONException e) {
+ Log.d("InstallShortcutReceiver",
+ "Exception reading shortcut to add: " + e);
+ } catch (java.net.URISyntaxException e) {
+ Log.d("InstallShortcutReceiver",
+ "Exception reading shortcut to add: " + e);
+ }
+ }
+ sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit();
+ return infos;
+ }
+ }
+
+ // Determines whether to defer installing shortcuts immediately until
+ // processAllPendingInstalls() is called.
+ private static boolean mUseInstallQueue = false;
+
+ private static class PendingInstallShortcutInfo {
+ Intent data;
+ Intent launchIntent;
+ String name;
+ Bitmap icon;
+ Intent.ShortcutIconResource iconResource;
+
+ public PendingInstallShortcutInfo(Intent rawData, String shortcutName,
+ Intent shortcutIntent) {
+ data = rawData;
+ name = shortcutName;
+ launchIntent = shortcutIntent;
+ }
+ }
+
+ public void onReceive(Context context, Intent data) {
+ if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
+ return;
+ }
+
+ Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+ if (intent == null) {
+ return;
+ }
+ // This name is only used for comparisons and notifications, so fall back to activity name
+ // if not supplied
+ String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+ if (name == null) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
+ name = info.loadLabel(pm).toString();
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ return;
+ }
+ }
+ Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+ Intent.ShortcutIconResource iconResource =
+ data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+
+ // Queue the item up for adding if launcher has not loaded properly yet
+ LauncherAppState.setApplicationContext(context.getApplicationContext());
+ LauncherAppState app = LauncherAppState.getInstance();
+ boolean launcherNotLoaded = (app.getDynamicGrid() == null);
+
+ PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, name, intent);
+ info.icon = icon;
+ info.iconResource = iconResource;
+
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ addToInstallQueue(sp, info);
+ if (!mUseInstallQueue && !launcherNotLoaded) {
+ flushInstallQueue(context);
+ }
+ }
+
+ static void enableInstallQueue() {
+ mUseInstallQueue = true;
+ }
+ static void disableAndFlushInstallQueue(Context context) {
+ mUseInstallQueue = false;
+ flushInstallQueue(context);
+ }
+ static void flushInstallQueue(Context context) {
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp);
+ if (!installQueue.isEmpty()) {
+ Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator();
+ ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
+ int result = INSTALL_SHORTCUT_SUCCESSFUL;
+ String duplicateName = "";
+ while (iter.hasNext()) {
+ final PendingInstallShortcutInfo pendingInfo = iter.next();
+ //final Intent data = pendingInfo.data;
+ final Intent intent = pendingInfo.launchIntent;
+ final String name = pendingInfo.name;
+ final boolean exists = LauncherModel.shortcutExists(context, name, intent);
+ //final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
+
+ // TODO-XXX: Disable duplicates for now
+ if (!exists /* && allowDuplicate */) {
+ // Generate a shortcut info to add into the model
+ ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
+ pendingInfo.launchIntent);
+ addShortcuts.add(info);
+ }
+ /*
+ else if (exists && !allowDuplicate) {
+ result = INSTALL_SHORTCUT_IS_DUPLICATE;
+ duplicateName = name;
+ }
+ */
+ }
+
+ // Notify the user once if we weren't able to place any duplicates
+ if (result == INSTALL_SHORTCUT_IS_DUPLICATE) {
+ Toast.makeText(context, context.getString(R.string.shortcut_duplicate,
+ duplicateName), Toast.LENGTH_SHORT).show();
+ }
+
+ // Add the new apps to the model and bind them
+ if (!addShortcuts.isEmpty()) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ app.getModel().addAndBindAddedApps(context, addShortcuts, null);
+ }
+ }
+ }
+
+ private static ShortcutInfo getShortcutInfo(Context context, Intent data,
+ Intent launchIntent) {
+ if (launchIntent.getAction() == null) {
+ launchIntent.setAction(Intent.ACTION_VIEW);
+ } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
+ launchIntent.getCategories() != null &&
+ launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+ launchIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ }
+ LauncherAppState app = LauncherAppState.getInstance();
+ return app.getModel().infoFromShortcutIntent(context, data, null);
+ }
+}
diff --git a/src/com/android/launcher3/InstallWidgetReceiver.java b/src/com/android/launcher3/InstallWidgetReceiver.java
new file mode 100644
index 0000000..0ef4780
--- /dev/null
+++ b/src/com/android/launcher3/InstallWidgetReceiver.java
@@ -0,0 +1,192 @@
+/*
+ * 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 java.util.List;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+
+/**
+ * We will likely flesh this out later, to handle allow external apps to place widgets, but for now,
+ * we just want to expose the action around for checking elsewhere.
+ */
+public class InstallWidgetReceiver {
+ public static final String ACTION_INSTALL_WIDGET =
+ "com.android.launcher3.action.INSTALL_WIDGET";
+ public static final String ACTION_SUPPORTS_CLIPDATA_MIMETYPE =
+ "com.android.launcher3.action.SUPPORTS_CLIPDATA_MIMETYPE";
+
+ // Currently not exposed. Put into Intent when we want to make it public.
+ // TEMP: Should we call this "EXTRA_APPWIDGET_PROVIDER"?
+ public static final String EXTRA_APPWIDGET_COMPONENT =
+ "com.android.launcher3.extra.widget.COMPONENT";
+ public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE =
+ "com.android.launcher3.extra.widget.CONFIGURATION_DATA_MIME_TYPE";
+ public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA =
+ "com.android.launcher3.extra.widget.CONFIGURATION_DATA";
+
+ /**
+ * A simple data class that contains per-item information that the adapter below can reference.
+ */
+ public static class WidgetMimeTypeHandlerData {
+ public ResolveInfo resolveInfo;
+ public AppWidgetProviderInfo widgetInfo;
+
+ public WidgetMimeTypeHandlerData(ResolveInfo rInfo, AppWidgetProviderInfo wInfo) {
+ resolveInfo = rInfo;
+ widgetInfo = wInfo;
+ }
+ }
+
+ /**
+ * The ListAdapter which presents all the valid widgets that can be created for a given drop.
+ */
+ public static class WidgetListAdapter implements ListAdapter, DialogInterface.OnClickListener {
+ private LayoutInflater mInflater;
+ private Launcher mLauncher;
+ private String mMimeType;
+ private ClipData mClipData;
+ private List<WidgetMimeTypeHandlerData> mActivities;
+ private int mTargetLayoutScreen;
+ private int[] mTargetLayoutPos;
+
+ public WidgetListAdapter(Launcher l, String mimeType, ClipData data,
+ List<WidgetMimeTypeHandlerData> list, int targetScreen, int[] targetPos) {
+ mLauncher = l;
+ mMimeType = mimeType;
+ mClipData = data;
+ mActivities = list;
+ mTargetLayoutScreen = targetScreen;
+ mTargetLayoutPos = targetPos;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ }
+
+ @Override
+ public int getCount() {
+ return mActivities.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Context context = parent.getContext();
+ final PackageManager packageManager = context.getPackageManager();
+
+ // Lazy-create inflater
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(context);
+ }
+
+ // Use the convert-view where possible
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.external_widget_drop_list_item, parent,
+ false);
+ }
+
+ final WidgetMimeTypeHandlerData data = mActivities.get(position);
+ final ResolveInfo resolveInfo = data.resolveInfo;
+ final AppWidgetProviderInfo widgetInfo = data.widgetInfo;
+
+ // Set the icon
+ Drawable d = resolveInfo.loadIcon(packageManager);
+ ImageView i = (ImageView) convertView.findViewById(R.id.provider_icon);
+ i.setImageDrawable(d);
+
+ // Set the text
+ final CharSequence component = resolveInfo.loadLabel(packageManager);
+ final int[] widgetSpan = new int[2];
+ CellLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, widgetSpan);
+ TextView t = (TextView) convertView.findViewById(R.id.provider);
+ t.setText(context.getString(R.string.external_drop_widget_pick_format,
+ component, widgetSpan[0], widgetSpan[1]));
+
+ return convertView;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mActivities.isEmpty();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final AppWidgetProviderInfo widgetInfo = mActivities.get(which).widgetInfo;
+
+ final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType,
+ mClipData);
+ mLauncher.addAppWidgetFromDrop(createInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ mTargetLayoutScreen, null, null, mTargetLayoutPos);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/InterruptibleInOutAnimator.java b/src/com/android/launcher3/InterruptibleInOutAnimator.java
new file mode 100644
index 0000000..2898b34
--- /dev/null
+++ b/src/com/android/launcher3/InterruptibleInOutAnimator.java
@@ -0,0 +1,131 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.view.View;
+
+/**
+ * A convenience class for two-way animations, e.g. a fadeIn/fadeOut animation.
+ * With a regular ValueAnimator, if you call reverse to show the 'out' animation, you'll get
+ * a frame-by-frame mirror of the 'in' animation -- i.e., the interpolated values will
+ * be exactly reversed. Using this class, both the 'in' and the 'out' animation use the
+ * interpolator in the same direction.
+ */
+public class InterruptibleInOutAnimator {
+ private long mOriginalDuration;
+ private float mOriginalFromValue;
+ private float mOriginalToValue;
+ private ValueAnimator mAnimator;
+
+ private boolean mFirstRun = true;
+
+ private Object mTag = null;
+
+ private static final int STOPPED = 0;
+ private static final int IN = 1;
+ private static final int OUT = 2;
+
+ // TODO: This isn't really necessary, but is here to help diagnose a bug in the drag viz
+ private int mDirection = STOPPED;
+
+ public InterruptibleInOutAnimator(View view, long duration, float fromValue, float toValue) {
+ mAnimator = LauncherAnimUtils.ofFloat(view, fromValue, toValue).setDuration(duration);
+ mOriginalDuration = duration;
+ mOriginalFromValue = fromValue;
+ mOriginalToValue = toValue;
+
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDirection = STOPPED;
+ }
+ });
+ }
+
+ private void animate(int direction) {
+ final long currentPlayTime = mAnimator.getCurrentPlayTime();
+ final float toValue = (direction == IN) ? mOriginalToValue : mOriginalFromValue;
+ final float startValue = mFirstRun ? mOriginalFromValue :
+ ((Float) mAnimator.getAnimatedValue()).floatValue();
+
+ // Make sure it's stopped before we modify any values
+ cancel();
+
+ // TODO: We don't really need to do the animation if startValue == toValue, but
+ // somehow that doesn't seem to work, possibly a quirk of the animation framework
+ mDirection = direction;
+
+ // Ensure we don't calculate a non-sensical duration
+ long duration = mOriginalDuration - currentPlayTime;
+ mAnimator.setDuration(Math.max(0, Math.min(duration, mOriginalDuration)));
+
+ mAnimator.setFloatValues(startValue, toValue);
+ mAnimator.start();
+ mFirstRun = false;
+ }
+
+ public void cancel() {
+ mAnimator.cancel();
+ mDirection = STOPPED;
+ }
+
+ public void end() {
+ mAnimator.end();
+ mDirection = STOPPED;
+ }
+
+ /**
+ * Return true when the animation is not running and it hasn't even been started.
+ */
+ public boolean isStopped() {
+ return mDirection == STOPPED;
+ }
+
+ /**
+ * This is the equivalent of calling Animator.start(), except that it can be called when
+ * the animation is running in the opposite direction, in which case we reverse
+ * direction and animate for a correspondingly shorter duration.
+ */
+ public void animateIn() {
+ animate(IN);
+ }
+
+ /**
+ * This is the roughly the equivalent of calling Animator.reverse(), except that it uses the
+ * same interpolation curve as animateIn(), rather than mirroring it. Also, like animateIn(),
+ * if the animation is currently running in the opposite direction, we reverse
+ * direction and animate for a correspondingly shorter duration.
+ */
+ public void animateOut() {
+ animate(OUT);
+ }
+
+ public void setTag(Object tag) {
+ mTag = tag;
+ }
+
+ public Object getTag() {
+ return mTag;
+ }
+
+ public ValueAnimator getAnimator() {
+ return mAnimator;
+ }
+}
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
new file mode 100644
index 0000000..8c4cefd
--- /dev/null
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.ContentValues;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Represents an item in the launcher.
+ */
+class ItemInfo {
+
+ static final int NO_ID = -1;
+
+ /**
+ * The id in the settings database for this item
+ */
+ long id = NO_ID;
+
+ /**
+ * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_FOLDER}, or
+ * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}.
+ */
+ int itemType;
+
+ /**
+ * The id of the container that holds this item. For the desktop, this will be
+ * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it
+ * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders
+ * it will be the id of the folder.
+ */
+ long container = NO_ID;
+
+ /**
+ * Iindicates the screen in which the shortcut appears.
+ */
+ long screenId = -1;
+
+ /**
+ * Indicates the X position of the associated cell.
+ */
+ int cellX = -1;
+
+ /**
+ * Indicates the Y position of the associated cell.
+ */
+ int cellY = -1;
+
+ /**
+ * Indicates the X cell span.
+ */
+ int spanX = 1;
+
+ /**
+ * Indicates the Y cell span.
+ */
+ int spanY = 1;
+
+ /**
+ * Indicates the minimum X cell span.
+ */
+ int minSpanX = 1;
+
+ /**
+ * Indicates the minimum Y cell span.
+ */
+ int minSpanY = 1;
+
+ /**
+ * Indicates that this item needs to be updated in the db
+ */
+ boolean requiresDbUpdate = false;
+
+ /**
+ * Title of the item
+ */
+ CharSequence title;
+
+ /**
+ * The position of the item in a drag-and-drop operation.
+ */
+ int[] dropPos = null;
+
+ ItemInfo() {
+ }
+
+ ItemInfo(ItemInfo info) {
+ id = info.id;
+ cellX = info.cellX;
+ cellY = info.cellY;
+ spanX = info.spanX;
+ spanY = info.spanY;
+ screenId = info.screenId;
+ itemType = info.itemType;
+ container = info.container;
+ // tempdebug:
+ LauncherModel.checkItemInfo(this);
+ }
+
+ protected Intent getIntent() {
+ throw new RuntimeException("Unexpected Intent");
+ }
+
+ /**
+ * Write the fields of this item to the DB
+ *
+ * @param values
+ */
+ void onAddToDatabase(ContentValues values) {
+ values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
+ values.put(LauncherSettings.Favorites.CONTAINER, container);
+ values.put(LauncherSettings.Favorites.SCREEN, screenId);
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ values.put(LauncherSettings.Favorites.SPANX, spanX);
+ values.put(LauncherSettings.Favorites.SPANY, spanY);
+ }
+
+ void updateValuesWithCoordinates(ContentValues values, int cellX, int cellY) {
+ values.put(LauncherSettings.Favorites.CELLX, cellX);
+ values.put(LauncherSettings.Favorites.CELLY, cellY);
+ }
+
+ static byte[] flattenBitmap(Bitmap bitmap) {
+ // Try go guesstimate how much space the icon will take when serialized
+ // to avoid unnecessary allocations/copies during the write.
+ int size = bitmap.getWidth() * bitmap.getHeight() * 4;
+ ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+ try {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ out.close();
+ return out.toByteArray();
+ } catch (IOException e) {
+ Log.w("Favorite", "Could not write icon");
+ return null;
+ }
+ }
+
+ static void writeBitmap(ContentValues values, Bitmap bitmap) {
+ if (bitmap != null) {
+ byte[] data = flattenBitmap(bitmap);
+ values.put(LauncherSettings.Favorites.ICON, data);
+ }
+ }
+
+ /**
+ * It is very important that sub-classes implement this if they contain any references
+ * to the activity (anything in the view hierarchy etc.). If not, leaks can result since
+ * ItemInfo objects persist across rotation and can hence leak by holding stale references
+ * to the old view hierarchy / activity.
+ */
+ void unbind() {
+ }
+
+ @Override
+ public String toString() {
+ return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
+ + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
+ + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+ }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
new file mode 100644
index 0000000..ee13f29
--- /dev/null
+++ b/src/com/android/launcher3/Launcher.java
@@ -0,0 +1,4494 @@
+
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.speech.RecognizerIntent;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.TextKeyListener;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Advanceable;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.launcher3.DropTarget.DragObject;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Default launcher application.
+ */
+public class Launcher extends Activity
+ implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
+ View.OnTouchListener {
+ static final String TAG = "Launcher";
+ static final boolean LOGD = false;
+
+ static final boolean PROFILE_STARTUP = false;
+ static final boolean DEBUG_WIDGETS = false;
+ static final boolean DEBUG_STRICT_MODE = false;
+ static final boolean DEBUG_RESUME_TIME = false;
+ static final boolean DEBUG_DUMP_LOG = false;
+
+ private static final int REQUEST_CREATE_SHORTCUT = 1;
+ private static final int REQUEST_CREATE_APPWIDGET = 5;
+ private static final int REQUEST_PICK_APPLICATION = 6;
+ private static final int REQUEST_PICK_SHORTCUT = 7;
+ private static final int REQUEST_PICK_APPWIDGET = 9;
+ private static final int REQUEST_PICK_WALLPAPER = 10;
+
+ private static final int REQUEST_BIND_APPWIDGET = 11;
+
+ /**
+ * IntentStarter uses request codes starting with this. This must be greater than all activity
+ * request codes used internally.
+ */
+ protected static final int REQUEST_LAST = 100;
+
+ static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
+
+ static final int SCREEN_COUNT = 5;
+ static final int DEFAULT_SCREEN = 2;
+
+ private static final String PREFERENCES = "launcher.preferences";
+ // To turn on these properties, type
+ // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
+ static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
+ static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
+
+ // The Intent extra that defines whether to ignore the launch animation
+ static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
+ "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
+
+ // Type: int
+ private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
+ // Type: int
+ private static final String RUNTIME_STATE = "launcher.state";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
+ // Type: boolean
+ private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
+ // Type: long
+ private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
+ // Type: int
+ private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
+ // Type: parcelable
+ private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
+
+ private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
+ private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
+ "com.android.launcher.toolbar_search_icon";
+ private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME =
+ "com.android.launcher.toolbar_voice_search_icon";
+
+ public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
+ public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
+
+ /** The different states that Launcher can be in. */
+ private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
+ private State mState = State.WORKSPACE;
+ private AnimatorSet mStateAnimation;
+
+ static final int APPWIDGET_HOST_ID = 1024;
+ private static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
+ private static final int EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT = 600;
+ private static final int SHOW_CLING_DURATION = 250;
+ private static final int DISMISS_CLING_DURATION = 200;
+
+ private static final Object sLock = new Object();
+ private static int sScreen = DEFAULT_SCREEN;
+
+ // How long to wait before the new-shortcut animation automatically pans the workspace
+ private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
+ private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
+ private static int NEW_APPS_ANIMATION_DELAY = 500;
+
+ private final BroadcastReceiver mCloseSystemDialogsReceiver
+ = new CloseSystemDialogsIntentReceiver();
+ private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
+
+ private LayoutInflater mInflater;
+
+ private Workspace mWorkspace;
+ private View mLauncherView;
+ private DragLayer mDragLayer;
+ private DragController mDragController;
+ private View mWeightWatcher;
+
+ private AppWidgetManager mAppWidgetManager;
+ private LauncherAppWidgetHost mAppWidgetHost;
+
+ private ItemInfo mPendingAddInfo = new ItemInfo();
+ private AppWidgetProviderInfo mPendingAddWidgetInfo;
+
+ private int[] mTmpAddItemCellCoordinates = new int[2];
+
+ private FolderInfo mFolderInfo;
+
+ private Hotseat mHotseat;
+ private View mOverviewPanel;
+
+ private View mAllAppsButton;
+
+ private SearchDropTargetBar mSearchDropTargetBar;
+ private AppsCustomizeTabHost mAppsCustomizeTabHost;
+ private AppsCustomizePagedView mAppsCustomizeContent;
+ private boolean mAutoAdvanceRunning = false;
+ private View mQsbBar;
+
+ private Bundle mSavedState;
+ // We set the state in both onCreate and then onNewIntent in some cases, which causes both
+ // scroll issues (because the workspace may not have been measured yet) and extra work.
+ // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
+ private State mOnResumeState = State.NONE;
+
+ private SpannableStringBuilder mDefaultKeySsb = null;
+
+ private boolean mWorkspaceLoading = true;
+
+ private boolean mPaused = true;
+ private boolean mRestoring;
+ private boolean mWaitingForResult;
+ private boolean mOnResumeNeedsLoad;
+
+ private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
+ private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
+
+ // Keep track of whether the user has left launcher
+ private static boolean sPausedFromUserAction = false;
+
+ private Bundle mSavedInstanceState;
+
+ private LauncherModel mModel;
+ private IconCache mIconCache;
+ private boolean mUserPresent = true;
+ private boolean mVisible = false;
+ private boolean mHasFocus = false;
+ private boolean mAttached = false;
+ private static final boolean DISABLE_CLINGS = false;
+ private static final boolean DISABLE_CUSTOM_CLINGS = true;
+
+ private static LocaleConfiguration sLocaleConfiguration = null;
+
+ private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
+
+ private View.OnTouchListener mHapticFeedbackTouchListener;
+
+ // Related to the auto-advancing of widgets
+ private final int ADVANCE_MSG = 1;
+ private final int mAdvanceInterval = 20000;
+ private final int mAdvanceStagger = 250;
+ private long mAutoAdvanceSentTime;
+ private long mAutoAdvanceTimeLeft = -1;
+ private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
+ new HashMap<View, AppWidgetProviderInfo>();
+
+ // Determines how long to wait after a rotation before restoring the screen orientation to
+ // match the sensor state.
+ private final int mRestoreScreenOrientationDelay = 500;
+
+ // External icons saved in case of resource changes, orientation, etc.
+ private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
+ private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
+ private static Drawable.ConstantState[] sAppMarketIcon = new Drawable.ConstantState[2];
+
+ private Intent mAppMarketIntent = null;
+ private static final boolean DISABLE_MARKET_BUTTON = true;
+
+ private Drawable mWorkspaceBackgroundDrawable;
+
+ private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
+
+ static final ArrayList<String> sDumpLogs = new ArrayList<String>();
+ static Date sDateStamp = new Date();
+ static DateFormat sDateFormat =
+ DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+ static long sRunStart = System.currentTimeMillis();
+ static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
+
+ // We only want to get the SharedPreferences once since it does an FS stat each time we get
+ // it from the context.
+ private SharedPreferences mSharedPrefs;
+
+ private static ArrayList<ComponentName> mIntentsOnWorkspaceFromUpgradePath = null;
+
+ // Holds the page that we need to animate to, and the icon views that we need to animate up
+ // when we scroll to that page on resume.
+ private ImageView mFolderIconImageView;
+ private Bitmap mFolderIconBitmap;
+ private Canvas mFolderIconCanvas;
+ private Rect mRectForFolderAnimation = new Rect();
+
+ private BubbleTextView mWaitingForResume;
+
+ private HideFromAccessibilityHelper mHideFromAccessibilityHelper
+ = new HideFromAccessibilityHelper();
+
+ private Runnable mBuildLayersRunnable = new Runnable() {
+ public void run() {
+ if (mWorkspace != null) {
+ mWorkspace.buildPageHardwareLayers();
+ }
+ }
+ };
+
+ private static ArrayList<PendingAddArguments> sPendingAddList
+ = new ArrayList<PendingAddArguments>();
+
+ public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
+
+ private static class PendingAddArguments {
+ int requestCode;
+ Intent intent;
+ long container;
+ long screenId;
+ int cellX;
+ int cellY;
+ }
+
+ private Stats mStats;
+
+ private static boolean isPropertyEnabled(String propertyName) {
+ return Log.isLoggable(propertyName, Log.VERBOSE);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG_STRICT_MODE) {
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+ .detectDiskReads()
+ .detectDiskWrites()
+ .detectNetwork() // or .detectAll() for all detectable problems
+ .penaltyLog()
+ .build());
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+ .detectLeakedSqlLiteObjects()
+ .detectLeakedClosableObjects()
+ .penaltyLog()
+ .penaltyDeath()
+ .build());
+ }
+
+ super.onCreate(savedInstanceState);
+
+ LauncherAppState.setApplicationContext(getApplicationContext());
+ LauncherAppState app = LauncherAppState.getInstance();
+
+ // Determine the dynamic grid properties
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ Point realSize = new Point();
+ Display display = getWindowManager().getDefaultDisplay();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+ display.getRealSize(realSize);
+ DisplayMetrics dm = new DisplayMetrics();
+ display.getMetrics(dm);
+ // Lazy-initialize the dynamic grid
+ DeviceProfile grid = app.initDynamicGrid(this,
+ Math.min(smallestSize.x, smallestSize.y),
+ Math.min(largestSize.x, largestSize.y),
+ realSize.x, realSize.y,
+ dm.widthPixels, dm.heightPixels);
+
+ // the LauncherApplication should call this, but in case of Instrumentation it might not be present yet
+ mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
+ Context.MODE_PRIVATE);
+ mModel = app.setLauncher(this);
+ mIconCache = app.getIconCache();
+ mDragController = new DragController(this);
+ mInflater = getLayoutInflater();
+
+ mStats = new Stats(this);
+
+ mAppWidgetManager = AppWidgetManager.getInstance(this);
+
+ mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
+ mAppWidgetHost.startListening();
+
+ // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
+ // this also ensures that any synchronous binding below doesn't re-trigger another
+ // LauncherModel load.
+ mPaused = false;
+
+ if (PROFILE_STARTUP) {
+ android.os.Debug.startMethodTracing(
+ Environment.getExternalStorageDirectory() + "/launcher");
+ }
+
+
+ checkForLocaleChange();
+ setContentView(R.layout.launcher);
+
+ setupViews();
+ grid.layout(this);
+
+ registerContentObservers();
+
+ lockAllApps();
+
+ mSavedState = savedInstanceState;
+ restoreState(mSavedState);
+
+ // Update customization drawer _after_ restoring the states
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.onPackagesUpdated(
+ LauncherModel.getSortedWidgetsAndShortcuts(this));
+ }
+
+ if (PROFILE_STARTUP) {
+ android.os.Debug.stopMethodTracing();
+ }
+
+ if (!mRestoring) {
+ if (sPausedFromUserAction) {
+ // If the user leaves launcher, then we should just load items asynchronously when
+ // they return.
+ mModel.startLoader(true, -1);
+ } else {
+ // We only load the page synchronously if the user rotates (or triggers a
+ // configuration change) while launcher is in the foreground
+ mModel.startLoader(true, mWorkspace.getCurrentPage());
+ }
+ }
+
+ // For handling default keys
+ mDefaultKeySsb = new SpannableStringBuilder();
+ Selection.setSelection(mDefaultKeySsb, 0);
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ registerReceiver(mCloseSystemDialogsReceiver, filter);
+
+ updateGlobalIcons();
+
+ // On large interfaces, we want the screen to auto-rotate based on the current orientation
+ unlockScreenOrientation(true);
+
+ showFirstRunCling();
+ }
+
+ protected void onUserLeaveHint() {
+ super.onUserLeaveHint();
+ sPausedFromUserAction = true;
+ }
+
+ /** To be overriden by subclasses to hint to Launcher that we have custom content */
+ protected boolean hasCustomContentToLeft() {
+ return false;
+ }
+
+ /**
+ * To be overridden by subclasses to create the custom content and call
+ * {@link #addToCustomContentPage}. This will only be invoked if
+ * {@link #hasCustomContentToLeft()} is {@code true}.
+ */
+ protected void addCustomContentToLeft() {
+ }
+
+ /**
+ * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
+ * ensure the custom content page is added or removed if necessary.
+ */
+ protected void invalidateHasCustomContentToLeft() {
+ if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
+ // Not bound yet, wait for bindScreens to be called.
+ return;
+ }
+
+ if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
+ // Create the custom content page and call the subclass to populate it.
+ mWorkspace.createCustomContentPage();
+ addCustomContentToLeft();
+ } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
+ mWorkspace.removeCustomContentPage();
+ }
+ }
+
+ private void updateGlobalIcons() {
+ boolean searchVisible = false;
+ boolean voiceVisible = false;
+ // If we have a saved version of these external icons, we load them up immediately
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null ||
+ sAppMarketIcon[coi] == null) {
+ if (!DISABLE_MARKET_BUTTON) {
+ updateAppMarketIcon();
+ }
+ searchVisible = updateGlobalSearchIcon();
+ voiceVisible = updateVoiceSearchIcon(searchVisible);
+ }
+ if (sGlobalSearchIcon[coi] != null) {
+ updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
+ searchVisible = true;
+ }
+ if (sVoiceSearchIcon[coi] != null) {
+ updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
+ voiceVisible = true;
+ }
+ if (!DISABLE_MARKET_BUTTON && sAppMarketIcon[coi] != null) {
+ updateAppMarketIcon(sAppMarketIcon[coi]);
+ }
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
+ }
+ }
+
+ private void checkForLocaleChange() {
+ if (sLocaleConfiguration == null) {
+ new AsyncTask<Void, Void, LocaleConfiguration>() {
+ @Override
+ protected LocaleConfiguration doInBackground(Void... unused) {
+ LocaleConfiguration localeConfiguration = new LocaleConfiguration();
+ readConfiguration(Launcher.this, localeConfiguration);
+ return localeConfiguration;
+ }
+
+ @Override
+ protected void onPostExecute(LocaleConfiguration result) {
+ sLocaleConfiguration = result;
+ checkForLocaleChange(); // recursive, but now with a locale configuration
+ }
+ }.execute();
+ return;
+ }
+
+ final Configuration configuration = getResources().getConfiguration();
+
+ final String previousLocale = sLocaleConfiguration.locale;
+ final String locale = configuration.locale.toString();
+
+ final int previousMcc = sLocaleConfiguration.mcc;
+ final int mcc = configuration.mcc;
+
+ final int previousMnc = sLocaleConfiguration.mnc;
+ final int mnc = configuration.mnc;
+
+ boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;
+
+ if (localeChanged) {
+ sLocaleConfiguration.locale = locale;
+ sLocaleConfiguration.mcc = mcc;
+ sLocaleConfiguration.mnc = mnc;
+
+ mIconCache.flush();
+
+ final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
+ new Thread("WriteLocaleConfiguration") {
+ @Override
+ public void run() {
+ writeConfiguration(Launcher.this, localeConfiguration);
+ }
+ }.start();
+ }
+ }
+
+ private static class LocaleConfiguration {
+ public String locale;
+ public int mcc = -1;
+ public int mnc = -1;
+ }
+
+ private static void readConfiguration(Context context, LocaleConfiguration configuration) {
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(context.openFileInput(PREFERENCES));
+ configuration.locale = in.readUTF();
+ configuration.mcc = in.readInt();
+ configuration.mnc = in.readInt();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException e) {
+ // Ignore
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(context.openFileOutput(PREFERENCES, MODE_PRIVATE));
+ out.writeUTF(configuration.locale);
+ out.writeInt(configuration.mcc);
+ out.writeInt(configuration.mnc);
+ out.flush();
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException e) {
+ //noinspection ResultOfMethodCallIgnored
+ context.getFileStreamPath(PREFERENCES).delete();
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ public Stats getStats() {
+ return mStats;
+ }
+
+ public LayoutInflater getInflater() {
+ return mInflater;
+ }
+
+ public DragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ boolean isDraggingEnabled() {
+ // We prevent dragging when we are loading the workspace as it is possible to pick up a view
+ // that is subsequently removed from the workspace in startBinding().
+ return !mModel.isLoadingWorkspace();
+ }
+
+ static int getScreen() {
+ synchronized (sLock) {
+ return sScreen;
+ }
+ }
+
+ static void setScreen(int screen) {
+ synchronized (sLock) {
+ sScreen = screen;
+ }
+ }
+
+ /**
+ * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
+ * a configuration step, this allows the proper animations to run after other transitions.
+ */
+ private boolean completeAdd(PendingAddArguments args) {
+ boolean result = false;
+ switch (args.requestCode) {
+ case REQUEST_PICK_APPLICATION:
+ completeAddApplication(args.intent, args.container, args.screenId, args.cellX,
+ args.cellY);
+ break;
+ case REQUEST_PICK_SHORTCUT:
+ processShortcut(args.intent);
+ break;
+ case REQUEST_CREATE_SHORTCUT:
+ completeAddShortcut(args.intent, args.container, args.screenId, args.cellX,
+ args.cellY);
+ result = true;
+ break;
+ case REQUEST_CREATE_APPWIDGET:
+ int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ completeAddAppWidget(appWidgetId, args.container, args.screenId, null, null);
+ result = true;
+ break;
+ }
+ // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
+ // if you turned the screen off and then back while in All Apps, Launcher would not
+ // return to the workspace. Clearing mAddInfo.container here fixes this issue
+ resetAddInfo();
+ return result;
+ }
+
+ @Override
+ protected void onActivityResult(
+ final int requestCode, final int resultCode, final Intent data) {
+ // Reset the startActivity waiting flag
+ mWaitingForResult = false;
+
+ if (requestCode == REQUEST_BIND_APPWIDGET) {
+ int appWidgetId = data != null ?
+ data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
+ if (resultCode == RESULT_CANCELED) {
+ completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
+ } else if (resultCode == RESULT_OK) {
+ addAppWidgetImpl(appWidgetId, mPendingAddInfo, null, mPendingAddWidgetInfo);
+ }
+ return;
+ } else if (requestCode == REQUEST_PICK_WALLPAPER) {
+ if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
+ mWorkspace.exitOverviewMode(false);
+ }
+ return;
+ }
+
+ boolean delayExitSpringLoadedMode = false;
+ boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
+ requestCode == REQUEST_CREATE_APPWIDGET);
+
+ // We have special handling for widgets
+ if (isWidgetDrop) {
+ int appWidgetId = data != null ?
+ data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
+ if (appWidgetId < 0) {
+ Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
+ "widget configuration activity.");
+ completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
+ mWorkspace.stripEmptyScreens();
+ } else {
+ completeTwoStageWidgetDrop(resultCode, appWidgetId);
+ }
+ return;
+ }
+
+ // The pattern used here is that a user PICKs a specific application,
+ // which, depending on the target, might need to CREATE the actual target.
+
+ // For example, the user would PICK_SHORTCUT for "Music playlist", and we
+ // launch over to the Music app to actually CREATE_SHORTCUT.
+ if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
+ final PendingAddArguments args = new PendingAddArguments();
+ args.requestCode = requestCode;
+ args.intent = data;
+ args.container = mPendingAddInfo.container;
+ args.screenId = mPendingAddInfo.screenId;
+ args.cellX = mPendingAddInfo.cellX;
+ args.cellY = mPendingAddInfo.cellY;
+ if (isWorkspaceLocked()) {
+ sPendingAddList.add(args);
+ } else {
+ delayExitSpringLoadedMode = completeAdd(args);
+ }
+ } else if (resultCode == RESULT_CANCELED) {
+ mWorkspace.stripEmptyScreens();
+ }
+ mDragLayer.clearAnimatedView();
+ // Exit spring loaded mode if necessary after cancelling the configuration of a widget
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), delayExitSpringLoadedMode,
+ null);
+ }
+
+ private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
+ CellLayout cellLayout =
+ (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+ Runnable onCompleteRunnable = null;
+ int animationType = 0;
+
+ AppWidgetHostView boundWidget = null;
+ if (resultCode == RESULT_OK) {
+ animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
+ final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
+ mPendingAddWidgetInfo);
+ boundWidget = layout;
+ onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
+ mPendingAddInfo.screenId, layout, null);
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+ null);
+ }
+ };
+ } else if (resultCode == RESULT_CANCELED) {
+ animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
+ onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED), false,
+ null);
+ }
+ };
+ }
+ if (mDragLayer.getAnimatedView() != null) {
+ mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
+ (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
+ animationType, boundWidget, true);
+ } else {
+ // The animated view may be null in the case of a rotation during widget configuration
+ onCompleteRunnable.run();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ FirstFrameAnimatorHelper.setIsVisible(false);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ FirstFrameAnimatorHelper.setIsVisible(true);
+ }
+
+ @Override
+ protected void onResume() {
+ long startTime = 0;
+ if (DEBUG_RESUME_TIME) {
+ startTime = System.currentTimeMillis();
+ Log.v(TAG, "Launcher.onResume()");
+ }
+ super.onResume();
+
+ // Restore the previous launcher state
+ if (mOnResumeState == State.WORKSPACE) {
+ showWorkspace(false);
+ } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
+ showAllApps(false, AppsCustomizePagedView.ContentType.Applications, false);
+ }
+ mOnResumeState = State.NONE;
+
+ // Background was set to gradient in onPause(), restore to black if in all apps.
+ setWorkspaceBackground(mState == State.WORKSPACE);
+
+ mPaused = false;
+ sPausedFromUserAction = false;
+ if (mRestoring || mOnResumeNeedsLoad) {
+ mWorkspaceLoading = true;
+ mModel.startLoader(true, -1);
+ mRestoring = false;
+ mOnResumeNeedsLoad = false;
+ }
+ if (mBindOnResumeCallbacks.size() > 0) {
+ // We might have postponed some bind calls until onResume (see waitUntilResume) --
+ // execute them here
+ long startTimeCallbacks = 0;
+ if (DEBUG_RESUME_TIME) {
+ startTimeCallbacks = System.currentTimeMillis();
+ }
+
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.setBulkBind(true);
+ }
+ for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
+ mBindOnResumeCallbacks.get(i).run();
+ }
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.setBulkBind(false);
+ }
+ mBindOnResumeCallbacks.clear();
+ if (DEBUG_RESUME_TIME) {
+ Log.d(TAG, "Time spent processing callbacks in onResume: " +
+ (System.currentTimeMillis() - startTimeCallbacks));
+ }
+ }
+ if (mOnResumeCallbacks.size() > 0) {
+ for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
+ mOnResumeCallbacks.get(i).run();
+ }
+ mOnResumeCallbacks.clear();
+ }
+
+ // Reset the pressed state of icons that were locked in the press state while activities
+ // were launching
+ if (mWaitingForResume != null) {
+ // Resets the previous workspace icon press state
+ mWaitingForResume.setStayPressed(false);
+ }
+ if (mAppsCustomizeContent != null) {
+ // Resets the previous all apps icon press state
+ mAppsCustomizeContent.resetDrawableState();
+ }
+
+ // It is possible that widgets can receive updates while launcher is not in the foreground.
+ // Consequently, the widgets will be inflated in the orientation of the foreground activity
+ // (framework issue). On resuming, we ensure that any widgets are inflated for the current
+ // orientation.
+ getWorkspace().reinflateWidgetsIfNecessary();
+
+ // Process any items that were added while Launcher was away.
+ InstallShortcutReceiver.disableAndFlushInstallQueue(this);
+
+ // Update the voice search button proxy
+ updateVoiceButtonProxyVisible(false);
+
+ // Again, as with the above scenario, it's possible that one or more of the global icons
+ // were updated in the wrong orientation.
+ updateGlobalIcons();
+ if (DEBUG_RESUME_TIME) {
+ Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
+ }
+
+ if (mWorkspace.getCustomContentCallbacks() != null) {
+ // If we are resuming and the custom content is the current page, we call onShow().
+ // It is also poassible that onShow will instead be called slightly after first layout
+ // if PagedView#setRestorePage was set to the custom content page in onCreate().
+ if (mWorkspace.isOnOrMovingToCustomContent()) {
+ mWorkspace.getCustomContentCallbacks().onShow();
+ }
+ }
+ mWorkspace.updateInteractionForState();
+ mWorkspace.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ // Ensure that items added to Launcher are queued until Launcher returns
+ InstallShortcutReceiver.enableInstallQueue();
+
+ super.onPause();
+ mPaused = true;
+ mDragController.cancelDrag();
+ mDragController.resetLastGestureUpTime();
+
+ // We call onHide() aggressively. The custom content callbacks should be able to
+ // debounce excess onHide calls.
+ if (mWorkspace.getCustomContentCallbacks() != null) {
+ mWorkspace.getCustomContentCallbacks().onHide();
+ }
+ }
+
+ protected void onFinishBindingItems() {
+ if (mWorkspace != null && hasCustomContentToLeft() && mWorkspace.hasCustomContent()) {
+ addCustomContentToLeft();
+ }
+ }
+
+ QSBScroller mQsbScroller = new QSBScroller() {
+ int scrollY = 0;
+
+ @Override
+ public void setScrollY(int scroll) {
+ scrollY = scroll;
+
+ if (mWorkspace.isOnOrMovingToCustomContent()) {
+ mSearchDropTargetBar.setTranslationY(- scrollY);
+ getQsbBar().setTranslationY(-scrollY);
+ }
+ }
+ };
+
+ public void resetQSBScroll() {
+ mSearchDropTargetBar.animate().translationY(0).start();
+ getQsbBar().animate().translationY(0).start();
+ }
+
+ public interface CustomContentCallbacks {
+ // Custom content is completely shown
+ public void onShow();
+
+ // Custom content is completely hidden
+ public void onHide();
+
+ // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
+ public void onScrollProgressChanged(float progress);
+ }
+
+ protected void startSettings() {
+ }
+
+ public interface QSBScroller {
+ public void setScrollY(int scrollY);
+ }
+
+ public QSBScroller addToCustomContentPage(View customContent,
+ CustomContentCallbacks callbacks, String description) {
+ mWorkspace.addToCustomContentPage(customContent, callbacks, description);
+ return mQsbScroller;
+ }
+
+ // The custom content needs to offset its content to account for the QSB
+ public int getTopOffsetForCustomContent() {
+ return mWorkspace.getPaddingTop();
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ // Flag the loader to stop early before switching
+ mModel.stopLoader();
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.surrender();
+ }
+ return Boolean.TRUE;
+ }
+
+ // We can't hide the IME if it was forced open. So don't bother
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ mHasFocus = hasFocus;
+ }
+
+ private boolean acceptFilter() {
+ final InputMethodManager inputManager = (InputMethodManager)
+ getSystemService(Context.INPUT_METHOD_SERVICE);
+ return !inputManager.isFullscreenMode();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ final int uniChar = event.getUnicodeChar();
+ final boolean handled = super.onKeyDown(keyCode, event);
+ final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
+ if (!handled && acceptFilter() && isKeyNotWhitespace) {
+ boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
+ keyCode, event);
+ if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
+ // something usable has been typed - start a search
+ // the typed text will be retrieved and cleared by
+ // showSearchDialog()
+ // If there are multiple keystrokes before the search dialog takes focus,
+ // onSearchRequested() will be called for every keystroke,
+ // but it is idempotent, so it's fine.
+ return onSearchRequested();
+ }
+ }
+
+ // Eat the long press event so the keyboard doesn't come up.
+ if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
+ return true;
+ }
+
+ return handled;
+ }
+
+ private String getTypedText() {
+ return mDefaultKeySsb.toString();
+ }
+
+ private void clearTypedText() {
+ mDefaultKeySsb.clear();
+ mDefaultKeySsb.clearSpans();
+ Selection.setSelection(mDefaultKeySsb, 0);
+ }
+
+ /**
+ * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
+ * State
+ */
+ private static State intToState(int stateOrdinal) {
+ State state = State.WORKSPACE;
+ final State[] stateValues = State.values();
+ for (int i = 0; i < stateValues.length; i++) {
+ if (stateValues[i].ordinal() == stateOrdinal) {
+ state = stateValues[i];
+ break;
+ }
+ }
+ return state;
+ }
+
+ /**
+ * Restores the previous state, if it exists.
+ *
+ * @param savedState The previous state.
+ */
+ private void restoreState(Bundle savedState) {
+ if (savedState == null) {
+ return;
+ }
+
+ State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+ if (state == State.APPS_CUSTOMIZE) {
+ mOnResumeState = State.APPS_CUSTOMIZE;
+ }
+
+ int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
+ PagedView.INVALID_RESTORE_PAGE);
+ if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
+ mWorkspace.setRestorePage(currentScreen);
+ }
+
+ final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
+ final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
+
+ if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
+ mPendingAddInfo.container = pendingAddContainer;
+ mPendingAddInfo.screenId = pendingAddScreen;
+ mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
+ mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
+ mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
+ mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
+ mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
+ mWaitingForResult = true;
+ mRestoring = true;
+ }
+
+ boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
+ if (renameFolder) {
+ long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
+ mFolderInfo = mModel.getFolderById(this, sFolders, id);
+ mRestoring = true;
+ }
+
+ // Restore the AppsCustomize tab
+ if (mAppsCustomizeTabHost != null) {
+ String curTab = savedState.getString("apps_customize_currentTab");
+ if (curTab != null) {
+ mAppsCustomizeTabHost.setContentTypeImmediate(
+ mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
+ mAppsCustomizeContent.loadAssociatedPages(
+ mAppsCustomizeContent.getCurrentPage());
+ }
+
+ int currentIndex = savedState.getInt("apps_customize_currentIndex");
+ mAppsCustomizeContent.restorePageForIndex(currentIndex);
+ }
+ }
+
+ /**
+ * Finds all the views we need and configure them properly.
+ */
+ private void setupViews() {
+ final DragController dragController = mDragController;
+
+ mLauncherView = findViewById(R.id.launcher);
+ mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
+ mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
+
+ mLauncherView.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
+
+ // Setup the drag layer
+ mDragLayer.setup(this, dragController);
+
+ // Setup the hotseat
+ mHotseat = (Hotseat) findViewById(R.id.hotseat);
+ if (mHotseat != null) {
+ mHotseat.setup(this);
+ mHotseat.setOnLongClickListener(this);
+ }
+
+ mOverviewPanel = findViewById(R.id.overview_panel);
+ View widgetButton = findViewById(R.id.widget_button);
+ widgetButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+ }
+ });
+ widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
+
+ View wallpaperButton = findViewById(R.id.wallpaper_button);
+ wallpaperButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startWallpaper();
+ }
+ });
+ wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
+
+ View settingsButton = findViewById(R.id.settings_button);
+ settingsButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ startSettings();
+ }
+ });
+ settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
+ mOverviewPanel.setAlpha(0f);
+
+ // Setup the workspace
+ mWorkspace.setHapticFeedbackEnabled(false);
+ mWorkspace.setOnLongClickListener(this);
+ mWorkspace.setup(dragController);
+ dragController.addDragListener(mWorkspace);
+
+ // Get the search/delete bar
+ mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar);
+
+ // Setup AppsCustomize
+ mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
+ mAppsCustomizeContent = (AppsCustomizePagedView)
+ mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
+ mAppsCustomizeContent.setup(this, dragController);
+
+ // Setup the drag controller (drop targets have to be added in reverse order in priority)
+ dragController.setDragScoller(mWorkspace);
+ dragController.setScrollView(mDragLayer);
+ dragController.setMoveTarget(mWorkspace);
+ dragController.addDropTarget(mWorkspace);
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.setup(this, dragController);
+ }
+
+ if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
+ Log.v(TAG, "adding WeightWatcher");
+ mWeightWatcher = new WeightWatcher(this);
+ mWeightWatcher.setAlpha(0.5f);
+ ((FrameLayout) mLauncherView).addView(mWeightWatcher,
+ new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ Gravity.BOTTOM)
+ );
+
+ boolean show = shouldShowWeightWatcher();
+ mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ /**
+ * Creates a view representing a shortcut.
+ *
+ * @param info The data structure describing the shortcut.
+ *
+ * @return A View inflated from R.layout.application.
+ */
+ View createShortcut(ShortcutInfo info) {
+ return createShortcut(R.layout.application,
+ (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
+ }
+
+ /**
+ * Creates a view representing a shortcut inflated from the specified resource.
+ *
+ * @param layoutResId The id of the XML layout used to create the shortcut.
+ * @param parent The group the shortcut belongs to.
+ * @param info The data structure describing the shortcut.
+ *
+ * @return A View inflated from layoutResId.
+ */
+ View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
+ BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
+ favorite.applyFromShortcutInfo(info, mIconCache);
+ favorite.setOnClickListener(this);
+ return favorite;
+ }
+
+ /**
+ * Add an application shortcut to the workspace.
+ *
+ * @param data The intent describing the application.
+ * @param cellInfo The position on screen where to create the shortcut.
+ */
+ void completeAddApplication(Intent data, long container, long screenId, int cellX, int cellY) {
+ final int[] cellXY = mTmpAddItemCellCoordinates;
+ final CellLayout layout = getCellLayout(container, screenId);
+
+ // First we check if we already know the exact location where we want to add this item.
+ if (cellX >= 0 && cellY >= 0) {
+ cellXY[0] = cellX;
+ cellXY[1] = cellY;
+ } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
+ showOutOfSpaceMessage(isHotseatLayout(layout));
+ return;
+ }
+
+ final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this);
+
+ if (info != null) {
+ info.setActivity(this, data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ info.container = ItemInfo.NO_ID;
+ mWorkspace.addApplicationShortcut(info, layout, container, screenId, cellXY[0], cellXY[1],
+ isWorkspaceLocked(), cellX, cellY);
+ } else {
+ Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
+ }
+ }
+
+ /**
+ * Add a shortcut to the workspace.
+ *
+ * @param data The intent describing the shortcut.
+ * @param cellInfo The position on screen where to create the shortcut.
+ */
+ private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
+ int cellY) {
+ int[] cellXY = mTmpAddItemCellCoordinates;
+ int[] touchXY = mPendingAddInfo.dropPos;
+ CellLayout layout = getCellLayout(container, screenId);
+
+ boolean foundCellSpan = false;
+
+ ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
+ if (info == null) {
+ return;
+ }
+ final View view = createShortcut(info);
+
+ // First we check if we already know the exact location where we want to add this item.
+ if (cellX >= 0 && cellY >= 0) {
+ cellXY[0] = cellX;
+ cellXY[1] = cellY;
+ foundCellSpan = true;
+
+ // If appropriate, either create a folder or add to an existing folder
+ if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
+ true, null,null)) {
+ return;
+ }
+ DragObject dragObject = new DragObject();
+ dragObject.dragInfo = info;
+ if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
+ true)) {
+ return;
+ }
+ } else if (touchXY != null) {
+ // when dragging and dropping, just find the closest free spot
+ int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
+ foundCellSpan = (result != null);
+ } else {
+ foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
+ }
+
+ if (!foundCellSpan) {
+ showOutOfSpaceMessage(isHotseatLayout(layout));
+ return;
+ }
+
+ LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
+
+ if (!mRestoring) {
+ mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
+ isWorkspaceLocked());
+ }
+ }
+
+ static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
+ int minHeight) {
+ Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
+ // We want to account for the extra amount of padding that we are adding to the widget
+ // to ensure that it gets the full amount of space that it has requested
+ int requiredWidth = minWidth + padding.left + padding.right;
+ int requiredHeight = minHeight + padding.top + padding.bottom;
+ return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
+ }
+
+ static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
+ return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
+ }
+
+ static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
+ return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
+ }
+
+ static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
+ return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
+ }
+
+ static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
+ return getSpanForWidget(context, info.componentName, info.minResizeWidth,
+ info.minResizeHeight);
+ }
+
+ /**
+ * Add a widget to the workspace.
+ *
+ * @param appWidgetId The app widget id
+ * @param cellInfo The position on screen where to create the widget.
+ */
+ private void completeAddAppWidget(final int appWidgetId, long container, long screenId,
+ AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
+ if (appWidgetInfo == null) {
+ appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ }
+
+ // Calculate the grid spans needed to fit this widget
+ CellLayout layout = getCellLayout(container, screenId);
+
+ int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo);
+ int[] spanXY = getSpanForWidget(this, appWidgetInfo);
+
+ // Try finding open space on Launcher screen
+ // We have saved the position to which the widget was dragged-- this really only matters
+ // if we are placing widgets on a "spring-loaded" screen
+ int[] cellXY = mTmpAddItemCellCoordinates;
+ int[] touchXY = mPendingAddInfo.dropPos;
+ int[] finalSpan = new int[2];
+ boolean foundCellSpan = false;
+ if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
+ cellXY[0] = mPendingAddInfo.cellX;
+ cellXY[1] = mPendingAddInfo.cellY;
+ spanXY[0] = mPendingAddInfo.spanX;
+ spanXY[1] = mPendingAddInfo.spanY;
+ foundCellSpan = true;
+ } else if (touchXY != null) {
+ // when dragging and dropping, just find the closest free spot
+ int[] result = layout.findNearestVacantArea(
+ touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
+ spanXY[1], cellXY, finalSpan);
+ spanXY[0] = finalSpan[0];
+ spanXY[1] = finalSpan[1];
+ foundCellSpan = (result != null);
+ } else {
+ foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]);
+ }
+
+ if (!foundCellSpan) {
+ if (appWidgetId != -1) {
+ // Deleting an app widget ID is a void call but writes to disk before returning
+ // to the caller...
+ new Thread("deleteAppWidgetId") {
+ public void run() {
+ mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+ }
+ }.start();
+ }
+ showOutOfSpaceMessage(isHotseatLayout(layout));
+ return;
+ }
+
+ // Build Launcher-specific widget info and save to database
+ LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId,
+ appWidgetInfo.provider);
+ launcherInfo.spanX = spanXY[0];
+ launcherInfo.spanY = spanXY[1];
+ launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
+ launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
+
+ LauncherModel.addItemToDatabase(this, launcherInfo,
+ container, screenId, cellXY[0], cellXY[1], false);
+
+ if (!mRestoring) {
+ if (hostView == null) {
+ // Perform actual inflation because we're live
+ launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+ launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
+ } else {
+ // The AppWidgetHostView has already been inflated and instantiated
+ launcherInfo.hostView = hostView;
+ }
+
+ launcherInfo.hostView.setTag(launcherInfo);
+ launcherInfo.hostView.setVisibility(View.VISIBLE);
+ launcherInfo.notifyWidgetSizeChanged(this);
+
+ mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, cellXY[0], cellXY[1],
+ launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
+
+ addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
+ }
+ resetAddInfo();
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mUserPresent = false;
+ mDragLayer.clearAllResizeFrames();
+ updateRunning();
+
+ // Reset AllApps to its initial state only if we are not in the middle of
+ // processing a multi-step drop
+ if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
+ showWorkspace(false);
+ }
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+ mUserPresent = true;
+ updateRunning();
+ }
+ }
+ };
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Listen for broadcasts related to user-presence
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ registerReceiver(mReceiver, filter);
+ FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
+ mAttached = true;
+ mVisible = true;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mVisible = false;
+
+ if (mAttached) {
+ unregisterReceiver(mReceiver);
+ mAttached = false;
+ }
+ updateRunning();
+ }
+
+ public void onWindowVisibilityChanged(int visibility) {
+ mVisible = visibility == View.VISIBLE;
+ updateRunning();
+ // The following code used to be in onResume, but it turns out onResume is called when
+ // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
+ // is a more appropriate event to handle
+ if (mVisible) {
+ mAppsCustomizeTabHost.onWindowVisible();
+ if (!mWorkspaceLoading) {
+ final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
+ // We want to let Launcher draw itself at least once before we force it to build
+ // layers on all the workspace pages, so that transitioning to Launcher from other
+ // apps is nice and speedy.
+ observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+ private boolean mStarted = false;
+ public void onDraw() {
+ if (mStarted) return;
+ mStarted = true;
+ // We delay the layer building a bit in order to give
+ // other message processing a time to run. In particular
+ // this avoids a delay in hiding the IME if it was
+ // currently shown, because doing that may involve
+ // some communication back with the app.
+ mWorkspace.postDelayed(mBuildLayersRunnable, 500);
+ final ViewTreeObserver.OnDrawListener listener = this;
+ mWorkspace.post(new Runnable() {
+ public void run() {
+ if (mWorkspace != null &&
+ mWorkspace.getViewTreeObserver() != null) {
+ mWorkspace.getViewTreeObserver().
+ removeOnDrawListener(listener);
+ }
+ }
+ });
+ return;
+ }
+ });
+ }
+ // When Launcher comes back to foreground, a different Activity might be responsible for
+ // the app market intent, so refresh the icon
+ if (!DISABLE_MARKET_BUTTON) {
+ updateAppMarketIcon();
+ }
+ clearTypedText();
+ }
+ }
+
+ private void sendAdvanceMessage(long delay) {
+ mHandler.removeMessages(ADVANCE_MSG);
+ Message msg = mHandler.obtainMessage(ADVANCE_MSG);
+ mHandler.sendMessageDelayed(msg, delay);
+ mAutoAdvanceSentTime = System.currentTimeMillis();
+ }
+
+ private void updateRunning() {
+ boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
+ if (autoAdvanceRunning != mAutoAdvanceRunning) {
+ mAutoAdvanceRunning = autoAdvanceRunning;
+ if (autoAdvanceRunning) {
+ long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
+ sendAdvanceMessage(delay);
+ } else {
+ if (!mWidgetsToAdvance.isEmpty()) {
+ mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
+ (System.currentTimeMillis() - mAutoAdvanceSentTime));
+ }
+ mHandler.removeMessages(ADVANCE_MSG);
+ mHandler.removeMessages(0); // Remove messages sent using postDelayed()
+ }
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == ADVANCE_MSG) {
+ int i = 0;
+ for (View key: mWidgetsToAdvance.keySet()) {
+ final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
+ final int delay = mAdvanceStagger * i;
+ if (v instanceof Advanceable) {
+ postDelayed(new Runnable() {
+ public void run() {
+ ((Advanceable) v).advance();
+ }
+ }, delay);
+ }
+ i++;
+ }
+ sendAdvanceMessage(mAdvanceInterval);
+ }
+ }
+ };
+
+ void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
+ if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
+ View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
+ if (v instanceof Advanceable) {
+ mWidgetsToAdvance.put(hostView, appWidgetInfo);
+ ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
+ updateRunning();
+ }
+ }
+
+ void removeWidgetToAutoAdvance(View hostView) {
+ if (mWidgetsToAdvance.containsKey(hostView)) {
+ mWidgetsToAdvance.remove(hostView);
+ updateRunning();
+ }
+ }
+
+ public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
+ removeWidgetToAutoAdvance(launcherInfo.hostView);
+ launcherInfo.hostView = null;
+ }
+
+ void showOutOfSpaceMessage(boolean isHotseatLayout) {
+ int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
+ Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
+ }
+
+ public LauncherAppWidgetHost getAppWidgetHost() {
+ return mAppWidgetHost;
+ }
+
+ public LauncherModel getModel() {
+ return mModel;
+ }
+
+ public void closeSystemDialogs() {
+ getWindow().closeAllPanels();
+
+ // Whatever we were doing is hereby canceled.
+ mWaitingForResult = false;
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ long startTime = 0;
+ if (DEBUG_RESUME_TIME) {
+ startTime = System.currentTimeMillis();
+ }
+ super.onNewIntent(intent);
+
+ // Close the menu
+ if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ // also will cancel mWaitingForResult.
+ closeSystemDialogs();
+
+ final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+ Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+ != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+
+ if (mWorkspace == null) {
+ // Can be cases where mWorkspace is null, this prevents a NPE
+ return;
+ }
+ Folder openFolder = mWorkspace.getOpenFolder();
+ // In all these cases, only animate if we're already on home
+ mWorkspace.exitWidgetResizeMode();
+ if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
+ openFolder == null) {
+ mWorkspace.moveToDefaultScreen(true);
+ }
+
+ closeFolder();
+ exitSpringLoadedDragMode();
+
+ // If we are already on home, then just animate back to the workspace,
+ // otherwise, just wait until onResume to set the state back to Workspace
+ if (alreadyOnHome) {
+ showWorkspace(true);
+ } else {
+ mOnResumeState = State.WORKSPACE;
+ }
+
+ final View v = getWindow().peekDecorView();
+ if (v != null && v.getWindowToken() != null) {
+ InputMethodManager imm = (InputMethodManager)getSystemService(
+ INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+
+ // Reset the apps customize page
+ if (mAppsCustomizeTabHost != null) {
+ mAppsCustomizeTabHost.reset();
+ }
+ }
+
+ if (DEBUG_RESUME_TIME) {
+ Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
+ }
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+ for (int page: mSynchronouslyBoundPages) {
+ mWorkspace.restoreInstanceStateForChild(page);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ if (mWorkspace.getChildCount() > 0) {
+ outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getRestorePage());
+ }
+ super.onSaveInstanceState(outState);
+
+ outState.putInt(RUNTIME_STATE, mState.ordinal());
+ // We close any open folder since it will not be re-opened, and we need to make sure
+ // this state is reflected.
+ closeFolder();
+
+ if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
+ mWaitingForResult) {
+ outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
+ outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
+ outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
+ outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
+ }
+
+ if (mFolderInfo != null && mWaitingForResult) {
+ outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
+ outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
+ }
+
+ // Save the current AppsCustomize tab
+ if (mAppsCustomizeTabHost != null) {
+ String currentTabTag = mAppsCustomizeTabHost.getCurrentTabTag();
+ if (currentTabTag != null) {
+ outState.putString("apps_customize_currentTab", currentTabTag);
+ }
+ int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
+ outState.putInt("apps_customize_currentIndex", currentIndex);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // Remove all pending runnables
+ mHandler.removeMessages(ADVANCE_MSG);
+ mHandler.removeMessages(0);
+ mWorkspace.removeCallbacks(mBuildLayersRunnable);
+
+ // Stop callbacks from LauncherModel
+ LauncherAppState app = (LauncherAppState.getInstance());
+ mModel.stopLoader();
+ app.setLauncher(null);
+
+ try {
+ mAppWidgetHost.stopListening();
+ } catch (NullPointerException ex) {
+ Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
+ }
+ mAppWidgetHost = null;
+
+ mWidgetsToAdvance.clear();
+
+ TextKeyListener.getInstance().release();
+
+ // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
+ // to prevent leaking Launcher activities on orientation change.
+ if (mModel != null) {
+ mModel.unbindItemInfosAndClearQueuedBindRunnables();
+ }
+
+ getContentResolver().unregisterContentObserver(mWidgetObserver);
+ unregisterReceiver(mCloseSystemDialogsReceiver);
+
+ mDragLayer.clearAllResizeFrames();
+ ((ViewGroup) mWorkspace.getParent()).removeAllViews();
+ mWorkspace.removeAllViews();
+ mWorkspace = null;
+ mDragController = null;
+
+ LauncherAnimUtils.onDestroyActivity();
+ }
+
+ public DragController getDragController() {
+ return mDragController;
+ }
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ if (requestCode >= 0) mWaitingForResult = true;
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ /**
+ * Indicates that we want global search for this activity by setting the globalSearch
+ * argument for {@link #startSearch} to true.
+ */
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData, boolean globalSearch) {
+
+ showWorkspace(true);
+
+ if (initialQuery == null) {
+ // Use any text typed in the launcher as the initial query
+ initialQuery = getTypedText();
+ }
+ if (appSearchData == null) {
+ appSearchData = new Bundle();
+ appSearchData.putString("source", "launcher-search");
+ }
+ Rect sourceBounds = new Rect();
+ if (mSearchDropTargetBar != null) {
+ sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
+ }
+
+ startSearch(initialQuery, selectInitialQuery,
+ appSearchData, sourceBounds);
+ }
+
+ public void startSearch(String initialQuery,
+ boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
+ startGlobalSearch(initialQuery, selectInitialQuery,
+ appSearchData, sourceBounds);
+ }
+
+ /**
+ * Starts the global search activity. This code is a copied from SearchManager
+ */
+ private void startGlobalSearch(String initialQuery,
+ boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
+ if (globalSearchActivity == null) {
+ Log.w(TAG, "No global search activity found.");
+ return;
+ }
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(globalSearchActivity);
+ // Make sure that we have a Bundle to put source in
+ if (appSearchData == null) {
+ appSearchData = new Bundle();
+ } else {
+ appSearchData = new Bundle(appSearchData);
+ }
+ // Set source to package name of app that starts global search, if not set already.
+ if (!appSearchData.containsKey("source")) {
+ appSearchData.putString("source", getPackageName());
+ }
+ intent.putExtra(SearchManager.APP_DATA, appSearchData);
+ if (!TextUtils.isEmpty(initialQuery)) {
+ intent.putExtra(SearchManager.QUERY, initialQuery);
+ }
+ if (selectInitialQuery) {
+ intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
+ }
+ intent.setSourceBounds(sourceBounds);
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ if (!mWorkspace.isInOverviewMode()) {
+ mWorkspace.enterOverviewMode();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ startSearch(null, false, null, true);
+ // Use a custom animation for launching search
+ return true;
+ }
+
+ public boolean isWorkspaceLocked() {
+ return mWorkspaceLoading || mWaitingForResult;
+ }
+
+ private void resetAddInfo() {
+ mPendingAddInfo.container = ItemInfo.NO_ID;
+ mPendingAddInfo.screenId = -1;
+ mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
+ mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
+ mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
+ mPendingAddInfo.dropPos = null;
+ }
+
+ void addAppWidgetImpl(final int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
+ AppWidgetProviderInfo appWidgetInfo) {
+ if (appWidgetInfo.configure != null) {
+ mPendingAddWidgetInfo = appWidgetInfo;
+
+ // Launch over to configure widget, if needed
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+ intent.setComponent(appWidgetInfo.configure);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_APPWIDGET);
+ } else {
+ // Otherwise just add it
+ completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
+ appWidgetInfo);
+ // Exit spring loaded mode if necessary after adding the widget
+ exitSpringLoadedDragModeDelayed(true, false, null);
+ }
+ }
+
+ protected void moveToCustomContentScreen(boolean animate) {
+ // Close any folders that may be open.
+ closeFolder();
+ mWorkspace.moveToCustomContentScreen(animate);
+ }
+ /**
+ * Process a shortcut drop.
+ *
+ * @param componentName The name of the component
+ * @param screenId The ID of the screen where it should be added
+ * @param cell The cell it should be added to, optional
+ * @param position The location on the screen where it was dropped, optional
+ */
+ void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
+ int[] cell, int[] loc) {
+ resetAddInfo();
+ mPendingAddInfo.container = container;
+ mPendingAddInfo.screenId = screenId;
+ mPendingAddInfo.dropPos = loc;
+
+ if (cell != null) {
+ mPendingAddInfo.cellX = cell[0];
+ mPendingAddInfo.cellY = cell[1];
+ }
+
+ Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ createShortcutIntent.setComponent(componentName);
+ processShortcut(createShortcutIntent);
+ }
+
+ /**
+ * Process a widget drop.
+ *
+ * @param info The PendingAppWidgetInfo of the widget being added.
+ * @param screenId The ID of the screen where it should be added
+ * @param cell The cell it should be added to, optional
+ * @param position The location on the screen where it was dropped, optional
+ */
+ void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
+ int[] cell, int[] span, int[] loc) {
+ resetAddInfo();
+ mPendingAddInfo.container = info.container = container;
+ mPendingAddInfo.screenId = info.screenId = screenId;
+ mPendingAddInfo.dropPos = loc;
+ mPendingAddInfo.minSpanX = info.minSpanX;
+ mPendingAddInfo.minSpanY = info.minSpanY;
+
+ if (cell != null) {
+ mPendingAddInfo.cellX = cell[0];
+ mPendingAddInfo.cellY = cell[1];
+ }
+ if (span != null) {
+ mPendingAddInfo.spanX = span[0];
+ mPendingAddInfo.spanY = span[1];
+ }
+
+ AppWidgetHostView hostView = info.boundWidget;
+ int appWidgetId;
+ if (hostView != null) {
+ appWidgetId = hostView.getAppWidgetId();
+ addAppWidgetImpl(appWidgetId, info, hostView, info.info);
+ } else {
+ // In this case, we either need to start an activity to get permission to bind
+ // the widget, or we need to start an activity to configure the widget, or both.
+ appWidgetId = getAppWidgetHost().allocateAppWidgetId();
+ Bundle options = info.bindOptions;
+
+ boolean success = false;
+ if (options != null) {
+ success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+ info.componentName, options);
+ } else {
+ success = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+ info.componentName);
+ }
+ if (success) {
+ addAppWidgetImpl(appWidgetId, info, null, info.info);
+ } else {
+ mPendingAddWidgetInfo = info.info;
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
+ // TODO: we need to make sure that this accounts for the options bundle.
+ // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
+ startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+ }
+ }
+ }
+
+ void processShortcut(Intent intent) {
+ // Handle case where user selected "Applications"
+ String applicationName = getResources().getString(R.string.group_applications);
+ String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+
+ if (applicationName != null && applicationName.equals(shortcutName)) {
+ Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+ Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
+ pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
+ pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
+ Utilities.startActivityForResultSafely(this, pickIntent, REQUEST_PICK_APPLICATION);
+ } else {
+ Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
+ }
+ }
+
+ void processWallpaper(Intent intent) {
+ startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
+ }
+
+ FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
+ int cellY) {
+ final FolderInfo folderInfo = new FolderInfo();
+ folderInfo.title = getText(R.string.folder_name);
+
+ // Update the model
+ LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
+ false);
+ sFolders.put(folderInfo.id, folderInfo);
+
+ // Create the view
+ FolderIcon newFolder =
+ FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
+ mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
+ isWorkspaceLocked());
+ // Force measure the new folder icon
+ CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
+ parent.getShortcutsAndWidgets().measureChild(newFolder);
+ return newFolder;
+ }
+
+ void removeFolder(FolderInfo folder) {
+ sFolders.remove(folder.id);
+ }
+
+ protected void startWallpaper() {
+ final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
+ pickWallpaper.setComponent(getWallpaperPickerComponent());
+ startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
+ }
+
+ protected ComponentName getWallpaperPickerComponent() {
+ return new ComponentName(getPackageName(), WallpaperPickerActivity.class.getName());
+ }
+
+ /**
+ * Registers various content observers. The current implementation registers
+ * only a favorites observer to keep track of the favorites applications.
+ */
+ private void registerContentObservers() {
+ ContentResolver resolver = getContentResolver();
+ resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
+ true, mWidgetObserver);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_HOME:
+ return true;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ if (isPropertyEnabled(DUMP_STATE_PROPERTY)) {
+ dumpState();
+ return true;
+ }
+ break;
+ }
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_HOME:
+ return true;
+ }
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (isAllAppsVisible()) {
+ if (mAppsCustomizeContent.getContentType() ==
+ AppsCustomizePagedView.ContentType.Applications) {
+ showWorkspace(true);
+ } else {
+ showOverviewMode(true);
+ }
+ } else if (mWorkspace.isInOverviewMode()) {
+ mWorkspace.exitOverviewMode(true);
+ } else if (mWorkspace.getOpenFolder() != null) {
+ Folder openFolder = mWorkspace.getOpenFolder();
+ if (openFolder.isEditingName()) {
+ openFolder.dismissEditingName();
+ } else {
+ closeFolder();
+ }
+ } else {
+ mWorkspace.exitWidgetResizeMode();
+
+ // Back button is a no-op here, but give at least some feedback for the button press
+ mWorkspace.showOutlinesTemporarily();
+ }
+ }
+
+ /**
+ * Re-listen when widgets are reset.
+ */
+ private void onAppWidgetReset() {
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.startListening();
+ }
+ }
+
+ /**
+ * Launches the intent referred by the clicked shortcut.
+ *
+ * @param v The view representing the clicked shortcut.
+ */
+ public void onClick(View v) {
+ // Make sure that rogue clicks don't get through while allapps is launching, or after the
+ // view has detached (it's possible for this to happen if the view is removed mid touch).
+ if (v.getWindowToken() == null) {
+ return;
+ }
+
+ if (!mWorkspace.isFinishedSwitchingState()) {
+ return;
+ }
+
+ if (v instanceof Workspace) {
+ if (mWorkspace.isInOverviewMode()) {
+ mWorkspace.exitOverviewMode(true);
+ }
+ return;
+ }
+
+ if (v instanceof CellLayout) {
+ if (mWorkspace.isInOverviewMode()) {
+ mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
+ }
+ }
+
+ Object tag = v.getTag();
+ if (tag instanceof ShortcutInfo) {
+ // Open shortcut
+ final ShortcutInfo shortcut = (ShortcutInfo) tag;
+ final Intent intent = shortcut.intent;
+
+ // Check for special shortcuts
+ if (intent.getComponent() != null) {
+ final String shortcutClass = intent.getComponent().getClassName();
+
+ if (shortcutClass.equals(WidgetAdder.class.getName())) {
+ showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
+ return;
+ } else if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
+ MemoryDumpActivity.startDump(this);
+ return;
+ } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
+ toggleShowWeightWatcher();
+ return;
+ }
+ }
+
+ // Start activities
+ int[] pos = new int[2];
+ v.getLocationOnScreen(pos);
+ intent.setSourceBounds(new Rect(pos[0], pos[1],
+ pos[0] + v.getWidth(), pos[1] + v.getHeight()));
+
+ boolean success = startActivitySafely(v, intent, tag);
+
+ mStats.recordLaunch(intent, shortcut);
+
+ if (success && v instanceof BubbleTextView) {
+ mWaitingForResume = (BubbleTextView) v;
+ mWaitingForResume.setStayPressed(true);
+ }
+ } else if (tag instanceof FolderInfo) {
+ if (v instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) v;
+ handleFolderClick(fi);
+ }
+ } else if (v == mAllAppsButton) {
+ if (isAllAppsVisible()) {
+ showWorkspace(true);
+ } else {
+ onClickAllAppsButton(v);
+ }
+ }
+ }
+
+ public boolean onTouch(View v, MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Event handler for the search button
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickSearchButton(View v) {
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+ onSearchRequested();
+ }
+
+ /**
+ * Event handler for the voice button
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickVoiceButton(View v) {
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+ startVoice();
+ }
+
+ public void startVoice() {
+ try {
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ ComponentName activityName = searchManager.getGlobalSearchActivity();
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (activityName != null) {
+ intent.setPackage(activityName.getPackageName());
+ }
+ startActivity(null, intent, "onClickVoiceButton");
+ } catch (ActivityNotFoundException e) {
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivitySafely(null, intent, "onClickVoiceButton");
+ }
+ }
+
+ /**
+ * Event handler for the "grid" button that appears on the home screen, which
+ * enters all apps mode.
+ *
+ * @param v The view that was clicked.
+ */
+ public void onClickAllAppsButton(View v) {
+ showAllApps(true, AppsCustomizePagedView.ContentType.Applications, true);
+ }
+
+ public void onTouchDownAllAppsButton(View v) {
+ // Provide the same haptic feedback that the system offers for virtual keys.
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+
+ public void performHapticFeedbackOnTouchDown(View v) {
+ // Provide the same haptic feedback that the system offers for virtual keys.
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+
+ public View.OnTouchListener getHapticFeedbackTouchListener() {
+ if (mHapticFeedbackTouchListener == null) {
+ mHapticFeedbackTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+ return false;
+ }
+ };
+ }
+ return mHapticFeedbackTouchListener;
+ }
+
+ public void onClickAppMarketButton(View v) {
+ if (!DISABLE_MARKET_BUTTON) {
+ if (mAppMarketIntent != null) {
+ startActivitySafely(v, mAppMarketIntent, "app market");
+ } else {
+ Log.e(TAG, "Invalid app market intent.");
+ }
+ }
+ }
+
+ /**
+ * Called when the user stops interacting with the launcher.
+ * This implies that the user is now on the homescreen and is not doing housekeeping.
+ */
+ protected void onInteractionEnd() {}
+
+ /**
+ * Called when the user starts interacting with the launcher.
+ * The possible interactions are:
+ * - open all apps
+ * - reorder an app shortcut, or a widget
+ * - open the overview mode.
+ * This is a good time to stop doing things that only make sense
+ * when the user is on the homescreen and not doing housekeeping.
+ */
+ protected void onInteractionBegin() {}
+
+ void startApplicationDetailsActivity(ComponentName componentName) {
+ String packageName = componentName.getPackageName();
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.fromParts("package", packageName, null));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivitySafely(null, intent, "startApplicationDetailsActivity");
+ }
+
+ // returns true if the activity was started
+ boolean startApplicationUninstallActivity(ComponentName componentName, int flags) {
+ if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
+ // System applications cannot be installed. For now, show a toast explaining that.
+ // We may give them the option of disabling apps this way.
+ int messageId = R.string.uninstall_system_app_text;
+ Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
+ return false;
+ } else {
+ String packageName = componentName.getPackageName();
+ String className = componentName.getClassName();
+ Intent intent = new Intent(
+ Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivity(intent);
+ return true;
+ }
+ }
+
+ boolean startActivity(View v, Intent intent, Object tag) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ try {
+ // Only launch using the new animation if the shortcut has not opted out (this is a
+ // private contract between launcher and may be ignored in the future).
+ boolean useLaunchAnimation = (v != null) &&
+ !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
+ if (useLaunchAnimation) {
+ ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
+ v.getMeasuredWidth(), v.getMeasuredHeight());
+
+ startActivity(intent, opts.toBundle());
+ } else {
+ startActivity(intent);
+ }
+ return true;
+ } catch (SecurityException e) {
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+ ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+ "or use the exported attribute for this activity. "
+ + "tag="+ tag + " intent=" + intent, e);
+ }
+ return false;
+ }
+
+ boolean startActivitySafely(View v, Intent intent, Object tag) {
+ boolean success = false;
+ try {
+ success = startActivity(v, intent, tag);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
+ }
+ return success;
+ }
+
+ private void handleFolderClick(FolderIcon folderIcon) {
+ final FolderInfo info = folderIcon.getFolderInfo();
+ Folder openFolder = mWorkspace.getFolderForTag(info);
+
+ // If the folder info reports that the associated folder is open, then verify that
+ // it is actually opened. There have been a few instances where this gets out of sync.
+ if (info.opened && openFolder == null) {
+ Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
+ + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
+ info.opened = false;
+ }
+
+ if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
+ // Close any open folder
+ closeFolder();
+ // Open the requested folder
+ openFolder(folderIcon);
+ } else {
+ // Find the open folder...
+ int folderScreen;
+ if (openFolder != null) {
+ folderScreen = mWorkspace.getPageForView(openFolder);
+ // .. and close it
+ closeFolder(openFolder);
+ if (folderScreen != mWorkspace.getCurrentPage()) {
+ // Close any folder open on the current screen
+ closeFolder();
+ // Pull the folder onto this screen
+ openFolder(folderIcon);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
+ * in the DragLayer in the exact absolute location of the original FolderIcon.
+ */
+ private void copyFolderIconToImage(FolderIcon fi) {
+ final int width = fi.getMeasuredWidth();
+ final int height = fi.getMeasuredHeight();
+
+ // Lazy load ImageView, Bitmap and Canvas
+ if (mFolderIconImageView == null) {
+ mFolderIconImageView = new ImageView(this);
+ }
+ if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
+ mFolderIconBitmap.getHeight() != height) {
+ mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mFolderIconCanvas = new Canvas(mFolderIconBitmap);
+ }
+
+ DragLayer.LayoutParams lp;
+ if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
+ lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
+ } else {
+ lp = new DragLayer.LayoutParams(width, height);
+ }
+
+ // The layout from which the folder is being opened may be scaled, adjust the starting
+ // view size by this scale factor.
+ float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
+ lp.customPosition = true;
+ lp.x = mRectForFolderAnimation.left;
+ lp.y = mRectForFolderAnimation.top;
+ lp.width = (int) (scale * width);
+ lp.height = (int) (scale * height);
+
+ mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ fi.draw(mFolderIconCanvas);
+ mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
+ if (fi.getFolder() != null) {
+ mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
+ mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
+ }
+ // Just in case this image view is still in the drag layer from a previous animation,
+ // we remove it and re-add it.
+ if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
+ mDragLayer.removeView(mFolderIconImageView);
+ }
+ mDragLayer.addView(mFolderIconImageView, lp);
+ if (fi.getFolder() != null) {
+ fi.getFolder().bringToFront();
+ }
+ }
+
+ private void growAndFadeOutFolderIcon(FolderIcon fi) {
+ if (fi == null) return;
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
+
+ FolderInfo info = (FolderInfo) fi.getTag();
+ if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ CellLayout cl = (CellLayout) fi.getParent().getParent();
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
+ cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ }
+
+ // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
+ copyFolderIconToImage(fi);
+ fi.setVisibility(View.INVISIBLE);
+
+ ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
+ scaleX, scaleY);
+ oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+ oa.start();
+ }
+
+ private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
+ if (fi == null) return;
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
+
+ final CellLayout cl = (CellLayout) fi.getParent().getParent();
+
+ // We remove and re-draw the FolderIcon in-case it has changed
+ mDragLayer.removeView(mFolderIconImageView);
+ copyFolderIconToImage(fi);
+ ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
+ scaleX, scaleY);
+ oa.setDuration(getResources().getInteger(R.integer.config_folderAnimDuration));
+ oa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (cl != null) {
+ cl.clearFolderLeaveBehind();
+ // Remove the ImageView copy of the FolderIcon and make the original visible.
+ mDragLayer.removeView(mFolderIconImageView);
+ fi.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ oa.start();
+ }
+
+ /**
+ * Opens the user folder described by the specified tag. The opening of the folder
+ * is animated relative to the specified View. If the View is null, no animation
+ * is played.
+ *
+ * @param folderInfo The FolderInfo describing the folder to open.
+ */
+ public void openFolder(FolderIcon folderIcon) {
+ Folder folder = folderIcon.getFolder();
+ FolderInfo info = folder.mInfo;
+
+ info.opened = true;
+
+ // Just verify that the folder hasn't already been added to the DragLayer.
+ // There was a one-off crash where the folder had a parent already.
+ if (folder.getParent() == null) {
+ mDragLayer.addView(folder);
+ mDragController.addDropTarget((DropTarget) folder);
+ } else {
+ Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
+ folder.getParent() + ").");
+ }
+ folder.animateOpen();
+ growAndFadeOutFolderIcon(folderIcon);
+
+ // Notify the accessibility manager that this folder "window" has appeared and occluded
+ // the workspace items
+ folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ }
+
+ public void closeFolder() {
+ Folder folder = mWorkspace.getOpenFolder();
+ if (folder != null) {
+ if (folder.isEditingName()) {
+ folder.dismissEditingName();
+ }
+ closeFolder(folder);
+
+ // Dismiss the folder cling
+ dismissFolderCling(null);
+ }
+ }
+
+ void closeFolder(Folder folder) {
+ folder.getInfo().opened = false;
+
+ ViewGroup parent = (ViewGroup) folder.getParent().getParent();
+ if (parent != null) {
+ FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
+ shrinkAndFadeInFolderIcon(fi);
+ }
+ folder.animateClosed();
+
+ // Notify the accessibility manager that this folder "window" has disappeard and no
+ // longer occludeds the workspace items
+ getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ public boolean onLongClick(View v) {
+ if (!isDraggingEnabled()) return false;
+ if (isWorkspaceLocked()) return false;
+ if (mState != State.WORKSPACE) return false;
+
+ if (v instanceof Workspace) {
+ if (!mWorkspace.isInOverviewMode()) {
+ if (mWorkspace.enterOverviewMode()) {
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ if (!(v instanceof CellLayout)) {
+ v = (View) v.getParent().getParent();
+ }
+
+ resetAddInfo();
+ CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
+ // This happens when long clicking an item with the dpad/trackball
+ if (longClickCellInfo == null) {
+ return true;
+ }
+
+ // The hotseat touch handling does not go through Workspace, and we always allow long press
+ // on hotseat items.
+ final View itemUnderLongClick = longClickCellInfo.cell;
+ boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
+ if (allowLongPress && !mDragController.isDragging()) {
+ if (itemUnderLongClick == null) {
+ // User long pressed on empty space
+ mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ // Disabling reordering until we sort out some issues.
+ if (mWorkspace.isInOverviewMode()) {
+ mWorkspace.startReordering(v);
+ } else {
+ mWorkspace.enterOverviewMode();
+ }
+ } else {
+ if (!(itemUnderLongClick instanceof Folder)) {
+ // User long pressed on an item
+ mWorkspace.startDrag(longClickCellInfo);
+ }
+ }
+ }
+ return true;
+ }
+
+ boolean isHotseatLayout(View layout) {
+ return mHotseat != null && layout != null &&
+ (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
+ }
+ Hotseat getHotseat() {
+ return mHotseat;
+ }
+ View getOverviewPanel() {
+ return mOverviewPanel;
+ }
+ SearchDropTargetBar getSearchBar() {
+ return mSearchDropTargetBar;
+ }
+
+ /**
+ * Returns the CellLayout of the specified container at the specified screen.
+ */
+ CellLayout getCellLayout(long container, long screenId) {
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ if (mHotseat != null) {
+ return mHotseat.getLayout();
+ } else {
+ return null;
+ }
+ } else {
+ return (CellLayout) mWorkspace.getScreenWithId(screenId);
+ }
+ }
+
+ Workspace getWorkspace() {
+ return mWorkspace;
+ }
+
+ public boolean isAllAppsVisible() {
+ return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
+ }
+
+ /**
+ * Helper method for the cameraZoomIn/cameraZoomOut animations
+ * @param view The view being animated
+ * @param scaleFactor The scale factor used for the zoom
+ */
+ private void setPivotsForZoom(View view, float scaleFactor) {
+ view.setPivotX(view.getWidth() / 2.0f);
+ view.setPivotY(view.getHeight() / 2.0f);
+ }
+
+ private void setWorkspaceBackground(boolean workspace) {
+ mLauncherView.setBackground(workspace ?
+ mWorkspaceBackgroundDrawable : null);
+ }
+
+ void updateWallpaperVisibility(boolean visible) {
+ int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
+ int curflags = getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ if (wpflags != curflags) {
+ getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
+ }
+ setWorkspaceBackground(visible);
+ }
+
+ private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace);
+ }
+ }
+
+ private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
+ }
+
+ // Update the workspace transition step as well
+ dispatchOnLauncherTransitionStep(v, 0f);
+ }
+
+ private void dispatchOnLauncherTransitionStep(View v, float t) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
+ }
+ }
+
+ private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
+ if (v instanceof LauncherTransitionable) {
+ ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
+ }
+
+ // Update the workspace transition step as well
+ dispatchOnLauncherTransitionStep(v, 1f);
+ }
+
+ /**
+ * Things to test when changing the following seven functions.
+ * - Home from workspace
+ * - from center screen
+ * - from other screens
+ * - Home from all apps
+ * - from center screen
+ * - from other screens
+ * - Back from all apps
+ * - from center screen
+ * - from other screens
+ * - Launch app from workspace and quit
+ * - with back
+ * - with home
+ * - Launch app from all apps and quit
+ * - with back
+ * - with home
+ * - Go to a screen that's not the default, then all
+ * apps, and launch and app, and go back
+ * - with back
+ * -with home
+ * - On workspace, long press power and go back
+ * - with back
+ * - with home
+ * - On all apps, long press power and go back
+ * - with back
+ * - with home
+ * - On workspace, power off
+ * - On all apps, power off
+ * - Launch an app and turn off the screen while in that app
+ * - Go back with home key
+ * - Go back with back key TODO: make this not go to workspace
+ * - From all apps
+ * - From workspace
+ * - Enter and exit car mode (becuase it causes an extra configuration changed)
+ * - From all apps
+ * - From the center workspace
+ * - From another workspace
+ */
+
+ /**
+ * Zoom the camera out from the workspace to reveal 'toView'.
+ * Assumes that the view to show is anchored at either the very top or very bottom
+ * of the screen.
+ */
+ private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
+ AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType();
+ showAppsCustomizeHelper(animated, springLoaded, contentType);
+ }
+ private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded,
+ final AppsCustomizePagedView.ContentType contentType) {
+ if (mStateAnimation != null) {
+ mStateAnimation.setDuration(0);
+ mStateAnimation.cancel();
+ mStateAnimation = null;
+ }
+ final Resources res = getResources();
+
+ final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
+ final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
+ final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
+ final View fromView = mWorkspace;
+ final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
+ final int startDelay =
+ res.getInteger(R.integer.config_workspaceAppsCustomizeAnimationStagger);
+
+ setPivotsForZoom(toView, scale);
+
+ // Shrink workspaces away if going to AppsCustomize from workspace
+ Animator workspaceAnim =
+ mWorkspace.getChangeStateAnimation(Workspace.State.SMALL, animated);
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ // Set the content type for the all apps space
+ mAppsCustomizeTabHost.setContentTypeImmediate(contentType);
+ }
+
+ if (animated) {
+ toView.setScaleX(scale);
+ toView.setScaleY(scale);
+ final LauncherViewPropertyAnimator scaleAnim = new LauncherViewPropertyAnimator(toView);
+ scaleAnim.
+ scaleX(1f).scaleY(1f).
+ setDuration(duration).
+ setInterpolator(new Workspace.ZoomOutInterpolator());
+
+ toView.setVisibility(View.VISIBLE);
+ toView.setAlpha(0f);
+ final ObjectAnimator alphaAnim = LauncherAnimUtils
+ .ofFloat(toView, "alpha", 0f, 1f)
+ .setDuration(fadeDuration);
+ alphaAnim.setInterpolator(new DecelerateInterpolator(1.5f));
+ alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (animation == null) {
+ throw new RuntimeException("animation is null");
+ }
+ float t = (Float) animation.getAnimatedValue();
+ dispatchOnLauncherTransitionStep(fromView, t);
+ dispatchOnLauncherTransitionStep(toView, t);
+ }
+ });
+
+ // toView should appear right at the end of the workspace shrink
+ // animation
+ mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+ mStateAnimation.play(scaleAnim).after(startDelay);
+ mStateAnimation.play(alphaAnim).after(startDelay);
+
+ mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Prepare the position
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setVisibility(View.VISIBLE);
+ toView.bringToFront();
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
+
+ // Hide the search bar
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.hideSearchBar(false);
+ }
+ }
+ });
+
+ if (workspaceAnim != null) {
+ mStateAnimation.play(workspaceAnim);
+ }
+
+ boolean delayAnim = false;
+
+ dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+ dispatchOnLauncherTransitionPrepare(toView, animated, false);
+
+ // If any of the objects being animated haven't been measured/laid out
+ // yet, delay the animation until we get a layout pass
+ if ((((LauncherTransitionable) toView).getContent().getMeasuredWidth() == 0) ||
+ (mWorkspace.getMeasuredWidth() == 0) ||
+ (toView.getMeasuredWidth() == 0)) {
+ delayAnim = true;
+ }
+
+ final AnimatorSet stateAnimation = mStateAnimation;
+ final Runnable startAnimRunnable = new Runnable() {
+ public void run() {
+ // Check that mStateAnimation hasn't changed while
+ // we waited for a layout/draw pass
+ if (mStateAnimation != stateAnimation)
+ return;
+ setPivotsForZoom(toView, scale);
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+ LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
+ }
+ };
+ if (delayAnim) {
+ final ViewTreeObserver observer = toView.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ startAnimRunnable.run();
+ toView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ } else {
+ startAnimRunnable.run();
+ }
+ } else {
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ toView.setVisibility(View.VISIBLE);
+ toView.bringToFront();
+
+ if (!springLoaded && !LauncherAppState.getInstance().isScreenLarge()) {
+ // Hide the search bar
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.hideSearchBar(false);
+ }
+ }
+ dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionPrepare(toView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
+ }
+ }
+
+ /**
+ * Zoom the camera back into the workspace, hiding 'fromView'.
+ * This is the opposite of showAppsCustomizeHelper.
+ * @param animated If true, the transition will be animated.
+ */
+ private void hideAppsCustomizeHelper(Workspace.State toState, final boolean animated,
+ final boolean springLoaded, final Runnable onCompleteRunnable) {
+
+ if (mStateAnimation != null) {
+ mStateAnimation.setDuration(0);
+ mStateAnimation.cancel();
+ mStateAnimation = null;
+ }
+ Resources res = getResources();
+
+ final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
+ final int fadeOutDuration =
+ res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
+ final float scaleFactor = (float)
+ res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
+ final View fromView = mAppsCustomizeTabHost;
+ final View toView = mWorkspace;
+ Animator workspaceAnim = null;
+ if (toState == Workspace.State.NORMAL) {
+ int stagger = res.getInteger(R.integer.config_appsCustomizeWorkspaceAnimationStagger);
+ workspaceAnim = mWorkspace.getChangeStateAnimation(
+ toState, animated, stagger, -1);
+ } else if (toState == Workspace.State.SPRING_LOADED ||
+ toState == Workspace.State.OVERVIEW) {
+ workspaceAnim = mWorkspace.getChangeStateAnimation(
+ toState, animated);
+ }
+
+ setPivotsForZoom(fromView, scaleFactor);
+ showHotseat(animated);
+ if (animated) {
+ final LauncherViewPropertyAnimator scaleAnim =
+ new LauncherViewPropertyAnimator(fromView);
+ scaleAnim.
+ scaleX(scaleFactor).scaleY(scaleFactor).
+ setDuration(duration).
+ setInterpolator(new Workspace.ZoomInInterpolator());
+
+ final ObjectAnimator alphaAnim = LauncherAnimUtils
+ .ofFloat(fromView, "alpha", 1f, 0f)
+ .setDuration(fadeOutDuration);
+ alphaAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+ alphaAnim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = 1f - (Float) animation.getAnimatedValue();
+ dispatchOnLauncherTransitionStep(fromView, t);
+ dispatchOnLauncherTransitionStep(toView, t);
+ }
+ });
+
+ mStateAnimation = LauncherAnimUtils.createAnimatorSet();
+
+ dispatchOnLauncherTransitionPrepare(fromView, animated, true);
+ dispatchOnLauncherTransitionPrepare(toView, animated, true);
+ mAppsCustomizeContent.pauseScrolling();
+
+ mStateAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ fromView.setVisibility(View.GONE);
+ dispatchOnLauncherTransitionEnd(fromView, animated, true);
+ dispatchOnLauncherTransitionEnd(toView, animated, true);
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ mAppsCustomizeContent.updateCurrentPageScroll();
+ mAppsCustomizeContent.resumeScrolling();
+ }
+ });
+
+ mStateAnimation.playTogether(scaleAnim, alphaAnim);
+ if (workspaceAnim != null) {
+ mStateAnimation.play(workspaceAnim);
+ }
+ dispatchOnLauncherTransitionStart(fromView, animated, true);
+ dispatchOnLauncherTransitionStart(toView, animated, true);
+ LauncherAnimUtils.startAnimationAfterNextDraw(mStateAnimation, toView);
+ } else {
+ fromView.setVisibility(View.GONE);
+ dispatchOnLauncherTransitionPrepare(fromView, animated, true);
+ dispatchOnLauncherTransitionStart(fromView, animated, true);
+ dispatchOnLauncherTransitionEnd(fromView, animated, true);
+ dispatchOnLauncherTransitionPrepare(toView, animated, true);
+ dispatchOnLauncherTransitionStart(toView, animated, true);
+ dispatchOnLauncherTransitionEnd(toView, animated, true);
+ }
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
+ mAppsCustomizeTabHost.onTrimMemory();
+ }
+ }
+
+ protected void showWorkspace(boolean animated) {
+ showWorkspace(animated, null);
+ }
+
+ protected void showWorkspace() {
+ showWorkspace(true);
+ }
+
+ void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
+ if (mWorkspace.isInOverviewMode()) {
+ mWorkspace.exitOverviewMode(animated);
+ }
+ if (mState != State.WORKSPACE) {
+ boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
+ mWorkspace.setVisibility(View.VISIBLE);
+ hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable);
+
+ // Show the search bar (only animate if we were showing the drop target bar in spring
+ // loaded mode)
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
+ }
+
+ // Set focus to the AppsCustomize button
+ if (mAllAppsButton != null) {
+ mAllAppsButton.requestFocus();
+ }
+ }
+
+ // Change the state *after* we've called all the transition code
+ mState = State.WORKSPACE;
+
+ // Resume the auto-advance of widgets
+ mUserPresent = true;
+ updateRunning();
+
+ // Send an accessibility event to announce the context change
+ getWindow().getDecorView()
+ .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+
+ onWorkspaceShown(animated);
+ }
+
+ void showOverviewMode(boolean animated) {
+ mWorkspace.setVisibility(View.VISIBLE);
+ hideAppsCustomizeHelper(Workspace.State.OVERVIEW, animated, false, null);
+ mState = State.WORKSPACE;
+ onWorkspaceShown(animated);
+ }
+
+ public void onWorkspaceShown(boolean animated) {
+ }
+
+ void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType,
+ boolean resetPageToZero) {
+ if (mState != State.WORKSPACE) return;
+
+ if (resetPageToZero) {
+ mAppsCustomizeTabHost.reset();
+ }
+ showAppsCustomizeHelper(animated, false, contentType);
+ mAppsCustomizeTabHost.requestFocus();
+
+ // Change the state *after* we've called all the transition code
+ mState = State.APPS_CUSTOMIZE;
+
+ // Pause the auto-advance of widgets until we are out of AllApps
+ mUserPresent = false;
+ updateRunning();
+ closeFolder();
+
+ // Send an accessibility event to announce the context change
+ getWindow().getDecorView()
+ .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ void enterSpringLoadedDragMode() {
+ if (isAllAppsVisible()) {
+ hideAppsCustomizeHelper(Workspace.State.SPRING_LOADED, true, true, null);
+ mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
+ }
+ }
+
+ void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, boolean extendedDelay,
+ final Runnable onCompleteRunnable) {
+ if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (successfulDrop) {
+ // Before we show workspace, hide all apps again because
+ // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
+ // clean up our state transition functions
+ mAppsCustomizeTabHost.setVisibility(View.GONE);
+ showWorkspace(true, onCompleteRunnable);
+ } else {
+ exitSpringLoadedDragMode();
+ }
+ }
+ }, (extendedDelay ?
+ EXIT_SPRINGLOADED_MODE_LONG_TIMEOUT :
+ EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT));
+ }
+
+ void exitSpringLoadedDragMode() {
+ if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
+ final boolean animated = true;
+ final boolean springLoaded = true;
+ showAppsCustomizeHelper(animated, springLoaded);
+ mState = State.APPS_CUSTOMIZE;
+ }
+ // Otherwise, we are not in spring loaded mode, so don't do anything.
+ }
+
+ void lockAllApps() {
+ // TODO
+ }
+
+ void unlockAllApps() {
+ // TODO
+ }
+
+ /**
+ * Shows the hotseat area.
+ */
+ void showHotseat(boolean animated) {
+ if (!LauncherAppState.getInstance().isScreenLarge()) {
+ if (animated) {
+ if (mHotseat.getAlpha() != 1f) {
+ int duration = 0;
+ if (mSearchDropTargetBar != null) {
+ duration = mSearchDropTargetBar.getTransitionInDuration();
+ }
+ mHotseat.animate().alpha(1f).setDuration(duration);
+ }
+ } else {
+ mHotseat.setAlpha(1f);
+ }
+ }
+ }
+
+ /**
+ * Hides the hotseat area.
+ */
+ void hideHotseat(boolean animated) {
+ if (!LauncherAppState.getInstance().isScreenLarge()) {
+ if (animated) {
+ if (mHotseat.getAlpha() != 0f) {
+ int duration = 0;
+ if (mSearchDropTargetBar != null) {
+ duration = mSearchDropTargetBar.getTransitionOutDuration();
+ }
+ mHotseat.animate().alpha(0f).setDuration(duration);
+ }
+ } else {
+ mHotseat.setAlpha(0f);
+ }
+ }
+ }
+
+ /**
+ * Add an item from all apps or customize onto the given workspace screen.
+ * If layout is null, add to the current screen.
+ */
+ void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
+ if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
+ showOutOfSpaceMessage(isHotseatLayout(layout));
+ }
+ }
+
+ /** Maps the current orientation to an index for referencing orientation correct global icons */
+ private int getCurrentOrientationIndexForGlobalIcons() {
+ // default - 0, landscape - 1
+ switch (getResources().getConfiguration().orientation) {
+ case Configuration.ORIENTATION_LANDSCAPE:
+ return 1;
+ default:
+ return 0;
+ }
+ }
+
+ private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) {
+ try {
+ PackageManager packageManager = getPackageManager();
+ // Look for the toolbar icon specified in the activity meta-data
+ Bundle metaData = packageManager.getActivityInfo(
+ activityName, PackageManager.GET_META_DATA).metaData;
+ if (metaData != null) {
+ int iconResId = metaData.getInt(resourceName);
+ if (iconResId != 0) {
+ Resources res = packageManager.getResourcesForActivity(activityName);
+ return res.getDrawable(iconResId);
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // This can happen if the activity defines an invalid drawable
+ Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
+ " not found", e);
+ } catch (Resources.NotFoundException nfe) {
+ // This can happen if the activity defines an invalid drawable
+ Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
+ nfe);
+ }
+ return null;
+ }
+
+ // if successful in getting icon, return it; otherwise, set button to use default drawable
+ private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
+ int buttonId, ComponentName activityName, int fallbackDrawableId,
+ String toolbarResourceName) {
+ Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
+ Resources r = getResources();
+ int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
+ int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
+
+ TextView button = (TextView) findViewById(buttonId);
+ // If we were unable to find the icon via the meta-data, use a generic one
+ if (toolbarIcon == null) {
+ toolbarIcon = r.getDrawable(fallbackDrawableId);
+ toolbarIcon.setBounds(0, 0, w, h);
+ if (button != null) {
+ button.setCompoundDrawables(toolbarIcon, null, null, null);
+ }
+ return null;
+ } else {
+ toolbarIcon.setBounds(0, 0, w, h);
+ if (button != null) {
+ button.setCompoundDrawables(toolbarIcon, null, null, null);
+ }
+ return toolbarIcon.getConstantState();
+ }
+ }
+
+ // if successful in getting icon, return it; otherwise, set button to use default drawable
+ private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
+ int buttonId, ComponentName activityName, int fallbackDrawableId,
+ String toolbarResourceName) {
+ ImageView button = (ImageView) findViewById(buttonId);
+ Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
+
+ if (button != null) {
+ // If we were unable to find the icon via the meta-data, use a
+ // generic one
+ if (toolbarIcon == null) {
+ button.setImageResource(fallbackDrawableId);
+ } else {
+ button.setImageDrawable(toolbarIcon);
+ }
+ }
+
+ return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
+
+ }
+
+ private void updateTextButtonWithDrawable(int buttonId, Drawable d) {
+ TextView button = (TextView) findViewById(buttonId);
+ button.setCompoundDrawables(d, null, null, null);
+ }
+
+ private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
+ ImageView button = (ImageView) findViewById(buttonId);
+ button.setImageDrawable(d.newDrawable(getResources()));
+ }
+
+ private void invalidatePressedFocusedStates(View container, View button) {
+ if (container instanceof HolographicLinearLayout) {
+ HolographicLinearLayout layout = (HolographicLinearLayout) container;
+ layout.invalidatePressedFocusedStates();
+ } else if (button instanceof HolographicImageView) {
+ HolographicImageView view = (HolographicImageView) button;
+ view.invalidatePressedFocusedStates();
+ }
+ }
+
+ public View getQsbBar() {
+ if (mQsbBar == null) {
+ mQsbBar = mInflater.inflate(R.layout.search_bar, mSearchDropTargetBar, false);
+ mSearchDropTargetBar.addView(mQsbBar);
+ }
+ return mQsbBar;
+ }
+
+ protected boolean updateGlobalSearchIcon() {
+ final View searchButtonContainer = findViewById(R.id.search_button_container);
+ final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
+ final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+ final View voiceButton = findViewById(R.id.voice_button);
+
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ ComponentName activityName = searchManager.getGlobalSearchActivity();
+ if (activityName != null) {
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+ R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
+ TOOLBAR_SEARCH_ICON_METADATA_NAME);
+ if (sGlobalSearchIcon[coi] == null) {
+ sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+ R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
+ TOOLBAR_ICON_METADATA_NAME);
+ }
+
+ if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
+ searchButton.setVisibility(View.VISIBLE);
+ invalidatePressedFocusedStates(searchButtonContainer, searchButton);
+ return true;
+ } else {
+ // We disable both search and voice search when there is no global search provider
+ if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
+ if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
+ if (searchButton != null) searchButton.setVisibility(View.GONE);
+ if (voiceButton != null) voiceButton.setVisibility(View.GONE);
+ updateVoiceButtonProxyVisible(false);
+ return false;
+ }
+ }
+
+ protected void updateGlobalSearchIcon(Drawable.ConstantState d) {
+ final View searchButtonContainer = findViewById(R.id.search_button_container);
+ final View searchButton = (ImageView) findViewById(R.id.search_button);
+ updateButtonWithDrawable(R.id.search_button, d);
+ invalidatePressedFocusedStates(searchButtonContainer, searchButton);
+ }
+
+ protected boolean updateVoiceSearchIcon(boolean searchVisible) {
+ final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+ final View voiceButton = findViewById(R.id.voice_button);
+
+ // We only show/update the voice search icon if the search icon is enabled as well
+ final SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
+
+ ComponentName activityName = null;
+ if (globalSearchActivity != null) {
+ // Check if the global search activity handles voice search
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ intent.setPackage(globalSearchActivity.getPackageName());
+ activityName = intent.resolveActivity(getPackageManager());
+ }
+
+ if (activityName == null) {
+ // Fallback: check if an activity other than the global search activity
+ // resolves this
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ activityName = intent.resolveActivity(getPackageManager());
+ }
+ if (searchVisible && activityName != null) {
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+ R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
+ TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME);
+ if (sVoiceSearchIcon[coi] == null) {
+ sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
+ R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
+ TOOLBAR_ICON_METADATA_NAME);
+ }
+ if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
+ voiceButton.setVisibility(View.VISIBLE);
+ updateVoiceButtonProxyVisible(false);
+ invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
+ return true;
+ } else {
+ if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
+ if (voiceButton != null) voiceButton.setVisibility(View.GONE);
+ updateVoiceButtonProxyVisible(false);
+ return false;
+ }
+ }
+
+ protected void updateVoiceSearchIcon(Drawable.ConstantState d) {
+ final View voiceButtonContainer = findViewById(R.id.voice_button_container);
+ final View voiceButton = findViewById(R.id.voice_button);
+ updateButtonWithDrawable(R.id.voice_button, d);
+ invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
+ }
+
+ public void updateVoiceButtonProxyVisible(boolean forceDisableVoiceButtonProxy) {
+ final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
+ if (voiceButtonProxy != null) {
+ boolean visible = !forceDisableVoiceButtonProxy &&
+ mWorkspace.shouldVoiceButtonProxyBeVisible();
+ voiceButtonProxy.setVisibility(visible ? View.VISIBLE : View.GONE);
+ voiceButtonProxy.bringToFront();
+ }
+ }
+
+ /**
+ * This is an overrid eot disable the voice button proxy. If disabled is true, then the voice button proxy
+ * will be hidden regardless of what shouldVoiceButtonProxyBeVisible() returns.
+ */
+ public void disableVoiceButtonProxy(boolean disabled) {
+ updateVoiceButtonProxyVisible(disabled);
+ }
+ /**
+ * Sets the app market icon
+ */
+ private void updateAppMarketIcon() {
+ if (!DISABLE_MARKET_BUTTON) {
+ final View marketButton = findViewById(R.id.market_button);
+ Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET);
+ // Find the app market activity by resolving an intent.
+ // (If multiple app markets are installed, it will return the ResolverActivity.)
+ ComponentName activityName = intent.resolveActivity(getPackageManager());
+ if (activityName != null) {
+ int coi = getCurrentOrientationIndexForGlobalIcons();
+ mAppMarketIntent = intent;
+ sAppMarketIcon[coi] = updateTextButtonWithIconFromExternalActivity(
+ R.id.market_button, activityName, R.drawable.ic_launcher_market_holo,
+ TOOLBAR_ICON_METADATA_NAME);
+ marketButton.setVisibility(View.VISIBLE);
+ } else {
+ // We should hide and disable the view so that we don't try and restore the visibility
+ // of it when we swap between drag & normal states from IconDropTarget subclasses.
+ marketButton.setVisibility(View.GONE);
+ marketButton.setEnabled(false);
+ }
+ }
+ }
+
+ private void updateAppMarketIcon(Drawable.ConstantState d) {
+ if (!DISABLE_MARKET_BUTTON) {
+ // Ensure that the new drawable we are creating has the approprate toolbar icon bounds
+ Resources r = getResources();
+ Drawable marketIconDrawable = d.newDrawable(r);
+ int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
+ int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
+ marketIconDrawable.setBounds(0, 0, w, h);
+
+ updateTextButtonWithDrawable(R.id.market_button, marketIconDrawable);
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ final boolean result = super.dispatchPopulateAccessibilityEvent(event);
+ final List<CharSequence> text = event.getText();
+ text.clear();
+ // Populate event with a fake title based on the current state.
+ if (mState == State.APPS_CUSTOMIZE) {
+ text.add(mAppsCustomizeTabHost.getCurrentTabView().getContentDescription());
+ } else {
+ text.add(getString(R.string.all_apps_home_button_label));
+ }
+ return result;
+ }
+
+ /**
+ * Receives notifications when system dialogs are to be closed.
+ */
+ private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ closeSystemDialogs();
+ }
+ }
+
+ /**
+ * Receives notifications whenever the appwidgets are reset.
+ */
+ private class AppWidgetResetObserver extends ContentObserver {
+ public AppWidgetResetObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onAppWidgetReset();
+ }
+ }
+
+ /**
+ * If the activity is currently paused, signal that we need to run the passed Runnable
+ * in onResume.
+ *
+ * This needs to be called from incoming places where resources might have been loaded
+ * while we are paused. That is becaues the Configuration might be wrong
+ * when we're not running, and if it comes back to what it was when we
+ * were paused, we are not restarted.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ *
+ * @return true if we are currently paused. The caller might be able to
+ * skip some work in that case since we will come back again.
+ */
+ private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
+ if (mPaused) {
+ Log.i(TAG, "Deferring update until onResume");
+ if (deletePreviousRunnables) {
+ while (mBindOnResumeCallbacks.remove(run)) {
+ }
+ }
+ mBindOnResumeCallbacks.add(run);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean waitUntilResume(Runnable run) {
+ return waitUntilResume(run, false);
+ }
+
+ public void addOnResumeCallback(Runnable run) {
+ mOnResumeCallbacks.add(run);
+ }
+
+ /**
+ * If the activity is currently paused, signal that we need to re-run the loader
+ * in onResume.
+ *
+ * This needs to be called from incoming places where resources might have been loaded
+ * while we are paused. That is becaues the Configuration might be wrong
+ * when we're not running, and if it comes back to what it was when we
+ * were paused, we are not restarted.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ *
+ * @return true if we are currently paused. The caller might be able to
+ * skip some work in that case since we will come back again.
+ */
+ public boolean setLoadOnResume() {
+ if (mPaused) {
+ Log.i(TAG, "setLoadOnResume");
+ mOnResumeNeedsLoad = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public int getCurrentWorkspaceScreen() {
+ if (mWorkspace != null) {
+ return mWorkspace.getCurrentPage();
+ } else {
+ return SCREEN_COUNT / 2;
+ }
+ }
+
+ /**
+ * Refreshes the shortcuts shown on the workspace.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void startBinding() {
+ // If we're starting binding all over again, clear any bind calls we'd postponed in
+ // the past (see waitUntilResume) -- we don't need them since we're starting binding
+ // from scratch again
+ mBindOnResumeCallbacks.clear();
+
+ // Clear the workspace because it's going to be rebound
+ mWorkspace.clearDropTargets();
+ mWorkspace.removeAllWorkspaceScreens();
+
+ mWidgetsToAdvance.clear();
+ if (mHotseat != null) {
+ mHotseat.resetLayout();
+ }
+ }
+
+ @Override
+ public void bindScreens(ArrayList<Long> orderedScreenIds) {
+ bindAddScreens(orderedScreenIds);
+
+ // If there are no screens, we need to have an empty screen
+ if (orderedScreenIds.size() == 0) {
+ mWorkspace.addExtraEmptyScreen();
+ }
+
+ // Create the custom content page (this call updates mDefaultScreen which calls
+ // setCurrentPage() so ensure that all pages are added before calling this).
+ // The actual content of the custom page will be added during onFinishBindingItems().
+ if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
+ mWorkspace.createCustomContentPage();
+ }
+ }
+
+ @Override
+ public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
+ int count = orderedScreenIds.size();
+ for (int i = 0; i < count; i++) {
+ mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
+ }
+ }
+
+ private boolean shouldShowWeightWatcher() {
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
+
+ return show;
+ }
+
+ private void toggleShowWeightWatcher() {
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
+
+ show = !show;
+
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
+ editor.commit();
+
+ if (mWeightWatcher != null) {
+ mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ public void bindAppsAdded(final ArrayList<Long> newScreens,
+ final ArrayList<ItemInfo> addNotAnimated,
+ final ArrayList<ItemInfo> addAnimated,
+ final ArrayList<AppInfo> addedApps) {
+ Runnable r = new Runnable() {
+ public void run() {
+ bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
+ }
+
+ // Add the new screens
+ bindAddScreens(newScreens);
+
+ // We add the items without animation on non-visible pages, and with
+ // animations on the new page (which we will try and snap to).
+ if (!addNotAnimated.isEmpty()) {
+ bindItems(addNotAnimated, 0,
+ addNotAnimated.size(), false);
+ }
+ if (!addAnimated.isEmpty()) {
+ bindItems(addAnimated, 0,
+ addAnimated.size(), true);
+ }
+
+ // Remove the extra empty screen
+ mWorkspace.removeExtraEmptyScreen();
+
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ addedApps != null && mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.addApps(addedApps);
+ }
+ }
+
+ /**
+ * Bind the items start-end from the list.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
+ final boolean forceAnimateIcons) {
+ Runnable r = new Runnable() {
+ public void run() {
+ bindItems(shortcuts, start, end, forceAnimateIcons);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
+ }
+
+ // Get the list of added shortcuts and intersect them with the set of shortcuts here
+ final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
+ final Collection<Animator> bounceAnims = new ArrayList<Animator>();
+ final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
+ Workspace workspace = mWorkspace;
+ long newShortcutsScreenId = -1;
+ for (int i = start; i < end; i++) {
+ final ItemInfo item = shortcuts.get(i);
+
+ // Short circuit if we are loading dock items for a configuration which has no dock
+ if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
+ mHotseat == null) {
+ continue;
+ }
+
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ ShortcutInfo info = (ShortcutInfo) item;
+ View shortcut = createShortcut(info);
+
+ /*
+ * TODO: FIX collision case
+ */
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
+ if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
+ throw new RuntimeException("OCCUPIED");
+ }
+ }
+
+ workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
+ item.cellY, 1, 1);
+ if (animateIcons) {
+ // Animate all the applications up now
+ shortcut.setAlpha(0f);
+ shortcut.setScaleX(0f);
+ shortcut.setScaleY(0f);
+ bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
+ newShortcutsScreenId = item.screenId;
+ }
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
+ (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
+ (FolderInfo) item, mIconCache);
+ workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
+ item.cellY, 1, 1);
+ break;
+ default:
+ throw new RuntimeException("Invalid Item Type");
+ }
+ }
+
+ if (animateIcons) {
+ // Animate to the correct page
+ if (newShortcutsScreenId > -1) {
+ long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
+ final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
+ final Runnable startBounceAnimRunnable = new Runnable() {
+ public void run() {
+ anim.playTogether(bounceAnims);
+ anim.start();
+ }
+ };
+ if (newShortcutsScreenId != currentScreenId) {
+ // We post the animation slightly delayed to prevent slowdowns
+ // when we are loading right after we return to launcher.
+ mWorkspace.postDelayed(new Runnable() {
+ public void run() {
+ mWorkspace.snapToPage(newScreenIndex);
+ mWorkspace.postDelayed(startBounceAnimRunnable,
+ NEW_APPS_ANIMATION_DELAY);
+ }
+ }, NEW_APPS_PAGE_MOVE_DELAY);
+ } else {
+ mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
+ }
+ }
+ }
+ workspace.requestLayout();
+ }
+
+ /**
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindFolders(final HashMap<Long, FolderInfo> folders) {
+ Runnable r = new Runnable() {
+ public void run() {
+ bindFolders(folders);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
+ }
+ sFolders.clear();
+ sFolders.putAll(folders);
+ }
+
+ /**
+ * Add the views for a widget to the workspace.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAppWidget(final LauncherAppWidgetInfo item) {
+ Runnable r = new Runnable() {
+ public void run() {
+ bindAppWidget(item);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
+ }
+
+ final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "bindAppWidget: " + item);
+ }
+ final Workspace workspace = mWorkspace;
+
+ final int appWidgetId = item.appWidgetId;
+ final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider);
+ }
+
+ item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+
+ item.hostView.setTag(item);
+ item.onBindAppWidget(this);
+
+ workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
+ item.cellY, item.spanX, item.spanY, false);
+ addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
+
+ workspace.requestLayout();
+
+ if (DEBUG_WIDGETS) {
+ Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
+ + (SystemClock.uptimeMillis()-start) + "ms");
+ }
+ }
+
+ public void onPageBoundSynchronously(int page) {
+ mSynchronouslyBoundPages.add(page);
+ }
+
+ /**
+ * Callback saying that there aren't any more items to bind.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void finishBindingItems(final boolean upgradePath) {
+ Runnable r = new Runnable() {
+ public void run() {
+ finishBindingItems(upgradePath);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
+ }
+ if (mSavedState != null) {
+ if (!mWorkspace.hasFocus()) {
+ mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
+ }
+ mSavedState = null;
+ }
+
+ mWorkspace.restoreInstanceStateForRemainingPages();
+
+ // If we received the result of any pending adds while the loader was running (e.g. the
+ // widget configuration forced an orientation change), process them now.
+ for (int i = 0; i < sPendingAddList.size(); i++) {
+ completeAdd(sPendingAddList.get(i));
+ }
+ sPendingAddList.clear();
+
+ // Update the market app icon as necessary (the other icons will be managed in response to
+ // package changes in bindSearchablesChanged()
+ if (!DISABLE_MARKET_BUTTON) {
+ updateAppMarketIcon();
+ }
+
+ mWorkspaceLoading = false;
+ if (upgradePath) {
+ mWorkspace.getUniqueComponents(true, null);
+ mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
+ }
+
+ mWorkspace.post(new Runnable() {
+ @Override
+ public void run() {
+ onFinishBindingItems();
+ }
+ });
+ }
+
+ public boolean isAllAppsButtonRank(int rank) {
+ if (mHotseat != null) {
+ return mHotseat.isAllAppsButtonRank(rank);
+ }
+ return false;
+ }
+
+ private boolean canRunNewAppsAnimation() {
+ long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
+ return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+ }
+
+ private ValueAnimator createNewAppBounceAnimation(View v, int i) {
+ ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
+ PropertyValuesHolder.ofFloat("alpha", 1f),
+ PropertyValuesHolder.ofFloat("scaleX", 1f),
+ PropertyValuesHolder.ofFloat("scaleY", 1f));
+ bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
+ bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
+ bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
+ return bounceAnim;
+ }
+
+ @Override
+ public void bindSearchablesChanged() {
+ boolean searchVisible = updateGlobalSearchIcon();
+ boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
+ if (mSearchDropTargetBar != null) {
+ mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
+ }
+ }
+
+ /**
+ * Add the icons for all apps.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAllApplications(final ArrayList<AppInfo> apps) {
+ if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ if (mIntentsOnWorkspaceFromUpgradePath != null) {
+ if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
+ getHotseat().addAllAppsFolder(mIconCache, apps,
+ mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
+ }
+ mIntentsOnWorkspaceFromUpgradePath = null;
+ }
+ } else {
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.setApps(apps);
+ }
+ }
+ }
+
+ /**
+ * A package was updated.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
+ Runnable r = new Runnable() {
+ public void run() {
+ bindAppsUpdated(apps);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
+ }
+
+ if (mWorkspace != null) {
+ mWorkspace.updateShortcuts(apps);
+ }
+
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.updateApps(apps);
+ }
+ }
+
+ /**
+ * A package was uninstalled. We take both the super set of packageNames
+ * in addition to specific applications to remove, the reason being that
+ * this can be called when a package is updated as well. In that scenario,
+ * we only remove specific components from the workspace, where as
+ * package-removal should clear all items by package name.
+ *
+ * Implementation of the method from LauncherModel.Callbacks.
+ */
+ public void bindComponentsRemoved(final ArrayList<String> packageNames,
+ final ArrayList<AppInfo> appInfos,
+ final boolean packageRemoved) {
+ Runnable r = new Runnable() {
+ public void run() {
+ bindComponentsRemoved(packageNames, appInfos, packageRemoved);
+ }
+ };
+ if (waitUntilResume(r)) {
+ return;
+ }
+
+ if (packageRemoved) {
+ mWorkspace.removeItemsByPackageName(packageNames);
+ } else {
+ mWorkspace.removeItemsByApplicationInfo(appInfos);
+ }
+
+ // Notify the drag controller
+ mDragController.onAppsRemoved(appInfos, this);
+
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.removeApps(appInfos);
+ }
+ }
+
+ /**
+ * A number of packages were updated.
+ */
+ private ArrayList<Object> mWidgetsAndShortcuts;
+ private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
+ public void run() {
+ bindPackagesUpdated(mWidgetsAndShortcuts);
+ mWidgetsAndShortcuts = null;
+ }
+ };
+
+ public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
+ if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
+ mWidgetsAndShortcuts = widgetsAndShortcuts;
+ return;
+ }
+
+ // Update the widgets pane
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS &&
+ mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
+ }
+ }
+
+ private int mapConfigurationOriActivityInfoOri(int configOri) {
+ final Display d = getWindowManager().getDefaultDisplay();
+ int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
+ switch (d.getRotation()) {
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ // We are currently in the same basic orientation as the natural orientation
+ naturalOri = configOri;
+ break;
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_270:
+ // We are currently in the other basic orientation to the natural orientation
+ naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
+ Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+ break;
+ }
+
+ int[] oriMap = {
+ ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+ };
+ // Since the map starts at portrait, we need to offset if this device's natural orientation
+ // is landscape.
+ int indexOffset = 0;
+ if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
+ indexOffset = 1;
+ }
+ return oriMap[(d.getRotation() + indexOffset) % 4];
+ }
+
+ public boolean isRotationEnabled() {
+ boolean enableRotation = sForceEnableRotation ||
+ getResources().getBoolean(R.bool.allow_rotation);
+ return enableRotation;
+ }
+ public void lockScreenOrientation() {
+ if (isRotationEnabled()) {
+ setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
+ .getConfiguration().orientation));
+ }
+ }
+ public void unlockScreenOrientation(boolean immediate) {
+ if (isRotationEnabled()) {
+ if (immediate) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ } else {
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+ }, mRestoreScreenOrientationDelay);
+ }
+ }
+ }
+
+ /* Cling related */
+ private boolean isClingsEnabled() {
+ if (DISABLE_CLINGS) {
+ return false;
+ }
+
+ // For now, limit only to phones
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ if (grid.isTablet()) {
+ return false;
+ }
+
+ // disable clings when running in a test harness
+ if(ActivityManager.isRunningInTestHarness()) return false;
+
+ // Disable clings for accessibility when explore by touch is enabled
+ final AccessibilityManager a11yManager = (AccessibilityManager) getSystemService(
+ ACCESSIBILITY_SERVICE);
+ if (a11yManager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ // Restricted secondary users (child mode) will potentially have very few apps
+ // seeded when they start up for the first time. Clings won't work well with that
+// boolean supportsLimitedUsers =
+// android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+// Account[] accounts = AccountManager.get(this).getAccounts();
+// if (supportsLimitedUsers && accounts.length == 0) {
+// UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+// Bundle restrictions = um.getUserRestrictions();
+// if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
+// return false;
+// }
+// }
+ return true;
+ }
+
+ private Cling initCling(int clingId, int scrimId, boolean animate,
+ boolean dimNavBarVisibilty) {
+ Cling cling = (Cling) findViewById(clingId);
+ View scrim = null;
+ if (scrimId > 0) {
+ scrim = findViewById(R.id.cling_scrim);
+ }
+ if (cling != null) {
+ cling.init(this, scrim);
+ cling.show(animate, SHOW_CLING_DURATION);
+
+ if (dimNavBarVisibilty) {
+ cling.setSystemUiVisibility(cling.getSystemUiVisibility() |
+ View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ }
+ }
+ return cling;
+ }
+
+ private void dismissCling(final Cling cling, final Runnable postAnimationCb,
+ final String flag, int duration, boolean restoreNavBarVisibilty) {
+ // To catch cases where siblings of top-level views are made invisible, just check whether
+ // the cling is directly set to GONE before dismissing it.
+ if (cling != null && cling.getVisibility() != View.GONE) {
+ final Runnable cleanUpClingCb = new Runnable() {
+ public void run() {
+ cling.cleanup();
+ // We should update the shared preferences on a background thread
+ new Thread("dismissClingThread") {
+ public void run() {
+ SharedPreferences.Editor editor = mSharedPrefs.edit();
+ editor.putBoolean(flag, true);
+ editor.commit();
+ }
+ }.start();
+ if (postAnimationCb != null) {
+ postAnimationCb.run();
+ }
+ }
+ };
+ if (duration <= 0) {
+ cleanUpClingCb.run();
+ } else {
+ cling.hide(duration, cleanUpClingCb);
+ }
+ mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
+
+ if (restoreNavBarVisibilty) {
+ cling.setSystemUiVisibility(cling.getSystemUiVisibility() &
+ ~View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ }
+ }
+ }
+
+ private void removeCling(int id) {
+ final View cling = findViewById(id);
+ if (cling != null) {
+ final ViewGroup parent = (ViewGroup) cling.getParent();
+ parent.post(new Runnable() {
+ @Override
+ public void run() {
+ parent.removeView(cling);
+ }
+ });
+ mHideFromAccessibilityHelper.restoreImportantForAccessibility(mDragLayer);
+ }
+ }
+
+ private boolean skipCustomClingIfNoAccounts() {
+ Cling cling = (Cling) findViewById(R.id.workspace_cling);
+ boolean customCling = cling.getDrawIdentifier().equals("workspace_custom");
+ if (customCling) {
+ AccountManager am = AccountManager.get(this);
+ if (am == null) return false;
+ Account[] accounts = am.getAccountsByType("com.google");
+ return accounts.length == 0;
+ }
+ return false;
+ }
+
+ public void updateCustomContentHintVisibility() {
+ Cling cling = (Cling) findViewById(R.id.first_run_cling);
+ String ccHintStr = getFirstRunCustomContentHint();
+
+ if (mWorkspace.hasCustomContent()) {
+ // Show the custom content hint if ccHintStr is not empty
+ if (cling != null) {
+ setCustomContentHintVisibility(cling, ccHintStr, true, true);
+ }
+ } else {
+ // Hide the custom content hint
+ if (cling != null) {
+ setCustomContentHintVisibility(cling, ccHintStr, false, true);
+ }
+ }
+ }
+
+ private void setCustomContentHintVisibility(Cling cling, String ccHintStr, boolean visible,
+ boolean animate) {
+ final TextView ccHint = (TextView) cling.findViewById(R.id.custom_content_hint);
+ if (ccHint != null) {
+ if (visible && !ccHintStr.isEmpty()) {
+ ccHint.setText(ccHintStr);
+ ccHint.setVisibility(View.VISIBLE);
+ if (animate) {
+ ccHint.setAlpha(0f);
+ ccHint.animate().alpha(1f)
+ .setDuration(SHOW_CLING_DURATION)
+ .start();
+ } else {
+ ccHint.setAlpha(1f);
+ }
+ } else {
+ if (animate) {
+ ccHint.animate().alpha(0f)
+ .setDuration(SHOW_CLING_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ccHint.setVisibility(View.GONE);
+ }
+ })
+ .start();
+ } else {
+ ccHint.setAlpha(0f);
+ ccHint.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+
+ public void showFirstRunCling() {
+ if (isClingsEnabled() &&
+ !mSharedPrefs.getBoolean(Cling.FIRST_RUN_CLING_DISMISSED_KEY, false) &&
+ !skipCustomClingIfNoAccounts() ) {
+ // If we're not using the default workspace layout, replace workspace cling
+ // with a custom workspace cling (usually specified in an overlay)
+ // For now, only do this on tablets
+ if (!DISABLE_CUSTOM_CLINGS) {
+ if (mSharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
+ getResources().getBoolean(R.bool.config_useCustomClings)) {
+ // Use a custom cling
+ View cling = findViewById(R.id.workspace_cling);
+ ViewGroup clingParent = (ViewGroup) cling.getParent();
+ int clingIndex = clingParent.indexOfChild(cling);
+ clingParent.removeViewAt(clingIndex);
+ View customCling = mInflater.inflate(R.layout.custom_workspace_cling, clingParent, false);
+ clingParent.addView(customCling, clingIndex);
+ customCling.setId(R.id.workspace_cling);
+ }
+ }
+ Cling cling = (Cling) findViewById(R.id.first_run_cling);
+ if (cling != null) {
+ String sbHintStr = getFirstRunClingSearchBarHint();
+ String ccHintStr = getFirstRunCustomContentHint();
+ if (!sbHintStr.isEmpty()) {
+ TextView sbHint = (TextView) cling.findViewById(R.id.search_bar_hint);
+ sbHint.setText(sbHintStr);
+ sbHint.setVisibility(View.VISIBLE);
+ }
+ setCustomContentHintVisibility(cling, ccHintStr, true, false);
+ }
+ initCling(R.id.first_run_cling, 0, false, true);
+ } else {
+ removeCling(R.id.first_run_cling);
+ }
+ }
+
+ protected String getFirstRunClingSearchBarHint() {
+ return "";
+ }
+ protected String getFirstRunCustomContentHint() {
+ return "";
+ }
+ protected int getFirstRunFocusedHotseatAppDrawableId() {
+ return -1;
+ }
+ protected ComponentName getFirstRunFocusedHotseatAppComponentName() {
+ return null;
+ }
+ protected int getFirstRunFocusedHotseatAppRank() {
+ return -1;
+ }
+ protected String getFirstRunFocusedHotseatAppBubbleTitle() {
+ return "";
+ }
+ protected String getFirstRunFocusedHotseatAppBubbleDescription() {
+ return "";
+ }
+
+ public void showFirstRunWorkspaceCling() {
+ // Enable the clings only if they have not been dismissed before
+ if (isClingsEnabled() &&
+ !mSharedPrefs.getBoolean(Cling.WORKSPACE_CLING_DISMISSED_KEY, false)) {
+ Cling c = initCling(R.id.workspace_cling, 0, false, true);
+
+ // Set the focused hotseat app if there is one
+ c.setFocusedHotseatApp(getFirstRunFocusedHotseatAppDrawableId(),
+ getFirstRunFocusedHotseatAppRank(),
+ getFirstRunFocusedHotseatAppComponentName(),
+ getFirstRunFocusedHotseatAppBubbleTitle(),
+ getFirstRunFocusedHotseatAppBubbleDescription());
+ } else {
+ removeCling(R.id.workspace_cling);
+ }
+ }
+ public Cling showFirstRunFoldersCling() {
+ // Enable the clings only if they have not been dismissed before
+ if (isClingsEnabled() &&
+ !mSharedPrefs.getBoolean(Cling.FOLDER_CLING_DISMISSED_KEY, false)) {
+ Cling cling = initCling(R.id.folder_cling, R.id.cling_scrim,
+ true, true);
+ return cling;
+ } else {
+ removeCling(R.id.folder_cling);
+ return null;
+ }
+ }
+ protected SharedPreferences getSharedPrefs() {
+ return mSharedPrefs;
+ }
+ public boolean isFolderClingVisible() {
+ Cling cling = (Cling) findViewById(R.id.folder_cling);
+ if (cling != null) {
+ return cling.getVisibility() == View.VISIBLE;
+ }
+ return false;
+ }
+ public void dismissFirstRunCling(View v) {
+ Cling cling = (Cling) findViewById(R.id.first_run_cling);
+ Runnable cb = new Runnable() {
+ public void run() {
+ // Show the workspace cling next
+ showFirstRunWorkspaceCling();
+ }
+ };
+ dismissCling(cling, cb, Cling.FIRST_RUN_CLING_DISMISSED_KEY,
+ DISMISS_CLING_DURATION, false);
+
+ // Fade out the search bar for the workspace cling coming up
+ mSearchDropTargetBar.hideSearchBar(true);
+ }
+ public void dismissWorkspaceCling(View v) {
+ Cling cling = (Cling) findViewById(R.id.workspace_cling);
+ Runnable cb = null;
+ if (v == null) {
+ cb = new Runnable() {
+ public void run() {
+ mWorkspace.enterOverviewMode();
+ }
+ };
+ }
+ dismissCling(cling, cb, Cling.WORKSPACE_CLING_DISMISSED_KEY,
+ DISMISS_CLING_DURATION, true);
+
+ // Fade in the search bar
+ mSearchDropTargetBar.showSearchBar(true);
+ }
+ public void dismissFolderCling(View v) {
+ Cling cling = (Cling) findViewById(R.id.folder_cling);
+ dismissCling(cling, null, Cling.FOLDER_CLING_DISMISSED_KEY,
+ DISMISS_CLING_DURATION, true);
+ }
+
+ /**
+ * Prints out out state for debugging.
+ */
+ public void dumpState() {
+ Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
+ Log.d(TAG, "mSavedState=" + mSavedState);
+ Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
+ Log.d(TAG, "mRestoring=" + mRestoring);
+ Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
+ Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
+ Log.d(TAG, "sFolders.size=" + sFolders.size());
+ mModel.dumpState();
+
+ if (mAppsCustomizeContent != null) {
+ mAppsCustomizeContent.dumpState();
+ }
+ Log.d(TAG, "END launcher3 dump state");
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ synchronized (sDumpLogs) {
+ writer.println(" ");
+ writer.println("Debug logs: ");
+ for (int i = 0; i < sDumpLogs.size(); i++) {
+ writer.println(" " + sDumpLogs.get(i));
+ }
+ }
+ }
+
+ public static void dumpDebugLogsToConsole() {
+ if (DEBUG_DUMP_LOG) {
+ synchronized (sDumpLogs) {
+ Log.d(TAG, "");
+ Log.d(TAG, "*********************");
+ Log.d(TAG, "Launcher debug logs: ");
+ for (int i = 0; i < sDumpLogs.size(); i++) {
+ Log.d(TAG, " " + sDumpLogs.get(i));
+ }
+ Log.d(TAG, "*********************");
+ Log.d(TAG, "");
+ }
+ }
+ }
+
+ public static void addDumpLog(String tag, String log, boolean debugLog) {
+ if (debugLog) {
+ Log.d(tag, log);
+ }
+ if (DEBUG_DUMP_LOG) {
+ sDateStamp.setTime(System.currentTimeMillis());
+ synchronized (sDumpLogs) {
+ sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log);
+ }
+ }
+ }
+
+ public void dumpLogsToLocalData() {
+ if (DEBUG_DUMP_LOG) {
+ new Thread("DumpLogsToLocalData") {
+ @Override
+ public void run() {
+ boolean success = false;
+ sDateStamp.setTime(sRunStart);
+ String FILENAME = sDateStamp.getMonth() + "-"
+ + sDateStamp.getDay() + "_"
+ + sDateStamp.getHours() + "-"
+ + sDateStamp.getMinutes() + "_"
+ + sDateStamp.getSeconds() + ".txt";
+
+ FileOutputStream fos = null;
+ File outFile = null;
+ try {
+ outFile = new File(getFilesDir(), FILENAME);
+ outFile.createNewFile();
+ fos = new FileOutputStream(outFile);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if (fos != null) {
+ PrintWriter writer = new PrintWriter(fos);
+
+ writer.println(" ");
+ writer.println("Debug logs: ");
+ synchronized (sDumpLogs) {
+ for (int i = 0; i < sDumpLogs.size(); i++) {
+ writer.println(" " + sDumpLogs.get(i));
+ }
+ }
+ writer.close();
+ }
+ try {
+ if (fos != null) {
+ fos.close();
+ success = true;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+ }
+ }
+}
+
+interface LauncherTransitionable {
+ View getContent();
+ void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
+ void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
+ void onLauncherTransitionStep(Launcher l, float t);
+ void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
+}
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
new file mode 100644
index 0000000..01f72a7
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2012 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.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import java.util.HashSet;
+
+public class LauncherAnimUtils {
+ static HashSet<Animator> sAnimators = new HashSet<Animator>();
+ static Animator.AnimatorListener sEndAnimListener = new Animator.AnimatorListener() {
+ public void onAnimationStart(Animator animation) {
+ }
+
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ public void onAnimationEnd(Animator animation) {
+ sAnimators.remove(animation);
+ }
+
+ public void onAnimationCancel(Animator animation) {
+ sAnimators.remove(animation);
+ }
+ };
+
+ public static void cancelOnDestroyActivity(Animator a) {
+ sAnimators.add(a);
+ a.addListener(sEndAnimListener);
+ }
+
+ // Helper method. Assumes a draw is pending, and that if the animation's duration is 0
+ // it should be cancelled
+ public static void startAnimationAfterNextDraw(final Animator animator, final View view) {
+ view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+ private boolean mStarted = false;
+ public void onDraw() {
+ if (mStarted) return;
+ mStarted = true;
+ // Use this as a signal that the animation was cancelled
+ if (animator.getDuration() == 0) {
+ return;
+ }
+ animator.start();
+
+ final ViewTreeObserver.OnDrawListener listener = this;
+ view.post(new Runnable() {
+ public void run() {
+ view.getViewTreeObserver().removeOnDrawListener(listener);
+ }
+ });
+ }
+ });
+ }
+
+ public static void onDestroyActivity() {
+ HashSet<Animator> animators = new HashSet<Animator>(sAnimators);
+ for (Animator a : animators) {
+ if (a.isRunning()) {
+ a.cancel();
+ } else {
+ sAnimators.remove(a);
+ }
+ }
+ }
+
+ public static AnimatorSet createAnimatorSet() {
+ AnimatorSet anim = new AnimatorSet();
+ cancelOnDestroyActivity(anim);
+ return anim;
+ }
+
+ public static ValueAnimator ofFloat(View target, float... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setFloatValues(values);
+ cancelOnDestroyActivity(anim);
+ return anim;
+ }
+
+ public static ObjectAnimator ofFloat(View target, String propertyName, float... values) {
+ ObjectAnimator anim = new ObjectAnimator();
+ anim.setTarget(target);
+ anim.setPropertyName(propertyName);
+ anim.setFloatValues(values);
+ cancelOnDestroyActivity(anim);
+ new FirstFrameAnimatorHelper(anim, target);
+ return anim;
+ }
+
+ public static ObjectAnimator ofPropertyValuesHolder(View target,
+ PropertyValuesHolder... values) {
+ ObjectAnimator anim = new ObjectAnimator();
+ anim.setTarget(target);
+ anim.setValues(values);
+ cancelOnDestroyActivity(anim);
+ new FirstFrameAnimatorHelper(anim, target);
+ return anim;
+ }
+
+ public static ObjectAnimator ofPropertyValuesHolder(Object target,
+ View view, PropertyValuesHolder... values) {
+ ObjectAnimator anim = new ObjectAnimator();
+ anim.setTarget(target);
+ anim.setValues(values);
+ cancelOnDestroyActivity(anim);
+ new FirstFrameAnimatorHelper(anim, view);
+ return anim;
+ }
+}
diff --git a/src/com/android/launcher3/LauncherAnimatorUpdateListener.java b/src/com/android/launcher3/LauncherAnimatorUpdateListener.java
new file mode 100644
index 0000000..ec9fd4d
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAnimatorUpdateListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 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.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+
+abstract class LauncherAnimatorUpdateListener implements AnimatorUpdateListener {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final float b = (Float) animation.getAnimatedValue();
+ final float a = 1f - b;
+ onAnimationUpdate(a, b);
+ }
+
+ abstract void onAnimationUpdate(float a, float b);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
new file mode 100644
index 0000000..a255b89
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2013 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.app.SearchManager;
+import android.content.*;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Display;
+
+import java.lang.ref.WeakReference;
+
+public class LauncherAppState {
+ private static final String TAG = "LauncherAppState";
+ private static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
+
+ private LauncherModel mModel;
+ private IconCache mIconCache;
+ private AppFilter mAppFilter;
+ private WidgetPreviewLoader.CacheDb mWidgetPreviewCacheDb;
+ private boolean mIsScreenLarge;
+ private float mScreenDensity;
+ private int mLongPressTimeout = 300;
+
+ private static WeakReference<LauncherProvider> sLauncherProvider;
+ private static Context sContext;
+
+ private static LauncherAppState INSTANCE;
+
+ private DynamicGrid mDynamicGrid;
+
+ public static LauncherAppState getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new LauncherAppState();
+ }
+ return INSTANCE;
+ }
+
+ public static LauncherAppState getInstanceNoCreate() {
+ return INSTANCE;
+ }
+
+ public Context getContext() {
+ return sContext;
+ }
+
+ public static void setApplicationContext(Context context) {
+ if (sContext != null) {
+ Log.w(Launcher.TAG, "setApplicationContext called twice! old=" + sContext + " new=" + context);
+ }
+ sContext = context.getApplicationContext();
+ }
+
+ private LauncherAppState() {
+ if (sContext == null) {
+ throw new IllegalStateException("LauncherAppState inited before app context set");
+ }
+
+ Log.v(Launcher.TAG, "LauncherAppState inited");
+
+ if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
+ MemoryTracker.startTrackingMe(sContext, "L");
+ }
+
+ // set sIsScreenXLarge and mScreenDensity *before* creating icon cache
+ mIsScreenLarge = isScreenLarge(sContext.getResources());
+ mScreenDensity = sContext.getResources().getDisplayMetrics().density;
+
+ mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext);
+ mIconCache = new IconCache(sContext);
+
+ mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
+ mModel = new LauncherModel(this, mIconCache, mAppFilter);
+
+ // Register intent receivers
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+ sContext.registerReceiver(mModel, filter);
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ sContext.registerReceiver(mModel, filter);
+ filter = new IntentFilter();
+ filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+ sContext.registerReceiver(mModel, filter);
+ filter = new IntentFilter();
+ filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+ sContext.registerReceiver(mModel, filter);
+
+ // Register for changes to the favorites
+ ContentResolver resolver = sContext.getContentResolver();
+ resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
+ mFavoritesObserver);
+ }
+
+ /**
+ * Call from Application.onTerminate(), which is not guaranteed to ever be called.
+ */
+ public void onTerminate() {
+ sContext.unregisterReceiver(mModel);
+
+ ContentResolver resolver = sContext.getContentResolver();
+ resolver.unregisterContentObserver(mFavoritesObserver);
+ }
+
+ /**
+ * Receives notifications whenever the user favorites have changed.
+ */
+ private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ // If the database has ever changed, then we really need to force a reload of the
+ // workspace on the next load
+ mModel.resetLoadedState(false, true);
+ mModel.startLoaderFromBackground();
+ }
+ };
+
+ LauncherModel setLauncher(Launcher launcher) {
+ if (mModel == null) {
+ throw new IllegalStateException("setLauncher() called before init()");
+ }
+ mModel.initialize(launcher);
+ return mModel;
+ }
+
+ IconCache getIconCache() {
+ return mIconCache;
+ }
+
+ LauncherModel getModel() {
+ return mModel;
+ }
+
+ boolean shouldShowAppOrWidgetProvider(ComponentName componentName) {
+ return mAppFilter == null || mAppFilter.shouldShowApp(componentName);
+ }
+
+ WidgetPreviewLoader.CacheDb getWidgetPreviewCacheDb() {
+ return mWidgetPreviewCacheDb;
+ }
+
+ static void setLauncherProvider(LauncherProvider provider) {
+ sLauncherProvider = new WeakReference<LauncherProvider>(provider);
+ }
+
+ static LauncherProvider getLauncherProvider() {
+ return sLauncherProvider.get();
+ }
+
+ public static String getSharedPreferencesKey() {
+ return SHARED_PREFERENCES_KEY;
+ }
+
+ DeviceProfile initDynamicGrid(Context context, int minWidth, int minHeight,
+ int width, int height,
+ int availableWidth, int availableHeight) {
+ if (mDynamicGrid == null) {
+ mDynamicGrid = new DynamicGrid(context,
+ context.getResources(),
+ minWidth, minHeight, width, height,
+ availableWidth, availableHeight);
+ }
+
+ // Update the icon size
+ DeviceProfile grid = mDynamicGrid.getDeviceProfile();
+ Utilities.setIconSize(grid.iconSizePx);
+ grid.updateFromConfiguration(context.getResources(), width, height,
+ availableWidth, availableHeight);
+ return grid;
+ }
+ DynamicGrid getDynamicGrid() {
+ return mDynamicGrid;
+ }
+
+ public boolean isScreenLarge() {
+ return mIsScreenLarge;
+ }
+
+ // Need a version that doesn't require an instance of LauncherAppState for the wallpaper picker
+ public static boolean isScreenLarge(Resources res) {
+ return res.getBoolean(R.bool.is_large_tablet);
+ }
+
+ public static boolean isScreenLandscape(Context context) {
+ return context.getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ public float getScreenDensity() {
+ return mScreenDensity;
+ }
+
+ public int getLongPressTimeout() {
+ return mLongPressTimeout;
+ }
+}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
new file mode 100644
index 0000000..7b08d44
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 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.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+
+/**
+ * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView}
+ * which correctly captures all long-press events. This ensures that users can
+ * always pick up and move widgets.
+ */
+public class LauncherAppWidgetHost extends AppWidgetHost {
+
+ Launcher mLauncher;
+
+ public LauncherAppWidgetHost(Launcher launcher, int hostId) {
+ super(launcher, hostId);
+ mLauncher = launcher;
+ }
+
+ @Override
+ protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+ AppWidgetProviderInfo appWidget) {
+ return new LauncherAppWidgetHostView(context);
+ }
+
+ @Override
+ public void stopListening() {
+ super.stopListening();
+ clearViews();
+ }
+
+ protected void onProvidersChanged() {
+ // Once we get the message that widget packages are updated, we need to rebind items
+ // in AppsCustomize accordingly.
+ mLauncher.bindPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(mLauncher));
+ }
+}
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
new file mode 100644
index 0000000..83aef1a
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2009 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.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.DragLayer.TouchCompleteListener;
+
+/**
+ * {@inheritDoc}
+ */
+public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener {
+ private CheckLongPressHelper mLongPressHelper;
+ private LayoutInflater mInflater;
+ private Context mContext;
+ private int mPreviousOrientation;
+ private DragLayer mDragLayer;
+
+ public LauncherAppWidgetHostView(Context context) {
+ super(context);
+ mContext = context;
+ mLongPressHelper = new CheckLongPressHelper(this);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDragLayer = ((Launcher) context).getDragLayer();
+ }
+
+ @Override
+ protected View getErrorView() {
+ return mInflater.inflate(R.layout.appwidget_error, this, false);
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ // Store the orientation in which the widget was inflated
+ mPreviousOrientation = mContext.getResources().getConfiguration().orientation;
+ super.updateAppWidget(remoteViews);
+ }
+
+ public boolean orientationChangedSincedInflation() {
+ int orientation = mContext.getResources().getConfiguration().orientation;
+ if (mPreviousOrientation != orientation) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Consume any touch events for ourselves after longpress is triggered
+ if (mLongPressHelper.hasPerformedLongPress()) {
+ mLongPressHelper.cancelLongPress();
+ return true;
+ }
+
+ // Watch for longpress events at this level to make sure
+ // users can always pick up this widget
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ mLongPressHelper.postCheckForLongPress();
+ mDragLayer.setTouchCompleteListener(this);
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mLongPressHelper.cancelLongPress();
+ break;
+ }
+
+ // Otherwise continue letting touch events fall through to children
+ return false;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ // If the widget does not handle touch, then cancel
+ // long press when we release the touch
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mLongPressHelper.cancelLongPress();
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+ mLongPressHelper.cancelLongPress();
+ }
+
+ @Override
+ public void onTouchComplete() {
+ mLongPressHelper.cancelLongPress();
+ }
+
+ @Override
+ public int getDescendantFocusability() {
+ return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+ }
+
+
+}
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
new file mode 100644
index 0000000..28df90f
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009 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.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.ContentValues;
+
+/**
+ * Represents a widget (either instantiated or about to be) in the Launcher.
+ */
+class LauncherAppWidgetInfo extends ItemInfo {
+
+ /**
+ * Indicates that the widget hasn't been instantiated yet.
+ */
+ static final int NO_ID = -1;
+
+ /**
+ * Identifier for this widget when talking with
+ * {@link android.appwidget.AppWidgetManager} for updates.
+ */
+ int appWidgetId = NO_ID;
+
+ ComponentName providerName;
+
+ // TODO: Are these necessary here?
+ int minWidth = -1;
+ int minHeight = -1;
+
+ private boolean mHasNotifiedInitialWidgetSizeChanged;
+
+ /**
+ * View that holds this widget after it's been created. This view isn't created
+ * until Launcher knows it's needed.
+ */
+ AppWidgetHostView hostView = null;
+
+ LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ this.appWidgetId = appWidgetId;
+ this.providerName = providerName;
+
+ // Since the widget isn't instantiated yet, we don't know these values. Set them to -1
+ // to indicate that they should be calculated based on the layout and minWidth/minHeight
+ spanX = -1;
+ spanY = -1;
+ }
+
+ @Override
+ void onAddToDatabase(ContentValues values) {
+ super.onAddToDatabase(values);
+ values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
+ values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
+ }
+
+ /**
+ * When we bind the widget, we should notify the widget that the size has changed if we have not
+ * done so already (only really for default workspace widgets).
+ */
+ void onBindAppWidget(Launcher launcher) {
+ if (!mHasNotifiedInitialWidgetSizeChanged) {
+ notifyWidgetSizeChanged(launcher);
+ }
+ }
+
+ /**
+ * Trigger an update callback to the widget to notify it that its size has changed.
+ */
+ void notifyWidgetSizeChanged(Launcher launcher) {
+ AppWidgetResizeFrame.updateWidgetSizeRanges(hostView, launcher, spanX, spanY);
+ mHasNotifiedInitialWidgetSizeChanged = true;
+ }
+
+ @Override
+ public String toString() {
+ return "AppWidget(id=" + Integer.toString(appWidgetId) + ")";
+ }
+
+ @Override
+ void unbind() {
+ super.unbind();
+ hostView = null;
+ }
+}
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
new file mode 100644
index 0000000..8b179f1
--- /dev/null
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.app.Application;
+
+public class LauncherApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ LauncherAppState.setApplicationContext(this);
+ LauncherAppState.getInstance();
+ }
+
+ @Override
+ public void onTerminate() {
+ super.onTerminate();
+ LauncherAppState.getInstance().onTerminate();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
new file mode 100644
index 0000000..2b5059b
--- /dev/null
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 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.app.backup.BackupAgentHelper;
+import android.app.backup.BackupManager;
+import android.content.Context;
+
+public class LauncherBackupAgentHelper extends BackupAgentHelper {
+
+ private static BackupManager sBackupManager;
+
+ /**
+ * Notify the backup manager that out database is dirty.
+ *
+ * <P>This does not force an immediate backup.
+ *
+ * @param context application context
+ */
+ public static void dataChanged(Context context) {
+ if (sBackupManager == null) {
+ sBackupManager = new BackupManager(context);
+ }
+ sBackupManager.dataChanged();
+ }
+
+
+ @Override
+ public void onCreate() {
+ addHelper(LauncherBackupHelper.LAUNCHER_PREFIX, new LauncherBackupHelper(this));
+ }
+}
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
new file mode 100644
index 0000000..9b901ee
--- /dev/null
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -0,0 +1,1016 @@
+/*
+ * Copyright (C) 2013 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 com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.WorkspaceScreens;
+import com.android.launcher3.backup.BackupProtos;
+import com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.BackupProtos.Journal;
+import com.android.launcher3.backup.BackupProtos.Key;
+import com.android.launcher3.backup.BackupProtos.Resource;
+import com.android.launcher3.backup.BackupProtos.Screen;
+import com.android.launcher3.backup.BackupProtos.Widget;
+
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupHelper;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.CRC32;
+
+/**
+ * Persist the launcher home state across calamities.
+ */
+public class LauncherBackupHelper implements BackupHelper {
+
+ private static final String TAG = "LauncherBackupHelper";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_PAYLOAD = false;
+
+ private static final int MAX_JOURNAL_SIZE = 1000000;
+
+ /** icons are large, dribble them out */
+ private static final int MAX_ICONS_PER_PASS = 10;
+
+ /** widgets contain previews, which are very large, dribble them out */
+ private static final int MAX_WIDGETS_PER_PASS = 5;
+
+ public static final int IMAGE_COMPRESSION_QUALITY = 75;
+
+ public static final String LAUNCHER_PREFIX = "L";
+
+ private static final Bitmap.CompressFormat IMAGE_FORMAT =
+ android.graphics.Bitmap.CompressFormat.PNG;
+
+ private static BackupManager sBackupManager;
+
+ private static final String[] FAVORITE_PROJECTION = {
+ Favorites._ID, // 0
+ Favorites.MODIFIED, // 1
+ Favorites.INTENT, // 2
+ Favorites.APPWIDGET_PROVIDER, // 3
+ Favorites.APPWIDGET_ID, // 4
+ Favorites.CELLX, // 5
+ Favorites.CELLY, // 6
+ Favorites.CONTAINER, // 7
+ Favorites.ICON, // 8
+ Favorites.ICON_PACKAGE, // 9
+ Favorites.ICON_RESOURCE, // 10
+ Favorites.ICON_TYPE, // 11
+ Favorites.ITEM_TYPE, // 12
+ Favorites.SCREEN, // 13
+ Favorites.SPANX, // 14
+ Favorites.SPANY, // 15
+ Favorites.TITLE, // 16
+ };
+
+ private static final int ID_INDEX = 0;
+ private static final int ID_MODIFIED = 1;
+ private static final int INTENT_INDEX = 2;
+ private static final int APPWIDGET_PROVIDER_INDEX = 3;
+ private static final int APPWIDGET_ID_INDEX = 4;
+ private static final int CELLX_INDEX = 5;
+ private static final int CELLY_INDEX = 6;
+ private static final int CONTAINER_INDEX = 7;
+ private static final int ICON_INDEX = 8;
+ private static final int ICON_PACKAGE_INDEX = 9;
+ private static final int ICON_RESOURCE_INDEX = 10;
+ private static final int ICON_TYPE_INDEX = 11;
+ private static final int ITEM_TYPE_INDEX = 12;
+ private static final int SCREEN_INDEX = 13;
+ private static final int SPANX_INDEX = 14;
+ private static final int SPANY_INDEX = 15;
+ private static final int TITLE_INDEX = 16;
+
+ private static final String[] SCREEN_PROJECTION = {
+ WorkspaceScreens._ID, // 0
+ WorkspaceScreens.MODIFIED, // 1
+ WorkspaceScreens.SCREEN_RANK // 2
+ };
+
+ private static final int SCREEN_RANK_INDEX = 2;
+
+ private final Context mContext;
+
+ private HashMap<ComponentName, AppWidgetProviderInfo> mWidgetMap;
+
+ private ArrayList<Key> mKeys;
+
+ public LauncherBackupHelper(Context context) {
+ mContext = context;
+ }
+
+ private void dataChanged() {
+ if (sBackupManager == null) {
+ sBackupManager = new BackupManager(mContext);
+ }
+ sBackupManager.dataChanged();
+ }
+
+ /**
+ * Back up launcher data so we can restore the user's state on a new device.
+ *
+ * <P>The journal is a timestamp and a list of keys that were saved as of that time.
+ *
+ * <P>Keys may come back in any order, so each key/value is one complete row of the database.
+ *
+ * @param oldState notes from the last backup
+ * @param data incremental key/value pairs to persist off-device
+ * @param newState notes for the next backup
+ * @throws IOException
+ */
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ Log.v(TAG, "onBackup");
+
+ Journal in = readJournal(oldState);
+ Journal out = new Journal();
+
+ long lastBackupTime = in.t;
+ out.t = System.currentTimeMillis();
+ out.rows = 0;
+ out.bytes = 0;
+
+ Log.v(TAG, "lastBackupTime=" + lastBackupTime);
+
+ ArrayList<Key> keys = new ArrayList<Key>();
+ try {
+ backupFavorites(in, data, out, keys);
+ backupScreens(in, data, out, keys);
+ backupIcons(in, data, out, keys);
+ backupWidgets(in, data, out, keys);
+ } catch (IOException e) {
+ Log.e(TAG, "launcher backup has failed", e);
+ }
+
+ out.key = keys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+ writeJournal(newState, out);
+ Log.v(TAG, "onBackup: wrote " + out.bytes + "b in " + out.rows + " rows.");
+ }
+
+ /**
+ * Restore launcher configuration from the restored data stream.
+ *
+ * <P>Keys may arrive in any order.
+ *
+ * @param data the key/value pair from the server
+ */
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ Log.v(TAG, "restoreEntity");
+ if (mKeys == null) {
+ mKeys = new ArrayList<Key>();
+ }
+ byte[] buffer = new byte[512];
+ String backupKey = data.getKey();
+ int dataSize = data.size();
+ if (buffer.length < dataSize) {
+ buffer = new byte[dataSize];
+ }
+ Key key = null;
+ int bytesRead = 0;
+ try {
+ bytesRead = data.read(buffer, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "read " + bytesRead + " of " + dataSize + " available");
+ } catch (IOException e) {
+ Log.d(TAG, "failed to read entity from restore data", e);
+ }
+ try {
+ key = backupKeyToKey(backupKey);
+ switch (key.type) {
+ case Key.FAVORITE:
+ restoreFavorite(key, buffer, dataSize, mKeys);
+ break;
+
+ case Key.SCREEN:
+ restoreScreen(key, buffer, dataSize, mKeys);
+ break;
+
+ case Key.ICON:
+ restoreIcon(key, buffer, dataSize, mKeys);
+ break;
+
+ case Key.WIDGET:
+ restoreWidget(key, buffer, dataSize, mKeys);
+ break;
+
+ default:
+ Log.w(TAG, "unknown restore entity type: " + key.type);
+ break;
+ }
+ } catch (KeyParsingException e) {
+ Log.w(TAG, "ignoring unparsable backup key: " + backupKey);
+ }
+
+ }
+
+ /**
+ * Record the restore state for the next backup.
+ *
+ * @param newState notes about the backup state after restore.
+ */
+ @Override
+ public void writeNewStateDescription(ParcelFileDescriptor newState) {
+ // clear the output journal time, to force a full backup to
+ // will catch any changes the restore process might have made
+ Journal out = new Journal();
+ out.t = 0;
+ out.key = mKeys.toArray(BackupProtos.Key.EMPTY_ARRAY);
+ writeJournal(newState, out);
+ Log.v(TAG, "onRestore: read " + mKeys.size() + " rows");
+ mKeys.clear();
+ }
+
+ /**
+ * Write all modified favorites to the data stream.
+ *
+ *
+ * @param in notes from last backup
+ * @param data output stream for key/value pairs
+ * @param out notes about this backup
+ * @param keys keys to mark as clean in the notes for next backup
+ * @throws IOException
+ */
+ private void backupFavorites(Journal in, BackupDataOutput data, Journal out,
+ ArrayList<Key> keys)
+ throws IOException {
+ // read the old ID set
+ Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
+ if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
+
+ // persist things that have changed since the last backup
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+ null, null, null);
+ Set<String> currentIds = new HashSet<String>(cursor.getCount());
+ try {
+ cursor.moveToPosition(-1);
+ while(cursor.moveToNext()) {
+ final long id = cursor.getLong(ID_INDEX);
+ final long updateTime = cursor.getLong(ID_MODIFIED);
+ Key key = getKey(Key.FAVORITE, id);
+ keys.add(key);
+ currentIds.add(keyToBackupKey(key));
+ if (updateTime > in.t) {
+ byte[] blob = packFavorite(cursor);
+ writeRowToBackup(key, blob, out, data);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ if (DEBUG) Log.d(TAG, "favorite currentIds.size()=" + currentIds.size());
+
+ // these IDs must have been deleted
+ savedIds.removeAll(currentIds);
+ out.rows += removeDeletedKeysFromBackup(savedIds, data);
+ }
+
+ /**
+ * Read a favorite from the stream.
+ *
+ * <P>Keys arrive in any order, so screens and containers may not exist yet.
+ *
+ * @param key identifier for the row
+ * @param buffer the serialized proto from the stream, may be larger than dataSize
+ * @param dataSize the size of the proto from the stream
+ * @param keys keys to mark as clean in the notes for next backup
+ */
+ private void restoreFavorite(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+ Log.v(TAG, "unpacking favorite " + key.id + " (" + dataSize + " bytes)");
+ if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+ Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+
+ try {
+ Favorite favorite = unpackFavorite(buffer, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "unpacked " + favorite.itemType);
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.w(TAG, "failed to decode proto", e);
+ }
+ }
+
+ /**
+ * Write all modified screens to the data stream.
+ *
+ *
+ * @param in notes from last backup
+ * @param data output stream for key/value pairs
+ * @param out notes about this backup
+ * @param keys keys to mark as clean in the notes for next backup
+ * @throws IOException
+ */
+ private void backupScreens(Journal in, BackupDataOutput data, Journal out,
+ ArrayList<Key> keys)
+ throws IOException {
+ // read the old ID set
+ Set<String> savedIds = getSavedIdsByType(Key.SCREEN, in);
+ if (DEBUG) Log.d(TAG, "screen savedIds.size()=" + savedIds.size());
+
+ // persist things that have changed since the last backup
+ ContentResolver cr = mContext.getContentResolver();
+ Cursor cursor = cr.query(WorkspaceScreens.CONTENT_URI, SCREEN_PROJECTION,
+ null, null, null);
+ Set<String> currentIds = new HashSet<String>(cursor.getCount());
+ try {
+ cursor.moveToPosition(-1);
+ while(cursor.moveToNext()) {
+ final long id = cursor.getLong(ID_INDEX);
+ final long updateTime = cursor.getLong(ID_MODIFIED);
+ Key key = getKey(Key.SCREEN, id);
+ keys.add(key);
+ currentIds.add(keyToBackupKey(key));
+ if (updateTime > in.t) {
+ byte[] blob = packScreen(cursor);
+ writeRowToBackup(key, blob, out, data);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ if (DEBUG) Log.d(TAG, "screen currentIds.size()=" + currentIds.size());
+
+ // these IDs must have been deleted
+ savedIds.removeAll(currentIds);
+ out.rows += removeDeletedKeysFromBackup(savedIds, data);
+ }
+
+ /**
+ * Read a screen from the stream.
+ *
+ * <P>Keys arrive in any order, so children of this screen may already exist.
+ *
+ * @param key identifier for the row
+ * @param buffer the serialized proto from the stream, may be larger than dataSize
+ * @param dataSize the size of the proto from the stream
+ * @param keys keys to mark as clean in the notes for next backup
+ */
+ private void restoreScreen(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+ Log.v(TAG, "unpacking screen " + key.id);
+ if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+ Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+ try {
+ Screen screen = unpackScreen(buffer, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "unpacked " + screen.rank);
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.w(TAG, "failed to decode proto", e);
+ }
+ }
+
+ /**
+ * Write all the static icon resources we need to render placeholders
+ * for a package that is not installed.
+ *
+ * @param in notes from last backup
+ * @param data output stream for key/value pairs
+ * @param out notes about this backup
+ * @param keys keys to mark as clean in the notes for next backup
+ * @throws IOException
+ */
+ private void backupIcons(Journal in, BackupDataOutput data, Journal out,
+ ArrayList<Key> keys) throws IOException {
+ // persist icons that haven't been persisted yet
+ final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (appState == null) {
+ dataChanged(); // try again later
+ if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
+ return;
+ }
+ final ContentResolver cr = mContext.getContentResolver();
+ final IconCache iconCache = appState.getIconCache();
+ final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
+
+ // read the old ID set
+ Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
+ if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
+
+ int startRows = out.rows;
+ if (DEBUG) Log.d(TAG, "starting here: " + startRows);
+ String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION;
+ Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+ where, null, null);
+ Set<String> currentIds = new HashSet<String>(cursor.getCount());
+ try {
+ cursor.moveToPosition(-1);
+ while(cursor.moveToNext()) {
+ final long id = cursor.getLong(ID_INDEX);
+ final String intentDescription = cursor.getString(INTENT_INDEX);
+ try {
+ Intent intent = Intent.parseUri(intentDescription, 0);
+ ComponentName cn = intent.getComponent();
+ Key key = null;
+ String backupKey = null;
+ if (cn != null) {
+ key = getKey(Key.ICON, cn.flattenToShortString());
+ backupKey = keyToBackupKey(key);
+ currentIds.add(backupKey);
+ } else {
+ Log.w(TAG, "empty intent on application favorite: " + id);
+ }
+ if (savedIds.contains(backupKey)) {
+ if (DEBUG) Log.d(TAG, "already saved icon " + backupKey);
+
+ // remember that we already backed this up previously
+ keys.add(key);
+ } else if (backupKey != null) {
+ if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
+ if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
+ if (DEBUG) Log.d(TAG, "saving icon " + backupKey);
+ Bitmap icon = iconCache.getIcon(intent);
+ keys.add(key);
+ if (icon != null && !iconCache.isDefaultIcon(icon)) {
+ byte[] blob = packIcon(dpi, icon);
+ writeRowToBackup(key, blob, out, data);
+ }
+ } else {
+ if (DEBUG) Log.d(TAG, "scheduling another run for icon " + backupKey);
+ // too many icons for this pass, request another.
+ dataChanged();
+ }
+ }
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "invalid URI on application favorite: " + id);
+ } catch (IOException e) {
+ Log.w(TAG, "unable to save application icon for favorite: " + id);
+ }
+
+ }
+ } finally {
+ cursor.close();
+ }
+ if (DEBUG) Log.d(TAG, "icon currentIds.size()=" + currentIds.size());
+
+ // these IDs must have been deleted
+ savedIds.removeAll(currentIds);
+ out.rows += removeDeletedKeysFromBackup(savedIds, data);
+ }
+
+ /**
+ * Read an icon from the stream.
+ *
+ * <P>Keys arrive in any order, so shortcuts that use this icon may already exist.
+ *
+ * @param key identifier for the row
+ * @param buffer the serialized proto from the stream, may be larger than dataSize
+ * @param dataSize the size of the proto from the stream
+ * @param keys keys to mark as clean in the notes for next backup
+ */
+ private void restoreIcon(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+ Log.v(TAG, "unpacking icon " + key.id);
+ if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+ Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+ try {
+ Resource res = unpackIcon(buffer, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "unpacked " + res.dpi);
+ if (DEBUG) Log.d(TAG, "read " +
+ Base64.encodeToString(res.data, 0, res.data.length,
+ Base64.NO_WRAP));
+ Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
+ if (icon == null) {
+ Log.w(TAG, "failed to unpack icon for " + key.name);
+ }
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.w(TAG, "failed to decode proto", e);
+ }
+ }
+
+ /**
+ * Write all the static widget resources we need to render placeholders
+ * for a package that is not installed.
+ *
+ * @param in notes from last backup
+ * @param data output stream for key/value pairs
+ * @param out notes about this backup
+ * @param keys keys to mark as clean in the notes for next backup
+ * @throws IOException
+ */
+ private void backupWidgets(Journal in, BackupDataOutput data, Journal out,
+ ArrayList<Key> keys) throws IOException {
+ // persist static widget info that hasn't been persisted yet
+ final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (appState == null) {
+ dataChanged(); // try again later
+ if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying widget backup");
+ return;
+ }
+ final ContentResolver cr = mContext.getContentResolver();
+ final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
+ final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
+ final IconCache iconCache = appState.getIconCache();
+ final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
+ final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
+ if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
+
+ // read the old ID set
+ Set<String> savedIds = getSavedIdsByType(Key.WIDGET, in);
+ if (DEBUG) Log.d(TAG, "widgets savedIds.size()=" + savedIds.size());
+
+ int startRows = out.rows;
+ if (DEBUG) Log.d(TAG, "starting here: " + startRows);
+ String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPWIDGET;
+ Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
+ where, null, null);
+ Set<String> currentIds = new HashSet<String>(cursor.getCount());
+ try {
+ cursor.moveToPosition(-1);
+ while(cursor.moveToNext()) {
+ final long id = cursor.getLong(ID_INDEX);
+ final String providerName = cursor.getString(APPWIDGET_PROVIDER_INDEX);
+ final int spanX = cursor.getInt(SPANX_INDEX);
+ final int spanY = cursor.getInt(SPANY_INDEX);
+ final ComponentName provider = ComponentName.unflattenFromString(providerName);
+ Key key = null;
+ String backupKey = null;
+ if (provider != null) {
+ key = getKey(Key.WIDGET, providerName);
+ backupKey = keyToBackupKey(key);
+ currentIds.add(backupKey);
+ } else {
+ Log.w(TAG, "empty intent on appwidget: " + id);
+ }
+ if (savedIds.contains(backupKey)) {
+ if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
+
+ // remember that we already backed this up previously
+ keys.add(key);
+ } else if (backupKey != null) {
+ if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
+ if ((out.rows - startRows) < MAX_WIDGETS_PER_PASS) {
+ if (DEBUG) Log.d(TAG, "saving widget " + backupKey);
+ previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
+ spanY * profile.cellHeightPx, widgetSpacingLayout);
+ byte[] blob = packWidget(dpi, previewLoader, iconCache, provider);
+ keys.add(key);
+ writeRowToBackup(key, blob, out, data);
+
+ } else {
+ if (DEBUG) Log.d(TAG, "scheduling another run for widget " + backupKey);
+ // too many widgets for this pass, request another.
+ dataChanged();
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ if (DEBUG) Log.d(TAG, "widget currentIds.size()=" + currentIds.size());
+
+ // these IDs must have been deleted
+ savedIds.removeAll(currentIds);
+ out.rows += removeDeletedKeysFromBackup(savedIds, data);
+ }
+
+ /**
+ * Read a widget from the stream.
+ *
+ * <P>Keys arrive in any order, so widgets that use this data may already exist.
+ *
+ * @param key identifier for the row
+ * @param buffer the serialized proto from the stream, may be larger than dataSize
+ * @param dataSize the size of the proto from the stream
+ * @param keys keys to mark as clean in the notes for next backup
+ */
+ private void restoreWidget(Key key, byte[] buffer, int dataSize, ArrayList<Key> keys) {
+ Log.v(TAG, "unpacking widget " + key.id);
+ if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
+ Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+ try {
+ Widget widget = unpackWidget(buffer, 0, dataSize);
+ if (DEBUG) Log.d(TAG, "unpacked " + widget.provider);
+ if (widget.icon.data != null) {
+ Bitmap icon = BitmapFactory
+ .decodeByteArray(widget.icon.data, 0, widget.icon.data.length);
+ if (icon == null) {
+ Log.w(TAG, "failed to unpack widget icon for " + key.name);
+ }
+ }
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.w(TAG, "failed to decode proto", e);
+ }
+ }
+
+ /** create a new key, with an integer ID.
+ *
+ * <P> Keys contain their own checksum instead of using
+ * the heavy-weight CheckedMessage wrapper.
+ */
+ private Key getKey(int type, long id) {
+ Key key = new Key();
+ key.type = type;
+ key.id = id;
+ key.checksum = checkKey(key);
+ return key;
+ }
+
+ /** create a new key for a named object.
+ *
+ * <P> Keys contain their own checksum instead of using
+ * the heavy-weight CheckedMessage wrapper.
+ */
+ private Key getKey(int type, String name) {
+ Key key = new Key();
+ key.type = type;
+ key.name = name;
+ key.checksum = checkKey(key);
+ return key;
+ }
+
+ /** keys need to be strings, serialize and encode. */
+ private String keyToBackupKey(Key key) {
+ return Base64.encodeToString(Key.toByteArray(key), Base64.NO_WRAP);
+ }
+
+ /** keys need to be strings, decode and parse. */
+ private Key backupKeyToKey(String backupKey) throws KeyParsingException {
+ try {
+ Key key = Key.parseFrom(Base64.decode(backupKey, Base64.DEFAULT));
+ if (key.checksum != checkKey(key)) {
+ key = null;
+ throw new KeyParsingException("invalid key read from stream" + backupKey);
+ }
+ return key;
+ } catch (InvalidProtocolBufferNanoException e) {
+ throw new KeyParsingException(e);
+ } catch (IllegalArgumentException e) {
+ throw new KeyParsingException(e);
+ }
+ }
+
+ private String getKeyName(Key key) {
+ if (TextUtils.isEmpty(key.name)) {
+ return Long.toString(key.id);
+ } else {
+ return key.name;
+ }
+
+ }
+
+ private String geKeyType(Key key) {
+ switch (key.type) {
+ case Key.FAVORITE:
+ return "favorite";
+ case Key.SCREEN:
+ return "screen";
+ case Key.ICON:
+ return "icon";
+ case Key.WIDGET:
+ return "widget";
+ default:
+ return "anonymous";
+ }
+ }
+
+ /** Compute the checksum over the important bits of a key. */
+ private long checkKey(Key key) {
+ CRC32 checksum = new CRC32();
+ checksum.update(key.type);
+ checksum.update((int) (key.id & 0xffff));
+ checksum.update((int) ((key.id >> 32) & 0xffff));
+ if (!TextUtils.isEmpty(key.name)) {
+ checksum.update(key.name.getBytes());
+ }
+ return checksum.getValue();
+ }
+
+ /** Serialize a Favorite for persistence, including a checksum wrapper. */
+ private byte[] packFavorite(Cursor c) {
+ Favorite favorite = new Favorite();
+ favorite.id = c.getLong(ID_INDEX);
+ favorite.screen = c.getInt(SCREEN_INDEX);
+ favorite.container = c.getInt(CONTAINER_INDEX);
+ favorite.cellX = c.getInt(CELLX_INDEX);
+ favorite.cellY = c.getInt(CELLY_INDEX);
+ favorite.spanX = c.getInt(SPANX_INDEX);
+ favorite.spanY = c.getInt(SPANY_INDEX);
+ favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+ if (favorite.iconType == Favorites.ICON_TYPE_RESOURCE) {
+ String iconPackage = c.getString(ICON_PACKAGE_INDEX);
+ if (!TextUtils.isEmpty(iconPackage)) {
+ favorite.iconPackage = iconPackage;
+ }
+ String iconResource = c.getString(ICON_RESOURCE_INDEX);
+ if (!TextUtils.isEmpty(iconResource)) {
+ favorite.iconResource = iconResource;
+ }
+ }
+ if (favorite.iconType == Favorites.ICON_TYPE_BITMAP) {
+ byte[] blob = c.getBlob(ICON_INDEX);
+ if (blob != null && blob.length > 0) {
+ favorite.icon = blob;
+ }
+ }
+ String title = c.getString(TITLE_INDEX);
+ if (!TextUtils.isEmpty(title)) {
+ favorite.title = title;
+ }
+ String intent = c.getString(INTENT_INDEX);
+ if (!TextUtils.isEmpty(intent)) {
+ favorite.intent = intent;
+ }
+ favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
+ if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+ favorite.appWidgetId = c.getInt(APPWIDGET_ID_INDEX);
+ String appWidgetProvider = c.getString(APPWIDGET_PROVIDER_INDEX);
+ if (!TextUtils.isEmpty(appWidgetProvider)) {
+ favorite.appWidgetProvider = appWidgetProvider;
+ }
+ }
+
+ return writeCheckedBytes(favorite);
+ }
+
+ /** Deserialize a Favorite from persistence, after verifying checksum wrapper. */
+ private Favorite unpackFavorite(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ Favorite favorite = new Favorite();
+ MessageNano.mergeFrom(favorite, readCheckedBytes(buffer, offset, dataSize));
+ return favorite;
+ }
+
+ /** Serialize a Screen for persistence, including a checksum wrapper. */
+ private byte[] packScreen(Cursor c) {
+ Screen screen = new Screen();
+ screen.id = c.getLong(ID_INDEX);
+ screen.rank = c.getInt(SCREEN_RANK_INDEX);
+
+ return writeCheckedBytes(screen);
+ }
+
+ /** Deserialize a Screen from persistence, after verifying checksum wrapper. */
+ private Screen unpackScreen(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ Screen screen = new Screen();
+ MessageNano.mergeFrom(screen, readCheckedBytes(buffer, offset, dataSize));
+ return screen;
+ }
+
+ /** Serialize an icon Resource for persistence, including a checksum wrapper. */
+ private byte[] packIcon(int dpi, Bitmap icon) {
+ Resource res = new Resource();
+ res.dpi = dpi;
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
+ res.data = os.toByteArray();
+ }
+ return writeCheckedBytes(res);
+ }
+
+ /** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
+ private Resource unpackIcon(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ Resource res = new Resource();
+ MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
+ return res;
+ }
+
+ /** Serialize a widget for persistence, including a checksum wrapper. */
+ private byte[] packWidget(int dpi, WidgetPreviewLoader previewLoader, IconCache iconCache,
+ ComponentName provider) {
+ final AppWidgetProviderInfo info = findAppWidgetProviderInfo(provider);
+ Widget widget = new Widget();
+ widget.provider = provider.flattenToShortString();
+ widget.label = info.label;
+ widget.configure = info.configure != null;
+ if (info.icon != 0) {
+ widget.icon = new Resource();
+ Drawable fullResIcon = iconCache.getFullResIcon(provider.getPackageName(), info.icon);
+ Bitmap icon = Utilities.createIconBitmap(fullResIcon, mContext);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ if (icon.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
+ widget.icon.data = os.toByteArray();
+ widget.icon.dpi = dpi;
+ }
+ }
+ if (info.previewImage != 0) {
+ widget.preview = new Resource();
+ Bitmap preview = previewLoader.generateWidgetPreview(info, null);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ if (preview.compress(IMAGE_FORMAT, IMAGE_COMPRESSION_QUALITY, os)) {
+ widget.preview.data = os.toByteArray();
+ widget.preview.dpi = dpi;
+ }
+ }
+ return writeCheckedBytes(widget);
+ }
+
+ /** Deserialize a widget from persistence, after verifying checksum wrapper. */
+ private Widget unpackWidget(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ Widget widget = new Widget();
+ MessageNano.mergeFrom(widget, readCheckedBytes(buffer, offset, dataSize));
+ return widget;
+ }
+
+ /**
+ * Read the old journal from the input file.
+ *
+ * In the event of any error, just pretend we didn't have a journal,
+ * in that case, do a full backup.
+ *
+ * @param oldState the read-0only file descriptor pointing to the old journal
+ * @return a Journal protocol bugffer
+ */
+ private Journal readJournal(ParcelFileDescriptor oldState) {
+ Journal journal = new Journal();
+ if (oldState == null) {
+ return journal;
+ }
+ FileInputStream inStream = new FileInputStream(oldState.getFileDescriptor());
+ try {
+ int remaining = inStream.available();
+ if (DEBUG) Log.d(TAG, "available " + remaining);
+ if (remaining < MAX_JOURNAL_SIZE) {
+ byte[] buffer = new byte[remaining];
+ int bytesRead = 0;
+ while (remaining > 0) {
+ try {
+ int result = inStream.read(buffer, bytesRead, remaining);
+ if (result > 0) {
+ if (DEBUG) Log.d(TAG, "read some bytes: " + result);
+ remaining -= result;
+ bytesRead += result;
+ } else {
+ // stop reading ands see what there is to parse
+ Log.w(TAG, "read error: " + result);
+ remaining = 0;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "failed to read the journal", e);
+ buffer = null;
+ remaining = 0;
+ }
+ }
+ if (DEBUG) Log.d(TAG, "journal bytes read: " + bytesRead);
+
+ if (buffer != null) {
+ try {
+ MessageNano.mergeFrom(journal, readCheckedBytes(buffer, 0, bytesRead));
+ } catch (InvalidProtocolBufferNanoException e) {
+ Log.d(TAG, "failed to read the journal", e);
+ journal.clear();
+ }
+ }
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "failed to close the journal", e);
+ } finally {
+ try {
+ inStream.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to close the journal", e);
+ }
+ }
+ return journal;
+ }
+
+ private void writeRowToBackup(Key key, byte[] blob, Journal out,
+ BackupDataOutput data) throws IOException {
+ String backupKey = keyToBackupKey(key);
+ data.writeEntityHeader(backupKey, blob.length);
+ data.writeEntityData(blob, blob.length);
+ out.rows++;
+ out.bytes += blob.length;
+ Log.v(TAG, "saving " + geKeyType(key) + " " + backupKey + ": " +
+ getKeyName(key) + "/" + blob.length);
+ if(DEBUG_PAYLOAD) {
+ String encoded = Base64.encodeToString(blob, 0, blob.length, Base64.NO_WRAP);
+ final int chunkSize = 1024;
+ for (int offset = 0; offset < encoded.length(); offset += chunkSize) {
+ int end = offset + chunkSize;
+ end = Math.min(end, encoded.length());
+ Log.d(TAG, "wrote " + encoded.substring(offset, end));
+ }
+ }
+ }
+
+ private Set<String> getSavedIdsByType(int type, Journal in) {
+ Set<String> savedIds = new HashSet<String>();
+ for(int i = 0; i < in.key.length; i++) {
+ Key key = in.key[i];
+ if (key.type == type) {
+ savedIds.add(keyToBackupKey(key));
+ }
+ }
+ return savedIds;
+ }
+
+ private int removeDeletedKeysFromBackup(Set<String> deletedIds, BackupDataOutput data)
+ throws IOException {
+ int rows = 0;
+ for(String deleted: deletedIds) {
+ Log.v(TAG, "dropping icon " + deleted);
+ data.writeEntityHeader(deleted, -1);
+ rows++;
+ }
+ return rows;
+ }
+
+ /**
+ * Write the new journal to the output file.
+ *
+ * In the event of any error, just pretend we didn't have a journal,
+ * in that case, do a full backup.
+
+ * @param newState the write-only file descriptor pointing to the new journal
+ * @param journal a Journal protocol buffer
+ */
+ private void writeJournal(ParcelFileDescriptor newState, Journal journal) {
+ FileOutputStream outStream = null;
+ try {
+ outStream = new FileOutputStream(newState.getFileDescriptor());
+ outStream.write(writeCheckedBytes(journal));
+ outStream.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to write backup journal", e);
+ }
+ }
+
+ /** Wrap a proto in a CheckedMessage and compute the checksum. */
+ private byte[] writeCheckedBytes(MessageNano proto) {
+ CheckedMessage wrapper = new CheckedMessage();
+ wrapper.payload = MessageNano.toByteArray(proto);
+ CRC32 checksum = new CRC32();
+ checksum.update(wrapper.payload);
+ wrapper.checksum = checksum.getValue();
+ return MessageNano.toByteArray(wrapper);
+ }
+
+ /** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
+ private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
+ throws InvalidProtocolBufferNanoException {
+ CheckedMessage wrapper = new CheckedMessage();
+ MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
+ CRC32 checksum = new CRC32();
+ checksum.update(wrapper.payload);
+ if (wrapper.checksum != checksum.getValue()) {
+ throw new InvalidProtocolBufferNanoException("checksum does not match");
+ }
+ return wrapper.payload;
+ }
+
+ private AppWidgetProviderInfo findAppWidgetProviderInfo(ComponentName component) {
+ if (mWidgetMap == null) {
+ List<AppWidgetProviderInfo> widgets =
+ AppWidgetManager.getInstance(mContext).getInstalledProviders();
+ mWidgetMap = new HashMap<ComponentName, AppWidgetProviderInfo>(widgets.size());
+ for (AppWidgetProviderInfo info : widgets) {
+ mWidgetMap.put(info.provider, info);
+ }
+ }
+ return mWidgetMap.get(component);
+ }
+
+ private class KeyParsingException extends Throwable {
+ private KeyParsingException(Throwable cause) {
+ super(cause);
+ }
+
+ public KeyParsingException(String reason) {
+ super(reason);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
new file mode 100644
index 0000000..7e1442d
--- /dev/null
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -0,0 +1,3182 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.*;
+import android.content.Intent.ShortcutIconResource;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.BaseColumns;
+import android.util.Log;
+import android.util.Pair;
+import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
+
+import java.lang.ref.WeakReference;
+import java.net.URISyntaxException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Maintains in-memory state of the Launcher. It is expected that there should be only one
+ * LauncherModel object held in a static. Also provide APIs for updating the database state
+ * for the Launcher.
+ */
+public class LauncherModel extends BroadcastReceiver {
+ static final boolean DEBUG_LOADERS = false;
+ static final String TAG = "Launcher.Model";
+
+ // true = use a "More Apps" folder for non-workspace apps on upgrade
+ // false = strew non-workspace apps across the workspace on upgrade
+ public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
+
+ private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
+ private final boolean mAppsCanBeOnRemoveableStorage;
+
+ private final LauncherAppState mApp;
+ private final Object mLock = new Object();
+ private DeferredHandler mHandler = new DeferredHandler();
+ private LoaderTask mLoaderTask;
+ private boolean mIsLoaderTaskRunning;
+ private volatile boolean mFlushingWorkerThread;
+
+ // Specific runnable types that are run on the main thread deferred handler, this allows us to
+ // clear all queued binding runnables when the Launcher activity is destroyed.
+ private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
+ private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
+
+
+ private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
+ static {
+ sWorkerThread.start();
+ }
+ private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
+
+ // We start off with everything not loaded. After that, we assume that
+ // our monitoring of the package manager provides all updates and we never
+ // need to do a requery. These are only ever touched from the loader thread.
+ private boolean mWorkspaceLoaded;
+ private boolean mAllAppsLoaded;
+
+ // When we are loading pages synchronously, we can't just post the binding of items on the side
+ // pages as this delays the rotation process. Instead, we wait for a callback from the first
+ // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
+ // a normal load, we also clear this set of Runnables.
+ static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
+
+ private WeakReference<Callbacks> mCallbacks;
+
+ // < only access in worker thread >
+ AllAppsList mBgAllAppsList;
+
+ // The lock that must be acquired before referencing any static bg data structures. Unlike
+ // other locks, this one can generally be held long-term because we never expect any of these
+ // static data structures to be referenced outside of the worker thread except on the first
+ // load after configuration change.
+ static final Object sBgLock = new Object();
+
+ // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
+ // LauncherModel to their ids
+ static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
+
+ // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
+ // created by LauncherModel that are directly on the home screen (however, no widgets or
+ // shortcuts within folders).
+ static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
+
+ // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
+ static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
+ new ArrayList<LauncherAppWidgetInfo>();
+
+ // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
+ static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
+
+ // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
+ static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
+
+ // sBgWorkspaceScreens is the ordered set of workspace screens.
+ static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
+
+ // </ only access in worker thread >
+
+ private IconCache mIconCache;
+ private Bitmap mDefaultIcon;
+
+ protected int mPreviousConfigMcc;
+
+ public interface Callbacks {
+ public boolean setLoadOnResume();
+ public int getCurrentWorkspaceScreen();
+ public void startBinding();
+ public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
+ boolean forceAnimateIcons);
+ public void bindScreens(ArrayList<Long> orderedScreenIds);
+ public void bindAddScreens(ArrayList<Long> orderedScreenIds);
+ public void bindFolders(HashMap<Long,FolderInfo> folders);
+ public void finishBindingItems(boolean upgradePath);
+ public void bindAppWidget(LauncherAppWidgetInfo info);
+ public void bindAllApplications(ArrayList<AppInfo> apps);
+ public void bindAppsAdded(ArrayList<Long> newScreens,
+ ArrayList<ItemInfo> addNotAnimated,
+ ArrayList<ItemInfo> addAnimated,
+ ArrayList<AppInfo> addedApps);
+ public void bindAppsUpdated(ArrayList<AppInfo> apps);
+ public void bindComponentsRemoved(ArrayList<String> packageNames,
+ ArrayList<AppInfo> appInfos,
+ boolean matchPackageNamesOnly);
+ public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
+ public void bindSearchablesChanged();
+ public boolean isAllAppsButtonRank(int rank);
+ public void onPageBoundSynchronously(int page);
+ public void dumpLogsToLocalData();
+ }
+
+ public interface ItemInfoFilter {
+ public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
+ }
+
+ LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+ final Context context = app.getContext();
+
+ mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
+ mApp = app;
+ mBgAllAppsList = new AllAppsList(iconCache, appFilter);
+ mIconCache = iconCache;
+
+ mDefaultIcon = Utilities.createIconBitmap(
+ mIconCache.getFullResDefaultActivityIcon(), context);
+
+ final Resources res = context.getResources();
+ Configuration config = res.getConfiguration();
+ mPreviousConfigMcc = config.mcc;
+ }
+
+ /** Runs the specified runnable immediately if called from the main thread, otherwise it is
+ * posted on the main thread handler. */
+ private void runOnMainThread(Runnable r) {
+ runOnMainThread(r, 0);
+ }
+ private void runOnMainThread(Runnable r, int type) {
+ if (sWorkerThread.getThreadId() == Process.myTid()) {
+ // If we are on the worker thread, post onto the main handler
+ mHandler.post(r);
+ } else {
+ r.run();
+ }
+ }
+
+ /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
+ * posted on the worker thread handler. */
+ private static void runOnWorkerThread(Runnable r) {
+ if (sWorkerThread.getThreadId() == Process.myTid()) {
+ r.run();
+ } else {
+ // If we are not on the worker thread, then post to the worker handler
+ sWorker.post(r);
+ }
+ }
+
+ static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy,
+ long screen) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ final int xCount = (int) grid.numColumns;
+ final int yCount = (int) grid.numRows;
+ boolean[][] occupied = new boolean[xCount][yCount];
+
+ int cellX, cellY, spanX, spanY;
+ for (int i = 0; i < items.size(); ++i) {
+ final ItemInfo item = items.get(i);
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (item.screenId == screen) {
+ cellX = item.cellX;
+ cellY = item.cellY;
+ spanX = item.spanX;
+ spanY = item.spanY;
+ for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
+ for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
+ occupied[x][y] = true;
+ }
+ }
+ }
+ }
+ }
+
+ return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
+ }
+ static Pair<Long, int[]> findNextAvailableIconSpace(Context context, String name,
+ Intent launchIntent,
+ int firstScreenIndex,
+ ArrayList<Long> workspaceScreens) {
+ // Lock on the app so that we don't try and get the items while apps are being added
+ LauncherAppState app = LauncherAppState.getInstance();
+ LauncherModel model = app.getModel();
+ boolean found = false;
+ synchronized (app) {
+ if (sWorkerThread.getThreadId() != Process.myTid()) {
+ // Flush the LauncherModel worker thread, so that if we just did another
+ // processInstallShortcut, we give it time for its shortcut to get added to the
+ // database (getItemsInLocalCoordinates reads the database)
+ model.flushWorkerThread();
+ }
+ final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
+
+ // Try adding to the workspace screens incrementally, starting at the default or center
+ // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
+ firstScreenIndex = Math.min(firstScreenIndex, workspaceScreens.size());
+ int count = workspaceScreens.size();
+ for (int screen = firstScreenIndex; screen < count && !found; screen++) {
+ int[] tmpCoordinates = new int[2];
+ if (findNextAvailableIconSpaceInScreen(items, tmpCoordinates,
+ workspaceScreens.get(screen))) {
+ // Update the Launcher db
+ return new Pair<Long, int[]>(workspaceScreens.get(screen), tmpCoordinates);
+ }
+ }
+ }
+ return null;
+ }
+
+ public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
+ final ArrayList<AppInfo> allAppsApps) {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
+ }
+ public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
+ final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
+ if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
+ return;
+ }
+ // Process the newly added applications and add them to the database first
+ Runnable r = new Runnable() {
+ public void run() {
+ final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
+ final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
+
+ // Get the list of workspace screens. We need to append to this list and
+ // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
+ // called.
+ ArrayList<Long> workspaceScreens = new ArrayList<Long>();
+ TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
+ for (Integer i : orderedScreens.keySet()) {
+ long screenId = orderedScreens.get(i);
+ workspaceScreens.add(screenId);
+ }
+
+ synchronized(sBgLock) {
+ Iterator<ItemInfo> iter = workspaceApps.iterator();
+ while (iter.hasNext()) {
+ ItemInfo a = iter.next();
+ final String name = a.title.toString();
+ final Intent launchIntent = a.getIntent();
+
+ // Short-circuit this logic if the icon exists somewhere on the workspace
+ if (LauncherModel.shortcutExists(context, name, launchIntent)) {
+ continue;
+ }
+
+ // Add this icon to the db, creating a new page if necessary. If there
+ // is only the empty page then we just add items to the first page.
+ // Otherwise, we add them to the next pages.
+ int startSearchPageIndex = workspaceScreens.isEmpty() ? 0 : 1;
+ Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
+ name, launchIntent, startSearchPageIndex, workspaceScreens);
+ if (coords == null) {
+ LauncherProvider lp = LauncherAppState.getLauncherProvider();
+
+ // If we can't find a valid position, then just add a new screen.
+ // This takes time so we need to re-queue the add until the new
+ // page is added. Create as many screens as necessary to satisfy
+ // the startSearchPageIndex.
+ int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 -
+ workspaceScreens.size());
+ while (numPagesToAdd > 0) {
+ long screenId = lp.generateNewScreenId();
+ // Save the screen id for binding in the workspace
+ workspaceScreens.add(screenId);
+ addedWorkspaceScreensFinal.add(screenId);
+ numPagesToAdd--;
+ }
+
+ // Find the coordinate again
+ coords = LauncherModel.findNextAvailableIconSpace(context,
+ name, launchIntent, startSearchPageIndex, workspaceScreens);
+ }
+ if (coords == null) {
+ throw new RuntimeException("Coordinates should not be null");
+ }
+
+ ShortcutInfo shortcutInfo;
+ if (a instanceof ShortcutInfo) {
+ shortcutInfo = (ShortcutInfo) a;
+ } else if (a instanceof AppInfo) {
+ shortcutInfo = ((AppInfo) a).makeShortcut();
+ } else {
+ throw new RuntimeException("Unexpected info type");
+ }
+
+ // Add the shortcut to the db
+ addItemToDatabase(context, shortcutInfo,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ coords.first, coords.second[0], coords.second[1], false);
+ // Save the ShortcutInfo for binding in the workspace
+ addedShortcutsFinal.add(shortcutInfo);
+ }
+ }
+
+ // Update the workspace screens
+ updateWorkspaceScreenOrder(context, workspaceScreens);
+
+ if (!addedShortcutsFinal.isEmpty() || !allAppsApps.isEmpty()) {
+ runOnMainThread(new Runnable() {
+ public void run() {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ if (callbacks == cb && cb != null) {
+ final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
+ final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
+ if (!addedShortcutsFinal.isEmpty()) {
+ ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
+ long lastScreenId = info.screenId;
+ for (ItemInfo i : addedShortcutsFinal) {
+ if (i.screenId == lastScreenId) {
+ addAnimated.add(i);
+ } else {
+ addNotAnimated.add(i);
+ }
+ }
+ }
+ callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
+ addNotAnimated, addAnimated, allAppsApps);
+ }
+ }
+ });
+ }
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ public Bitmap getFallbackIcon() {
+ return Bitmap.createBitmap(mDefaultIcon);
+ }
+
+ public void unbindItemInfosAndClearQueuedBindRunnables() {
+ if (sWorkerThread.getThreadId() == Process.myTid()) {
+ throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
+ "main thread");
+ }
+
+ // Clear any deferred bind runnables
+ mDeferredBindRunnables.clear();
+ // Remove any queued bind runnables
+ mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
+ // Unbind all the workspace items
+ unbindWorkspaceItemsOnMainThread();
+ }
+
+ /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
+ void unbindWorkspaceItemsOnMainThread() {
+ // Ensure that we don't use the same workspace items data structure on the main thread
+ // by making a copy of workspace items first.
+ final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
+ final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
+ synchronized (sBgLock) {
+ tmpWorkspaceItems.addAll(sBgWorkspaceItems);
+ tmpAppWidgets.addAll(sBgAppWidgets);
+ }
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ for (ItemInfo item : tmpWorkspaceItems) {
+ item.unbind();
+ }
+ for (ItemInfo item : tmpAppWidgets) {
+ item.unbind();
+ }
+ }
+ };
+ runOnMainThread(r);
+ }
+
+ /**
+ * Adds an item to the DB if it was not created previously, or move it to a new
+ * <container, screen, cellX, cellY>
+ */
+ static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
+ long screenId, int cellX, int cellY) {
+ if (item.container == ItemInfo.NO_ID) {
+ // From all apps
+ addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
+ } else {
+ // From somewhere else
+ moveItemInDatabase(context, item, container, screenId, cellX, cellY);
+ }
+ }
+
+ static void checkItemInfoLocked(
+ final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
+ ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+ if (modelItem != null && item != modelItem) {
+ // check all the data is consistent
+ if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+ ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
+ ShortcutInfo shortcut = (ShortcutInfo) item;
+ if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
+ modelShortcut.intent.filterEquals(shortcut.intent) &&
+ modelShortcut.id == shortcut.id &&
+ modelShortcut.itemType == shortcut.itemType &&
+ modelShortcut.container == shortcut.container &&
+ modelShortcut.screenId == shortcut.screenId &&
+ modelShortcut.cellX == shortcut.cellX &&
+ modelShortcut.cellY == shortcut.cellY &&
+ modelShortcut.spanX == shortcut.spanX &&
+ modelShortcut.spanY == shortcut.spanY &&
+ ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
+ (modelShortcut.dropPos != null &&
+ shortcut.dropPos != null &&
+ modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
+ modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
+ // For all intents and purposes, this is the same object
+ return;
+ }
+ }
+
+ // the modelItem needs to match up perfectly with item if our model is
+ // to be consistent with the database-- for now, just require
+ // modelItem == item or the equality check above
+ String msg = "item: " + ((item != null) ? item.toString() : "null") +
+ "modelItem: " +
+ ((modelItem != null) ? modelItem.toString() : "null") +
+ "Error: ItemInfo passed to checkItemInfo doesn't match original";
+ RuntimeException e = new RuntimeException(msg);
+ if (stackTrace != null) {
+ e.setStackTrace(stackTrace);
+ }
+ // TODO: something breaks this in the upgrade path
+ //throw e;
+ }
+ }
+
+ static void checkItemInfo(final ItemInfo item) {
+ final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ final long itemId = item.id;
+ Runnable r = new Runnable() {
+ public void run() {
+ synchronized (sBgLock) {
+ checkItemInfoLocked(itemId, item, stackTrace);
+ }
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ static void updateItemInDatabaseHelper(Context context, final ContentValues values,
+ final ItemInfo item, final String callingFunction) {
+ final long itemId = item.id;
+ final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
+ final ContentResolver cr = context.getContentResolver();
+
+ final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ Runnable r = new Runnable() {
+ public void run() {
+ cr.update(uri, values, null, null);
+ updateItemArrays(item, itemId, stackTrace);
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
+ final ArrayList<ItemInfo> items, final String callingFunction) {
+ final ContentResolver cr = context.getContentResolver();
+
+ final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ Runnable r = new Runnable() {
+ public void run() {
+ ArrayList<ContentProviderOperation> ops =
+ new ArrayList<ContentProviderOperation>();
+ int count = items.size();
+ for (int i = 0; i < count; i++) {
+ ItemInfo item = items.get(i);
+ final long itemId = item.id;
+ final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
+ ContentValues values = valuesList.get(i);
+
+ ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
+ updateItemArrays(item, itemId, stackTrace);
+
+ }
+ try {
+ cr.applyBatch(LauncherProvider.AUTHORITY, ops);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
+ // Lock on mBgLock *after* the db operation
+ synchronized (sBgLock) {
+ checkItemInfoLocked(itemId, item, stackTrace);
+
+ if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ // Item is in a folder, make sure this folder exists
+ if (!sBgFolders.containsKey(item.container)) {
+ // An items container is being set to a that of an item which is not in
+ // the list of Folders.
+ String msg = "item: " + item + " container being set to: " +
+ item.container + ", not in the list of folders";
+ Log.e(TAG, msg);
+ }
+ }
+
+ // Items are added/removed from the corresponding FolderInfo elsewhere, such
+ // as in Workspace.onDrop. Here, we just add/remove them from the list of items
+ // that are on the desktop, as appropriate
+ ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+ if (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+ modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ switch (modelItem.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ if (!sBgWorkspaceItems.contains(modelItem)) {
+ sBgWorkspaceItems.add(modelItem);
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ sBgWorkspaceItems.remove(modelItem);
+ }
+ }
+ }
+
+ public void flushWorkerThread() {
+ mFlushingWorkerThread = true;
+ Runnable waiter = new Runnable() {
+ public void run() {
+ synchronized (this) {
+ notifyAll();
+ mFlushingWorkerThread = false;
+ }
+ }
+ };
+
+ synchronized(waiter) {
+ runOnWorkerThread(waiter);
+ if (mLoaderTask != null) {
+ synchronized(mLoaderTask) {
+ mLoaderTask.notify();
+ }
+ }
+ boolean success = false;
+ while (!success) {
+ try {
+ waiter.wait();
+ success = true;
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Move an item in the DB to a new <container, screen, cellX, cellY>
+ */
+ static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
+ final long screenId, final int cellX, final int cellY) {
+ item.container = container;
+ item.cellX = cellX;
+ item.cellY = cellY;
+
+ // We store hotseat items in canonical form which is this orientation invariant position
+ // in the hotseat
+ if (context instanceof Launcher && screenId < 0 &&
+ container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
+ } else {
+ item.screenId = screenId;
+ }
+
+ final ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites.CONTAINER, item.container);
+ values.put(LauncherSettings.Favorites.CELLX, item.cellX);
+ values.put(LauncherSettings.Favorites.CELLY, item.cellY);
+ values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+
+ updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
+ }
+
+ /**
+ * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
+ * cellX, cellY have already been updated on the ItemInfos.
+ */
+ static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
+ final long container, final int screen) {
+
+ ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
+ int count = items.size();
+
+ for (int i = 0; i < count; i++) {
+ ItemInfo item = items.get(i);
+ item.container = container;
+
+ // We store hotseat items in canonical form which is this orientation invariant position
+ // in the hotseat
+ if (context instanceof Launcher && screen < 0 &&
+ container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
+ item.cellY);
+ } else {
+ item.screenId = screen;
+ }
+
+ final ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites.CONTAINER, item.container);
+ values.put(LauncherSettings.Favorites.CELLX, item.cellX);
+ values.put(LauncherSettings.Favorites.CELLY, item.cellY);
+ values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+
+ contentValues.add(values);
+ }
+ updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
+ }
+
+ /**
+ * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
+ */
+ static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
+ final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
+ item.container = container;
+ item.cellX = cellX;
+ item.cellY = cellY;
+ item.spanX = spanX;
+ item.spanY = spanY;
+
+ // We store hotseat items in canonical form which is this orientation invariant position
+ // in the hotseat
+ if (context instanceof Launcher && screenId < 0 &&
+ container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
+ } else {
+ item.screenId = screenId;
+ }
+
+ final ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites.CONTAINER, item.container);
+ values.put(LauncherSettings.Favorites.CELLX, item.cellX);
+ values.put(LauncherSettings.Favorites.CELLY, item.cellY);
+ values.put(LauncherSettings.Favorites.SPANX, item.spanX);
+ values.put(LauncherSettings.Favorites.SPANY, item.spanY);
+ values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
+
+ updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
+ }
+
+ /**
+ * Update an item to the database in a specified container.
+ */
+ static void updateItemInDatabase(Context context, final ItemInfo item) {
+ final ContentValues values = new ContentValues();
+ item.onAddToDatabase(values);
+ item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
+ updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
+ }
+
+ /**
+ * Returns true if the shortcuts already exists in the database.
+ * we identify a shortcut by its title and intent.
+ */
+ static boolean shortcutExists(Context context, String title, Intent intent) {
+ final ContentResolver cr = context.getContentResolver();
+ Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[] { "title", "intent" }, "title=? and intent=?",
+ new String[] { title, intent.toUri(0) }, null);
+ boolean result = false;
+ try {
+ result = c.moveToFirst();
+ } finally {
+ c.close();
+ }
+ return result;
+ }
+
+ /**
+ * Returns an ItemInfo array containing all the items in the LauncherModel.
+ * The ItemInfo.id is not set through this function.
+ */
+ static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
+ ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+ final ContentResolver cr = context.getContentResolver();
+ Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
+ LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
+ LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
+ LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
+
+ final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+ final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+ final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+ final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+ final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+ final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
+ final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
+
+ try {
+ while (c.moveToNext()) {
+ ItemInfo item = new ItemInfo();
+ item.cellX = c.getInt(cellXIndex);
+ item.cellY = c.getInt(cellYIndex);
+ item.spanX = Math.max(1, c.getInt(spanXIndex));
+ item.spanY = Math.max(1, c.getInt(spanYIndex));
+ item.container = c.getInt(containerIndex);
+ item.itemType = c.getInt(itemTypeIndex);
+ item.screenId = c.getInt(screenIndex);
+
+ items.add(item);
+ }
+ } catch (Exception e) {
+ items.clear();
+ } finally {
+ c.close();
+ }
+
+ return items;
+ }
+
+ /**
+ * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
+ */
+ FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
+ final ContentResolver cr = context.getContentResolver();
+ Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
+ "_id=? and (itemType=? or itemType=?)",
+ new String[] { String.valueOf(id),
+ String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
+
+ try {
+ if (c.moveToFirst()) {
+ final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+ final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
+ final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+ final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+ final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+ final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+
+ FolderInfo folderInfo = null;
+ switch (c.getInt(itemTypeIndex)) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ folderInfo = findOrMakeFolder(folderList, id);
+ break;
+ }
+
+ folderInfo.title = c.getString(titleIndex);
+ folderInfo.id = id;
+ folderInfo.container = c.getInt(containerIndex);
+ folderInfo.screenId = c.getInt(screenIndex);
+ folderInfo.cellX = c.getInt(cellXIndex);
+ folderInfo.cellY = c.getInt(cellYIndex);
+
+ return folderInfo;
+ }
+ } finally {
+ c.close();
+ }
+
+ return null;
+ }
+
+ /**
+ * Add an item to the database in a specified container. Sets the container, screen, cellX and
+ * cellY fields of the item. Also assigns an ID to the item.
+ */
+ static void addItemToDatabase(Context context, final ItemInfo item, final long container,
+ final long screenId, final int cellX, final int cellY, final boolean notify) {
+ item.container = container;
+ item.cellX = cellX;
+ item.cellY = cellY;
+ // We store hotseat items in canonical form which is this orientation invariant position
+ // in the hotseat
+ if (context instanceof Launcher && screenId < 0 &&
+ container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
+ } else {
+ item.screenId = screenId;
+ }
+
+ final ContentValues values = new ContentValues();
+ final ContentResolver cr = context.getContentResolver();
+ item.onAddToDatabase(values);
+
+ item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
+ values.put(LauncherSettings.Favorites._ID, item.id);
+ item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
+
+ Runnable r = new Runnable() {
+ public void run() {
+ cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
+ LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
+
+ // Lock on mBgLock *after* the db operation
+ synchronized (sBgLock) {
+ checkItemInfoLocked(item.id, item, null);
+ sBgItemsIdMap.put(item.id, item);
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ sBgFolders.put(item.id, (FolderInfo) item);
+ // Fall through
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+ item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ sBgWorkspaceItems.add(item);
+ } else {
+ if (!sBgFolders.containsKey(item.container)) {
+ // Adding an item to a folder that doesn't exist.
+ String msg = "adding item: " + item + " to a folder that " +
+ " doesn't exist";
+ Log.e(TAG, msg);
+ }
+ }
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ sBgAppWidgets.add((LauncherAppWidgetInfo) item);
+ break;
+ }
+ }
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ /**
+ * Creates a new unique child id, for a given cell span across all layouts.
+ */
+ static int getCellLayoutChildId(
+ long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
+ return (((int) container & 0xFF) << 24)
+ | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
+ }
+
+ /**
+ * Removes the specified item from the database
+ * @param context
+ * @param item
+ */
+ static void deleteItemFromDatabase(Context context, final ItemInfo item) {
+ final ContentResolver cr = context.getContentResolver();
+ final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
+
+ Runnable r = new Runnable() {
+ public void run() {
+ cr.delete(uriToDelete, null, null);
+
+ // Lock on mBgLock *after* the db operation
+ synchronized (sBgLock) {
+ switch (item.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ sBgFolders.remove(item.id);
+ for (ItemInfo info: sBgItemsIdMap.values()) {
+ if (info.container == item.id) {
+ // We are deleting a folder which still contains items that
+ // think they are contained by that folder.
+ String msg = "deleting a folder (" + item + ") which still " +
+ "contains items (" + info + ")";
+ Log.e(TAG, msg);
+ }
+ }
+ sBgWorkspaceItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ sBgWorkspaceItems.remove(item);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
+ break;
+ }
+ sBgItemsIdMap.remove(item.id);
+ sBgDbIconCache.remove(item);
+ }
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ /**
+ * Update the order of the workspace screens in the database. The array list contains
+ * a list of screen ids in the order that they should appear.
+ */
+ void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+ final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
+ final ContentResolver cr = context.getContentResolver();
+ final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+
+ // Remove any negative screen ids -- these aren't persisted
+ Iterator<Long> iter = screensCopy.iterator();
+ while (iter.hasNext()) {
+ long id = iter.next();
+ if (id < 0) {
+ iter.remove();
+ }
+ }
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ // Clear the table
+ cr.delete(uri, null, null);
+ int count = screensCopy.size();
+ ContentValues[] values = new ContentValues[count];
+ for (int i = 0; i < count; i++) {
+ ContentValues v = new ContentValues();
+ long screenId = screensCopy.get(i);
+ v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+ v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ values[i] = v;
+ }
+ cr.bulkInsert(uri, values);
+
+ synchronized (sBgLock) {
+ sBgWorkspaceScreens.clear();
+ sBgWorkspaceScreens.addAll(screensCopy);
+ }
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ /**
+ * Remove the contents of the specified folder from the database
+ */
+ static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
+ final ContentResolver cr = context.getContentResolver();
+
+ Runnable r = new Runnable() {
+ public void run() {
+ cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
+ // Lock on mBgLock *after* the db operation
+ synchronized (sBgLock) {
+ sBgItemsIdMap.remove(info.id);
+ sBgFolders.remove(info.id);
+ sBgDbIconCache.remove(info);
+ sBgWorkspaceItems.remove(info);
+ }
+
+ cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
+ LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
+ // Lock on mBgLock *after* the db operation
+ synchronized (sBgLock) {
+ for (ItemInfo childInfo : info.contents) {
+ sBgItemsIdMap.remove(childInfo.id);
+ sBgDbIconCache.remove(childInfo);
+ }
+ }
+ }
+ };
+ runOnWorkerThread(r);
+ }
+
+ /**
+ * Set this as the current Launcher activity object for the loader.
+ */
+ public void initialize(Callbacks callbacks) {
+ synchronized (mLock) {
+ mCallbacks = new WeakReference<Callbacks>(callbacks);
+ }
+ }
+
+ /**
+ * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
+ * ACTION_PACKAGE_CHANGED.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
+
+ final String action = intent.getAction();
+
+ if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
+ || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ final String packageName = intent.getData().getSchemeSpecificPart();
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+
+ int op = PackageUpdatedTask.OP_NONE;
+
+ if (packageName == null || packageName.length() == 0) {
+ // they sent us a bad intent
+ return;
+ }
+
+ if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ op = PackageUpdatedTask.OP_UPDATE;
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ if (!replacing) {
+ op = PackageUpdatedTask.OP_REMOVE;
+ }
+ // else, we are replacing the package, so a PACKAGE_ADDED will be sent
+ // later, we will update the package at this time
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ if (!replacing) {
+ op = PackageUpdatedTask.OP_ADD;
+ } else {
+ op = PackageUpdatedTask.OP_UPDATE;
+ }
+ }
+
+ if (op != PackageUpdatedTask.OP_NONE) {
+ enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
+ }
+
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ // First, schedule to add these apps back in.
+ String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
+ // Then, rebind everything.
+ startLoaderFromBackground();
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ enqueuePackageUpdated(new PackageUpdatedTask(
+ PackageUpdatedTask.OP_UNAVAILABLE, packages));
+ } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ // If we have changed locale we need to clear out the labels in all apps/workspace.
+ forceReload();
+ } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+ // Check if configuration change was an mcc/mnc change which would affect app resources
+ // and we would need to clear out the labels in all apps/workspace. Same handling as
+ // above for ACTION_LOCALE_CHANGED
+ Configuration currentConfig = context.getResources().getConfiguration();
+ if (mPreviousConfigMcc != currentConfig.mcc) {
+ Log.d(TAG, "Reload apps on config change. curr_mcc:"
+ + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
+ forceReload();
+ }
+ // Update previousConfig
+ mPreviousConfigMcc = currentConfig.mcc;
+ } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
+ SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
+ if (mCallbacks != null) {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ callbacks.bindSearchablesChanged();
+ }
+ }
+ }
+ }
+
+ private void forceReload() {
+ resetLoadedState(true, true);
+
+ // Do this here because if the launcher activity is running it will be restarted.
+ // If it's not running startLoaderFromBackground will merely tell it that it needs
+ // to reload.
+ startLoaderFromBackground();
+ }
+
+ public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
+ synchronized (mLock) {
+ // Stop any existing loaders first, so they don't set mAllAppsLoaded or
+ // mWorkspaceLoaded to true later
+ stopLoaderLocked();
+ if (resetAllAppsLoaded) mAllAppsLoaded = false;
+ if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
+ }
+ }
+
+ /**
+ * When the launcher is in the background, it's possible for it to miss paired
+ * configuration changes. So whenever we trigger the loader from the background
+ * tell the launcher that it needs to re-run the loader when it comes back instead
+ * of doing it now.
+ */
+ public void startLoaderFromBackground() {
+ boolean runLoader = false;
+ if (mCallbacks != null) {
+ Callbacks callbacks = mCallbacks.get();
+ if (callbacks != null) {
+ // Only actually run the loader if they're not paused.
+ if (!callbacks.setLoadOnResume()) {
+ runLoader = true;
+ }
+ }
+ }
+ if (runLoader) {
+ startLoader(false, -1);
+ }
+ }
+
+ // If there is already a loader task running, tell it to stop.
+ // returns true if isLaunching() was true on the old task
+ private boolean stopLoaderLocked() {
+ boolean isLaunching = false;
+ LoaderTask oldTask = mLoaderTask;
+ if (oldTask != null) {
+ if (oldTask.isLaunching()) {
+ isLaunching = true;
+ }
+ oldTask.stopLocked();
+ }
+ return isLaunching;
+ }
+
+ public void startLoader(boolean isLaunching, int synchronousBindPage) {
+ synchronized (mLock) {
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "startLoader isLaunching=" + isLaunching);
+ }
+
+ // Clear any deferred bind-runnables from the synchronized load process
+ // We must do this before any loading/binding is scheduled below.
+ mDeferredBindRunnables.clear();
+
+ // Don't bother to start the thread if we know it's not going to do anything
+ if (mCallbacks != null && mCallbacks.get() != null) {
+ // If there is already one running, tell it to stop.
+ // also, don't downgrade isLaunching if we're already running
+ isLaunching = isLaunching || stopLoaderLocked();
+ mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching);
+ if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
+ mLoaderTask.runBindSynchronousPage(synchronousBindPage);
+ } else {
+ sWorkerThread.setPriority(Thread.NORM_PRIORITY);
+ sWorker.post(mLoaderTask);
+ }
+ }
+ }
+ }
+
+ void bindRemainingSynchronousPages() {
+ // Post the remaining side pages to be loaded
+ if (!mDeferredBindRunnables.isEmpty()) {
+ for (final Runnable r : mDeferredBindRunnables) {
+ mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
+ }
+ mDeferredBindRunnables.clear();
+ }
+ }
+
+ public void stopLoader() {
+ synchronized (mLock) {
+ if (mLoaderTask != null) {
+ mLoaderTask.stopLocked();
+ }
+ }
+ }
+
+ /** Loads the workspace screens db into a map of Rank -> ScreenId */
+ private static TreeMap<Integer, Long> loadWorkspaceScreensDb(Context context) {
+ final ContentResolver contentResolver = context.getContentResolver();
+ final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+ final Cursor sc = contentResolver.query(screensUri, null, null, null, null);
+ TreeMap<Integer, Long> orderedScreens = new TreeMap<Integer, Long>();
+
+ try {
+ final int idIndex = sc.getColumnIndexOrThrow(
+ LauncherSettings.WorkspaceScreens._ID);
+ final int rankIndex = sc.getColumnIndexOrThrow(
+ LauncherSettings.WorkspaceScreens.SCREEN_RANK);
+ while (sc.moveToNext()) {
+ try {
+ long screenId = sc.getLong(idIndex);
+ int rank = sc.getInt(rankIndex);
+ orderedScreens.put(rank, screenId);
+ } catch (Exception e) {
+ Launcher.addDumpLog(TAG, "Desktop items loading interrupted - invalid screens: " + e, true);
+ }
+ }
+ } finally {
+ sc.close();
+ }
+ return orderedScreens;
+ }
+
+ public boolean isAllAppsLoaded() {
+ return mAllAppsLoaded;
+ }
+
+ boolean isLoadingWorkspace() {
+ synchronized (mLock) {
+ if (mLoaderTask != null) {
+ return mLoaderTask.isLoadingWorkspace();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Runnable for the thread that loads the contents of the launcher:
+ * - workspace icons
+ * - widgets
+ * - all apps icons
+ */
+ private class LoaderTask implements Runnable {
+ private Context mContext;
+ private boolean mIsLaunching;
+ private boolean mIsLoadingAndBindingWorkspace;
+ private boolean mStopped;
+ private boolean mLoadAndBindStepFinished;
+
+ private HashMap<Object, CharSequence> mLabelCache;
+
+ LoaderTask(Context context, boolean isLaunching) {
+ mContext = context;
+ mIsLaunching = isLaunching;
+ mLabelCache = new HashMap<Object, CharSequence>();
+ }
+
+ boolean isLaunching() {
+ return mIsLaunching;
+ }
+
+ boolean isLoadingWorkspace() {
+ return mIsLoadingAndBindingWorkspace;
+ }
+
+ /** Returns whether this is an upgrade path */
+ private boolean loadAndBindWorkspace() {
+ mIsLoadingAndBindingWorkspace = true;
+
+ // Load the workspace
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
+ }
+
+ boolean isUpgradePath = false;
+ if (!mWorkspaceLoaded) {
+ isUpgradePath = loadWorkspace();
+ synchronized (LoaderTask.this) {
+ if (mStopped) {
+ return isUpgradePath;
+ }
+ mWorkspaceLoaded = true;
+ }
+ }
+
+ // Bind the workspace
+ bindWorkspace(-1, isUpgradePath);
+ return isUpgradePath;
+ }
+
+ private void waitForIdle() {
+ // Wait until the either we're stopped or the other threads are done.
+ // This way we don't start loading all apps until the workspace has settled
+ // down.
+ synchronized (LoaderTask.this) {
+ final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+ mHandler.postIdle(new Runnable() {
+ public void run() {
+ synchronized (LoaderTask.this) {
+ mLoadAndBindStepFinished = true;
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "done with previous binding step");
+ }
+ LoaderTask.this.notify();
+ }
+ }
+ });
+
+ while (!mStopped && !mLoadAndBindStepFinished && !mFlushingWorkerThread) {
+ try {
+ // Just in case mFlushingWorkerThread changes but we aren't woken up,
+ // wait no longer than 1sec at a time
+ this.wait(1000);
+ } catch (InterruptedException ex) {
+ // Ignore
+ }
+ }
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "waited "
+ + (SystemClock.uptimeMillis()-workspaceWaitTime)
+ + "ms for previous step to finish binding");
+ }
+ }
+ }
+
+ void runBindSynchronousPage(int synchronousBindPage) {
+ if (synchronousBindPage < 0) {
+ // Ensure that we have a valid page index to load synchronously
+ throw new RuntimeException("Should not call runBindSynchronousPage() without " +
+ "valid page index");
+ }
+ if (!mAllAppsLoaded || !mWorkspaceLoaded) {
+ // Ensure that we don't try and bind a specified page when the pages have not been
+ // loaded already (we should load everything asynchronously in that case)
+ throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
+ }
+ synchronized (mLock) {
+ if (mIsLoaderTaskRunning) {
+ // Ensure that we are never running the background loading at this point since
+ // we also touch the background collections
+ throw new RuntimeException("Error! Background loading is already running");
+ }
+ }
+
+ // XXX: Throw an exception if we are already loading (since we touch the worker thread
+ // data structures, we can't allow any other thread to touch that data, but because
+ // this call is synchronous, we can get away with not locking).
+
+ // The LauncherModel is static in the LauncherAppState and mHandler may have queued
+ // operations from the previous activity. We need to ensure that all queued operations
+ // are executed before any synchronous binding work is done.
+ mHandler.flush();
+
+ // Divide the set of loaded items into those that we are binding synchronously, and
+ // everything else that is to be bound normally (asynchronously).
+ bindWorkspace(synchronousBindPage, false);
+ // XXX: For now, continue posting the binding of AllApps as there are other issues that
+ // arise from that.
+ onlyBindAllApps();
+ }
+
+ public void run() {
+ boolean isUpgrade = false;
+
+ synchronized (mLock) {
+ mIsLoaderTaskRunning = true;
+ }
+ // Optimize for end-user experience: if the Launcher is up and // running with the
+ // All Apps interface in the foreground, load All Apps first. Otherwise, load the
+ // workspace first (default).
+ keep_running: {
+ // Elevate priority when Home launches for the first time to avoid
+ // starving at boot time. Staring at a blank home is not cool.
+ synchronized (mLock) {
+ if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
+ (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
+ android.os.Process.setThreadPriority(mIsLaunching
+ ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
+ }
+ if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
+ isUpgrade = loadAndBindWorkspace();
+
+ if (mStopped) {
+ break keep_running;
+ }
+
+ // Whew! Hard work done. Slow us down, and wait until the UI thread has
+ // settled down.
+ synchronized (mLock) {
+ if (mIsLaunching) {
+ if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
+ android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ }
+ }
+ waitForIdle();
+
+ // second step
+ if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
+ loadAndBindAllApps();
+
+ // Restore the default thread priority after we are done loading items
+ synchronized (mLock) {
+ android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+ }
+ }
+
+ // Update the saved icons if necessary
+ if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
+ synchronized (sBgLock) {
+ for (Object key : sBgDbIconCache.keySet()) {
+ updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
+ }
+ sBgDbIconCache.clear();
+ }
+
+ if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ // Ensure that all the applications that are in the system are
+ // represented on the home screen.
+ if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
+ verifyApplications();
+ }
+ }
+
+ // Clear out this reference, otherwise we end up holding it until all of the
+ // callback runnables are done.
+ mContext = null;
+
+ synchronized (mLock) {
+ // If we are still the last one to be scheduled, remove ourselves.
+ if (mLoaderTask == this) {
+ mLoaderTask = null;
+ }
+ mIsLoaderTaskRunning = false;
+ }
+ }
+
+ public void stopLocked() {
+ synchronized (LoaderTask.this) {
+ mStopped = true;
+ this.notify();
+ }
+ }
+
+ /**
+ * Gets the callbacks object. If we've been stopped, or if the launcher object
+ * has somehow been garbage collected, return null instead. Pass in the Callbacks
+ * object that was around when the deferred message was scheduled, and if there's
+ * a new Callbacks object around then also return null. This will save us from
+ * calling onto it with data that will be ignored.
+ */
+ Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
+ synchronized (mLock) {
+ if (mStopped) {
+ return null;
+ }
+
+ if (mCallbacks == null) {
+ return null;
+ }
+
+ final Callbacks callbacks = mCallbacks.get();
+ if (callbacks != oldCallbacks) {
+ return null;
+ }
+ if (callbacks == null) {
+ Log.w(TAG, "no mCallbacks");
+ return null;
+ }
+
+ return callbacks;
+ }
+ }
+
+ private void verifyApplications() {
+ final Context context = mApp.getContext();
+
+ // Cross reference all the applications in our apps list with items in the workspace
+ ArrayList<ItemInfo> tmpInfos;
+ ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
+ synchronized (sBgLock) {
+ for (AppInfo app : mBgAllAppsList.data) {
+ tmpInfos = getItemInfoForComponentName(app.componentName);
+ if (tmpInfos.isEmpty()) {
+ // We are missing an application icon, so add this to the workspace
+ added.add(app);
+ // This is a rare event, so lets log it
+ Log.e(TAG, "Missing Application on load: " + app);
+ }
+ }
+ }
+ if (!added.isEmpty()) {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ addAndBindAddedApps(context, added, cb, null);
+ }
+ }
+
+ private boolean checkItemDimensions(ItemInfo info) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ return (info.cellX + info.spanX) > (int) grid.numColumns ||
+ (info.cellY + info.spanY) > (int) grid.numRows;
+ }
+
+ // check & update map of what's occupied; used to discard overlapping/invalid items
+ private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
+ AtomicBoolean deleteOnItemOverlap) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ int countX = (int) grid.numColumns;
+ int countY = (int) grid.numRows;
+
+ long containerIndex = item.screenId;
+ if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ // Return early if we detect that an item is under the hotseat button
+ if (mCallbacks == null ||
+ mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
+ deleteOnItemOverlap.set(true);
+ return false;
+ }
+
+ if (occupied.containsKey(LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
+ if (occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+ [(int) item.screenId][0] != null) {
+ Log.e(TAG, "Error loading shortcut into hotseat " + item
+ + " into position (" + item.screenId + ":" + item.cellX + ","
+ + item.cellY + ") occupied by "
+ + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+ [(int) item.screenId][0]);
+ return false;
+ }
+ } else {
+ ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
+ items[(int) item.screenId][0] = item;
+ occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
+ return true;
+ }
+ } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ // Skip further checking if it is not the hotseat or workspace container
+ return true;
+ }
+
+ if (!occupied.containsKey(item.screenId)) {
+ ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
+ occupied.put(item.screenId, items);
+ }
+
+ ItemInfo[][] screens = occupied.get(item.screenId);
+ // Check if any workspace icons overlap with each other
+ for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
+ for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
+ if (screens[x][y] != null) {
+ Log.e(TAG, "Error loading shortcut " + item
+ + " into cell (" + containerIndex + "-" + item.screenId + ":"
+ + x + "," + y
+ + ") occupied by "
+ + screens[x][y]);
+ return false;
+ }
+ }
+ }
+ for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
+ for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
+ screens[x][y] = item;
+ }
+ }
+
+ return true;
+ }
+
+ /** Clears all the sBg data structures */
+ private void clearSBgDataStructures() {
+ synchronized (sBgLock) {
+ sBgWorkspaceItems.clear();
+ sBgAppWidgets.clear();
+ sBgFolders.clear();
+ sBgItemsIdMap.clear();
+ sBgDbIconCache.clear();
+ sBgWorkspaceScreens.clear();
+ }
+ }
+
+ /** Returns whether this is an upgradge path */
+ private boolean loadWorkspace() {
+ final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+ final Context context = mContext;
+ final ContentResolver contentResolver = context.getContentResolver();
+ final PackageManager manager = context.getPackageManager();
+ final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
+ final boolean isSafeMode = manager.isSafeMode();
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ int countX = (int) grid.numColumns;
+ int countY = (int) grid.numRows;
+
+ // Make sure the default workspace is loaded, if needed
+ LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
+
+ // Check if we need to do any upgrade-path logic
+ boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
+
+ synchronized (sBgLock) {
+ clearSBgDataStructures();
+
+ final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
+ final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
+ if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
+ final Cursor c = contentResolver.query(contentUri, null, null, null, null);
+
+ // +1 for the hotseat (it can be larger than the workspace)
+ // Load workspace in reverse order to ensure that latest items are loaded first (and
+ // before any earlier duplicates)
+ final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
+
+ try {
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+ final int intentIndex = c.getColumnIndexOrThrow
+ (LauncherSettings.Favorites.INTENT);
+ final int titleIndex = c.getColumnIndexOrThrow
+ (LauncherSettings.Favorites.TITLE);
+ final int iconTypeIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.ICON_TYPE);
+ final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+ final int iconPackageIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.ICON_PACKAGE);
+ final int iconResourceIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.ICON_RESOURCE);
+ final int containerIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.CONTAINER);
+ final int itemTypeIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.ITEM_TYPE);
+ final int appWidgetIdIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.APPWIDGET_ID);
+ final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.APPWIDGET_PROVIDER);
+ final int screenIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.SCREEN);
+ final int cellXIndex = c.getColumnIndexOrThrow
+ (LauncherSettings.Favorites.CELLX);
+ final int cellYIndex = c.getColumnIndexOrThrow
+ (LauncherSettings.Favorites.CELLY);
+ final int spanXIndex = c.getColumnIndexOrThrow
+ (LauncherSettings.Favorites.SPANX);
+ final int spanYIndex = c.getColumnIndexOrThrow(
+ LauncherSettings.Favorites.SPANY);
+ //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
+ //final int displayModeIndex = c.getColumnIndexOrThrow(
+ // LauncherSettings.Favorites.DISPLAY_MODE);
+
+ ShortcutInfo info;
+ String intentDescription;
+ LauncherAppWidgetInfo appWidgetInfo;
+ int container;
+ long id;
+ Intent intent;
+
+ while (!mStopped && c.moveToNext()) {
+ AtomicBoolean deleteOnItemOverlap = new AtomicBoolean(false);
+ try {
+ int itemType = c.getInt(itemTypeIndex);
+
+ switch (itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ id = c.getLong(idIndex);
+ intentDescription = c.getString(intentIndex);
+ try {
+ intent = Intent.parseUri(intentDescription, 0);
+ ComponentName cn = intent.getComponent();
+ if (cn != null && !isValidPackageComponent(manager, cn)) {
+ if (!mAppsCanBeOnRemoveableStorage) {
+ // Log the invalid package, and remove it from the db
+ Launcher.addDumpLog(TAG, "Invalid package removed: " + cn, true);
+ itemsToRemove.add(id);
+ } else {
+ // If apps can be on external storage, then we just
+ // leave them for the user to remove (maybe add
+ // visual treatment to it)
+ Launcher.addDumpLog(TAG, "Invalid package found: " + cn, true);
+ }
+ continue;
+ }
+ } catch (URISyntaxException e) {
+ Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, true);
+ continue;
+ }
+
+ if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ info = getShortcutInfo(manager, intent, context, c, iconIndex,
+ titleIndex, mLabelCache);
+ } else {
+ info = getShortcutInfo(c, context, iconTypeIndex,
+ iconPackageIndex, iconResourceIndex, iconIndex,
+ titleIndex);
+
+ // App shortcuts that used to be automatically added to Launcher
+ // didn't always have the correct intent flags set, so do that
+ // here
+ if (intent.getAction() != null &&
+ intent.getCategories() != null &&
+ intent.getAction().equals(Intent.ACTION_MAIN) &&
+ intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ }
+ }
+
+ if (info != null) {
+ info.id = id;
+ info.intent = intent;
+ container = c.getInt(containerIndex);
+ info.container = container;
+ info.screenId = c.getInt(screenIndex);
+ info.cellX = c.getInt(cellXIndex);
+ info.cellY = c.getInt(cellYIndex);
+ info.spanX = 1;
+ info.spanY = 1;
+ // Skip loading items that are out of bounds
+ if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (checkItemDimensions(info)) {
+ Launcher.addDumpLog(TAG, "Skipped loading out of bounds shortcut: "
+ + info + ", " + grid.numColumns + "x" + grid.numRows, true);
+ continue;
+ }
+ }
+ // check & update map of what's occupied
+ deleteOnItemOverlap.set(false);
+ if (!checkItemPlacement(occupied, info, deleteOnItemOverlap)) {
+ if (deleteOnItemOverlap.get()) {
+ itemsToRemove.add(id);
+ }
+ break;
+ }
+
+ switch (container) {
+ case LauncherSettings.Favorites.CONTAINER_DESKTOP:
+ case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
+ sBgWorkspaceItems.add(info);
+ break;
+ default:
+ // Item is in a user folder
+ FolderInfo folderInfo =
+ findOrMakeFolder(sBgFolders, container);
+ folderInfo.add(info);
+ break;
+ }
+ sBgItemsIdMap.put(info.id, info);
+
+ // now that we've loaded everthing re-save it with the
+ // icon in case it disappears somehow.
+ queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
+ } else {
+ throw new RuntimeException("Unexpected null ShortcutInfo");
+ }
+ break;
+
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ id = c.getLong(idIndex);
+ FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+
+ folderInfo.title = c.getString(titleIndex);
+ folderInfo.id = id;
+ container = c.getInt(containerIndex);
+ folderInfo.container = container;
+ folderInfo.screenId = c.getInt(screenIndex);
+ folderInfo.cellX = c.getInt(cellXIndex);
+ folderInfo.cellY = c.getInt(cellYIndex);
+ folderInfo.spanX = 1;
+ folderInfo.spanY = 1;
+
+ // Skip loading items that are out of bounds
+ if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (checkItemDimensions(folderInfo)) {
+ Log.d(TAG, "Skipped loading out of bounds folder");
+ continue;
+ }
+ }
+ // check & update map of what's occupied
+ deleteOnItemOverlap.set(false);
+ if (!checkItemPlacement(occupied, folderInfo,
+ deleteOnItemOverlap)) {
+ if (deleteOnItemOverlap.get()) {
+ itemsToRemove.add(id);
+ }
+ break;
+ }
+
+ switch (container) {
+ case LauncherSettings.Favorites.CONTAINER_DESKTOP:
+ case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
+ sBgWorkspaceItems.add(folderInfo);
+ break;
+ }
+
+ sBgItemsIdMap.put(folderInfo.id, folderInfo);
+ sBgFolders.put(folderInfo.id, folderInfo);
+ break;
+
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ // Read all Launcher-specific widget details
+ int appWidgetId = c.getInt(appWidgetIdIndex);
+ String savedProvider = c.getString(appWidgetProviderIndex);
+
+ id = c.getLong(idIndex);
+
+ final AppWidgetProviderInfo provider =
+ widgets.getAppWidgetInfo(appWidgetId);
+
+ if (!isSafeMode && (provider == null || provider.provider == null ||
+ provider.provider.getPackageName() == null)) {
+ String log = "Deleting widget that isn't installed anymore: id="
+ + id + " appWidgetId=" + appWidgetId;
+ Log.e(TAG, log);
+ Launcher.addDumpLog(TAG, log, false);
+ itemsToRemove.add(id);
+ } else {
+ appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
+ provider.provider);
+ appWidgetInfo.id = id;
+ appWidgetInfo.screenId = c.getInt(screenIndex);
+ appWidgetInfo.cellX = c.getInt(cellXIndex);
+ appWidgetInfo.cellY = c.getInt(cellYIndex);
+ appWidgetInfo.spanX = c.getInt(spanXIndex);
+ appWidgetInfo.spanY = c.getInt(spanYIndex);
+ int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
+ appWidgetInfo.minSpanX = minSpan[0];
+ appWidgetInfo.minSpanY = minSpan[1];
+
+ container = c.getInt(containerIndex);
+ if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ Log.e(TAG, "Widget found where container != " +
+ "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+ continue;
+ }
+
+ appWidgetInfo.container = c.getInt(containerIndex);
+ // Skip loading items that are out of bounds
+ if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (checkItemDimensions(appWidgetInfo)) {
+ Log.d(TAG, "Skipped loading out of bounds app widget");
+ continue;
+ }
+ }
+ // check & update map of what's occupied
+ deleteOnItemOverlap.set(false);
+ if (!checkItemPlacement(occupied, appWidgetInfo,
+ deleteOnItemOverlap)) {
+ if (deleteOnItemOverlap.get()) {
+ itemsToRemove.add(id);
+ }
+ break;
+ }
+ String providerName = provider.provider.flattenToString();
+ if (!providerName.equals(savedProvider)) {
+ ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
+ providerName);
+ String where = BaseColumns._ID + "= ?";
+ String[] args = {Integer.toString(c.getInt(idIndex))};
+ contentResolver.update(contentUri, values, where, args);
+ }
+ sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
+ sBgAppWidgets.add(appWidgetInfo);
+ }
+ break;
+ }
+ } catch (Exception e) {
+ Launcher.addDumpLog(TAG, "Desktop items loading interrupted: " + e, true);
+ }
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // Break early if we've stopped loading
+ if (mStopped) {
+ clearSBgDataStructures();
+ return false;
+ }
+
+ if (itemsToRemove.size() > 0) {
+ ContentProviderClient client = contentResolver.acquireContentProviderClient(
+ LauncherSettings.Favorites.CONTENT_URI);
+ // Remove dead items
+ for (long id : itemsToRemove) {
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "Removed id = " + id);
+ }
+ // Don't notify content observers
+ try {
+ client.delete(LauncherSettings.Favorites.getContentUri(id, false),
+ null, null);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not remove id = " + id);
+ }
+ }
+ }
+
+ if (loadedOldDb) {
+ long maxScreenId = 0;
+ // If we're importing we use the old screen order.
+ for (ItemInfo item: sBgItemsIdMap.values()) {
+ long screenId = item.screenId;
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ !sBgWorkspaceScreens.contains(screenId)) {
+ sBgWorkspaceScreens.add(screenId);
+ if (screenId > maxScreenId) {
+ maxScreenId = screenId;
+ }
+ }
+ }
+ Collections.sort(sBgWorkspaceScreens);
+
+ LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
+ updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+
+ // Update the max item id after we load an old db
+ long maxItemId = 0;
+ // If we're importing we use the old screen order.
+ for (ItemInfo item: sBgItemsIdMap.values()) {
+ maxItemId = Math.max(maxItemId, item.id);
+ }
+ LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
+ } else {
+ TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
+ for (Integer i : orderedScreens.keySet()) {
+ sBgWorkspaceScreens.add(orderedScreens.get(i));
+ }
+
+ // Remove any empty screens
+ ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
+ for (ItemInfo item: sBgItemsIdMap.values()) {
+ long screenId = item.screenId;
+ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ unusedScreens.contains(screenId)) {
+ unusedScreens.remove(screenId);
+ }
+ }
+
+ // If there are any empty screens remove them, and update.
+ if (unusedScreens.size() != 0) {
+ sBgWorkspaceScreens.removeAll(unusedScreens);
+ updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+ }
+ }
+
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
+ Log.d(TAG, "workspace layout: ");
+ int nScreens = occupied.size();
+ for (int y = 0; y < countY; y++) {
+ String line = "";
+
+ Iterator<Long> iter = occupied.keySet().iterator();
+ while (iter.hasNext()) {
+ long screenId = iter.next();
+ if (screenId > 0) {
+ line += " | ";
+ }
+ for (int x = 0; x < countX; x++) {
+ line += ((occupied.get(screenId)[x][y] != null) ? "#" : ".");
+ }
+ }
+ Log.d(TAG, "[ " + line + " ]");
+ }
+ }
+ }
+ return loadedOldDb;
+ }
+
+ /** Filters the set of items who are directly or indirectly (via another container) on the
+ * specified screen. */
+ private void filterCurrentWorkspaceItems(int currentScreen,
+ ArrayList<ItemInfo> allWorkspaceItems,
+ ArrayList<ItemInfo> currentScreenItems,
+ ArrayList<ItemInfo> otherScreenItems) {
+ // Purge any null ItemInfos
+ Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
+ while (iter.hasNext()) {
+ ItemInfo i = iter.next();
+ if (i == null) {
+ iter.remove();
+ }
+ }
+
+ // If we aren't filtering on a screen, then the set of items to load is the full set of
+ // items given.
+ if (currentScreen < 0) {
+ currentScreenItems.addAll(allWorkspaceItems);
+ }
+
+ // Order the set of items by their containers first, this allows use to walk through the
+ // list sequentially, build up a list of containers that are in the specified screen,
+ // as well as all items in those containers.
+ Set<Long> itemsOnScreen = new HashSet<Long>();
+ Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
+ @Override
+ public int compare(ItemInfo lhs, ItemInfo rhs) {
+ return (int) (lhs.container - rhs.container);
+ }
+ });
+ for (ItemInfo info : allWorkspaceItems) {
+ if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (info.screenId == currentScreen) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ otherScreenItems.add(info);
+ }
+ } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ if (itemsOnScreen.contains(info.container)) {
+ currentScreenItems.add(info);
+ itemsOnScreen.add(info.id);
+ } else {
+ otherScreenItems.add(info);
+ }
+ }
+ }
+ }
+
+ /** Filters the set of widgets which are on the specified screen. */
+ private void filterCurrentAppWidgets(int currentScreen,
+ ArrayList<LauncherAppWidgetInfo> appWidgets,
+ ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
+ ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
+ // If we aren't filtering on a screen, then the set of items to load is the full set of
+ // widgets given.
+ if (currentScreen < 0) {
+ currentScreenWidgets.addAll(appWidgets);
+ }
+
+ for (LauncherAppWidgetInfo widget : appWidgets) {
+ if (widget == null) continue;
+ if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ widget.screenId == currentScreen) {
+ currentScreenWidgets.add(widget);
+ } else {
+ otherScreenWidgets.add(widget);
+ }
+ }
+ }
+
+ /** Filters the set of folders which are on the specified screen. */
+ private void filterCurrentFolders(int currentScreen,
+ HashMap<Long, ItemInfo> itemsIdMap,
+ HashMap<Long, FolderInfo> folders,
+ HashMap<Long, FolderInfo> currentScreenFolders,
+ HashMap<Long, FolderInfo> otherScreenFolders) {
+ // If we aren't filtering on a screen, then the set of items to load is the full set of
+ // widgets given.
+ if (currentScreen < 0) {
+ currentScreenFolders.putAll(folders);
+ }
+
+ for (long id : folders.keySet()) {
+ ItemInfo info = itemsIdMap.get(id);
+ FolderInfo folder = folders.get(id);
+ if (info == null || folder == null) continue;
+ if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
+ info.screenId == currentScreen) {
+ currentScreenFolders.put(id, folder);
+ } else {
+ otherScreenFolders.put(id, folder);
+ }
+ }
+ }
+
+ /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
+ * right) */
+ private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
+ final LauncherAppState app = LauncherAppState.getInstance();
+ final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ // XXX: review this
+ Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
+ @Override
+ public int compare(ItemInfo lhs, ItemInfo rhs) {
+ int cellCountX = (int) grid.numColumns;
+ int cellCountY = (int) grid.numRows;
+ int screenOffset = cellCountX * cellCountY;
+ int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
+ long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
+ lhs.cellY * cellCountX + lhs.cellX);
+ long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
+ rhs.cellY * cellCountX + rhs.cellX);
+ return (int) (lr - rr);
+ }
+ });
+ }
+
+ private void bindWorkspaceScreens(final Callbacks oldCallbacks,
+ final ArrayList<Long> orderedScreens) {
+ final Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.bindScreens(orderedScreens);
+ }
+ }
+ };
+ runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+ }
+
+ private void bindWorkspaceItems(final Callbacks oldCallbacks,
+ final ArrayList<ItemInfo> workspaceItems,
+ final ArrayList<LauncherAppWidgetInfo> appWidgets,
+ final HashMap<Long, FolderInfo> folders,
+ ArrayList<Runnable> deferredBindRunnables) {
+
+ final boolean postOnMainThread = (deferredBindRunnables != null);
+
+ // Bind the workspace items
+ int N = workspaceItems.size();
+ for (int i = 0; i < N; i += ITEMS_CHUNK) {
+ final int start = i;
+ final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
+ final Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.bindItems(workspaceItems, start, start+chunkSize,
+ false);
+ }
+ }
+ };
+ if (postOnMainThread) {
+ deferredBindRunnables.add(r);
+ } else {
+ runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+ }
+ }
+
+ // Bind the folders
+ if (!folders.isEmpty()) {
+ final Runnable r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.bindFolders(folders);
+ }
+ }
+ };
+ if (postOnMainThread) {
+ deferredBindRunnables.add(r);
+ } else {
+ runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+ }
+ }
+
+ // Bind the widgets, one at a time
+ N = appWidgets.size();
+ for (int i = 0; i < N; i++) {
+ final LauncherAppWidgetInfo widget = appWidgets.get(i);
+ final Runnable r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.bindAppWidget(widget);
+ }
+ }
+ };
+ if (postOnMainThread) {
+ deferredBindRunnables.add(r);
+ } else {
+ runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+ }
+ }
+ }
+
+ /**
+ * Binds all loaded data to actual views on the main thread.
+ */
+ private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
+ final long t = SystemClock.uptimeMillis();
+ Runnable r;
+
+ // Don't use these two variables in any of the callback runnables.
+ // Otherwise we hold a reference to them.
+ final Callbacks oldCallbacks = mCallbacks.get();
+ if (oldCallbacks == null) {
+ // This launcher has exited and nobody bothered to tell us. Just bail.
+ Log.w(TAG, "LoaderTask running with no launcher");
+ return;
+ }
+
+ final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
+ final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
+ oldCallbacks.getCurrentWorkspaceScreen();
+
+ // Load all the items that are on the current page first (and in the process, unbind
+ // all the existing workspace items before we call startBinding() below.
+ unbindWorkspaceItemsOnMainThread();
+ ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
+ ArrayList<LauncherAppWidgetInfo> appWidgets =
+ new ArrayList<LauncherAppWidgetInfo>();
+ HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
+ HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
+ ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
+ synchronized (sBgLock) {
+ workspaceItems.addAll(sBgWorkspaceItems);
+ appWidgets.addAll(sBgAppWidgets);
+ folders.putAll(sBgFolders);
+ itemsIdMap.putAll(sBgItemsIdMap);
+ orderedScreenIds.addAll(sBgWorkspaceScreens);
+ }
+
+ ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
+ ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
+ ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
+ new ArrayList<LauncherAppWidgetInfo>();
+ ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
+ new ArrayList<LauncherAppWidgetInfo>();
+ HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
+ HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
+
+ // Separate the items that are on the current screen, and all the other remaining items
+ filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
+ otherWorkspaceItems);
+ filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
+ otherAppWidgets);
+ filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
+ otherFolders);
+ sortWorkspaceItemsSpatially(currentWorkspaceItems);
+ sortWorkspaceItemsSpatially(otherWorkspaceItems);
+
+ // Tell the workspace that we're about to start binding items
+ r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.startBinding();
+ }
+ }
+ };
+ runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+
+ bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
+
+ // Load items on the current page
+ bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
+ currentFolders, null);
+ if (isLoadingSynchronously) {
+ r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.onPageBoundSynchronously(currentScreen);
+ }
+ }
+ };
+ runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+ }
+
+ // Load all the remaining pages (if we are loading synchronously, we want to defer this
+ // work until after the first render)
+ mDeferredBindRunnables.clear();
+ bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
+ (isLoadingSynchronously ? mDeferredBindRunnables : null));
+
+ // Tell the workspace that we're done binding items
+ r = new Runnable() {
+ public void run() {
+ Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.finishBindingItems(isUpgradePath);
+ }
+
+ // If we're profiling, ensure this is the last thing in the queue.
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "bound workspace in "
+ + (SystemClock.uptimeMillis()-t) + "ms");
+ }
+
+ mIsLoadingAndBindingWorkspace = false;
+ }
+ };
+ if (isLoadingSynchronously) {
+ mDeferredBindRunnables.add(r);
+ } else {
+ runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
+ }
+ }
+
+ private void loadAndBindAllApps() {
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
+ }
+ if (!mAllAppsLoaded) {
+ loadAllApps();
+ synchronized (LoaderTask.this) {
+ if (mStopped) {
+ return;
+ }
+ mAllAppsLoaded = true;
+ }
+ } else {
+ onlyBindAllApps();
+ }
+ }
+
+ private void onlyBindAllApps() {
+ final Callbacks oldCallbacks = mCallbacks.get();
+ if (oldCallbacks == null) {
+ // This launcher has exited and nobody bothered to tell us. Just bail.
+ Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
+ return;
+ }
+
+ // shallow copy
+ @SuppressWarnings("unchecked")
+ final ArrayList<AppInfo> list
+ = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
+ Runnable r = new Runnable() {
+ public void run() {
+ final long t = SystemClock.uptimeMillis();
+ final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.bindAllApplications(list);
+ }
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "bound all " + list.size() + " apps from cache in "
+ + (SystemClock.uptimeMillis()-t) + "ms");
+ }
+ }
+ };
+ boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
+ if (isRunningOnMainThread) {
+ r.run();
+ } else {
+ mHandler.post(r);
+ }
+ }
+
+ private void loadAllApps() {
+ final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+
+ final Callbacks oldCallbacks = mCallbacks.get();
+ if (oldCallbacks == null) {
+ // This launcher has exited and nobody bothered to tell us. Just bail.
+ Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
+ return;
+ }
+
+ final PackageManager packageManager = mContext.getPackageManager();
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+ // Clear the list of apps
+ mBgAllAppsList.clear();
+
+ // Query for the set of apps
+ final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+ List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "queryIntentActivities took "
+ + (SystemClock.uptimeMillis()-qiaTime) + "ms");
+ Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
+ }
+ // Fail if we don't have any apps
+ if (apps == null || apps.isEmpty()) {
+ return;
+ }
+ // Sort the applications by name
+ final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+ Collections.sort(apps,
+ new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "sort took "
+ + (SystemClock.uptimeMillis()-sortTime) + "ms");
+ }
+
+ // Create the ApplicationInfos
+ for (int i = 0; i < apps.size(); i++) {
+ ResolveInfo app = apps.get(i);
+ // This builds the icon bitmaps.
+ mBgAllAppsList.add(new AppInfo(packageManager, app,
+ mIconCache, mLabelCache));
+ }
+
+ // Huh? Shouldn't this be inside the Runnable below?
+ final ArrayList<AppInfo> added = mBgAllAppsList.added;
+ mBgAllAppsList.added = new ArrayList<AppInfo>();
+
+ // Post callback on main thread
+ mHandler.post(new Runnable() {
+ public void run() {
+ final long bindTime = SystemClock.uptimeMillis();
+ final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
+ if (callbacks != null) {
+ callbacks.bindAllApplications(added);
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "bound " + added.size() + " apps in "
+ + (SystemClock.uptimeMillis() - bindTime) + "ms");
+ }
+ } else {
+ Log.i(TAG, "not binding apps: no Launcher activity");
+ }
+ }
+ });
+
+ if (DEBUG_LOADERS) {
+ Log.d(TAG, "Icons processed in "
+ + (SystemClock.uptimeMillis() - loadTime) + "ms");
+ }
+ }
+
+ public void dumpState() {
+ synchronized (sBgLock) {
+ Log.d(TAG, "mLoaderTask.mContext=" + mContext);
+ Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
+ Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
+ Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
+ Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
+ }
+ }
+ }
+
+ void enqueuePackageUpdated(PackageUpdatedTask task) {
+ sWorker.post(task);
+ }
+
+ private class PackageUpdatedTask implements Runnable {
+ int mOp;
+ String[] mPackages;
+
+ public static final int OP_NONE = 0;
+ public static final int OP_ADD = 1;
+ public static final int OP_UPDATE = 2;
+ public static final int OP_REMOVE = 3; // uninstlled
+ public static final int OP_UNAVAILABLE = 4; // external media unmounted
+
+
+ public PackageUpdatedTask(int op, String[] packages) {
+ mOp = op;
+ mPackages = packages;
+ }
+
+ public void run() {
+ final Context context = mApp.getContext();
+
+ final String[] packages = mPackages;
+ final int N = packages.length;
+ switch (mOp) {
+ case OP_ADD:
+ for (int i=0; i<N; i++) {
+ if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
+ mBgAllAppsList.addPackage(context, packages[i]);
+ }
+ break;
+ case OP_UPDATE:
+ for (int i=0; i<N; i++) {
+ if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+ mBgAllAppsList.updatePackage(context, packages[i]);
+ WidgetPreviewLoader.removePackageFromDb(
+ mApp.getWidgetPreviewCacheDb(), packages[i]);
+ }
+ break;
+ case OP_REMOVE:
+ case OP_UNAVAILABLE:
+ for (int i=0; i<N; i++) {
+ if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
+ mBgAllAppsList.removePackage(packages[i]);
+ WidgetPreviewLoader.removePackageFromDb(
+ mApp.getWidgetPreviewCacheDb(), packages[i]);
+ }
+ break;
+ }
+
+ ArrayList<AppInfo> added = null;
+ ArrayList<AppInfo> modified = null;
+ final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
+
+ if (mBgAllAppsList.added.size() > 0) {
+ added = new ArrayList<AppInfo>(mBgAllAppsList.added);
+ mBgAllAppsList.added.clear();
+ }
+ if (mBgAllAppsList.modified.size() > 0) {
+ modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
+ mBgAllAppsList.modified.clear();
+ }
+ if (mBgAllAppsList.removed.size() > 0) {
+ removedApps.addAll(mBgAllAppsList.removed);
+ mBgAllAppsList.removed.clear();
+ }
+
+ final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
+ if (callbacks == null) {
+ Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
+ return;
+ }
+
+ if (added != null) {
+ // Ensure that we add all the workspace applications to the db
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ if (!AppsCustomizePagedView.DISABLE_ALL_APPS) {
+ addAndBindAddedApps(context, new ArrayList<ItemInfo>(), cb, added);
+ } else {
+ final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
+ addAndBindAddedApps(context, addedInfos, cb, added);
+ }
+ }
+ if (modified != null) {
+ final ArrayList<AppInfo> modifiedFinal = modified;
+
+ // Update the launcher db to reflect the changes
+ for (AppInfo a : modifiedFinal) {
+ ArrayList<ItemInfo> infos =
+ getItemInfoForComponentName(a.componentName);
+ for (ItemInfo i : infos) {
+ if (isShortcutInfoUpdateable(i)) {
+ ShortcutInfo info = (ShortcutInfo) i;
+ info.title = a.title.toString();
+ updateItemInDatabase(context, info);
+ }
+ }
+ }
+
+ mHandler.post(new Runnable() {
+ public void run() {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ if (callbacks == cb && cb != null) {
+ callbacks.bindAppsUpdated(modifiedFinal);
+ }
+ }
+ });
+ }
+ // If a package has been removed, or an app has been removed as a result of
+ // an update (for example), make the removed callback.
+ if (mOp == OP_REMOVE || !removedApps.isEmpty()) {
+ final boolean packageRemoved = (mOp == OP_REMOVE);
+ final ArrayList<String> removedPackageNames =
+ new ArrayList<String>(Arrays.asList(packages));
+
+ // Update the launcher db to reflect the removal of apps
+ if (packageRemoved) {
+ for (String pn : removedPackageNames) {
+ ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
+ for (ItemInfo i : infos) {
+ deleteItemFromDatabase(context, i);
+ }
+ }
+
+ // Remove any queued items from the install queue
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp =
+ context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
+ } else {
+ for (AppInfo a : removedApps) {
+ ArrayList<ItemInfo> infos =
+ getItemInfoForComponentName(a.componentName);
+ for (ItemInfo i : infos) {
+ deleteItemFromDatabase(context, i);
+ }
+ }
+ }
+
+ mHandler.post(new Runnable() {
+ public void run() {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ if (callbacks == cb && cb != null) {
+ callbacks.bindComponentsRemoved(removedPackageNames,
+ removedApps, packageRemoved);
+ }
+ }
+ });
+ }
+
+ final ArrayList<Object> widgetsAndShortcuts =
+ getSortedWidgetsAndShortcuts(context);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ if (callbacks == cb && cb != null) {
+ callbacks.bindPackagesUpdated(widgetsAndShortcuts);
+ }
+ }
+ });
+
+ // Write all the logs to disk
+ mHandler.post(new Runnable() {
+ public void run() {
+ Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
+ if (callbacks == cb && cb != null) {
+ callbacks.dumpLogsToLocalData();
+ }
+ }
+ });
+ }
+ }
+
+ // Returns a list of ResolveInfos/AppWindowInfos in sorted order
+ public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
+ widgetsAndShortcuts.addAll(AppWidgetManager.getInstance(context).getInstalledProviders());
+ Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+ widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
+ Collections.sort(widgetsAndShortcuts,
+ new LauncherModel.WidgetAndShortcutNameComparator(packageManager));
+ return widgetsAndShortcuts;
+ }
+
+ private boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
+ if (cn == null) {
+ return false;
+ }
+
+ try {
+ // Skip if the application is disabled
+ PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
+ if (!pi.applicationInfo.enabled) {
+ return false;
+ }
+
+ // Check the activity
+ return (pm.getActivityInfo(cn, 0) != null);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * This is called from the code that adds shortcuts from the intent receiver. This
+ * doesn't have a Cursor, but
+ */
+ public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
+ return getShortcutInfo(manager, intent, context, null, -1, -1, null);
+ }
+
+ /**
+ * Make an ShortcutInfo object for a shortcut that is an application.
+ *
+ * If c is not null, then it will be used to fill in missing data like the title and icon.
+ */
+ public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
+ Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
+ ComponentName componentName = intent.getComponent();
+ final ShortcutInfo info = new ShortcutInfo();
+ if (componentName != null && !isValidPackageComponent(manager, componentName)) {
+ Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
+ return null;
+ } else {
+ try {
+ PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
+ info.initFlagsAndFirstInstallTime(pi);
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "getPackInfo failed for package " +
+ componentName.getPackageName());
+ }
+ }
+
+ // TODO: See if the PackageManager knows about this case. If it doesn't
+ // then return null & delete this.
+
+ // the resource -- This may implicitly give us back the fallback icon,
+ // but don't worry about that. All we're doing with usingFallbackIcon is
+ // to avoid saving lots of copies of that in the database, and most apps
+ // have icons anyway.
+
+ // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
+ // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
+ // via resolveActivity().
+ Bitmap icon = null;
+ ResolveInfo resolveInfo = null;
+ ComponentName oldComponent = intent.getComponent();
+ Intent newIntent = new Intent(intent.getAction(), null);
+ newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ newIntent.setPackage(oldComponent.getPackageName());
+ List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
+ for (ResolveInfo i : infos) {
+ ComponentName cn = new ComponentName(i.activityInfo.packageName,
+ i.activityInfo.name);
+ if (cn.equals(oldComponent)) {
+ resolveInfo = i;
+ }
+ }
+ if (resolveInfo == null) {
+ resolveInfo = manager.resolveActivity(intent, 0);
+ }
+ if (resolveInfo != null) {
+ icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
+ }
+ // the db
+ if (icon == null) {
+ if (c != null) {
+ icon = getIconFromCursor(c, iconIndex, context);
+ }
+ }
+ // the fallback icon
+ if (icon == null) {
+ icon = getFallbackIcon();
+ info.usingFallbackIcon = true;
+ }
+ info.setIcon(icon);
+
+ // from the resource
+ if (resolveInfo != null) {
+ ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
+ if (labelCache != null && labelCache.containsKey(key)) {
+ info.title = labelCache.get(key);
+ } else {
+ info.title = resolveInfo.activityInfo.loadLabel(manager);
+ if (labelCache != null) {
+ labelCache.put(key, info.title);
+ }
+ }
+ }
+ // from the db
+ if (info.title == null) {
+ if (c != null) {
+ info.title = c.getString(titleIndex);
+ }
+ }
+ // fall back to the class name of the activity
+ if (info.title == null) {
+ info.title = componentName.getClassName();
+ }
+ info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ return info;
+ }
+
+ static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
+ ItemInfoFilter f) {
+ HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
+ for (ItemInfo i : infos) {
+ if (i instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) i;
+ ComponentName cn = info.intent.getComponent();
+ if (cn != null && f.filterItem(null, info, cn)) {
+ filtered.add(info);
+ }
+ } else if (i instanceof FolderInfo) {
+ FolderInfo info = (FolderInfo) i;
+ for (ShortcutInfo s : info.contents) {
+ ComponentName cn = s.intent.getComponent();
+ if (cn != null && f.filterItem(info, s, cn)) {
+ filtered.add(s);
+ }
+ }
+ } else if (i instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
+ ComponentName cn = info.providerName;
+ if (cn != null && f.filterItem(null, info, cn)) {
+ filtered.add(info);
+ }
+ }
+ }
+ return new ArrayList<ItemInfo>(filtered);
+ }
+
+ private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) {
+ ItemInfoFilter filter = new ItemInfoFilter() {
+ @Override
+ public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
+ return cn.getPackageName().equals(pn);
+ }
+ };
+ return filterItemInfos(sBgItemsIdMap.values(), filter);
+ }
+
+ private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) {
+ ItemInfoFilter filter = new ItemInfoFilter() {
+ @Override
+ public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
+ return cn.equals(cname);
+ }
+ };
+ return filterItemInfos(sBgItemsIdMap.values(), filter);
+ }
+
+ public static boolean isShortcutInfoUpdateable(ItemInfo i) {
+ if (i instanceof ShortcutInfo) {
+ ShortcutInfo info = (ShortcutInfo) i;
+ // We need to check for ACTION_MAIN otherwise getComponent() might
+ // return null for some shortcuts (for instance, for shortcuts to
+ // web pages.)
+ Intent intent = info.intent;
+ ComponentName name = intent.getComponent();
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
+ Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Make an ShortcutInfo object for a shortcut that isn't an application.
+ */
+ private ShortcutInfo getShortcutInfo(Cursor c, Context context,
+ int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
+ int titleIndex) {
+
+ Bitmap icon = null;
+ final ShortcutInfo info = new ShortcutInfo();
+ info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+
+ // TODO: If there's an explicit component and we can't install that, delete it.
+
+ info.title = c.getString(titleIndex);
+
+ int iconType = c.getInt(iconTypeIndex);
+ switch (iconType) {
+ case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
+ String packageName = c.getString(iconPackageIndex);
+ String resourceName = c.getString(iconResourceIndex);
+ PackageManager packageManager = context.getPackageManager();
+ info.customIcon = false;
+ // the resource
+ try {
+ Resources resources = packageManager.getResourcesForApplication(packageName);
+ if (resources != null) {
+ final int id = resources.getIdentifier(resourceName, null, null);
+ icon = Utilities.createIconBitmap(
+ mIconCache.getFullResIcon(resources, id), context);
+ }
+ } catch (Exception e) {
+ // drop this. we have other places to look for icons
+ }
+ // the db
+ if (icon == null) {
+ icon = getIconFromCursor(c, iconIndex, context);
+ }
+ // the fallback icon
+ if (icon == null) {
+ icon = getFallbackIcon();
+ info.usingFallbackIcon = true;
+ }
+ break;
+ case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
+ icon = getIconFromCursor(c, iconIndex, context);
+ if (icon == null) {
+ icon = getFallbackIcon();
+ info.customIcon = false;
+ info.usingFallbackIcon = true;
+ } else {
+ info.customIcon = true;
+ }
+ break;
+ default:
+ icon = getFallbackIcon();
+ info.usingFallbackIcon = true;
+ info.customIcon = false;
+ break;
+ }
+ info.setIcon(icon);
+ return info;
+ }
+
+ Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
+ @SuppressWarnings("all") // suppress dead code warning
+ final boolean debug = false;
+ if (debug) {
+ Log.d(TAG, "getIconFromCursor app="
+ + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
+ }
+ byte[] data = c.getBlob(iconIndex);
+ try {
+ return Utilities.createIconBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length), context);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
+ int cellX, int cellY, boolean notify) {
+ final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
+ if (info == null) {
+ return null;
+ }
+ addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
+
+ return info;
+ }
+
+ /**
+ * Attempts to find an AppWidgetProviderInfo that matches the given component.
+ */
+ AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
+ ComponentName component) {
+ List<AppWidgetProviderInfo> widgets =
+ AppWidgetManager.getInstance(context).getInstalledProviders();
+ for (AppWidgetProviderInfo info : widgets) {
+ if (info.provider.equals(component)) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of all the widgets that can handle configuration with a particular mimeType.
+ */
+ List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
+ final PackageManager packageManager = context.getPackageManager();
+ final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
+ new ArrayList<WidgetMimeTypeHandlerData>();
+
+ final Intent supportsIntent =
+ new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
+ supportsIntent.setType(mimeType);
+
+ // Create a set of widget configuration components that we can test against
+ final List<AppWidgetProviderInfo> widgets =
+ AppWidgetManager.getInstance(context).getInstalledProviders();
+ final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
+ new HashMap<ComponentName, AppWidgetProviderInfo>();
+ for (AppWidgetProviderInfo info : widgets) {
+ configurationComponentToWidget.put(info.configure, info);
+ }
+
+ // Run through each of the intents that can handle this type of clip data, and cross
+ // reference them with the components that are actual configuration components
+ final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ for (ResolveInfo info : activities) {
+ final ActivityInfo activityInfo = info.activityInfo;
+ final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
+ activityInfo.name);
+ if (configurationComponentToWidget.containsKey(infoComponent)) {
+ supportedConfigurationActivities.add(
+ new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
+ configurationComponentToWidget.get(infoComponent)));
+ }
+ }
+ return supportedConfigurationActivities;
+ }
+
+ ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
+ Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+ String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+ Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+
+ if (intent == null) {
+ // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
+ Log.e(TAG, "Can't construct ShorcutInfo with null intent");
+ return null;
+ }
+
+ Bitmap icon = null;
+ boolean customIcon = false;
+ ShortcutIconResource iconResource = null;
+
+ if (bitmap != null && bitmap instanceof Bitmap) {
+ icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
+ customIcon = true;
+ } else {
+ Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+ if (extra != null && extra instanceof ShortcutIconResource) {
+ try {
+ iconResource = (ShortcutIconResource) extra;
+ final PackageManager packageManager = context.getPackageManager();
+ Resources resources = packageManager.getResourcesForApplication(
+ iconResource.packageName);
+ final int id = resources.getIdentifier(iconResource.resourceName, null, null);
+ icon = Utilities.createIconBitmap(
+ mIconCache.getFullResIcon(resources, id), context);
+ } catch (Exception e) {
+ Log.w(TAG, "Could not load shortcut icon: " + extra);
+ }
+ }
+ }
+
+ final ShortcutInfo info = new ShortcutInfo();
+
+ if (icon == null) {
+ if (fallbackIcon != null) {
+ icon = fallbackIcon;
+ } else {
+ icon = getFallbackIcon();
+ info.usingFallbackIcon = true;
+ }
+ }
+ info.setIcon(icon);
+
+ info.title = name;
+ info.intent = intent;
+ info.customIcon = customIcon;
+ info.iconResource = iconResource;
+
+ return info;
+ }
+
+ boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
+ int iconIndex) {
+ // If apps can't be on SD, don't even bother.
+ if (!mAppsCanBeOnRemoveableStorage) {
+ return false;
+ }
+ // If this icon doesn't have a custom icon, check to see
+ // what's stored in the DB, and if it doesn't match what
+ // we're going to show, store what we are going to show back
+ // into the DB. We do this so when we're loading, if the
+ // package manager can't find an icon (for example because
+ // the app is on SD) then we can use that instead.
+ if (!info.customIcon && !info.usingFallbackIcon) {
+ cache.put(info, c.getBlob(iconIndex));
+ return true;
+ }
+ return false;
+ }
+ void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
+ boolean needSave = false;
+ try {
+ if (data != null) {
+ Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
+ Bitmap loaded = info.getIcon(mIconCache);
+ needSave = !saved.sameAs(loaded);
+ } else {
+ needSave = true;
+ }
+ } catch (Exception e) {
+ needSave = true;
+ }
+ if (needSave) {
+ Log.d(TAG, "going to save icon bitmap for info=" + info);
+ // This is slower than is ideal, but this only happens once
+ // or when the app is updated with a new icon.
+ updateItemInDatabase(context, info);
+ }
+ }
+
+ /**
+ * Return an existing FolderInfo object if we have encountered this ID previously,
+ * or make a new one.
+ */
+ private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
+ // See if a placeholder was created for us already
+ FolderInfo folderInfo = folders.get(id);
+ if (folderInfo == null) {
+ // No placeholder -- create a new instance
+ folderInfo = new FolderInfo();
+ folders.put(id, folderInfo);
+ }
+ return folderInfo;
+ }
+
+ public static final Comparator<AppInfo> getAppNameComparator() {
+ final Collator collator = Collator.getInstance();
+ return new Comparator<AppInfo>() {
+ public final int compare(AppInfo a, AppInfo b) {
+ int result = collator.compare(a.title.toString().trim(),
+ b.title.toString().trim());
+ if (result == 0) {
+ result = a.componentName.compareTo(b.componentName);
+ }
+ return result;
+ }
+ };
+ }
+ public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
+ = new Comparator<AppInfo>() {
+ public final int compare(AppInfo a, AppInfo b) {
+ if (a.firstInstallTime < b.firstInstallTime) return 1;
+ if (a.firstInstallTime > b.firstInstallTime) return -1;
+ return 0;
+ }
+ };
+ public static final Comparator<AppWidgetProviderInfo> getWidgetNameComparator() {
+ final Collator collator = Collator.getInstance();
+ return new Comparator<AppWidgetProviderInfo>() {
+ public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) {
+ return collator.compare(a.label.toString().trim(), b.label.toString().trim());
+ }
+ };
+ }
+ static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
+ if (info.activityInfo != null) {
+ return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+ } else {
+ return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
+ }
+ }
+ public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
+ private Collator mCollator;
+ private PackageManager mPackageManager;
+ private HashMap<Object, CharSequence> mLabelCache;
+ ShortcutNameComparator(PackageManager pm) {
+ mPackageManager = pm;
+ mLabelCache = new HashMap<Object, CharSequence>();
+ mCollator = Collator.getInstance();
+ }
+ ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
+ mPackageManager = pm;
+ mLabelCache = labelCache;
+ mCollator = Collator.getInstance();
+ }
+ public final int compare(ResolveInfo a, ResolveInfo b) {
+ CharSequence labelA, labelB;
+ ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
+ ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
+ if (mLabelCache.containsKey(keyA)) {
+ labelA = mLabelCache.get(keyA);
+ } else {
+ labelA = a.loadLabel(mPackageManager).toString().trim();
+
+ mLabelCache.put(keyA, labelA);
+ }
+ if (mLabelCache.containsKey(keyB)) {
+ labelB = mLabelCache.get(keyB);
+ } else {
+ labelB = b.loadLabel(mPackageManager).toString().trim();
+
+ mLabelCache.put(keyB, labelB);
+ }
+ return mCollator.compare(labelA, labelB);
+ }
+ };
+ public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
+ private Collator mCollator;
+ private PackageManager mPackageManager;
+ private HashMap<Object, String> mLabelCache;
+ WidgetAndShortcutNameComparator(PackageManager pm) {
+ mPackageManager = pm;
+ mLabelCache = new HashMap<Object, String>();
+ mCollator = Collator.getInstance();
+ }
+ public final int compare(Object a, Object b) {
+ String labelA, labelB;
+ if (mLabelCache.containsKey(a)) {
+ labelA = mLabelCache.get(a);
+ } else {
+ labelA = (a instanceof AppWidgetProviderInfo) ?
+ ((AppWidgetProviderInfo) a).label :
+ ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
+ mLabelCache.put(a, labelA);
+ }
+ if (mLabelCache.containsKey(b)) {
+ labelB = mLabelCache.get(b);
+ } else {
+ labelB = (b instanceof AppWidgetProviderInfo) ?
+ ((AppWidgetProviderInfo) b).label :
+ ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
+ mLabelCache.put(b, labelB);
+ }
+ return mCollator.compare(labelA, labelB);
+ }
+ };
+
+ public void dumpState() {
+ Log.d(TAG, "mCallbacks=" + mCallbacks);
+ AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
+ AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
+ AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
+ AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
+ if (mLoaderTask != null) {
+ mLoaderTask.dumpState();
+ } else {
+ Log.d(TAG, "mLoaderTask=null");
+ }
+ }
+}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
new file mode 100644
index 0000000..d0f6770
--- /dev/null
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -0,0 +1,1452 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.SearchManager;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteStatement;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.config.ProviderConfig;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LauncherProvider extends ContentProvider {
+ private static final String TAG = "Launcher.LauncherProvider";
+ private static final boolean LOGD = false;
+
+ private static final String DATABASE_NAME = "launcher.db";
+
+ private static final int DATABASE_VERSION = 15;
+
+ static final String OLD_AUTHORITY = "com.android.launcher2.settings";
+ static final String AUTHORITY = ProviderConfig.AUTHORITY;
+
+ static final String TABLE_FAVORITES = "favorites";
+ static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens";
+ static final String PARAMETER_NOTIFY = "notify";
+ static final String UPGRADED_FROM_OLD_DATABASE =
+ "UPGRADED_FROM_OLD_DATABASE";
+ static final String EMPTY_DATABASE_CREATED =
+ "EMPTY_DATABASE_CREATED";
+ static final String DEFAULT_WORKSPACE_RESOURCE_ID =
+ "DEFAULT_WORKSPACE_RESOURCE_ID";
+
+ private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
+ "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
+
+ /**
+ * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
+ * {@link AppWidgetHost#deleteHost()} is called during database creation.
+ * Use this to recall {@link AppWidgetHost#startListening()} if needed.
+ */
+ static final Uri CONTENT_APPWIDGET_RESET_URI =
+ Uri.parse("content://" + AUTHORITY + "/appWidgetReset");
+
+ private DatabaseHelper mOpenHelper;
+ private static boolean sJustLoadedFromOldDb;
+
+ @Override
+ public boolean onCreate() {
+ final Context context = getContext();
+ mOpenHelper = new DatabaseHelper(context);
+ LauncherAppState.setLauncherProvider(this);
+ return true;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ SqlArguments args = new SqlArguments(uri, null, null);
+ if (TextUtils.isEmpty(args.where)) {
+ return "vnd.android.cursor.dir/" + args.table;
+ } else {
+ return "vnd.android.cursor.item/" + args.table;
+ }
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+
+ SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(args.table);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
+ result.setNotificationUri(getContext().getContentResolver(), uri);
+
+ return result;
+ }
+
+ private static long dbInsertAndCheck(DatabaseHelper helper,
+ SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
+ if (!values.containsKey(LauncherSettings.Favorites._ID)) {
+ throw new RuntimeException("Error: attempting to add item without specifying an id");
+ }
+ return db.insert(table, nullColumnHack, values);
+ }
+
+ private static void deleteId(SQLiteDatabase db, long id) {
+ Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
+ SqlArguments args = new SqlArguments(uri, null, null);
+ db.delete(args.table, args.where, args.args);
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues initialValues) {
+ SqlArguments args = new SqlArguments(uri);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ addModifiedTime(initialValues);
+ final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
+ if (rowId <= 0) return null;
+
+ uri = ContentUris.withAppendedId(uri, rowId);
+ sendNotify(uri);
+
+ return uri;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ SqlArguments args = new SqlArguments(uri);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ int numValues = values.length;
+ for (int i = 0; i < numValues; i++) {
+ addModifiedTime(values[i]);
+ if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
+ return 0;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ sendNotify(uri);
+ return values.length;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count = db.delete(args.table, args.where, args.args);
+ if (count > 0) sendNotify(uri);
+
+ return count;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
+
+ addModifiedTime(values);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count = db.update(args.table, values, args.where, args.args);
+ if (count > 0) sendNotify(uri);
+
+ return count;
+ }
+
+ private void sendNotify(Uri uri) {
+ String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
+ if (notify == null || "true".equals(notify)) {
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+
+ // always notify the backup agent
+ LauncherBackupAgentHelper.dataChanged(getContext());
+ }
+
+ private void addModifiedTime(ContentValues values) {
+ values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
+ }
+
+ public long generateNewItemId() {
+ return mOpenHelper.generateNewItemId();
+ }
+
+ public void updateMaxItemId(long id) {
+ mOpenHelper.updateMaxItemId(id);
+ }
+
+ public long generateNewScreenId() {
+ return mOpenHelper.generateNewScreenId();
+ }
+
+ // This is only required one time while loading the workspace during the
+ // upgrade path, and should never be called from anywhere else.
+ public void updateMaxScreenId(long maxScreenId) {
+ mOpenHelper.updateMaxScreenId(maxScreenId);
+ }
+
+ /**
+ * @param Should we load the old db for upgrade? first run only.
+ */
+ synchronized public boolean justLoadedOldDb() {
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
+
+ boolean loadedOldDb = false || sJustLoadedFromOldDb;
+
+ sJustLoadedFromOldDb = false;
+ if (sp.getBoolean(UPGRADED_FROM_OLD_DATABASE, false)) {
+
+ SharedPreferences.Editor editor = sp.edit();
+ editor.remove(UPGRADED_FROM_OLD_DATABASE);
+ editor.commit();
+ loadedOldDb = true;
+ }
+ return loadedOldDb;
+ }
+
+ /**
+ * @param workspaceResId that can be 0 to use default or non-zero for specific resource
+ */
+ synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) {
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
+
+ if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
+ int workspaceResId = origWorkspaceResId;
+
+ // Use default workspace resource if none provided
+ if (workspaceResId == 0) {
+ workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
+ }
+
+ // Populate favorites table with initial favorites
+ SharedPreferences.Editor editor = sp.edit();
+ editor.remove(EMPTY_DATABASE_CREATED);
+ if (origWorkspaceResId != 0) {
+ editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId);
+ }
+
+ mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
+ mOpenHelper.setFlagJustLoadedOldDb();
+ editor.commit();
+ }
+ }
+
+ private static interface ContentValuesCallback {
+ public void onRow(ContentValues values);
+ }
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+ private static final String TAG_FAVORITES = "favorites";
+ private static final String TAG_FAVORITE = "favorite";
+ private static final String TAG_CLOCK = "clock";
+ private static final String TAG_SEARCH = "search";
+ private static final String TAG_APPWIDGET = "appwidget";
+ private static final String TAG_SHORTCUT = "shortcut";
+ private static final String TAG_FOLDER = "folder";
+ private static final String TAG_EXTRA = "extra";
+ private static final String TAG_INCLUDE = "include";
+
+ private final Context mContext;
+ private final AppWidgetHost mAppWidgetHost;
+ private long mMaxItemId = -1;
+ private long mMaxScreenId = -1;
+
+ DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ mContext = context;
+ mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);
+
+ // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
+ // the DB here
+ if (mMaxItemId == -1) {
+ mMaxItemId = initializeMaxItemId(getWritableDatabase());
+ }
+ if (mMaxScreenId == -1) {
+ mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
+ }
+ }
+
+ /**
+ * Send notification that we've deleted the {@link AppWidgetHost},
+ * probably as part of the initial database creation. The receiver may
+ * want to re-call {@link AppWidgetHost#startListening()} to ensure
+ * callbacks are correctly set.
+ */
+ private void sendAppWidgetResetNotify() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.notifyChange(CONTENT_APPWIDGET_RESET_URI, null);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ if (LOGD) Log.d(TAG, "creating new launcher database");
+
+ mMaxItemId = 1;
+ mMaxScreenId = 0;
+
+ db.execSQL("CREATE TABLE favorites (" +
+ "_id INTEGER PRIMARY KEY," +
+ "title TEXT," +
+ "intent TEXT," +
+ "container INTEGER," +
+ "screen INTEGER," +
+ "cellX INTEGER," +
+ "cellY INTEGER," +
+ "spanX INTEGER," +
+ "spanY INTEGER," +
+ "itemType INTEGER," +
+ "appWidgetId INTEGER NOT NULL DEFAULT -1," +
+ "isShortcut INTEGER," +
+ "iconType INTEGER," +
+ "iconPackage TEXT," +
+ "iconResource TEXT," +
+ "icon BLOB," +
+ "uri TEXT," +
+ "displayMode INTEGER," +
+ "appWidgetProvider TEXT," +
+ "modified INTEGER NOT NULL DEFAULT 0" +
+ ");");
+ addWorkspacesTable(db);
+
+ // Database was just created, so wipe any previous widgets
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.deleteHost();
+ sendAppWidgetResetNotify();
+ }
+
+ // Try converting the old database
+ ContentValuesCallback permuteScreensCb = new ContentValuesCallback() {
+ public void onRow(ContentValues values) {
+ int container = values.getAsInteger(LauncherSettings.Favorites.CONTAINER);
+ if (container == Favorites.CONTAINER_DESKTOP) {
+ int screen = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
+ screen = (int) upgradeLauncherDb_permuteScreens(screen);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ }
+ }
+ };
+ Uri uri = Uri.parse("content://" + Settings.AUTHORITY +
+ "/old_favorites?notify=true");
+ if (!convertDatabase(db, uri, permuteScreensCb, true)) {
+ // Try and upgrade from the Launcher2 db
+ uri = LauncherSettings.Favorites.OLD_CONTENT_URI;
+ if (!convertDatabase(db, uri, permuteScreensCb, false)) {
+ // If we fail, then set a flag to load the default workspace
+ setFlagEmptyDbCreated();
+ return;
+ }
+ }
+ // Right now, in non-default workspace cases, we want to run the final
+ // upgrade code (ie. to fix workspace screen indices -> ids, etc.), so
+ // set that flag too.
+ setFlagJustLoadedOldDb();
+ }
+
+ private void addWorkspacesTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
+ LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
+ LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
+ LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
+ ");");
+ }
+
+ private void setFlagJustLoadedOldDb() {
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, true);
+ editor.putBoolean(EMPTY_DATABASE_CREATED, false);
+ editor.commit();
+ }
+
+ private void setFlagEmptyDbCreated() {
+ String spKey = LauncherAppState.getSharedPreferencesKey();
+ SharedPreferences sp = mContext.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(EMPTY_DATABASE_CREATED, true);
+ editor.putBoolean(UPGRADED_FROM_OLD_DATABASE, false);
+ editor.commit();
+ }
+
+ // We rearrange the screens from the old launcher
+ // 12345 -> 34512
+ private long upgradeLauncherDb_permuteScreens(long screen) {
+ if (screen >= 2) {
+ return screen - 2;
+ } else {
+ return screen + 3;
+ }
+ }
+
+ private boolean convertDatabase(SQLiteDatabase db, Uri uri,
+ ContentValuesCallback cb, boolean deleteRows) {
+ if (LOGD) Log.d(TAG, "converting database from an older format, but not onUpgrade");
+ boolean converted = false;
+
+ final ContentResolver resolver = mContext.getContentResolver();
+ Cursor cursor = null;
+
+ try {
+ cursor = resolver.query(uri, null, null, null, null);
+ } catch (Exception e) {
+ // Ignore
+ }
+
+ // We already have a favorites database in the old provider
+ if (cursor != null) {
+ try {
+ if (cursor.getCount() > 0) {
+ converted = copyFromCursor(db, cursor, cb) > 0;
+ if (converted && deleteRows) {
+ resolver.delete(uri, null, null);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ if (converted) {
+ // Convert widgets from this import into widgets
+ if (LOGD) Log.d(TAG, "converted and now triggering widget upgrade");
+ convertWidgets(db);
+
+ // Update max item id
+ mMaxItemId = initializeMaxItemId(db);
+ if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
+ }
+
+ return converted;
+ }
+
+ private int copyFromCursor(SQLiteDatabase db, Cursor c, ContentValuesCallback cb) {
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+ final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+ final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
+ final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
+ final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
+ final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
+ final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
+ final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
+ final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+ final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+ final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+ final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+ final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
+ final int displayModeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
+
+ ContentValues[] rows = new ContentValues[c.getCount()];
+ int i = 0;
+ while (c.moveToNext()) {
+ ContentValues values = new ContentValues(c.getColumnCount());
+ values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
+ values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
+ values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
+ values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
+ values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex));
+ values.put(LauncherSettings.Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
+ values.put(LauncherSettings.Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
+ values.put(LauncherSettings.Favorites.CONTAINER, c.getInt(containerIndex));
+ values.put(LauncherSettings.Favorites.ITEM_TYPE, c.getInt(itemTypeIndex));
+ values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1);
+ values.put(LauncherSettings.Favorites.SCREEN, c.getInt(screenIndex));
+ values.put(LauncherSettings.Favorites.CELLX, c.getInt(cellXIndex));
+ values.put(LauncherSettings.Favorites.CELLY, c.getInt(cellYIndex));
+ values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
+ values.put(LauncherSettings.Favorites.DISPLAY_MODE, c.getInt(displayModeIndex));
+ if (cb != null) {
+ cb.onRow(values);
+ }
+ rows[i++] = values;
+ }
+
+ int total = 0;
+ if (i > 0) {
+ db.beginTransaction();
+ try {
+ int numValues = rows.length;
+ for (i = 0; i < numValues; i++) {
+ if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) < 0) {
+ return 0;
+ } else {
+ total++;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ return total;
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
+
+ int version = oldVersion;
+ if (version < 3) {
+ // upgrade 1,2 -> 3 added appWidgetId column
+ db.beginTransaction();
+ try {
+ // Insert new column for holding appWidgetIds
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN appWidgetId INTEGER NOT NULL DEFAULT -1;");
+ db.setTransactionSuccessful();
+ version = 3;
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+
+ // Convert existing widgets only if table upgrade was successful
+ if (version == 3) {
+ convertWidgets(db);
+ }
+ }
+
+ if (version < 4) {
+ version = 4;
+ }
+
+ // Where's version 5?
+ // - Donut and sholes on 2.0 shipped with version 4 of launcher1.
+ // - Passion shipped on 2.1 with version 6 of launcher3
+ // - Sholes shipped on 2.1r1 (aka Mr. 3) with version 5 of launcher 1
+ // but version 5 on there was the updateContactsShortcuts change
+ // which was version 6 in launcher 2 (first shipped on passion 2.1r1).
+ // The updateContactsShortcuts change is idempotent, so running it twice
+ // is okay so we'll do that when upgrading the devices that shipped with it.
+ if (version < 6) {
+ // We went from 3 to 5 screens. Move everything 1 to the right
+ db.beginTransaction();
+ try {
+ db.execSQL("UPDATE favorites SET screen=(screen + 1);");
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+
+ // We added the fast track.
+ if (updateContactsShortcuts(db)) {
+ version = 6;
+ }
+ }
+
+ if (version < 7) {
+ // Version 7 gets rid of the special search widget.
+ convertWidgets(db);
+ version = 7;
+ }
+
+ if (version < 8) {
+ // Version 8 (froyo) has the icons all normalized. This should
+ // already be the case in practice, but we now rely on it and don't
+ // resample the images each time.
+ normalizeIcons(db);
+ version = 8;
+ }
+
+ if (version < 9) {
+ // The max id is not yet set at this point (onUpgrade is triggered in the ctor
+ // before it gets a change to get set, so we need to read it here when we use it)
+ if (mMaxItemId == -1) {
+ mMaxItemId = initializeMaxItemId(db);
+ }
+
+ // Add default hotseat icons
+ loadFavorites(db, R.xml.update_workspace);
+ version = 9;
+ }
+
+ // We bumped the version three time during JB, once to update the launch flags, once to
+ // update the override for the default launch animation and once to set the mimetype
+ // to improve startup performance
+ if (version < 12) {
+ // Contact shortcuts need a different set of flags to be launched now
+ // The updateContactsShortcuts change is idempotent, so we can keep using it like
+ // back in the Donut days
+ updateContactsShortcuts(db);
+ version = 12;
+ }
+
+ if (version < 13) {
+ // With the new shrink-wrapped and re-orderable workspaces, it makes sense
+ // to persist workspace screens and their relative order.
+ mMaxScreenId = 0;
+
+ // This will never happen in the wild, but when we switch to using workspace
+ // screen ids, redo the import from old launcher.
+ sJustLoadedFromOldDb = true;
+
+ addWorkspacesTable(db);
+ version = 13;
+ }
+
+ if (version < 14) {
+ db.beginTransaction();
+ try {
+ // Insert new column for holding widget provider name
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN appWidgetProvider TEXT;");
+ db.setTransactionSuccessful();
+ version = 14;
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+
+ if (version < 15) {
+ db.beginTransaction();
+ try {
+ // Insert new column for holding update timestamp
+ db.execSQL("ALTER TABLE favorites " +
+ "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+ db.execSQL("ALTER TABLE workspaceScreens " +
+ "ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
+ db.setTransactionSuccessful();
+ version = 15;
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ if (version != DATABASE_VERSION) {
+ Log.w(TAG, "Destroying all old data.");
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
+
+ onCreate(db);
+ }
+ }
+
+ private boolean updateContactsShortcuts(SQLiteDatabase db) {
+ final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
+ new int[] { Favorites.ITEM_TYPE_SHORTCUT });
+
+ Cursor c = null;
+ final String actionQuickContact = "com.android.contacts.action.QUICK_CONTACT";
+ db.beginTransaction();
+ try {
+ // Select and iterate through each matching widget
+ c = db.query(TABLE_FAVORITES,
+ new String[] { Favorites._ID, Favorites.INTENT },
+ selectWhere, null, null, null, null);
+ if (c == null) return false;
+
+ if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
+
+ final int idIndex = c.getColumnIndex(Favorites._ID);
+ final int intentIndex = c.getColumnIndex(Favorites.INTENT);
+
+ while (c.moveToNext()) {
+ long favoriteId = c.getLong(idIndex);
+ final String intentUri = c.getString(intentIndex);
+ if (intentUri != null) {
+ try {
+ final Intent intent = Intent.parseUri(intentUri, 0);
+ android.util.Log.d("Home", intent.toString());
+ final Uri uri = intent.getData();
+ if (uri != null) {
+ final String data = uri.toString();
+ if ((Intent.ACTION_VIEW.equals(intent.getAction()) ||
+ actionQuickContact.equals(intent.getAction())) &&
+ (data.startsWith("content://contacts/people/") ||
+ data.startsWith("content://com.android.contacts/" +
+ "contacts/lookup/"))) {
+
+ final Intent newIntent = new Intent(actionQuickContact);
+ // When starting from the launcher, start in a new, cleared task
+ // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
+ // clear the whole thing preemptively here since
+ // QuickContactActivity will finish itself when launching other
+ // detail activities.
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ newIntent.putExtra(
+ Launcher.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
+ newIntent.setData(uri);
+ // Determine the type and also put that in the shortcut
+ // (that can speed up launch a bit)
+ newIntent.setDataAndType(uri, newIntent.resolveType(mContext));
+
+ final ContentValues values = new ContentValues();
+ values.put(LauncherSettings.Favorites.INTENT,
+ newIntent.toUri(0));
+
+ String updateWhere = Favorites._ID + "=" + favoriteId;
+ db.update(TABLE_FAVORITES, values, updateWhere, null);
+ }
+ }
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Problem upgrading shortcut", ex);
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Problem upgrading shortcut", e);
+ }
+ }
+ }
+
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ Log.w(TAG, "Problem while upgrading contacts", ex);
+ return false;
+ } finally {
+ db.endTransaction();
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ return true;
+ }
+
+ private void normalizeIcons(SQLiteDatabase db) {
+ Log.d(TAG, "normalizing icons");
+
+ db.beginTransaction();
+ Cursor c = null;
+ SQLiteStatement update = null;
+ try {
+ boolean logged = false;
+ update = db.compileStatement("UPDATE favorites "
+ + "SET icon=? WHERE _id=?");
+
+ c = db.rawQuery("SELECT _id, icon FROM favorites WHERE iconType=" +
+ Favorites.ICON_TYPE_BITMAP, null);
+
+ final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
+ final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
+
+ while (c.moveToNext()) {
+ long id = c.getLong(idIndex);
+ byte[] data = c.getBlob(iconIndex);
+ try {
+ Bitmap bitmap = Utilities.resampleIconBitmap(
+ BitmapFactory.decodeByteArray(data, 0, data.length),
+ mContext);
+ if (bitmap != null) {
+ update.bindLong(1, id);
+ data = ItemInfo.flattenBitmap(bitmap);
+ if (data != null) {
+ update.bindBlob(2, data);
+ update.execute();
+ }
+ bitmap.recycle();
+ }
+ } catch (Exception e) {
+ if (!logged) {
+ Log.e(TAG, "Failed normalizing icon " + id, e);
+ } else {
+ Log.e(TAG, "Also failed normalizing icon " + id);
+ }
+ logged = true;
+ }
+ }
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
+ } finally {
+ db.endTransaction();
+ if (update != null) {
+ update.close();
+ }
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ // Generates a new ID to use for an object in your database. This method should be only
+ // called from the main UI thread. As an exception, we do call it when we call the
+ // constructor from the worker thread; however, this doesn't extend until after the
+ // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
+ // after that point
+ public long generateNewItemId() {
+ if (mMaxItemId < 0) {
+ throw new RuntimeException("Error: max item id was not initialized");
+ }
+ mMaxItemId += 1;
+ return mMaxItemId;
+ }
+
+ public void updateMaxItemId(long id) {
+ mMaxItemId = id + 1;
+ }
+
+ private long initializeMaxItemId(SQLiteDatabase db) {
+ Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
+
+ // get the result
+ final int maxIdIndex = 0;
+ long id = -1;
+ if (c != null && c.moveToNext()) {
+ id = c.getLong(maxIdIndex);
+ }
+ if (c != null) {
+ c.close();
+ }
+
+ if (id == -1) {
+ throw new RuntimeException("Error: could not query max item id");
+ }
+
+ return id;
+ }
+
+ // Generates a new ID to use for an workspace screen in your database. This method
+ // should be only called from the main UI thread. As an exception, we do call it when we
+ // call the constructor from the worker thread; however, this doesn't extend until after the
+ // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
+ // after that point
+ public long generateNewScreenId() {
+ if (mMaxScreenId < 0) {
+ throw new RuntimeException("Error: max screen id was not initialized");
+ }
+ mMaxScreenId += 1;
+ return mMaxScreenId;
+ }
+
+ public void updateMaxScreenId(long maxScreenId) {
+ mMaxScreenId = maxScreenId;
+ }
+
+ private long initializeMaxScreenId(SQLiteDatabase db) {
+ Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
+
+ // get the result
+ final int maxIdIndex = 0;
+ long id = -1;
+ if (c != null && c.moveToNext()) {
+ id = c.getLong(maxIdIndex);
+ }
+ if (c != null) {
+ c.close();
+ }
+
+ if (id == -1) {
+ throw new RuntimeException("Error: could not query max screen id");
+ }
+
+ return id;
+ }
+
+ /**
+ * Upgrade existing clock and photo frame widgets into their new widget
+ * equivalents.
+ */
+ private void convertWidgets(SQLiteDatabase db) {
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+ final int[] bindSources = new int[] {
+ Favorites.ITEM_TYPE_WIDGET_CLOCK,
+ Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME,
+ Favorites.ITEM_TYPE_WIDGET_SEARCH,
+ };
+
+ final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE, bindSources);
+
+ Cursor c = null;
+
+ db.beginTransaction();
+ try {
+ // Select and iterate through each matching widget
+ c = db.query(TABLE_FAVORITES, new String[] { Favorites._ID, Favorites.ITEM_TYPE },
+ selectWhere, null, null, null, null);
+
+ if (LOGD) Log.d(TAG, "found upgrade cursor count=" + c.getCount());
+
+ final ContentValues values = new ContentValues();
+ while (c != null && c.moveToNext()) {
+ long favoriteId = c.getLong(0);
+ int favoriteType = c.getInt(1);
+
+ // Allocate and update database with new appWidgetId
+ try {
+ int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
+
+ if (LOGD) {
+ Log.d(TAG, "allocated appWidgetId=" + appWidgetId
+ + " for favoriteId=" + favoriteId);
+ }
+ values.clear();
+ values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
+ values.put(Favorites.APPWIDGET_ID, appWidgetId);
+
+ // Original widgets might not have valid spans when upgrading
+ if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
+ values.put(LauncherSettings.Favorites.SPANX, 4);
+ values.put(LauncherSettings.Favorites.SPANY, 1);
+ } else {
+ values.put(LauncherSettings.Favorites.SPANX, 2);
+ values.put(LauncherSettings.Favorites.SPANY, 2);
+ }
+
+ String updateWhere = Favorites._ID + "=" + favoriteId;
+ db.update(TABLE_FAVORITES, values, updateWhere, null);
+
+ if (favoriteType == Favorites.ITEM_TYPE_WIDGET_CLOCK) {
+ // TODO: check return value
+ appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+ new ComponentName("com.android.alarmclock",
+ "com.android.alarmclock.AnalogAppWidgetProvider"));
+ } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
+ // TODO: check return value
+ appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+ new ComponentName("com.android.camera",
+ "com.android.camera.PhotoAppWidgetProvider"));
+ } else if (favoriteType == Favorites.ITEM_TYPE_WIDGET_SEARCH) {
+ // TODO: check return value
+ appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,
+ getSearchWidgetProvider());
+ }
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Problem allocating appWidgetId", ex);
+ }
+ }
+
+ db.setTransactionSuccessful();
+ } catch (SQLException ex) {
+ Log.w(TAG, "Problem while allocating appWidgetIds for existing widgets", ex);
+ } finally {
+ db.endTransaction();
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // Update max item id
+ mMaxItemId = initializeMaxItemId(db);
+ if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId);
+ }
+
+ private static final void beginDocument(XmlPullParser parser, String firstElementName)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+ ", expected " + firstElementName);
+ }
+ }
+
+ /**
+ * Loads the default set of favorite packages from an xml file.
+ *
+ * @param db The database to write the values into
+ * @param filterContainerId The specific container id of items to load
+ */
+ private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
+ Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ ContentValues values = new ContentValues();
+
+ if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
+
+ PackageManager packageManager = mContext.getPackageManager();
+ int i = 0;
+ try {
+ XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ beginDocument(parser, TAG_FAVORITES);
+
+ final int depth = parser.getDepth();
+
+ int type;
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ boolean added = false;
+ final String name = parser.getName();
+
+ if (TAG_INCLUDE.equals(name)) {
+ final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Include);
+
+ final int resId = a.getResourceId(R.styleable.Include_workspace, 0);
+
+ if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s<include workspace=%08x>"),
+ "", resId));
+
+ if (resId != 0 && resId != workspaceResourceId) {
+ // recursively load some more favorites, why not?
+ i += loadFavorites(db, resId);
+ added = false;
+ mMaxItemId = -1;
+ } else {
+ Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
+ }
+
+ a.recycle();
+
+ if (LOGD) Log.v(TAG, String.format(("%" + (2*(depth+1)) + "s</include>"), ""));
+ continue;
+ }
+
+ // Assuming it's a <favorite> at this point
+ TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.Favorite);
+
+ long container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ if (a.hasValue(R.styleable.Favorite_container)) {
+ container = Long.valueOf(a.getString(R.styleable.Favorite_container));
+ }
+
+ String screen = a.getString(R.styleable.Favorite_screen);
+ String x = a.getString(R.styleable.Favorite_x);
+ String y = a.getString(R.styleable.Favorite_y);
+
+ values.clear();
+ values.put(LauncherSettings.Favorites.CONTAINER, container);
+ values.put(LauncherSettings.Favorites.SCREEN, screen);
+ values.put(LauncherSettings.Favorites.CELLX, x);
+ values.put(LauncherSettings.Favorites.CELLY, y);
+
+ if (LOGD) {
+ final String title = a.getString(R.styleable.Favorite_title);
+ final String pkg = a.getString(R.styleable.Favorite_packageName);
+ final String something = title != null ? title : pkg;
+ Log.v(TAG, String.format(
+ ("%" + (2*(depth+1)) + "s<%s%s c=%d s=%s x=%s y=%s>"),
+ "", name,
+ (something == null ? "" : (" \"" + something + "\"")),
+ container, screen, x, y));
+ }
+
+ if (TAG_FAVORITE.equals(name)) {
+ long id = addAppShortcut(db, values, a, packageManager, intent);
+ added = id >= 0;
+ } else if (TAG_SEARCH.equals(name)) {
+ added = addSearchWidget(db, values);
+ } else if (TAG_CLOCK.equals(name)) {
+ added = addClockWidget(db, values);
+ } else if (TAG_APPWIDGET.equals(name)) {
+ added = addAppWidget(parser, attrs, type, db, values, a, packageManager);
+ } else if (TAG_SHORTCUT.equals(name)) {
+ long id = addUriShortcut(db, values, a);
+ added = id >= 0;
+ } else if (TAG_FOLDER.equals(name)) {
+ String title;
+ int titleResId = a.getResourceId(R.styleable.Favorite_title, -1);
+ if (titleResId != -1) {
+ title = mContext.getResources().getString(titleResId);
+ } else {
+ title = mContext.getResources().getString(R.string.folder_name);
+ }
+ values.put(LauncherSettings.Favorites.TITLE, title);
+ long folderId = addFolder(db, values);
+ added = folderId >= 0;
+
+ ArrayList<Long> folderItems = new ArrayList<Long>();
+
+ int folderDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > folderDepth) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String folder_item_name = parser.getName();
+
+ TypedArray ar = mContext.obtainStyledAttributes(attrs,
+ R.styleable.Favorite);
+ values.clear();
+ values.put(LauncherSettings.Favorites.CONTAINER, folderId);
+
+ if (LOGD) {
+ final String pkg = ar.getString(R.styleable.Favorite_packageName);
+ final String uri = ar.getString(R.styleable.Favorite_uri);
+ Log.v(TAG, String.format(("%" + (2*(folderDepth+1)) + "s<%s \"%s\">"), "",
+ folder_item_name, uri != null ? uri : pkg));
+ }
+
+ if (TAG_FAVORITE.equals(folder_item_name) && folderId >= 0) {
+ long id =
+ addAppShortcut(db, values, ar, packageManager, intent);
+ if (id >= 0) {
+ folderItems.add(id);
+ }
+ } else if (TAG_SHORTCUT.equals(folder_item_name) && folderId >= 0) {
+ long id = addUriShortcut(db, values, ar);
+ if (id >= 0) {
+ folderItems.add(id);
+ }
+ } else {
+ throw new RuntimeException("Folders can " +
+ "contain only shortcuts");
+ }
+ ar.recycle();
+ }
+ // We can only have folders with >= 2 items, so we need to remove the
+ // folder and clean up if less than 2 items were included, or some
+ // failed to add, and less than 2 were actually added
+ if (folderItems.size() < 2 && folderId >= 0) {
+ // We just delete the folder and any items that made it
+ deleteId(db, folderId);
+ if (folderItems.size() > 0) {
+ deleteId(db, folderItems.get(0));
+ }
+ added = false;
+ }
+ }
+ if (added) i++;
+ a.recycle();
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Got exception parsing favorites.", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Got exception parsing favorites.", e);
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Got exception parsing favorites.", e);
+ }
+
+ // Update the max item id after we have loaded the database
+ if (mMaxItemId == -1) {
+ mMaxItemId = initializeMaxItemId(db);
+ }
+
+ return i;
+ }
+
+ private long addAppShortcut(SQLiteDatabase db, ContentValues values, TypedArray a,
+ PackageManager packageManager, Intent intent) {
+ long id = -1;
+ ActivityInfo info;
+ String packageName = a.getString(R.styleable.Favorite_packageName);
+ String className = a.getString(R.styleable.Favorite_className);
+ try {
+ ComponentName cn;
+ try {
+ cn = new ComponentName(packageName, className);
+ info = packageManager.getActivityInfo(cn, 0);
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ String[] packages = packageManager.currentToCanonicalPackageNames(
+ new String[] { packageName });
+ cn = new ComponentName(packages[0], className);
+ info = packageManager.getActivityInfo(cn, 0);
+ }
+ id = generateNewItemId();
+ intent.setComponent(cn);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ values.put(Favorites.INTENT, intent.toUri(0));
+ values.put(Favorites.TITLE, info.loadLabel(packageManager).toString());
+ values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPLICATION);
+ values.put(Favorites.SPANX, 1);
+ values.put(Favorites.SPANY, 1);
+ values.put(Favorites._ID, generateNewItemId());
+ if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
+ return -1;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to add favorite: " + packageName +
+ "/" + className, e);
+ }
+ return id;
+ }
+
+ private long addFolder(SQLiteDatabase db, ContentValues values) {
+ values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
+ values.put(Favorites.SPANX, 1);
+ values.put(Favorites.SPANY, 1);
+ long id = generateNewItemId();
+ values.put(Favorites._ID, id);
+ if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) <= 0) {
+ return -1;
+ } else {
+ return id;
+ }
+ }
+
+ private ComponentName getSearchWidgetProvider() {
+ SearchManager searchManager =
+ (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+ ComponentName searchComponent = searchManager.getGlobalSearchActivity();
+ if (searchComponent == null) return null;
+ return getProviderInPackage(searchComponent.getPackageName());
+ }
+
+ /**
+ * Gets an appwidget provider from the given package. If the package contains more than
+ * one appwidget provider, an arbitrary one is returned.
+ */
+ private ComponentName getProviderInPackage(String packageName) {
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+ List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
+ if (providers == null) return null;
+ final int providerCount = providers.size();
+ for (int i = 0; i < providerCount; i++) {
+ ComponentName provider = providers.get(i).provider;
+ if (provider != null && provider.getPackageName().equals(packageName)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private boolean addSearchWidget(SQLiteDatabase db, ContentValues values) {
+ ComponentName cn = getSearchWidgetProvider();
+ return addAppWidget(db, values, cn, 4, 1, null);
+ }
+
+ private boolean addClockWidget(SQLiteDatabase db, ContentValues values) {
+ ComponentName cn = new ComponentName("com.android.alarmclock",
+ "com.android.alarmclock.AnalogAppWidgetProvider");
+ return addAppWidget(db, values, cn, 2, 2, null);
+ }
+
+ private boolean addAppWidget(XmlResourceParser parser, AttributeSet attrs, int type,
+ SQLiteDatabase db, ContentValues values, TypedArray a,
+ PackageManager packageManager) throws XmlPullParserException, IOException {
+
+ String packageName = a.getString(R.styleable.Favorite_packageName);
+ String className = a.getString(R.styleable.Favorite_className);
+
+ if (packageName == null || className == null) {
+ return false;
+ }
+
+ boolean hasPackage = true;
+ ComponentName cn = new ComponentName(packageName, className);
+ try {
+ packageManager.getReceiverInfo(cn, 0);
+ } catch (Exception e) {
+ String[] packages = packageManager.currentToCanonicalPackageNames(
+ new String[] { packageName });
+ cn = new ComponentName(packages[0], className);
+ try {
+ packageManager.getReceiverInfo(cn, 0);
+ } catch (Exception e1) {
+ hasPackage = false;
+ }
+ }
+
+ if (hasPackage) {
+ int spanX = a.getInt(R.styleable.Favorite_spanX, 0);
+ int spanY = a.getInt(R.styleable.Favorite_spanY, 0);
+
+ // Read the extras
+ Bundle extras = new Bundle();
+ int widgetDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > widgetDepth) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ TypedArray ar = mContext.obtainStyledAttributes(attrs, R.styleable.Extra);
+ if (TAG_EXTRA.equals(parser.getName())) {
+ String key = ar.getString(R.styleable.Extra_key);
+ String value = ar.getString(R.styleable.Extra_value);
+ if (key != null && value != null) {
+ extras.putString(key, value);
+ } else {
+ throw new RuntimeException("Widget extras must have a key and value");
+ }
+ } else {
+ throw new RuntimeException("Widgets can contain only extras");
+ }
+ ar.recycle();
+ }
+
+ return addAppWidget(db, values, cn, spanX, spanY, extras);
+ }
+
+ return false;
+ }
+ private boolean addAppWidget(SQLiteDatabase db, ContentValues values, ComponentName cn,
+ int spanX, int spanY, Bundle extras) {
+ boolean allocatedAppWidgets = false;
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+
+ try {
+ int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
+
+ values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
+ values.put(Favorites.SPANX, spanX);
+ values.put(Favorites.SPANY, spanY);
+ values.put(Favorites.APPWIDGET_ID, appWidgetId);
+ values.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
+ values.put(Favorites._ID, generateNewItemId());
+ dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values);
+
+ allocatedAppWidgets = true;
+
+ // TODO: need to check return value
+ appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn);
+
+ // Send a broadcast to configure the widget
+ if (extras != null && !extras.isEmpty()) {
+ Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
+ intent.setComponent(cn);
+ intent.putExtras(extras);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ mContext.sendBroadcast(intent);
+ }
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Problem allocating appWidgetId", ex);
+ }
+
+ return allocatedAppWidgets;
+ }
+
+ private long addUriShortcut(SQLiteDatabase db, ContentValues values,
+ TypedArray a) {
+ Resources r = mContext.getResources();
+
+ final int iconResId = a.getResourceId(R.styleable.Favorite_icon, 0);
+ final int titleResId = a.getResourceId(R.styleable.Favorite_title, 0);
+
+ Intent intent;
+ String uri = null;
+ try {
+ uri = a.getString(R.styleable.Favorite_uri);
+ intent = Intent.parseUri(uri, 0);
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Shortcut has malformed uri: " + uri);
+ return -1; // Oh well
+ }
+
+ if (iconResId == 0 || titleResId == 0) {
+ Log.w(TAG, "Shortcut is missing title or icon resource ID");
+ return -1;
+ }
+
+ long id = generateNewItemId();
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ values.put(Favorites.INTENT, intent.toUri(0));
+ values.put(Favorites.TITLE, r.getString(titleResId));
+ values.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_SHORTCUT);
+ values.put(Favorites.SPANX, 1);
+ values.put(Favorites.SPANY, 1);
+ values.put(Favorites.ICON_TYPE, Favorites.ICON_TYPE_RESOURCE);
+ values.put(Favorites.ICON_PACKAGE, mContext.getPackageName());
+ values.put(Favorites.ICON_RESOURCE, r.getResourceName(iconResId));
+ values.put(Favorites._ID, id);
+
+ if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, values) < 0) {
+ return -1;
+ }
+ return id;
+ }
+ }
+
+ /**
+ * Build a query string that will match any row where the column matches
+ * anything in the values list.
+ */
+ static String buildOrWhereString(String column, int[] values) {
+ StringBuilder selectWhere = new StringBuilder();
+ for (int i = values.length - 1; i >= 0; i--) {
+ selectWhere.append(column).append("=").append(values[i]);
+ if (i > 0) {
+ selectWhere.append(" OR ");
+ }
+ }
+ return selectWhere.toString();
+ }
+
+ static class SqlArguments {
+ public final String table;
+ public final String where;
+ public final String[] args;
+
+ SqlArguments(Uri url, String where, String[] args) {
+ if (url.getPathSegments().size() == 1) {
+ this.table = url.getPathSegments().get(0);
+ this.where = where;
+ this.args = args;
+ } else if (url.getPathSegments().size() != 2) {
+ throw new IllegalArgumentException("Invalid URI: " + url);
+ } else if (!TextUtils.isEmpty(where)) {
+ throw new UnsupportedOperationException("WHERE clause not supported: " + url);
+ } else {
+ this.table = url.getPathSegments().get(0);
+ this.where = "_id=" + ContentUris.parseId(url);
+ this.args = null;
+ }
+ }
+
+ SqlArguments(Uri url) {
+ if (url.getPathSegments().size() == 1) {
+ table = url.getPathSegments().get(0);
+ where = null;
+ args = null;
+ } else {
+ throw new IllegalArgumentException("Invalid URI: " + url);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
new file mode 100644
index 0000000..988e5ef
--- /dev/null
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+/**
+ * Settings related utilities.
+ */
+class LauncherSettings {
+ /** Columns required on table staht will be subject to backup and restore. */
+ static interface ChangeLogColumns extends BaseColumns {
+ /**
+ * The time of the last update to this row.
+ * <P>Type: INTEGER</P>
+ */
+ static final String MODIFIED = "modified";
+ }
+
+ static interface BaseLauncherColumns extends ChangeLogColumns {
+ /**
+ * Descriptive name of the gesture that can be displayed to the user.
+ * <P>Type: TEXT</P>
+ */
+ static final String TITLE = "title";
+
+ /**
+ * The Intent URL of the gesture, describing what it points to. This
+ * value is given to {@link android.content.Intent#parseUri(String, int)} to create
+ * an Intent that can be launched.
+ * <P>Type: TEXT</P>
+ */
+ static final String INTENT = "intent";
+
+ /**
+ * The type of the gesture
+ *
+ * <P>Type: INTEGER</P>
+ */
+ static final String ITEM_TYPE = "itemType";
+
+ /**
+ * The gesture is an application
+ */
+ static final int ITEM_TYPE_APPLICATION = 0;
+
+ /**
+ * The gesture is an application created shortcut
+ */
+ static final int ITEM_TYPE_SHORTCUT = 1;
+
+ /**
+ * The icon type.
+ * <P>Type: INTEGER</P>
+ */
+ static final String ICON_TYPE = "iconType";
+
+ /**
+ * The icon is a resource identified by a package name and an integer id.
+ */
+ static final int ICON_TYPE_RESOURCE = 0;
+
+ /**
+ * The icon is a bitmap.
+ */
+ static final int ICON_TYPE_BITMAP = 1;
+
+ /**
+ * The icon package name, if icon type is ICON_TYPE_RESOURCE.
+ * <P>Type: TEXT</P>
+ */
+ static final String ICON_PACKAGE = "iconPackage";
+
+ /**
+ * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
+ * <P>Type: TEXT</P>
+ */
+ static final String ICON_RESOURCE = "iconResource";
+
+ /**
+ * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
+ * <P>Type: BLOB</P>
+ */
+ static final String ICON = "icon";
+ }
+
+ /**
+ * Workspace Screens.
+ *
+ * Tracks the order of workspace screens.
+ */
+ static final class WorkspaceScreens implements ChangeLogColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ static final Uri CONTENT_URI = Uri.parse("content://" +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_WORKSPACE_SCREENS +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+ /**
+ * The rank of this screen -- ie. how it is ordered relative to the other screens.
+ * <P>Type: INTEGER</P>
+ */
+ static final String SCREEN_RANK = "screenRank";
+ }
+
+ /**
+ * Favorites.
+ */
+ static final class Favorites implements BaseLauncherColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ static final Uri CONTENT_URI = Uri.parse("content://" +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+ /**
+ * The content:// style URL for this table
+ */
+ static final Uri OLD_CONTENT_URI = Uri.parse("content://" +
+ LauncherProvider.OLD_AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+ /**
+ * The content:// style URL for this table. When this Uri is used, no notification is
+ * sent if the content changes.
+ */
+ static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
+ LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES +
+ "?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
+
+ /**
+ * The content:// style URL for a given row, identified by its id.
+ *
+ * @param id The row id.
+ * @param notify True to send a notification is the content changes.
+ *
+ * @return The unique content URL for the specified row.
+ */
+ static Uri getContentUri(long id, boolean notify) {
+ return Uri.parse("content://" + LauncherProvider.AUTHORITY +
+ "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" +
+ LauncherProvider.PARAMETER_NOTIFY + "=" + notify);
+ }
+
+ /**
+ * The container holding the favorite
+ * <P>Type: INTEGER</P>
+ */
+ static final String CONTAINER = "container";
+
+ /**
+ * The icon is a resource identified by a package name and an integer id.
+ */
+ static final int CONTAINER_DESKTOP = -100;
+ static final int CONTAINER_HOTSEAT = -101;
+
+ /**
+ * The screen holding the favorite (if container is CONTAINER_DESKTOP)
+ * <P>Type: INTEGER</P>
+ */
+ static final String SCREEN = "screen";
+
+ /**
+ * The X coordinate of the cell holding the favorite
+ * (if container is CONTAINER_HOTSEAT or CONTAINER_HOTSEAT)
+ * <P>Type: INTEGER</P>
+ */
+ static final String CELLX = "cellX";
+
+ /**
+ * The Y coordinate of the cell holding the favorite
+ * (if container is CONTAINER_DESKTOP)
+ * <P>Type: INTEGER</P>
+ */
+ static final String CELLY = "cellY";
+
+ /**
+ * The X span of the cell holding the favorite
+ * <P>Type: INTEGER</P>
+ */
+ static final String SPANX = "spanX";
+
+ /**
+ * The Y span of the cell holding the favorite
+ * <P>Type: INTEGER</P>
+ */
+ static final String SPANY = "spanY";
+
+ /**
+ * The favorite is a user created folder
+ */
+ static final int ITEM_TYPE_FOLDER = 2;
+
+ /**
+ * The favorite is a live folder
+ *
+ * Note: live folders can no longer be added to Launcher, and any live folders which
+ * exist within the launcher database will be ignored when loading. That said, these
+ * entries in the database may still exist, and are not automatically stripped.
+ */
+ static final int ITEM_TYPE_LIVE_FOLDER = 3;
+
+ /**
+ * The favorite is a widget
+ */
+ static final int ITEM_TYPE_APPWIDGET = 4;
+
+ /**
+ * The favorite is a clock
+ */
+ static final int ITEM_TYPE_WIDGET_CLOCK = 1000;
+
+ /**
+ * The favorite is a search widget
+ */
+ static final int ITEM_TYPE_WIDGET_SEARCH = 1001;
+
+ /**
+ * The favorite is a photo frame
+ */
+ static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002;
+
+ /**
+ * The appWidgetId of the widget
+ *
+ * <P>Type: INTEGER</P>
+ */
+ static final String APPWIDGET_ID = "appWidgetId";
+
+ /**
+ * The ComponentName of the widget provider
+ *
+ * <P>Type: STRING</P>
+ */
+ public static final String APPWIDGET_PROVIDER = "appWidgetProvider";
+
+ /**
+ * Indicates whether this favorite is an application-created shortcut or not.
+ * If the value is 0, the favorite is not an application-created shortcut, if the
+ * value is 1, it is an application-created shortcut.
+ * <P>Type: INTEGER</P>
+ */
+ @Deprecated
+ static final String IS_SHORTCUT = "isShortcut";
+
+ /**
+ * The URI associated with the favorite. It is used, for instance, by
+ * live folders to find the content provider.
+ * <P>Type: TEXT</P>
+ */
+ static final String URI = "uri";
+
+ /**
+ * The display mode if the item is a live folder.
+ * <P>Type: INTEGER</P>
+ *
+ * @see android.provider.LiveFolders#DISPLAY_MODE_GRID
+ * @see android.provider.LiveFolders#DISPLAY_MODE_LIST
+ */
+ static final String DISPLAY_MODE = "displayMode";
+ }
+}
diff --git a/src/com/android/launcher3/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
new file mode 100644
index 0000000..4cafbbf
--- /dev/null
+++ b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2012 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.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.TimeInterpolator;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+
+public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
+ enum Properties {
+ TRANSLATION_X,
+ TRANSLATION_Y,
+ SCALE_X,
+ SCALE_Y,
+ ROTATION_Y,
+ ALPHA,
+ START_DELAY,
+ DURATION,
+ INTERPOLATOR,
+ WITH_LAYER
+ }
+ EnumSet<Properties> mPropertiesToSet = EnumSet.noneOf(Properties.class);
+ ViewPropertyAnimator mViewPropertyAnimator;
+ View mTarget;
+
+ float mTranslationX;
+ float mTranslationY;
+ float mScaleX;
+ float mScaleY;
+ float mRotationY;
+ float mAlpha;
+ long mStartDelay;
+ long mDuration;
+ TimeInterpolator mInterpolator;
+ ArrayList<Animator.AnimatorListener> mListeners;
+ boolean mRunning = false;
+ FirstFrameAnimatorHelper mFirstFrameHelper;
+
+ public LauncherViewPropertyAnimator(View target) {
+ mTarget = target;
+ mListeners = new ArrayList<Animator.AnimatorListener>();
+ }
+
+ @Override
+ public void addListener(Animator.AnimatorListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void cancel() {
+ if (mViewPropertyAnimator != null) {
+ mViewPropertyAnimator.cancel();
+ }
+ }
+
+ @Override
+ public Animator clone() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void end() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public ArrayList<Animator.AnimatorListener> getListeners() {
+ return mListeners;
+ }
+
+ @Override
+ public long getStartDelay() {
+ return mStartDelay;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationCancel(this);
+ }
+ mRunning = false;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationEnd(this);
+ }
+ mRunning = false;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationRepeat(this);
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // This is the first time we get a handle to the internal ValueAnimator
+ // used by the ViewPropertyAnimator.
+ mFirstFrameHelper.onAnimationStart(animation);
+
+ for (int i = 0; i < mListeners.size(); i++) {
+ Animator.AnimatorListener listener = mListeners.get(i);
+ listener.onAnimationStart(this);
+ }
+ mRunning = true;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ @Override
+ public boolean isStarted() {
+ return mViewPropertyAnimator != null;
+ }
+
+ @Override
+ public void removeAllListeners() {
+ mListeners.clear();
+ }
+
+ @Override
+ public void removeListener(Animator.AnimatorListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public Animator setDuration(long duration) {
+ mPropertiesToSet.add(Properties.DURATION);
+ mDuration = duration;
+ return this;
+ }
+
+ @Override
+ public void setInterpolator(TimeInterpolator value) {
+ mPropertiesToSet.add(Properties.INTERPOLATOR);
+ mInterpolator = value;
+ }
+
+ @Override
+ public void setStartDelay(long startDelay) {
+ mPropertiesToSet.add(Properties.START_DELAY);
+ mStartDelay = startDelay;
+ }
+
+ @Override
+ public void setTarget(Object target) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setupEndValues() {
+
+ }
+
+ @Override
+ public void setupStartValues() {
+ }
+
+ @Override
+ public void start() {
+ mViewPropertyAnimator = mTarget.animate();
+
+ // FirstFrameAnimatorHelper hooks itself up to the updates on the animator,
+ // and then adjusts the play time to keep the first two frames jank-free
+ mFirstFrameHelper = new FirstFrameAnimatorHelper(mViewPropertyAnimator, mTarget);
+
+ if (mPropertiesToSet.contains(Properties.TRANSLATION_X)) {
+ mViewPropertyAnimator.translationX(mTranslationX);
+ }
+ if (mPropertiesToSet.contains(Properties.TRANSLATION_Y)) {
+ mViewPropertyAnimator.translationY(mTranslationY);
+ }
+ if (mPropertiesToSet.contains(Properties.SCALE_X)) {
+ mViewPropertyAnimator.scaleX(mScaleX);
+ }
+ if (mPropertiesToSet.contains(Properties.ROTATION_Y)) {
+ mViewPropertyAnimator.rotationY(mRotationY);
+ }
+ if (mPropertiesToSet.contains(Properties.SCALE_Y)) {
+ mViewPropertyAnimator.scaleY(mScaleY);
+ }
+ if (mPropertiesToSet.contains(Properties.ALPHA)) {
+ mViewPropertyAnimator.alpha(mAlpha);
+ }
+ if (mPropertiesToSet.contains(Properties.START_DELAY)) {
+ mViewPropertyAnimator.setStartDelay(mStartDelay);
+ }
+ if (mPropertiesToSet.contains(Properties.DURATION)) {
+ mViewPropertyAnimator.setDuration(mDuration);
+ }
+ if (mPropertiesToSet.contains(Properties.INTERPOLATOR)) {
+ mViewPropertyAnimator.setInterpolator(mInterpolator);
+ }
+ if (mPropertiesToSet.contains(Properties.WITH_LAYER)) {
+ mViewPropertyAnimator.withLayer();
+ }
+ mViewPropertyAnimator.setListener(this);
+ mViewPropertyAnimator.start();
+ LauncherAnimUtils.cancelOnDestroyActivity(this);
+ }
+
+ public LauncherViewPropertyAnimator translationX(float value) {
+ mPropertiesToSet.add(Properties.TRANSLATION_X);
+ mTranslationX = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator translationY(float value) {
+ mPropertiesToSet.add(Properties.TRANSLATION_Y);
+ mTranslationY = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator scaleX(float value) {
+ mPropertiesToSet.add(Properties.SCALE_X);
+ mScaleX = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator scaleY(float value) {
+ mPropertiesToSet.add(Properties.SCALE_Y);
+ mScaleY = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator rotationY(float value) {
+ mPropertiesToSet.add(Properties.ROTATION_Y);
+ mRotationY = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator alpha(float value) {
+ mPropertiesToSet.add(Properties.ALPHA);
+ mAlpha = value;
+ return this;
+ }
+
+ public LauncherViewPropertyAnimator withLayer() {
+ mPropertiesToSet.add(Properties.WITH_LAYER);
+ return this;
+ }
+}
diff --git a/src/com/android/launcher3/LiveWallpaperListAdapter.java b/src/com/android/launcher3/LiveWallpaperListAdapter.java
new file mode 100644
index 0000000..54e0af7
--- /dev/null
+++ b/src/com/android/launcher3/LiveWallpaperListAdapter.java
@@ -0,0 +1,203 @@
+/*
+ * 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.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.service.wallpaper.WallpaperService;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class LiveWallpaperListAdapter extends BaseAdapter implements ListAdapter {
+ private static final String LOG_TAG = "LiveWallpaperListAdapter";
+
+ private final LayoutInflater mInflater;
+ private final PackageManager mPackageManager;
+
+ private List<LiveWallpaperTile> mWallpapers;
+
+ @SuppressWarnings("unchecked")
+ public LiveWallpaperListAdapter(Context context) {
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mPackageManager = context.getPackageManager();
+
+ List<ResolveInfo> list = mPackageManager.queryIntentServices(
+ new Intent(WallpaperService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+
+ mWallpapers = new ArrayList<LiveWallpaperTile>();
+
+ new LiveWallpaperEnumerator(context).execute(list);
+ }
+
+ public int getCount() {
+ if (mWallpapers == null) {
+ return 0;
+ }
+ return mWallpapers.size();
+ }
+
+ public LiveWallpaperTile getItem(int position) {
+ return mWallpapers.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.wallpaper_picker_live_wallpaper_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
+
+ LiveWallpaperTile wallpaperInfo = mWallpapers.get(position);
+ wallpaperInfo.setView(view);
+ ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+ ImageView icon = (ImageView) view.findViewById(R.id.wallpaper_icon);
+ if (wallpaperInfo.mThumbnail != null) {
+ image.setImageDrawable(wallpaperInfo.mThumbnail);
+ icon.setVisibility(View.GONE);
+ } else {
+ icon.setImageDrawable(wallpaperInfo.mInfo.loadIcon(mPackageManager));
+ icon.setVisibility(View.VISIBLE);
+ }
+
+ TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
+ label.setText(wallpaperInfo.mInfo.loadLabel(mPackageManager));
+
+ return view;
+ }
+
+ public static class LiveWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+ private Drawable mThumbnail;
+ private WallpaperInfo mInfo;
+ public LiveWallpaperTile(Drawable thumbnail, WallpaperInfo info, Intent intent) {
+ mThumbnail = thumbnail;
+ mInfo = info;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
+ preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
+ mInfo.getComponent());
+ a.onLiveWallpaperPickerLaunch();
+ Utilities.startActivityForResultSafely(
+ a, preview, WallpaperPickerActivity.PICK_LIVE_WALLPAPER);
+ }
+ }
+
+ private class LiveWallpaperEnumerator extends
+ AsyncTask<List<ResolveInfo>, LiveWallpaperTile, Void> {
+ private Context mContext;
+ private int mWallpaperPosition;
+
+ public LiveWallpaperEnumerator(Context context) {
+ super();
+ mContext = context;
+ mWallpaperPosition = 0;
+ }
+
+ @Override
+ protected Void doInBackground(List<ResolveInfo>... params) {
+ final PackageManager packageManager = mContext.getPackageManager();
+
+ List<ResolveInfo> list = params[0];
+
+ Collections.sort(list, new Comparator<ResolveInfo>() {
+ final Collator mCollator;
+
+ {
+ mCollator = Collator.getInstance();
+ }
+
+ public int compare(ResolveInfo info1, ResolveInfo info2) {
+ return mCollator.compare(info1.loadLabel(packageManager),
+ info2.loadLabel(packageManager));
+ }
+ });
+
+ for (ResolveInfo resolveInfo : list) {
+ WallpaperInfo info = null;
+ try {
+ info = new WallpaperInfo(mContext, resolveInfo);
+ } catch (XmlPullParserException e) {
+ Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+ continue;
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e);
+ continue;
+ }
+
+
+ Drawable thumb = info.loadThumbnail(packageManager);
+ Intent launchIntent = new Intent(WallpaperService.SERVICE_INTERFACE);
+ launchIntent.setClassName(info.getPackageName(), info.getServiceName());
+ LiveWallpaperTile wallpaper = new LiveWallpaperTile(thumb, info, launchIntent);
+ publishProgress(wallpaper);
+ }
+ // Send a null object to show loading is finished
+ publishProgress((LiveWallpaperTile) null);
+
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(LiveWallpaperTile...infos) {
+ for (LiveWallpaperTile info : infos) {
+ if (info == null) {
+ LiveWallpaperListAdapter.this.notifyDataSetChanged();
+ break;
+ }
+ info.mThumbnail.setDither(true);
+ if (mWallpaperPosition < mWallpapers.size()) {
+ mWallpapers.set(mWallpaperPosition, info);
+ } else {
+ mWallpapers.add(info);
+ }
+ mWallpaperPosition++;
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/MemoryDumpActivity.java b/src/com/android/launcher3/MemoryDumpActivity.java
new file mode 100644
index 0000000..d79be80
--- /dev/null
+++ b/src/com/android/launcher3/MemoryDumpActivity.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2013 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.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.*;
+import android.util.Log;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public class MemoryDumpActivity extends Activity {
+ private static final String TAG = "MemoryDumpActivity";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ public static String zipUp(ArrayList<String> paths) {
+ final int BUFSIZ = 256 * 1024; // 256K
+ final byte[] buf = new byte[BUFSIZ];
+ final String zipfilePath = String.format("%s/hprof-%d.zip",
+ Environment.getExternalStorageDirectory(),
+ System.currentTimeMillis());
+ ZipOutputStream zos = null;
+ try {
+ OutputStream os = new FileOutputStream(zipfilePath);
+ zos = new ZipOutputStream(new BufferedOutputStream(os));
+ for (String filename : paths) {
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(new FileInputStream(filename));
+ ZipEntry entry = new ZipEntry(filename);
+ zos.putNextEntry(entry);
+ int len;
+ while ( 0 < (len = is.read(buf, 0, BUFSIZ)) ) {
+ zos.write(buf, 0, len);
+ }
+ zos.closeEntry();
+ } finally {
+ is.close();
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "error zipping up profile data", e);
+ return null;
+ } finally {
+ if (zos != null) {
+ try {
+ zos.close();
+ } catch (IOException e) {
+ // ugh, whatever
+ }
+ }
+ }
+ return zipfilePath;
+ }
+
+ public static void dumpHprofAndShare(final Context context, MemoryTracker tracker) {
+ final StringBuilder body = new StringBuilder();
+
+ final ArrayList<String> paths = new ArrayList<String>();
+ final int myPid = android.os.Process.myPid();
+
+ final int[] pids_orig = tracker.getTrackedProcesses();
+ final int[] pids_copy = Arrays.copyOf(pids_orig, pids_orig.length);
+ for (int pid : pids_copy) {
+ MemoryTracker.ProcessMemInfo info = tracker.getMemInfo(pid);
+ if (info != null) {
+ body.append("pid ").append(pid).append(":")
+ .append(" up=").append(info.getUptime())
+ .append(" pss=").append(info.currentPss)
+ .append(" uss=").append(info.currentUss)
+ .append("\n");
+ }
+ if (pid == myPid) {
+ final String path = String.format("%s/launcher-memory-%d.ahprof",
+ Environment.getExternalStorageDirectory(),
+ pid);
+ Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
+ try {
+ android.os.Debug.dumpHprofData(path); // will block
+ } catch (IOException e) {
+ Log.e(TAG, "error dumping memory:", e);
+ }
+ paths.add(path);
+ }
+ }
+
+ String zipfile = zipUp(paths);
+
+ if (zipfile == null) return;
+
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ shareIntent.setType("application/zip");
+
+ final PackageManager pm = context.getPackageManager();
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump (%d)", myPid));
+ String appVersion;
+ try {
+ appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ appVersion = "?";
+ }
+
+ body.append("\nApp version: ").append(appVersion).append("\nBuild: ").append(Build.DISPLAY).append("\n");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
+
+ final File pathFile = new File(zipfile);
+ final Uri pathUri = Uri.fromFile(pathFile);
+
+ shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri);
+ context.startActivity(shareIntent);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ startDump(this, new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
+ }
+
+ public static void startDump(final Context context) {
+ startDump(context, null);
+ }
+
+ public static void startDump(final Context context, final Runnable andThen) {
+ final ServiceConnection connection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.v(TAG, "service connected, dumping...");
+ dumpHprofAndShare(context,
+ ((MemoryTracker.MemoryTrackerInterface) service).getService());
+ context.unbindService(this);
+ if (andThen != null) andThen.run();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ }
+ };
+ Log.v(TAG, "attempting to bind to memory tracker");
+ context.bindService(new Intent(context, MemoryTracker.class),
+ connection, Context.BIND_AUTO_CREATE);
+ }
+}
diff --git a/src/com/android/launcher3/MemoryTracker.java b/src/com/android/launcher3/MemoryTracker.java
new file mode 100644
index 0000000..2d37c80
--- /dev/null
+++ b/src/com/android/launcher3/MemoryTracker.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2013 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.app.ActivityManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.*;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MemoryTracker extends Service {
+ public static final String TAG = MemoryTracker.class.getSimpleName();
+ public static final String ACTION_START_TRACKING = "com.android.launcher3.action.START_TRACKING";
+
+ private static final long UPDATE_RATE = 5000;
+
+ private static final int MSG_START = 1;
+ private static final int MSG_STOP = 2;
+ private static final int MSG_UPDATE = 3;
+
+ public static class ProcessMemInfo {
+ public int pid;
+ public String name;
+ public long startTime;
+ public long currentPss, currentUss;
+ public long[] pss = new long[256];
+ public long[] uss = new long[256];
+ //= new Meminfo[(int) (30 * 60 / (UPDATE_RATE / 1000))]; // 30 minutes
+ public long max = 1;
+ public int head = 0;
+ public ProcessMemInfo(int pid, String name, long start) {
+ this.pid = pid;
+ this.name = name;
+ this.startTime = start;
+ }
+ public long getUptime() {
+ return System.currentTimeMillis() - startTime;
+ }
+ };
+ public final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<ProcessMemInfo>();
+ public final ArrayList<Long> mPids = new ArrayList<Long>();
+ private int[] mPidsArray = new int[0];
+ private final Object mLock = new Object();
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case MSG_START:
+ mHandler.removeMessages(MSG_UPDATE);
+ mHandler.sendEmptyMessage(MSG_UPDATE);
+ break;
+ case MSG_STOP:
+ mHandler.removeMessages(MSG_UPDATE);
+ break;
+ case MSG_UPDATE:
+ update();
+ mHandler.removeMessages(MSG_UPDATE);
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
+ break;
+ }
+ }
+ };
+
+ ActivityManager mAm;
+
+ public static void startTrackingMe(Context context, String name) {
+ context.startService(new Intent(context, MemoryTracker.class)
+ .setAction(MemoryTracker.ACTION_START_TRACKING)
+ .putExtra("pid", android.os.Process.myPid())
+ .putExtra("name", name)
+ );
+ }
+
+ public ProcessMemInfo getMemInfo(int pid) {
+ return mData.get(pid);
+ }
+
+ public int[] getTrackedProcesses() {
+ return mPidsArray;
+ }
+
+ public void startTrackingProcess(int pid, String name, long start) {
+ synchronized (mLock) {
+ final Long lpid = new Long(pid);
+
+ if (mPids.contains(lpid)) return;
+
+ mPids.add(lpid);
+ updatePidsArrayL();
+
+ mData.put(pid, new ProcessMemInfo(pid, name, start));
+ }
+ }
+
+ void updatePidsArrayL() {
+ final int N = mPids.size();
+ mPidsArray = new int[N];
+ StringBuffer sb = new StringBuffer("Now tracking processes: ");
+ for (int i=0; i<N; i++) {
+ final int p = mPids.get(i).intValue();
+ mPidsArray[i] = p;
+ sb.append(p); sb.append(" ");
+ }
+ Log.v(TAG, sb.toString());
+ }
+
+ void update() {
+ synchronized (mLock) {
+ Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray);
+ for (int i=0; i<dinfos.length; i++) {
+ Debug.MemoryInfo dinfo = dinfos[i];
+ if (i > mPids.size()) {
+ Log.e(TAG, "update: unknown process info received: " + dinfo);
+ break;
+ }
+ final long pid = mPids.get(i).intValue();
+ final ProcessMemInfo info = mData.get(pid);
+ info.head = (info.head+1) % info.pss.length;
+ info.pss[info.head] = info.currentPss = dinfo.getTotalPss();
+ info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty();
+ if (info.currentPss > info.max) info.max = info.currentPss;
+ if (info.currentUss > info.max) info.max = info.currentUss;
+ // Log.v(TAG, "update: pid " + pid + " pss=" + info.currentPss + " uss=" + info.currentUss);
+ if (info.currentPss == 0) {
+ Log.v(TAG, "update: pid " + pid + " has pss=0, it probably died");
+ mData.remove(pid);
+ }
+ }
+ for (int i=mPids.size()-1; i>=0; i--) {
+ final long pid = mPids.get(i).intValue();
+ if (mData.get(pid) == null) {
+ mPids.remove(i);
+ updatePidsArrayL();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ mAm = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+
+ // catch up in case we crashed but other processes are still running
+ List<ActivityManager.RunningServiceInfo> svcs = mAm.getRunningServices(256);
+ for (ActivityManager.RunningServiceInfo svc : svcs) {
+ if (svc.service.getPackageName().equals(getPackageName())) {
+ Log.v(TAG, "discovered running service: " + svc.process + " (" + svc.pid + ")");
+ startTrackingProcess(svc.pid, svc.process,
+ System.currentTimeMillis() - (SystemClock.elapsedRealtime() - svc.activeSince));
+ }
+ }
+
+ List<ActivityManager.RunningAppProcessInfo> procs = mAm.getRunningAppProcesses();
+ for (ActivityManager.RunningAppProcessInfo proc : procs) {
+ final String pname = proc.processName;
+ if (pname.startsWith(getPackageName())) {
+ Log.v(TAG, "discovered other running process: " + pname + " (" + proc.pid + ")");
+ startTrackingProcess(proc.pid, pname, System.currentTimeMillis());
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ mHandler.sendEmptyMessage(MSG_STOP);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.v(TAG, "Received start id " + startId + ": " + intent);
+
+ if (intent != null) {
+ if (ACTION_START_TRACKING.equals(intent.getAction())) {
+ final int pid = intent.getIntExtra("pid", -1);
+ final String name = intent.getStringExtra("name");
+ final long start = intent.getLongExtra("start", System.currentTimeMillis());
+ startTrackingProcess(pid, name, start);
+ }
+ }
+
+ mHandler.sendEmptyMessage(MSG_START);
+
+ return START_STICKY;
+ }
+
+ public class MemoryTrackerInterface extends Binder {
+ MemoryTracker getService() {
+ return MemoryTracker.this;
+ }
+ }
+
+ private final IBinder mBinder = new MemoryTrackerInterface();
+
+ public IBinder onBind(Intent intent) {
+ mHandler.sendEmptyMessage(MSG_START);
+
+ return mBinder;
+ }
+}
diff --git a/src/com/android/launcher3/PackageChangedReceiver.java b/src/com/android/launcher3/PackageChangedReceiver.java
new file mode 100644
index 0000000..e59f6d8
--- /dev/null
+++ b/src/com/android/launcher3/PackageChangedReceiver.java
@@ -0,0 +1,21 @@
+package com.android.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class PackageChangedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ final String packageName = intent.getData().getSchemeSpecificPart();
+
+ if (packageName == null || packageName.length() == 0) {
+ // they sent us a bad intent
+ return;
+ }
+ // in rare cases the receiver races with the application to set up LauncherAppState
+ LauncherAppState.setApplicationContext(context.getApplicationContext());
+ LauncherAppState app = LauncherAppState.getInstance();
+ WidgetPreviewLoader.removePackageFromDb(app.getWidgetPreviewCacheDb(), packageName);
+ }
+}
diff --git a/src/com/android/launcher3/PageIndicator.java b/src/com/android/launcher3/PageIndicator.java
new file mode 100644
index 0000000..08e5f72
--- /dev/null
+++ b/src/com/android/launcher3/PageIndicator.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2011 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.LayoutTransition;
+import android.animation.TimeInterpolator;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+
+public class PageIndicator extends LinearLayout {
+ @SuppressWarnings("unused")
+ private static final String TAG = "PageIndicator";
+ // Want this to look good? Keep it odd
+ private static final boolean MODULATE_ALPHA_ENABLED = false;
+
+ private LayoutInflater mLayoutInflater;
+ private int[] mWindowRange = new int[2];
+ private int mMaxWindowSize;
+
+ private ArrayList<PageIndicatorMarker> mMarkers =
+ new ArrayList<PageIndicatorMarker>();
+ private int mActiveMarkerIndex;
+
+ public static class PageMarkerResources {
+ int activeId;
+ int inactiveId;
+
+ public PageMarkerResources() {
+ activeId = R.drawable.ic_pageindicator_current;
+ inactiveId = R.drawable.ic_pageindicator_default;
+ }
+ public PageMarkerResources(int aId, int iaId) {
+ activeId = aId;
+ inactiveId = iaId;
+ }
+ }
+
+ public PageIndicator(Context context) {
+ this(context, null);
+ }
+
+ public PageIndicator(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PageIndicator(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.PageIndicator, defStyle, 0);
+ mMaxWindowSize = a.getInteger(R.styleable.PageIndicator_windowSize, 15);
+ mWindowRange[0] = 0;
+ mWindowRange[1] = 0;
+ mLayoutInflater = LayoutInflater.from(context);
+ a.recycle();
+
+ // Set the layout transition properties
+ LayoutTransition transition = getLayoutTransition();
+ transition.setDuration(175);
+ }
+
+ private void enableLayoutTransitions() {
+ LayoutTransition transition = getLayoutTransition();
+ transition.enableTransitionType(LayoutTransition.APPEARING);
+ transition.enableTransitionType(LayoutTransition.DISAPPEARING);
+ transition.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
+ transition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ }
+
+ private void disableLayoutTransitions() {
+ LayoutTransition transition = getLayoutTransition();
+ transition.disableTransitionType(LayoutTransition.APPEARING);
+ transition.disableTransitionType(LayoutTransition.DISAPPEARING);
+ transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+ transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ }
+
+ void offsetWindowCenterTo(int activeIndex, boolean allowAnimations) {
+ if (activeIndex < 0) {
+ new Throwable().printStackTrace();
+ }
+ int windowSize = Math.min(mMarkers.size(), mMaxWindowSize);
+ int hWindowSize = (int) windowSize / 2;
+ float hfWindowSize = windowSize / 2f;
+ int windowStart = Math.max(0, activeIndex - hWindowSize);
+ int windowEnd = Math.min(mMarkers.size(), windowStart + mMaxWindowSize);
+ windowStart = windowEnd - Math.min(mMarkers.size(), windowSize);
+ int windowMid = windowStart + (windowEnd - windowStart) / 2;
+ boolean windowAtStart = (windowStart == 0);
+ boolean windowAtEnd = (windowEnd == mMarkers.size());
+ boolean windowMoved = (mWindowRange[0] != windowStart) ||
+ (mWindowRange[1] != windowEnd);
+
+ if (!allowAnimations) {
+ disableLayoutTransitions();
+ }
+
+ // Remove all the previous children that are no longer in the window
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ PageIndicatorMarker marker = (PageIndicatorMarker) getChildAt(i);
+ int markerIndex = mMarkers.indexOf(marker);
+ if (markerIndex < windowStart || markerIndex >= windowEnd) {
+ removeView(marker);
+ }
+ }
+
+ // Add all the new children that belong in the window
+ for (int i = 0; i < mMarkers.size(); ++i) {
+ PageIndicatorMarker marker = (PageIndicatorMarker) mMarkers.get(i);
+ if (windowStart <= i && i < windowEnd) {
+ if (indexOfChild(marker) < 0) {
+ addView(marker, i - windowStart);
+ }
+ if (i == activeIndex) {
+ marker.activate(windowMoved);
+ } else {
+ marker.inactivate(windowMoved);
+ }
+ } else {
+ marker.inactivate(true);
+ }
+
+ if (MODULATE_ALPHA_ENABLED) {
+ // Update the marker's alpha
+ float alpha = 1f;
+ if (mMarkers.size() > windowSize) {
+ if ((windowAtStart && i > hWindowSize) ||
+ (windowAtEnd && i < (mMarkers.size() - hWindowSize)) ||
+ (!windowAtStart && !windowAtEnd)) {
+ alpha = 1f - Math.abs((i - windowMid) / hfWindowSize);
+ }
+ }
+ marker.animate().alpha(alpha).setDuration(500).start();
+ }
+ }
+
+ if (!allowAnimations) {
+ enableLayoutTransitions();
+ }
+
+ mWindowRange[0] = windowStart;
+ mWindowRange[1] = windowEnd;
+ }
+
+ void addMarker(int index, PageMarkerResources marker, boolean allowAnimations) {
+ index = Math.max(0, Math.min(index, mMarkers.size()));
+
+ PageIndicatorMarker m =
+ (PageIndicatorMarker) mLayoutInflater.inflate(R.layout.page_indicator_marker,
+ this, false);
+ m.setMarkerDrawables(marker.activeId, marker.inactiveId);
+
+ mMarkers.add(index, m);
+ offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
+ }
+ void addMarkers(ArrayList<PageMarkerResources> markers, boolean allowAnimations) {
+ for (int i = 0; i < markers.size(); ++i) {
+ addMarker(Integer.MAX_VALUE, markers.get(i), allowAnimations);
+ }
+ }
+
+ void updateMarker(int index, PageMarkerResources marker) {
+ PageIndicatorMarker m = mMarkers.get(index);
+ m.setMarkerDrawables(marker.activeId, marker.inactiveId);
+ }
+
+ void removeMarker(int index, boolean allowAnimations) {
+ if (mMarkers.size() > 0) {
+ index = Math.max(0, Math.min(mMarkers.size() - 1, index));
+ mMarkers.remove(index);
+ offsetWindowCenterTo(mActiveMarkerIndex, allowAnimations);
+ }
+ }
+ void removeAllMarkers(boolean allowAnimations) {
+ while (mMarkers.size() > 0) {
+ removeMarker(Integer.MAX_VALUE, allowAnimations);
+ }
+ }
+
+ void setActiveMarker(int index) {
+ // Center the active marker
+ mActiveMarkerIndex = index;
+ offsetWindowCenterTo(index, false);
+ }
+
+ void dumpState(String txt) {
+ System.out.println(txt);
+ System.out.println("\tmMarkers: " + mMarkers.size());
+ for (int i = 0; i < mMarkers.size(); ++i) {
+ PageIndicatorMarker m = mMarkers.get(i);
+ System.out.println("\t\t(" + i + ") " + m);
+ }
+ System.out.println("\twindow: [" + mWindowRange[0] + ", " + mWindowRange[1] + "]");
+ System.out.println("\tchildren: " + getChildCount());
+ for (int i = 0; i < getChildCount(); ++i) {
+ PageIndicatorMarker m = (PageIndicatorMarker) getChildAt(i);
+ System.out.println("\t\t(" + i + ") " + m);
+ }
+ System.out.println("\tactive: " + mActiveMarkerIndex);
+ }
+}
diff --git a/src/com/android/launcher3/PageIndicatorMarker.java b/src/com/android/launcher3/PageIndicatorMarker.java
new file mode 100644
index 0000000..b1025d6
--- /dev/null
+++ b/src/com/android/launcher3/PageIndicatorMarker.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2011 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.animation.AnimatorListenerAdapter;
+import android.animation.LayoutTransition;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.FrameLayout;
+import com.android.launcher3.R;
+
+public class PageIndicatorMarker extends FrameLayout {
+ @SuppressWarnings("unused")
+ private static final String TAG = "PageIndicator";
+
+ private static final int MARKER_FADE_DURATION = 175;
+
+ private ImageView mActiveMarker;
+ private ImageView mInactiveMarker;
+ private boolean mIsActive = false;
+
+ public PageIndicatorMarker(Context context) {
+ this(context, null);
+ }
+
+ public PageIndicatorMarker(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PageIndicatorMarker(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ protected void onFinishInflate() {
+ mActiveMarker = (ImageView) findViewById(R.id.active);
+ mInactiveMarker = (ImageView) findViewById(R.id.inactive);
+ }
+
+ void setMarkerDrawables(int activeResId, int inactiveResId) {
+ Resources r = getResources();
+ mActiveMarker.setImageDrawable(r.getDrawable(activeResId));
+ mInactiveMarker.setImageDrawable(r.getDrawable(inactiveResId));
+ }
+
+ void activate(boolean immediate) {
+ if (immediate) {
+ mActiveMarker.animate().cancel();
+ mActiveMarker.setAlpha(1f);
+ mActiveMarker.setScaleX(1f);
+ mActiveMarker.setScaleY(1f);
+ mInactiveMarker.animate().cancel();
+ mInactiveMarker.setAlpha(0f);
+ } else {
+ mActiveMarker.animate()
+ .alpha(1f)
+ .scaleX(1f)
+ .scaleY(1f)
+ .setDuration(MARKER_FADE_DURATION).start();
+ mInactiveMarker.animate()
+ .alpha(0f)
+ .setDuration(MARKER_FADE_DURATION).start();
+ }
+ mIsActive = true;
+ }
+
+ void inactivate(boolean immediate) {
+ if (immediate) {
+ mInactiveMarker.animate().cancel();
+ mInactiveMarker.setAlpha(1f);
+ mActiveMarker.animate().cancel();
+ mActiveMarker.setAlpha(0f);
+ mActiveMarker.setScaleX(0.5f);
+ mActiveMarker.setScaleY(0.5f);
+ } else {
+ mInactiveMarker.animate().alpha(1f)
+ .setDuration(MARKER_FADE_DURATION).start();
+ mActiveMarker.animate()
+ .alpha(0f)
+ .scaleX(0.5f)
+ .scaleY(0.5f)
+ .setDuration(MARKER_FADE_DURATION).start();
+ }
+ mIsActive = false;
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
new file mode 100644
index 0000000..e982985
--- /dev/null
+++ b/src/com/android/launcher3/PagedView.java
@@ -0,0 +1,2818 @@
+/*
+ * Copyright (C) 2012 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.Scroller;
+
+import java.util.ArrayList;
+
+interface Page {
+ public int getPageChildCount();
+ public View getChildOnPageAt(int i);
+ public void removeAllViewsOnPage();
+ public void removeViewOnPageAt(int i);
+ public int indexOfChildOnPage(View v);
+}
+
+/**
+ * An abstraction of the original Workspace which supports browsing through a
+ * sequential list of "pages"
+ */
+public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
+ private static final String TAG = "PagedView";
+ private static final boolean DEBUG = false;
+ protected static final int INVALID_PAGE = -1;
+
+ // the min drag distance for a fling to register, to prevent random page shifts
+ private static final int MIN_LENGTH_FOR_FLING = 25;
+
+ protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
+ protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
+ protected static final float NANOTIME_DIV = 1000000000.0f;
+
+ private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
+
+ private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
+ // The page is moved more than halfway, automatically move to the next page on touch up.
+ private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
+
+ // The following constants need to be scaled based on density. The scaled versions will be
+ // assigned to the corresponding member variables below.
+ private static final int FLING_THRESHOLD_VELOCITY = 500;
+ private static final int MIN_SNAP_VELOCITY = 1500;
+ private static final int MIN_FLING_VELOCITY = 250;
+
+ // We are disabling touch interaction of the widget region for factory ROM.
+ private static final boolean DISABLE_TOUCH_INTERACTION = false;
+ private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
+ private static final boolean DISABLE_FLING_TO_DELETE = true;
+
+ public static final int INVALID_RESTORE_PAGE = -1001;
+
+ private boolean mFreeScroll = false;
+ private int mFreeScrollMinScrollX = -1;
+ private int mFreeScrollMaxScrollX = -1;
+
+ static final int AUTOMATIC_PAGE_SPACING = -1;
+
+ protected int mFlingThresholdVelocity;
+ protected int mMinFlingVelocity;
+ protected int mMinSnapVelocity;
+
+ protected float mDensity;
+ protected float mSmoothingTime;
+ protected float mTouchX;
+
+ protected boolean mFirstLayout = true;
+ private int mNormalChildHeight;
+
+ protected int mCurrentPage;
+ protected int mRestorePage = INVALID_RESTORE_PAGE;
+ protected int mChildCountOnLastLayout;
+
+ protected int mNextPage = INVALID_PAGE;
+ protected int mMaxScrollX;
+ protected Scroller mScroller;
+ private VelocityTracker mVelocityTracker;
+
+ private float mParentDownMotionX;
+ private float mParentDownMotionY;
+ private float mDownMotionX;
+ private float mDownMotionY;
+ private float mDownScrollX;
+ private float mDragViewBaselineLeft;
+ protected float mLastMotionX;
+ protected float mLastMotionXRemainder;
+ protected float mLastMotionY;
+ protected float mTotalMotionX;
+ private int mLastScreenCenter = -1;
+
+ private boolean mCancelTap;
+
+ private int[] mPageScrolls;
+
+ protected final static int TOUCH_STATE_REST = 0;
+ protected final static int TOUCH_STATE_SCROLLING = 1;
+ protected final static int TOUCH_STATE_PREV_PAGE = 2;
+ protected final static int TOUCH_STATE_NEXT_PAGE = 3;
+ protected final static int TOUCH_STATE_REORDERING = 4;
+
+ protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
+
+ protected int mTouchState = TOUCH_STATE_REST;
+ protected boolean mForceScreenScrolled = false;
+
+ protected OnLongClickListener mLongClickListener;
+
+ protected int mTouchSlop;
+ private int mPagingTouchSlop;
+ private int mMaximumVelocity;
+ protected int mPageSpacing;
+ protected int mPageLayoutPaddingTop;
+ protected int mPageLayoutPaddingBottom;
+ protected int mPageLayoutPaddingLeft;
+ protected int mPageLayoutPaddingRight;
+ protected int mPageLayoutWidthGap;
+ protected int mPageLayoutHeightGap;
+ protected int mCellCountX = 0;
+ protected int mCellCountY = 0;
+ protected boolean mCenterPagesVertically;
+ protected boolean mAllowOverScroll = true;
+ protected int mUnboundedScrollX;
+ protected int[] mTempVisiblePagesRange = new int[2];
+ protected boolean mForceDrawAllChildrenNextFrame;
+
+ // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
+ // it is equal to the scaled overscroll position. We use a separate value so as to prevent
+ // the screens from continuing to translate beyond the normal bounds.
+ protected int mOverScrollX;
+
+ protected static final int INVALID_POINTER = -1;
+
+ protected int mActivePointerId = INVALID_POINTER;
+
+ private PageSwitchListener mPageSwitchListener;
+
+ protected ArrayList<Boolean> mDirtyPageContent;
+
+ // If true, syncPages and syncPageItems will be called to refresh pages
+ protected boolean mContentIsRefreshable = true;
+
+ // If true, modify alpha of neighboring pages as user scrolls left/right
+ protected boolean mFadeInAdjacentScreens = false;
+
+ // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
+ // to switch to a new page
+ protected boolean mUsePagingTouchSlop = true;
+
+ // If true, the subclass should directly update scrollX itself in its computeScroll method
+ // (SmoothPagedView does this)
+ protected boolean mDeferScrollUpdate = false;
+ protected boolean mDeferLoadAssociatedPagesUntilScrollCompletes = false;
+
+ protected boolean mIsPageMoving = false;
+
+ // All syncs and layout passes are deferred until data is ready.
+ protected boolean mIsDataReady = false;
+
+ protected boolean mAllowLongPress = true;
+
+ // Page Indicator
+ private int mPageIndicatorViewId;
+ private PageIndicator mPageIndicator;
+ private boolean mAllowPagedViewAnimations = true;
+
+ // The viewport whether the pages are to be contained (the actual view may be larger than the
+ // viewport)
+ private Rect mViewport = new Rect();
+
+ // Reordering
+ // We use the min scale to determine how much to expand the actually PagedView measured
+ // dimensions such that when we are zoomed out, the view is not clipped
+ private int REORDERING_DROP_REPOSITION_DURATION = 200;
+ protected int REORDERING_REORDER_REPOSITION_DURATION = 300;
+ protected int REORDERING_ZOOM_IN_OUT_DURATION = 250;
+ private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
+ private float mMinScale = 1f;
+ private boolean mUseMinScale = false;
+ protected View mDragView;
+ protected AnimatorSet mZoomInOutAnim;
+ private Runnable mSidePageHoverRunnable;
+ private int mSidePageHoverIndex = -1;
+ // This variable's scope is only for the duration of startReordering() and endReordering()
+ private boolean mReorderingStarted = false;
+ // This variable's scope is for the duration of startReordering() and after the zoomIn()
+ // animation after endReordering()
+ private boolean mIsReordering;
+ // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
+ private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
+ private int mPostReorderingPreZoomInRemainingAnimationCount;
+ private Runnable mPostReorderingPreZoomInRunnable;
+
+ // Convenience/caching
+ private Matrix mTmpInvMatrix = new Matrix();
+ private float[] mTmpPoint = new float[2];
+ private int[] mTmpIntPoint = new int[2];
+ private Rect mTmpRect = new Rect();
+ private Rect mAltTmpRect = new Rect();
+
+ // Fling to delete
+ private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
+ private float FLING_TO_DELETE_FRICTION = 0.035f;
+ // The degrees specifies how much deviation from the up vector to still consider a fling "up"
+ private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
+ protected int mFlingToDeleteThresholdVelocity = -1400;
+ // Drag to delete
+ private boolean mDeferringForDelete = false;
+ private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
+ private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
+
+ // Drop to delete
+ private View mDeleteDropTarget;
+
+ private boolean mAutoComputePageSpacing = false;
+ private boolean mRecomputePageSpacing = false;
+
+ // Bouncer
+ private boolean mTopAlignPageWhenShrinkingForBouncer = false;
+
+ protected final Rect mInsets = new Rect();
+
+ protected int mFirstChildLeft;
+
+ public interface PageSwitchListener {
+ void onPageSwitch(View newPage, int newPageIndex);
+ }
+
+ public PagedView(Context context) {
+ this(context, null);
+ }
+
+ public PagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.PagedView, defStyle, 0);
+ setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
+ if (mPageSpacing < 0) {
+ mAutoComputePageSpacing = mRecomputePageSpacing = true;
+ }
+ mPageLayoutPaddingTop = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingTop, 0);
+ mPageLayoutPaddingBottom = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingBottom, 0);
+ mPageLayoutPaddingLeft = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingLeft, 0);
+ mPageLayoutPaddingRight = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingRight, 0);
+ mPageLayoutWidthGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutWidthGap, 0);
+ mPageLayoutHeightGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutHeightGap, 0);
+ mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1);
+ a.recycle();
+
+ setHapticFeedbackEnabled(false);
+ init();
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ protected void init() {
+ mDirtyPageContent = new ArrayList<Boolean>();
+ mDirtyPageContent.ensureCapacity(32);
+ mScroller = new Scroller(getContext(), new ScrollInterpolator());
+ mCurrentPage = 0;
+ mCenterPagesVertically = true;
+
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledPagingTouchSlop();
+ mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mDensity = getResources().getDisplayMetrics().density;
+
+ // Scale the fling-to-delete threshold by the density
+ mFlingToDeleteThresholdVelocity =
+ (int) (mFlingToDeleteThresholdVelocity * mDensity);
+
+ mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+ mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
+ mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
+ setOnHierarchyChangeListener(this);
+ }
+
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Hook up the page indicator
+ ViewGroup parent = (ViewGroup) getParent();
+ if (mPageIndicator == null && mPageIndicatorViewId > -1) {
+ mPageIndicator = (PageIndicator) parent.findViewById(mPageIndicatorViewId);
+ mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations);
+
+ ArrayList<PageIndicator.PageMarkerResources> markers =
+ new ArrayList<PageIndicator.PageMarkerResources>();
+ for (int i = 0; i < getChildCount(); ++i) {
+ markers.add(getPageIndicatorMarker(i));
+ }
+
+ mPageIndicator.addMarkers(markers, mAllowPagedViewAnimations);
+
+ OnClickListener listener = getPageIndicatorClickListener();
+ if (listener != null) {
+ mPageIndicator.setOnClickListener(listener);
+ }
+ mPageIndicator.setContentDescription(getPageIndicatorDescription());
+ }
+ }
+
+ protected String getPageIndicatorDescription() {
+ return getCurrentPageDescription();
+ }
+
+ protected OnClickListener getPageIndicatorClickListener() {
+ return null;
+ }
+
+ protected void onDetachedFromWindow() {
+ // Unhook the page indicator
+ mPageIndicator = null;
+ }
+
+ void setDeleteDropTarget(View v) {
+ mDeleteDropTarget = v;
+ }
+
+ // Convenience methods to map points from self to parent and vice versa
+ float[] mapPointFromViewToParent(View v, float x, float y) {
+ mTmpPoint[0] = x;
+ mTmpPoint[1] = y;
+ v.getMatrix().mapPoints(mTmpPoint);
+ mTmpPoint[0] += v.getLeft();
+ mTmpPoint[1] += v.getTop();
+ return mTmpPoint;
+ }
+ float[] mapPointFromParentToView(View v, float x, float y) {
+ mTmpPoint[0] = x - v.getLeft();
+ mTmpPoint[1] = y - v.getTop();
+ v.getMatrix().invert(mTmpInvMatrix);
+ mTmpInvMatrix.mapPoints(mTmpPoint);
+ return mTmpPoint;
+ }
+
+ void updateDragViewTranslationDuringDrag() {
+ if (mDragView != null) {
+ float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
+ (mDragViewBaselineLeft - mDragView.getLeft());
+ float y = mLastMotionY - mDownMotionY;
+ mDragView.setTranslationX(x);
+ mDragView.setTranslationY(y);
+
+ if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
+ + x + ", " + y);
+ }
+ }
+
+ public void setMinScale(float f) {
+ mMinScale = f;
+ mUseMinScale = true;
+ requestLayout();
+ }
+
+ @Override
+ public void setScaleX(float scaleX) {
+ super.setScaleX(scaleX);
+ if (isReordering(true)) {
+ float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
+ mLastMotionX = p[0];
+ mLastMotionY = p[1];
+ updateDragViewTranslationDuringDrag();
+ }
+ }
+
+ // Convenience methods to get the actual width/height of the PagedView (since it is measured
+ // to be larger to account for the minimum possible scale)
+ int getViewportWidth() {
+ return mViewport.width();
+ }
+ int getViewportHeight() {
+ return mViewport.height();
+ }
+
+ // Convenience methods to get the offset ASSUMING that we are centering the pages in the
+ // PagedView both horizontally and vertically
+ int getViewportOffsetX() {
+ return (getMeasuredWidth() - getViewportWidth()) / 2;
+ }
+
+ int getViewportOffsetY() {
+ return (getMeasuredHeight() - getViewportHeight()) / 2;
+ }
+
+ PageIndicator getPageIndicator() {
+ return mPageIndicator;
+ }
+ protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
+ return new PageIndicator.PageMarkerResources();
+ }
+
+ public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
+ mPageSwitchListener = pageSwitchListener;
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+ }
+
+ /**
+ * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
+ */
+ public boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ }
+
+ /**
+ * Called by subclasses to mark that data is ready, and that we can begin loading and laying
+ * out pages.
+ */
+ protected void setDataIsReady() {
+ mIsDataReady = true;
+ }
+
+ protected boolean isDataReady() {
+ return mIsDataReady;
+ }
+
+ /**
+ * Returns the index of the currently displayed page.
+ *
+ * @return The index of the currently displayed page.
+ */
+ int getCurrentPage() {
+ return mCurrentPage;
+ }
+
+ int getNextPage() {
+ return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ }
+
+ int getPageCount() {
+ return getChildCount();
+ }
+
+ View getPageAt(int index) {
+ return getChildAt(index);
+ }
+
+ protected int indexToPage(int index) {
+ return index;
+ }
+
+ /**
+ * Updates the scroll of the current page immediately to its final scroll position. We use this
+ * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
+ * the previous tab page.
+ */
+ protected void updateCurrentPageScroll() {
+ // If the current page is invalid, just reset the scroll position to zero
+ int newX = 0;
+ if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
+ newX = getScrollForPage(mCurrentPage);
+ }
+ scrollTo(newX, 0);
+ mScroller.setFinalX(newX);
+ mScroller.forceFinished(true);
+ }
+
+ /**
+ * Called during AllApps/Home transitions to avoid unnecessary work. When that other animation
+ * ends, {@link #resumeScrolling()} should be called, along with
+ * {@link #updateCurrentPageScroll()} to correctly set the final state and re-enable scrolling.
+ */
+ void pauseScrolling() {
+ mScroller.forceFinished(true);
+ }
+
+ /**
+ * Enables scrolling again.
+ * @see #pauseScrolling()
+ */
+ void resumeScrolling() {
+ }
+ /**
+ * Sets the current page.
+ */
+ void setCurrentPage(int currentPage) {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ // We need to clean up the next page here to avoid computeScrollHelper from
+ // updating current page on the pass.
+ mNextPage = INVALID_PAGE;
+ }
+ // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
+ // the default
+ if (getChildCount() == 0) {
+ return;
+ }
+ mForceScreenScrolled = true;
+ mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
+ updateCurrentPageScroll();
+ notifyPageSwitchListener();
+ invalidate();
+ }
+
+ /**
+ * The restore page will be set in place of the current page at the next (likely first)
+ * layout.
+ */
+ void setRestorePage(int restorePage) {
+ mRestorePage = restorePage;
+ }
+
+ protected void notifyPageSwitchListener() {
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+
+ // Update the page indicator (when we aren't reordering)
+ if (mPageIndicator != null && !isReordering(false)) {
+ mPageIndicator.setActiveMarker(getNextPage());
+ }
+ }
+ protected void pageBeginMoving() {
+ if (!mIsPageMoving) {
+ mIsPageMoving = true;
+ onPageBeginMoving();
+ }
+ }
+
+ protected void pageEndMoving() {
+ if (mIsPageMoving) {
+ mIsPageMoving = false;
+ onPageEndMoving();
+ }
+ }
+
+ protected boolean isPageMoving() {
+ return mIsPageMoving;
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageBeginMoving() {
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageEndMoving() {
+ }
+
+ /**
+ * Registers the specified listener on each page contained in this workspace.
+ *
+ * @param l The listener used to respond to long clicks.
+ */
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mLongClickListener = l;
+ final int count = getPageCount();
+ for (int i = 0; i < count; i++) {
+ getPageAt(i).setOnLongClickListener(l);
+ }
+ super.setOnLongClickListener(l);
+ }
+
+ @Override
+ public void scrollBy(int x, int y) {
+ scrollTo(mUnboundedScrollX + x, getScrollY() + y);
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ // In free scroll mode, we clamp the scrollX
+ if (mFreeScroll) {
+ x = Math.min(x, mFreeScrollMaxScrollX);
+ x = Math.max(x, mFreeScrollMinScrollX);
+ }
+
+ final boolean isRtl = isLayoutRtl();
+ mUnboundedScrollX = x;
+
+ boolean isXBeforeFirstPage = isRtl ? (x > mMaxScrollX) : (x < 0);
+ boolean isXAfterLastPage = isRtl ? (x < 0) : (x > mMaxScrollX);
+ if (isXBeforeFirstPage) {
+ super.scrollTo(0, y);
+ if (mAllowOverScroll) {
+ if (isRtl) {
+ overScroll(x - mMaxScrollX);
+ } else {
+ overScroll(x);
+ }
+ }
+ } else if (isXAfterLastPage) {
+ super.scrollTo(mMaxScrollX, y);
+ if (mAllowOverScroll) {
+ if (isRtl) {
+ overScroll(x);
+ } else {
+ overScroll(x - mMaxScrollX);
+ }
+ }
+ } else {
+ mOverScrollX = x;
+ super.scrollTo(x, y);
+ }
+
+ mTouchX = x;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+
+ // Update the last motion events when scrolling
+ if (isReordering(true)) {
+ float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
+ mLastMotionX = p[0];
+ mLastMotionY = p[1];
+ updateDragViewTranslationDuringDrag();
+ }
+ }
+
+ private void sendScrollAccessibilityEvent() {
+ AccessibilityManager am =
+ (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (am.isEnabled()) {
+ AccessibilityEvent ev =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ ev.setItemCount(getChildCount());
+ ev.setFromIndex(mCurrentPage);
+
+ final int action;
+ if (getNextPage() >= mCurrentPage) {
+ action = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
+ } else {
+ action = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+ }
+
+ ev.setAction(action);
+ sendAccessibilityEventUnchecked(ev);
+ }
+ }
+
+ // we moved this functionality to a helper function so SmoothPagedView can reuse it
+ protected boolean computeScrollHelper() {
+ if (mScroller.computeScrollOffset()) {
+ // Don't bother scrolling if the page does not need to be moved
+ if (getScrollX() != mScroller.getCurrX()
+ || getScrollY() != mScroller.getCurrY()
+ || mOverScrollX != mScroller.getCurrX()) {
+ float scaleX = mFreeScroll ? getScaleX() : 1f;
+ int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
+ scrollTo(scrollX, mScroller.getCurrY());
+ }
+ invalidate();
+ return true;
+ } else if (mNextPage != INVALID_PAGE) {
+ sendScrollAccessibilityEvent();
+
+ mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
+ mNextPage = INVALID_PAGE;
+ notifyPageSwitchListener();
+
+ // Load the associated pages if necessary
+ if (mDeferLoadAssociatedPagesUntilScrollCompletes) {
+ loadAssociatedPages(mCurrentPage);
+ mDeferLoadAssociatedPagesUntilScrollCompletes = false;
+ }
+
+ // We don't want to trigger a page end moving unless the page has settled
+ // and the user has stopped scrolling
+ if (mTouchState == TOUCH_STATE_REST) {
+ pageEndMoving();
+ }
+
+ onPostReorderingAnimationCompleted();
+ AccessibilityManager am = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (am.isEnabled()) {
+ // Notify the user when the page changes
+ announceForAccessibility(getCurrentPageDescription());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void computeScroll() {
+ computeScrollHelper();
+ }
+
+ protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
+ return mTopAlignPageWhenShrinkingForBouncer;
+ }
+
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+ public boolean isFullScreenPage = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+ }
+
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ public void addFullScreenPage(View page) {
+ LayoutParams lp = generateDefaultLayoutParams();
+ lp.isFullScreenPage = true;
+ super.addView(page, 0, lp);
+ }
+
+ public int getNormalChildHeight() {
+ return mNormalChildHeight;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!mIsDataReady || getChildCount() == 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ // We measure the dimensions of the PagedView to be larger than the pages so that when we
+ // zoom out (and scale down), the view is still contained in the parent
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the
+ // viewport, we can be at most one and a half screens offset once we scale down
+ DisplayMetrics dm = getResources().getDisplayMetrics();
+ int maxSize = Math.max(dm.widthPixels, dm.heightPixels + mInsets.top + mInsets.bottom);
+
+ int parentWidthSize, parentHeightSize;
+ int scaledWidthSize, scaledHeightSize;
+ if (mUseMinScale) {
+ parentWidthSize = (int) (1.5f * maxSize);
+ parentHeightSize = maxSize;
+ scaledWidthSize = (int) (parentWidthSize / mMinScale);
+ scaledHeightSize = (int) (parentHeightSize / mMinScale);
+ } else {
+ scaledWidthSize = widthSize;
+ scaledHeightSize = heightSize;
+ }
+ mViewport.set(0, 0, widthSize, heightSize);
+
+ if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ // Return early if we aren't given a proper dimension
+ if (widthSize <= 0 || heightSize <= 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
+ * of the All apps view on XLarge displays to not take up more space then it needs. Width
+ * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
+ * each page to have the same width.
+ */
+ final int verticalPadding = getPaddingTop() + getPaddingBottom();
+ final int horizontalPadding = getPaddingLeft() + getPaddingRight();
+
+ // The children are given the same width and height as the workspace
+ // unless they were set to WRAP_CONTENT
+ if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
+ if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
+ if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
+ if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
+ if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ // disallowing padding in paged view (just pass 0)
+ final View child = getPageAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ int childWidthMode;
+ int childHeightMode;
+ int childWidth;
+ int childHeight;
+
+ if (!lp.isFullScreenPage) {
+ if (lp.width == LayoutParams.WRAP_CONTENT) {
+ childWidthMode = MeasureSpec.AT_MOST;
+ } else {
+ childWidthMode = MeasureSpec.EXACTLY;
+ }
+
+ if (lp.height == LayoutParams.WRAP_CONTENT) {
+ childHeightMode = MeasureSpec.AT_MOST;
+ } else {
+ childHeightMode = MeasureSpec.EXACTLY;
+ }
+
+ childWidth = widthSize - horizontalPadding;
+ childHeight = heightSize - verticalPadding - mInsets.top - mInsets.bottom;
+ mNormalChildHeight = childHeight;
+
+ } else {
+ childWidthMode = MeasureSpec.EXACTLY;
+ childHeightMode = MeasureSpec.EXACTLY;
+
+ if (mUseMinScale) {
+ childWidth = getViewportWidth();
+ childHeight = getViewportHeight();
+ } else {
+ childWidth = widthSize - getPaddingLeft() - getPaddingRight();
+ childHeight = heightSize - getPaddingTop() - getPaddingBottom();
+ }
+ }
+
+ final int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
+ final int childHeightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+ }
+ setMeasuredDimension(scaledWidthSize, scaledHeightSize);
+
+ if (childCount > 0) {
+ // Calculate the variable page spacing if necessary
+ if (mAutoComputePageSpacing && mRecomputePageSpacing) {
+ // The gap between pages in the PagedView should be equal to the gap from the page
+ // to the edge of the screen (so it is not visible in the current screen). To
+ // account for unequal padding on each side of the paged view, we take the maximum
+ // of the left/right gap and use that as the gap between each page.
+ int offset = (getViewportWidth() - getChildWidth(0)) / 2;
+ int spacing = Math.max(offset, widthSize - offset -
+ getChildAt(0).getMeasuredWidth());
+ setPageSpacing(spacing);
+ mRecomputePageSpacing = false;
+ }
+ }
+ }
+
+ public void setPageSpacing(int pageSpacing) {
+ mPageSpacing = pageSpacing;
+ requestLayout();
+ }
+
+ protected int getFirstChildLeft() {
+ return mFirstChildLeft;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mIsDataReady || getChildCount() == 0) {
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
+ final int childCount = getChildCount();
+
+ int screenWidth = getViewportWidth();
+
+ int offsetX = getViewportOffsetX();
+ int offsetY = getViewportOffsetY();
+
+ // Update the viewport offsets
+ mViewport.offset(offsetX, offsetY);
+
+ final boolean isRtl = isLayoutRtl();
+
+ final int startIndex = isRtl ? childCount - 1 : 0;
+ final int endIndex = isRtl ? -1 : childCount;
+ final int delta = isRtl ? -1 : 1;
+
+ int verticalPadding = getPaddingTop() + getPaddingBottom();
+
+ int childLeft = mFirstChildLeft = offsetX + (screenWidth - getChildWidth(startIndex)) / 2;
+ if (mPageScrolls == null || getChildCount() != mChildCountOnLastLayout) {
+ mPageScrolls = new int[getChildCount()];
+ }
+
+ for (int i = startIndex; i != endIndex; i += delta) {
+ final View child = getPageAt(i);
+ if (child.getVisibility() != View.GONE) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ int childTop;
+ if (lp.isFullScreenPage) {
+ childTop = offsetY;
+ } else {
+ childTop = offsetY + getPaddingTop() + mInsets.top;
+ if (mCenterPagesVertically) {
+ childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
+ }
+ }
+
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(), childTop + childHeight);
+
+ // We assume the left and right padding are equal, and hence center the pages
+ // horizontally
+ int scrollOffset = (getViewportWidth() - childWidth) / 2;
+ mPageScrolls[i] = childLeft - scrollOffset - offsetX;
+
+ if (i != endIndex - delta) {
+ childLeft += childWidth + scrollOffset;
+ int nextScrollOffset = (getViewportWidth() - getChildWidth(i + delta)) / 2;
+ childLeft += nextScrollOffset;
+ }
+ }
+ }
+
+ if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+ setHorizontalScrollBarEnabled(false);
+ updateCurrentPageScroll();
+ setHorizontalScrollBarEnabled(true);
+ mFirstLayout = false;
+ }
+
+ if (childCount > 0) {
+ final int index = isLayoutRtl() ? 0 : childCount - 1;
+ mMaxScrollX = getScrollForPage(index);
+ } else {
+ mMaxScrollX = 0;
+ }
+
+ if (mScroller.isFinished() && mChildCountOnLastLayout != getChildCount() &&
+ !mDeferringForDelete) {
+ if (mRestorePage != INVALID_RESTORE_PAGE) {
+ setCurrentPage(mRestorePage);
+ mRestorePage = INVALID_RESTORE_PAGE;
+ } else {
+ setCurrentPage(getNextPage());
+ }
+ }
+ mChildCountOnLastLayout = getChildCount();
+
+ if (isReordering(true)) {
+ updateDragViewTranslationDuringDrag();
+ }
+ }
+
+ protected void screenScrolled(int screenCenter) {
+ boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+
+ if (mFadeInAdjacentScreens && !isInOverscroll) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child != null) {
+ float scrollProgress = getScrollProgress(screenCenter, child, i);
+ float alpha = 1 - Math.abs(scrollProgress);
+ child.setAlpha(alpha);
+ }
+ }
+ invalidate();
+ }
+ }
+
+ protected void enablePagedViewAnimations() {
+ mAllowPagedViewAnimations = true;
+
+ }
+ protected void disablePagedViewAnimations() {
+ mAllowPagedViewAnimations = false;
+ }
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ // Update the page indicator, we don't update the page indicator as we
+ // add/remove pages
+ if (mPageIndicator != null && !isReordering(false)) {
+ int pageIndex = indexOfChild(child);
+ mPageIndicator.addMarker(pageIndex,
+ getPageIndicatorMarker(pageIndex),
+ mAllowPagedViewAnimations);
+ }
+
+ // This ensures that when children are added, they get the correct transforms / alphas
+ // in accordance with any scroll effects.
+ mForceScreenScrolled = true;
+ mRecomputePageSpacing = true;
+ updateFreescrollBounds();
+ invalidate();
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ mForceScreenScrolled = true;
+ updateFreescrollBounds();
+ invalidate();
+ }
+
+ private void removeMarkerForView(int index) {
+ // Update the page indicator, we don't update the page indicator as we
+ // add/remove pages
+ if (mPageIndicator != null && !isReordering(false)) {
+ mPageIndicator.removeMarker(index, mAllowPagedViewAnimations);
+ }
+ }
+
+ @Override
+ public void removeView(View v) {
+ // XXX: We should find a better way to hook into this before the view
+ // gets removed form its parent...
+ removeMarkerForView(indexOfChild(v));
+ super.removeView(v);
+ }
+ @Override
+ public void removeViewInLayout(View v) {
+ // XXX: We should find a better way to hook into this before the view
+ // gets removed form its parent...
+ removeMarkerForView(indexOfChild(v));
+ super.removeViewInLayout(v);
+ }
+ @Override
+ public void removeViewAt(int index) {
+ // XXX: We should find a better way to hook into this before the view
+ // gets removed form its parent...
+ removeViewAt(index);
+ super.removeViewAt(index);
+ }
+ @Override
+ public void removeAllViewsInLayout() {
+ // Update the page indicator, we don't update the page indicator as we
+ // add/remove pages
+ if (mPageIndicator != null) {
+ mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations);
+ }
+
+ super.removeAllViewsInLayout();
+ }
+
+ protected int getChildOffset(int index) {
+ if (index < 0 || index > getChildCount() - 1) return 0;
+
+ int offset = getPageAt(index).getLeft() - getViewportOffsetX();
+
+ return offset;
+ }
+
+ protected void getOverviewModePages(int[] range) {
+ range[0] = 0;
+ range[1] = Math.max(0, getChildCount() - 1);
+ }
+
+ protected void getVisiblePages(int[] range) {
+ final int pageCount = getChildCount();
+ mTmpIntPoint[0] = mTmpIntPoint[1] = 0;
+
+ range[0] = -1;
+ range[1] = -1;
+
+ if (pageCount > 0) {
+ int viewportWidth = getViewportWidth();
+ int curScreen = 0;
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View currPage = getPageAt(i);
+
+ mTmpIntPoint[0] = 0;
+ Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
+ if (mTmpIntPoint[0] > viewportWidth) {
+ if (range[0] == -1) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ mTmpIntPoint[0] = currPage.getMeasuredWidth();
+ Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
+ if (mTmpIntPoint[0] < 0) {
+ if (range[0] == -1) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ curScreen = i;
+ if (range[0] < 0) {
+ range[0] = curScreen;
+ }
+ }
+
+ range[1] = curScreen;
+ } else {
+ range[0] = -1;
+ range[1] = -1;
+ }
+ }
+
+ protected boolean shouldDrawChild(View child) {
+ return child.getAlpha() > 0 && child.getVisibility() == VISIBLE;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ int halfScreenSize = getViewportWidth() / 2;
+ // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
+ // Otherwise it is equal to the scaled overscroll position.
+ int screenCenter = mOverScrollX + halfScreenSize;
+
+ if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
+ // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
+ // set it for the next frame
+ mForceScreenScrolled = false;
+ screenScrolled(screenCenter);
+ mLastScreenCenter = screenCenter;
+ }
+
+ // Find out which screens are visible; as an optimization we only call draw on them
+ final int pageCount = getChildCount();
+ if (pageCount > 0) {
+ getVisiblePages(mTempVisiblePagesRange);
+ final int leftScreen = mTempVisiblePagesRange[0];
+ final int rightScreen = mTempVisiblePagesRange[1];
+ if (leftScreen != -1 && rightScreen != -1) {
+ final long drawingTime = getDrawingTime();
+ // Clip to the bounds
+ canvas.save();
+ canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
+ getScrollY() + getBottom() - getTop());
+
+ // Draw all the children, leaving the drag view for last
+ for (int i = pageCount - 1; i >= 0; i--) {
+ final View v = getPageAt(i);
+ if (v == mDragView) continue;
+ if (mForceDrawAllChildrenNextFrame ||
+ (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
+ drawChild(canvas, v, drawingTime);
+ }
+ }
+ // Draw the drag view on top (if there is one)
+ if (mDragView != null) {
+ drawChild(canvas, mDragView, drawingTime);
+ }
+
+ mForceDrawAllChildrenNextFrame = false;
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ int page = indexToPage(indexOfChild(child));
+ if (page != mCurrentPage || !mScroller.isFinished()) {
+ snapToPage(page);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ int focusablePage;
+ if (mNextPage != INVALID_PAGE) {
+ focusablePage = mNextPage;
+ } else {
+ focusablePage = mCurrentPage;
+ }
+ View v = getPageAt(focusablePage);
+ if (v != null) {
+ return v.requestFocus(direction, previouslyFocusedRect);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ // XXX-RTL: This will be fixed in a future CL
+ if (direction == View.FOCUS_LEFT) {
+ if (getCurrentPage() > 0) {
+ snapToPage(getCurrentPage() - 1);
+ return true;
+ }
+ } else if (direction == View.FOCUS_RIGHT) {
+ if (getCurrentPage() < getPageCount() - 1) {
+ snapToPage(getCurrentPage() + 1);
+ return true;
+ }
+ }
+ return super.dispatchUnhandledMove(focused, direction);
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ // XXX-RTL: This will be fixed in a future CL
+ if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
+ getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
+ }
+ if (direction == View.FOCUS_LEFT) {
+ if (mCurrentPage > 0) {
+ getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
+ }
+ } else if (direction == View.FOCUS_RIGHT){
+ if (mCurrentPage < getPageCount() - 1) {
+ getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
+ }
+ }
+ }
+
+ /**
+ * If one of our descendant views decides that it could be focused now, only
+ * pass that along if it's on the current page.
+ *
+ * This happens when live folders requery, and if they're off page, they
+ * end up calling requestFocus, which pulls it on page.
+ */
+ @Override
+ public void focusableViewAvailable(View focused) {
+ View current = getPageAt(mCurrentPage);
+ View v = focused;
+ while (true) {
+ if (v == current) {
+ super.focusableViewAvailable(focused);
+ return;
+ }
+ if (v == this) {
+ return;
+ }
+ ViewParent parent = v.getParent();
+ if (parent instanceof View) {
+ v = (View)v.getParent();
+ } else {
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (disallowIntercept) {
+ // We need to make sure to cancel our long press if
+ // a scrollable widget takes over touch events
+ final View currentPage = getPageAt(mCurrentPage);
+ currentPage.cancelLongPress();
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ /**
+ * Return true if a tap at (x, y) should trigger a flip to the previous page.
+ */
+ protected boolean hitsPreviousPage(float x, float y) {
+ int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
+ if (isLayoutRtl()) {
+ return (x > (getViewportOffsetX() + getViewportWidth() -
+ offset + mPageSpacing));
+ }
+ return (x < getViewportOffsetX() + offset - mPageSpacing);
+ }
+
+ /**
+ * Return true if a tap at (x, y) should trigger a flip to the next page.
+ */
+ protected boolean hitsNextPage(float x, float y) {
+ int offset = (getViewportWidth() - getChildWidth(mCurrentPage)) / 2;
+ if (isLayoutRtl()) {
+ return (x < getViewportOffsetX() + offset - mPageSpacing);
+ }
+ return (x > (getViewportOffsetX() + getViewportWidth() -
+ offset + mPageSpacing));
+ }
+
+ /** Returns whether x and y originated within the buffered viewport */
+ private boolean isTouchPointInViewportWithBuffer(int x, int y) {
+ mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
+ mViewport.right + mViewport.width() / 2, mViewport.bottom);
+ return mTmpRect.contains(x, y);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (DISABLE_TOUCH_INTERACTION) {
+ return false;
+ }
+
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onTouchEvent will be called and we do the actual
+ * scrolling there.
+ */
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ // Skip touch handling if there are no pages to swipe
+ if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) &&
+ (mTouchState == TOUCH_STATE_SCROLLING)) {
+ return true;
+ }
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+ if (mActivePointerId != INVALID_POINTER) {
+ determineScrollingStart(ev);
+ }
+ // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
+ // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
+ // i.e. fall through to the next case (don't break)
+ // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
+ // while it's small- this was causing a crash before we checked for INVALID_POINTER)
+ break;
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ // Remember location of down touch
+ mDownMotionX = x;
+ mDownMotionY = y;
+ mDownScrollX = getScrollX();
+ mLastMotionX = x;
+ mLastMotionY = y;
+ float[] p = mapPointFromViewToParent(this, x, y);
+ mParentDownMotionX = p[0];
+ mParentDownMotionY = p[1];
+ mLastMotionXRemainder = 0;
+ mTotalMotionX = 0;
+ mActivePointerId = ev.getPointerId(0);
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+ if (finishedScrolling) {
+ mTouchState = TOUCH_STATE_REST;
+ mScroller.abortAnimation();
+ } else {
+ if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
+ mTouchState = TOUCH_STATE_SCROLLING;
+ } else {
+ mTouchState = TOUCH_STATE_REST;
+ }
+ }
+
+ // check if this can be the beginning of a tap on the side of the pages
+ // to scroll the current page
+ if (!DISABLE_TOUCH_SIDE_PAGES) {
+ if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
+ if (getChildCount() > 0) {
+ if (hitsPreviousPage(x, y)) {
+ mTouchState = TOUCH_STATE_PREV_PAGE;
+ } else if (hitsNextPage(x, y)) {
+ mTouchState = TOUCH_STATE_NEXT_PAGE;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ resetTouchState();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ releaseVelocityTracker();
+ break;
+ }
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mTouchState != TOUCH_STATE_REST;
+ }
+
+ protected void determineScrollingStart(MotionEvent ev) {
+ determineScrollingStart(ev, 1.0f);
+ }
+
+ /*
+ * Determines if we should change the touch state to start scrolling after the
+ * user moves their touch point too far.
+ */
+ protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
+ // Disallow scrolling if we don't have a valid pointer index
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) return;
+
+ // Disallow scrolling if we started the gesture from outside the viewport
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+ if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
+
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
+ boolean xPaged = xDiff > mPagingTouchSlop;
+ boolean xMoved = xDiff > touchSlop;
+ boolean yMoved = yDiff > touchSlop;
+
+ if (xMoved || xPaged || yMoved) {
+ if (mUsePagingTouchSlop ? xPaged : xMoved) {
+ // Scroll if the user moved far enough along the X axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ mTotalMotionX += Math.abs(mLastMotionX - x);
+ mLastMotionX = x;
+ mLastMotionXRemainder = 0;
+ mTouchX = getViewportOffsetX() + getScrollX();
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ pageBeginMoving();
+ }
+ }
+ }
+
+ protected float getMaxScrollProgress() {
+ return 1.0f;
+ }
+
+ protected void cancelCurrentPageLongPress() {
+ if (mAllowLongPress) {
+ //mAllowLongPress = false;
+ // Try canceling the long press. It could also have been scheduled
+ // by a distant descendant, so use the mAllowLongPress flag to block
+ // everything
+ final View currentPage = getPageAt(mCurrentPage);
+ if (currentPage != null) {
+ currentPage.cancelLongPress();
+ }
+ }
+ }
+
+ protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
+ final int halfScreenSize = getViewportWidth() / 2;
+
+ screenCenter = Math.min(getScrollX() + halfScreenSize, screenCenter);
+ screenCenter = Math.max(halfScreenSize, screenCenter);
+
+ return getScrollProgress(screenCenter, v, page);
+ }
+
+ protected float getScrollProgress(int screenCenter, View v, int page) {
+ final int halfScreenSize = getViewportWidth() / 2;
+
+ int totalDistance = v.getMeasuredWidth() + mPageSpacing;
+ int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
+
+ float scrollProgress = delta / (totalDistance * 1.0f);
+ scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
+ scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
+ return scrollProgress;
+ }
+
+ public int getScrollForPage(int index) {
+ if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+ return 0;
+ } else {
+ return mPageScrolls[index];
+ }
+ }
+
+ // While layout transitions are occurring, a child's position may stray from its baseline
+ // position. This method returns the magnitude of this stray at any given time.
+ public int getLayoutTransitionOffsetForPage(int index) {
+ if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
+ return 0;
+ } else {
+ View child = getChildAt(index);
+ int scrollOffset = (getViewportWidth() - child.getMeasuredWidth()) / 2;
+ int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
+ return (int) (child.getX() - baselineX);
+ }
+ }
+
+ // This curve determines how the effect of scrolling over the limits of the page dimishes
+ // as the user pulls further and further from the bounds
+ private float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ protected void acceleratedOverScroll(float amount) {
+ int screenSize = getViewportWidth();
+
+ // We want to reach the max over scroll effect when the user has
+ // over scrolled half the size of the screen
+ float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
+
+ if (f == 0) return;
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ int overScrollAmount = (int) Math.round(f * screenSize);
+ if (amount < 0) {
+ mOverScrollX = overScrollAmount;
+ super.scrollTo(0, getScrollY());
+ } else {
+ mOverScrollX = mMaxScrollX + overScrollAmount;
+ super.scrollTo(mMaxScrollX, getScrollY());
+ }
+ invalidate();
+ }
+
+ protected void dampedOverScroll(float amount) {
+ int screenSize = getViewportWidth();
+
+ float f = (amount / screenSize);
+
+ if (f == 0) return;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
+ if (amount < 0) {
+ mOverScrollX = overScrollAmount;
+ super.scrollTo(0, getScrollY());
+ } else {
+ mOverScrollX = mMaxScrollX + overScrollAmount;
+ super.scrollTo(mMaxScrollX, getScrollY());
+ }
+ invalidate();
+ }
+
+ protected void overScroll(float amount) {
+ dampedOverScroll(amount);
+ }
+
+ protected float maxOverScroll() {
+ // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
+ // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
+ float f = 1.0f;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+ return OVERSCROLL_DAMP_FACTOR * f;
+ }
+
+ protected void enableFreeScroll() {
+ setEnableFreeScroll(true, -1);
+ }
+
+ protected void disableFreeScroll(int snapPage) {
+ setEnableFreeScroll(false, snapPage);
+ }
+
+ void updateFreescrollBounds() {
+ getOverviewModePages(mTempVisiblePagesRange);
+ if (isLayoutRtl()) {
+ mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
+ mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
+ } else {
+ mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
+ mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
+ }
+ }
+
+ private void setEnableFreeScroll(boolean freeScroll, int snapPage) {
+ mFreeScroll = freeScroll;
+
+ if (snapPage == -1) {
+ snapPage = getPageNearestToCenterOfScreen();
+ }
+
+ if (!mFreeScroll) {
+ snapToPage(snapPage);
+ } else {
+ updateFreescrollBounds();
+ getOverviewModePages(mTempVisiblePagesRange);
+ if (getCurrentPage() < mTempVisiblePagesRange[0]) {
+ setCurrentPage(mTempVisiblePagesRange[0]);
+ } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
+ setCurrentPage(mTempVisiblePagesRange[1]);
+ }
+ }
+
+ setEnableOverscroll(!freeScroll);
+ }
+
+ private void setEnableOverscroll(boolean enable) {
+ mAllowOverScroll = enable;
+ }
+
+ int getNearestHoverOverPageIndex() {
+ if (mDragView != null) {
+ int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
+ + mDragView.getTranslationX());
+ getOverviewModePages(mTempVisiblePagesRange);
+ int minDistance = Integer.MAX_VALUE;
+ int minIndex = indexOfChild(mDragView);
+ for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
+ View page = getPageAt(i);
+ int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
+ int d = Math.abs(dragX - pageX);
+ if (d < minDistance) {
+ minIndex = i;
+ minDistance = d;
+ }
+ }
+ return minIndex;
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (DISABLE_TOUCH_INTERACTION) {
+ return false;
+ }
+
+ super.onTouchEvent(ev);
+
+ // Skip touch handling if there are no pages to swipe
+ if (getChildCount() <= 0) return super.onTouchEvent(ev);
+
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ final int action = ev.getAction();
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ // Remember where the motion event started
+ mDownMotionX = mLastMotionX = ev.getX();
+ mDownMotionY = mLastMotionY = ev.getY();
+ mDownScrollX = getScrollX();
+ float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+ mParentDownMotionX = p[0];
+ mParentDownMotionY = p[1];
+ mLastMotionXRemainder = 0;
+ mTotalMotionX = 0;
+ mActivePointerId = ev.getPointerId(0);
+
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ pageBeginMoving();
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ // Scroll to follow the motion event
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+
+ if (pointerIndex == -1) return true;
+
+ final float x = ev.getX(pointerIndex);
+ final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
+
+ mTotalMotionX += Math.abs(deltaX);
+
+ // Only scroll and update mLastMotionX if we have moved some discrete amount. We
+ // keep the remainder because we are actually testing if we've moved from the last
+ // scrolled position (which is discrete).
+ if (Math.abs(deltaX) >= 1.0f) {
+ mTouchX += deltaX;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ if (!mDeferScrollUpdate) {
+ scrollBy((int) deltaX, 0);
+ if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
+ } else {
+ invalidate();
+ }
+ mLastMotionX = x;
+ mLastMotionXRemainder = deltaX - (int) deltaX;
+ } else {
+ awakenScrollBars();
+ }
+ } else if (mTouchState == TOUCH_STATE_REORDERING) {
+ // Update the last motion position
+ mLastMotionX = ev.getX();
+ mLastMotionY = ev.getY();
+
+ // Update the parent down so that our zoom animations take this new movement into
+ // account
+ float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+ mParentDownMotionX = pt[0];
+ mParentDownMotionY = pt[1];
+ updateDragViewTranslationDuringDrag();
+
+ // Find the closest page to the touch point
+ final int dragViewIndex = indexOfChild(mDragView);
+
+ // Change the drag view if we are hovering over the drop target
+ boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
+ (int) mParentDownMotionX, (int) mParentDownMotionY);
+ setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
+
+ if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
+ if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
+ if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
+ if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
+
+ final int pageUnderPointIndex = getNearestHoverOverPageIndex();
+ if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView) &&
+ !isHoveringOverDelete) {
+ mTempVisiblePagesRange[0] = 0;
+ mTempVisiblePagesRange[1] = getPageCount() - 1;
+ getOverviewModePages(mTempVisiblePagesRange);
+ if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
+ pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
+ pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
+ mSidePageHoverIndex = pageUnderPointIndex;
+ mSidePageHoverRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Setup the scroll to the correct page before we swap the views
+ snapToPage(pageUnderPointIndex);
+
+ // For each of the pages between the paged view and the drag view,
+ // animate them from the previous position to the new position in
+ // the layout (as a result of the drag view moving in the layout)
+ int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
+ int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
+ dragViewIndex + 1 : pageUnderPointIndex;
+ int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
+ dragViewIndex - 1 : pageUnderPointIndex;
+ for (int i = lowerIndex; i <= upperIndex; ++i) {
+ View v = getChildAt(i);
+ // dragViewIndex < pageUnderPointIndex, so after we remove the
+ // drag view all subsequent views to pageUnderPointIndex will
+ // shift down.
+ int oldX = getViewportOffsetX() + getChildOffset(i);
+ int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
+
+ // Animate the view translation from its old position to its new
+ // position
+ AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY);
+ if (anim != null) {
+ anim.cancel();
+ }
+
+ v.setTranslationX(oldX - newX);
+ anim = new AnimatorSet();
+ anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
+ anim.playTogether(
+ ObjectAnimator.ofFloat(v, "translationX", 0f));
+ anim.start();
+ v.setTag(anim);
+ }
+
+ removeView(mDragView);
+ onRemoveView(mDragView, false);
+ addView(mDragView, pageUnderPointIndex);
+ onAddView(mDragView, pageUnderPointIndex);
+ mSidePageHoverIndex = -1;
+ mPageIndicator.setActiveMarker(getNextPage());
+ }
+ };
+ postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
+ }
+ } else {
+ removeCallbacks(mSidePageHoverRunnable);
+ mSidePageHoverIndex = -1;
+ }
+ } else {
+ determineScrollingStart(ev);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ final int activePointerId = mActivePointerId;
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ final float x = ev.getX(pointerIndex);
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
+ final int deltaX = (int) (x - mDownMotionX);
+ final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
+ boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
+ SIGNIFICANT_MOVE_THRESHOLD;
+
+ mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
+
+ boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
+ Math.abs(velocityX) > mFlingThresholdVelocity;
+
+ if (!mFreeScroll) {
+ // In the case that the page is moved far to one direction and then is flung
+ // in the opposite direction, we use a threshold to determine whether we should
+ // just return to the starting page, or if we should skip one further.
+ boolean returnToOriginalPage = false;
+ if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
+ Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
+ returnToOriginalPage = true;
+ }
+
+ int finalPage;
+ // We give flings precedence over large moves, which is why we short-circuit our
+ // test for a large move if a fling has been registered. That is, a large
+ // move to the left and fling to the right will register as a fling to the right.
+ final boolean isRtl = isLayoutRtl();
+ boolean isDeltaXLeft = isRtl ? deltaX > 0 : deltaX < 0;
+ boolean isVelocityXLeft = isRtl ? velocityX > 0 : velocityX < 0;
+ if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
+ (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
+ finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+ snapToPageWithVelocity(finalPage, velocityX);
+ } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
+ (isFling && isVelocityXLeft)) &&
+ mCurrentPage < getChildCount() - 1) {
+ finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
+ snapToPageWithVelocity(finalPage, velocityX);
+ } else {
+ snapToDestination();
+ } } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.max(0, mCurrentPage - 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ } else {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ float scaleX = getScaleX();
+ int vX = (int) (-velocityX * scaleX);
+ int initialScrollX = (int) (getScrollX() * scaleX);
+
+ mScroller.fling(initialScrollX,
+ getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
+ invalidate();
+ }
+ } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ } else if (mTouchState == TOUCH_STATE_REORDERING) {
+ // Update the last motion position
+ mLastMotionX = ev.getX();
+ mLastMotionY = ev.getY();
+
+ // Update the parent down so that our zoom animations take this new movement into
+ // account
+ float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
+ mParentDownMotionX = pt[0];
+ mParentDownMotionY = pt[1];
+ updateDragViewTranslationDuringDrag();
+ boolean handledFling = false;
+ if (!DISABLE_FLING_TO_DELETE) {
+ // Check the velocity and see if we are flinging-to-delete
+ PointF flingToDeleteVector = isFlingingToDelete();
+ if (flingToDeleteVector != null) {
+ onFlingToDelete(flingToDeleteVector);
+ handledFling = true;
+ }
+ }
+ if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
+ (int) mParentDownMotionY)) {
+ onDropToDelete();
+ }
+ } else {
+ if (!mCancelTap) {
+ onUnhandledTap(ev);
+ }
+ }
+
+ // Remove the callback to wait for the side page hover timeout
+ removeCallbacks(mSidePageHoverRunnable);
+ // End any intermediate reordering states
+ resetTouchState();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ snapToDestination();
+ }
+ resetTouchState();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ releaseVelocityTracker();
+ break;
+ }
+
+ return true;
+ }
+
+ public void onFlingToDelete(View v) {}
+ public void onRemoveView(View v, boolean deletePermanently) {}
+ public void onRemoveViewAnimationCompleted() {}
+ public void onAddView(View v, int index) {}
+
+ private void resetTouchState() {
+ releaseVelocityTracker();
+ endReordering();
+ mCancelTap = false;
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+ }
+
+ protected void onUnhandledTap(MotionEvent ev) {
+ ((Launcher) getContext()).onClick(this);
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_SCROLL: {
+ // Handle mouse (or ext. device) by shifting the page depending on the scroll
+ final float vscroll;
+ final float hscroll;
+ if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
+ vscroll = 0;
+ hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ } else {
+ vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+ }
+ if (hscroll != 0 || vscroll != 0) {
+ boolean isForwardScroll = isLayoutRtl() ? (hscroll < 0 || vscroll < 0)
+ : (hscroll > 0 || vscroll > 0);
+ if (isForwardScroll) {
+ scrollRight();
+ } else {
+ scrollLeft();
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return super.onGenericMotionEvent(event);
+ }
+
+ private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
+ mLastMotionY = ev.getY(newPointerIndex);
+ mLastMotionXRemainder = 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ int page = indexToPage(indexOfChild(child));
+ if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
+ snapToPage(page);
+ }
+ }
+
+ protected int getChildWidth(int index) {
+ return getPageAt(index).getMeasuredWidth();
+ }
+
+ int getPageNearestToPoint(float x) {
+ int index = 0;
+ for (int i = 0; i < getChildCount(); ++i) {
+ if (x < getChildAt(i).getRight() - getScrollX()) {
+ return index;
+ } else {
+ index++;
+ }
+ }
+ return Math.min(index, getChildCount() - 1);
+ }
+
+ int getPageNearestToCenterOfScreen() {
+ int minDistanceFromScreenCenter = Integer.MAX_VALUE;
+ int minDistanceFromScreenCenterIndex = -1;
+ int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View layout = (View) getPageAt(i);
+ int childWidth = layout.getMeasuredWidth();
+ int halfChildWidth = (childWidth / 2);
+ int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
+ int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
+ if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
+ minDistanceFromScreenCenter = distanceFromScreenCenter;
+ minDistanceFromScreenCenterIndex = i;
+ }
+ }
+ return minDistanceFromScreenCenterIndex;
+ }
+
+ protected void snapToDestination() {
+ snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ private static class ScrollInterpolator implements Interpolator {
+ public ScrollInterpolator() {
+ }
+
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t*t*t*t*t + 1;
+ }
+ }
+
+ // We want the duration of the page snap animation to be influenced by the distance that
+ // the screen has to travel, however, we don't want this duration to be effected in a
+ // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+ // of travel has on the overall snap duration.
+ float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ protected void snapToPageWithVelocity(int whichPage, int velocity) {
+ whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+ int halfScreenSize = getViewportWidth() / 2;
+
+ final int newX = getScrollForPage(whichPage);
+ int delta = newX - mUnboundedScrollX;
+ int duration = 0;
+
+ if (Math.abs(velocity) < mMinFlingVelocity) {
+ // If the velocity is low enough, then treat this more as an automatic page advance
+ // as opposed to an apparent physical response to flinging
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ return;
+ }
+
+ // Here we compute a "distance" that will be used in the computation of the overall
+ // snap duration. This is a function of the actual distance that needs to be traveled;
+ // we keep this value close to half screen size in order to reduce the variance in snap
+ // duration as a function of the distance the page needs to travel.
+ float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
+ float distance = halfScreenSize + halfScreenSize *
+ distanceInfluenceForSnapDuration(distanceRatio);
+
+ velocity = Math.abs(velocity);
+ velocity = Math.max(mMinSnapVelocity, velocity);
+
+ // we want the page's snap velocity to approximately match the velocity at which the
+ // user flings, so we scale the duration by a value near to the derivative of the scroll
+ // interpolator at zero, ie. 5. We use 4 to make it a little slower.
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+
+ snapToPage(whichPage, delta, duration);
+ }
+
+ protected void snapToPage(int whichPage) {
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ protected void snapToPageImmediately(int whichPage) {
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+ }
+
+ protected void snapToPage(int whichPage, int duration) {
+ snapToPage(whichPage, duration, false);
+ }
+
+ protected void snapToPage(int whichPage, int duration, boolean immediate) {
+ whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
+
+ int newX = getScrollForPage(whichPage);
+ final int sX = mUnboundedScrollX;
+ final int delta = newX - sX;
+ snapToPage(whichPage, delta, duration, immediate);
+ }
+
+ protected void snapToPage(int whichPage, int delta, int duration) {
+ snapToPage(whichPage, delta, duration, false);
+ }
+
+ protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
+ mNextPage = whichPage;
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null && whichPage != mCurrentPage &&
+ focusedChild == getPageAt(mCurrentPage)) {
+ focusedChild.clearFocus();
+ }
+
+ sendScrollAccessibilityEvent();
+
+ pageBeginMoving();
+ awakenScrollBars(duration);
+ if (immediate) {
+ duration = 0;
+ } else if (duration == 0) {
+ duration = Math.abs(delta);
+ }
+
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
+
+ notifyPageSwitchListener();
+
+ // Trigger a compute() to finish switching pages if necessary
+ if (immediate) {
+ computeScroll();
+ }
+
+ // Defer loading associated pages until the scroll settles
+ mDeferLoadAssociatedPagesUntilScrollCompletes = true;
+
+ mForceScreenScrolled = true;
+ invalidate();
+ }
+
+ public void scrollLeft() {
+ if (getNextPage() > 0) snapToPage(getNextPage() - 1);
+ }
+
+ public void scrollRight() {
+ if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
+ }
+
+ public int getPageForView(View v) {
+ int result = -1;
+ if (v != null) {
+ ViewParent vp = v.getParent();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ if (vp == getPageAt(i)) {
+ return i;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return True is long presses are still allowed for the current touch
+ */
+ public boolean allowLongPress() {
+ return mAllowLongPress;
+ }
+
+ @Override
+ public boolean performLongClick() {
+ mCancelTap = true;
+ return super.performLongClick();
+ }
+
+ /**
+ * Set true to allow long-press events to be triggered, usually checked by
+ * {@link Launcher} to accept or block dpad-initiated long-presses.
+ */
+ public void setAllowLongPress(boolean allowLongPress) {
+ mAllowLongPress = allowLongPress;
+ }
+
+ public static class SavedState extends BaseSavedState {
+ int currentPage = -1;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ currentPage = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(currentPage);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ protected void loadAssociatedPages(int page) {
+ loadAssociatedPages(page, false);
+ }
+ protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
+ if (mContentIsRefreshable) {
+ final int count = getChildCount();
+ if (page < count) {
+ int lowerPageBound = getAssociatedLowerPageBound(page);
+ int upperPageBound = getAssociatedUpperPageBound(page);
+ if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
+ + upperPageBound);
+ // First, clear any pages that should no longer be loaded
+ for (int i = 0; i < count; ++i) {
+ Page layout = (Page) getPageAt(i);
+ if ((i < lowerPageBound) || (i > upperPageBound)) {
+ if (layout.getPageChildCount() > 0) {
+ layout.removeAllViewsOnPage();
+ }
+ mDirtyPageContent.set(i, true);
+ }
+ }
+ // Next, load any new pages
+ for (int i = 0; i < count; ++i) {
+ if ((i != page) && immediateAndOnly) {
+ continue;
+ }
+ if (lowerPageBound <= i && i <= upperPageBound) {
+ if (mDirtyPageContent.get(i)) {
+ syncPageItems(i, (i == page) && immediateAndOnly);
+ mDirtyPageContent.set(i, false);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected int getAssociatedLowerPageBound(int page) {
+ return Math.max(0, page - 1);
+ }
+ protected int getAssociatedUpperPageBound(int page) {
+ final int count = getChildCount();
+ return Math.min(page + 1, count - 1);
+ }
+
+ /**
+ * This method is called ONLY to synchronize the number of pages that the paged view has.
+ * To actually fill the pages with information, implement syncPageItems() below. It is
+ * guaranteed that syncPageItems() will be called for a particular page before it is shown,
+ * and therefore, individual page items do not need to be updated in this method.
+ */
+ public abstract void syncPages();
+
+ /**
+ * This method is called to synchronize the items that are on a particular page. If views on
+ * the page can be reused, then they should be updated within this method.
+ */
+ public abstract void syncPageItems(int page, boolean immediate);
+
+ protected void invalidatePageData() {
+ invalidatePageData(-1, false);
+ }
+ protected void invalidatePageData(int currentPage) {
+ invalidatePageData(currentPage, false);
+ }
+ protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
+ if (!mIsDataReady) {
+ return;
+ }
+
+ if (mContentIsRefreshable) {
+ // Force all scrolling-related behavior to end
+ mScroller.forceFinished(true);
+ mNextPage = INVALID_PAGE;
+
+ // Update all the pages
+ syncPages();
+
+ // We must force a measure after we've loaded the pages to update the content width and
+ // to determine the full scroll width
+ measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+
+ // Set a new page as the current page if necessary
+ if (currentPage > -1) {
+ setCurrentPage(Math.min(getPageCount() - 1, currentPage));
+ }
+
+ // Mark each of the pages as dirty
+ final int count = getChildCount();
+ mDirtyPageContent.clear();
+ for (int i = 0; i < count; ++i) {
+ mDirtyPageContent.add(true);
+ }
+
+ // Load any pages that are necessary for the current window of views
+ loadAssociatedPages(mCurrentPage, immediateAndOnly);
+ requestLayout();
+ }
+ if (isPageMoving()) {
+ // If the page is moving, then snap it to the final position to ensure we don't get
+ // stuck between pages
+ snapToDestination();
+ }
+ }
+
+ // Animate the drag view back to the original position
+ void animateDragViewToOriginalPosition() {
+ if (mDragView != null) {
+ AnimatorSet anim = new AnimatorSet();
+ anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
+ anim.playTogether(
+ ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
+ ObjectAnimator.ofFloat(mDragView, "translationY", 0f),
+ ObjectAnimator.ofFloat(mDragView, "scaleX", 1f),
+ ObjectAnimator.ofFloat(mDragView, "scaleY", 1f));
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onPostReorderingAnimationCompleted();
+ }
+ });
+ anim.start();
+ }
+ }
+
+ protected void onStartReordering() {
+ // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
+ mTouchState = TOUCH_STATE_REORDERING;
+ mIsReordering = true;
+
+ // We must invalidate to trigger a redraw to update the layers such that the drag view
+ // is always drawn on top
+ invalidate();
+ }
+
+ private void onPostReorderingAnimationCompleted() {
+ // Trigger the callback when reordering has settled
+ --mPostReorderingPreZoomInRemainingAnimationCount;
+ if (mPostReorderingPreZoomInRunnable != null &&
+ mPostReorderingPreZoomInRemainingAnimationCount == 0) {
+ mPostReorderingPreZoomInRunnable.run();
+ mPostReorderingPreZoomInRunnable = null;
+ }
+ }
+
+ protected void onEndReordering() {
+ mIsReordering = false;
+ }
+
+ public boolean startReordering(View v) {
+ int dragViewIndex = indexOfChild(v);
+
+ if (mTouchState != TOUCH_STATE_REST) return false;
+
+ mTempVisiblePagesRange[0] = 0;
+ mTempVisiblePagesRange[1] = getPageCount() - 1;
+ getOverviewModePages(mTempVisiblePagesRange);
+ mReorderingStarted = true;
+
+ // Check if we are within the reordering range
+ if (mTempVisiblePagesRange[0] <= dragViewIndex &&
+ dragViewIndex <= mTempVisiblePagesRange[1]) {
+ // Find the drag view under the pointer
+ mDragView = getChildAt(dragViewIndex);
+ mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
+ mDragViewBaselineLeft = mDragView.getLeft();
+ disableFreeScroll(-1);
+ onStartReordering();
+ return true;
+ }
+ return false;
+ }
+
+ boolean isReordering(boolean testTouchState) {
+ boolean state = mIsReordering;
+ if (testTouchState) {
+ state &= (mTouchState == TOUCH_STATE_REORDERING);
+ }
+ return state;
+ }
+ void endReordering() {
+ // For simplicity, we call endReordering sometimes even if reordering was never started.
+ // In that case, we don't want to do anything.
+ if (!mReorderingStarted) return;
+ mReorderingStarted = false;
+
+ // If we haven't flung-to-delete the current child, then we just animate the drag view
+ // back into position
+ final Runnable onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onEndReordering();
+ }
+ };
+ if (!mDeferringForDelete) {
+ mPostReorderingPreZoomInRunnable = new Runnable() {
+ public void run() {
+ onCompleteRunnable.run();
+ enableFreeScroll();
+ };
+ };
+
+ mPostReorderingPreZoomInRemainingAnimationCount =
+ NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
+ // Snap to the current page
+ snapToPage(indexOfChild(mDragView), 0);
+ // Animate the drag view back to the front position
+ animateDragViewToOriginalPosition();
+ } else {
+ // Handled in post-delete-animation-callbacks
+ }
+ }
+
+ /*
+ * Flinging to delete - IN PROGRESS
+ */
+ private PointF isFlingingToDelete() {
+ ViewConfiguration config = ViewConfiguration.get(getContext());
+ mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+
+ if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+ // Do a quick dot product test to ensure that we are flinging upwards
+ PointF vel = new PointF(mVelocityTracker.getXVelocity(),
+ mVelocityTracker.getYVelocity());
+ PointF upVec = new PointF(0f, -1f);
+ float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
+ (vel.length() * upVec.length()));
+ if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
+ return vel;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates an animation from the current drag view along its current velocity vector.
+ * For this animation, the alpha runs for a fixed duration and we update the position
+ * progressively.
+ */
+ private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
+ private View mDragView;
+ private PointF mVelocity;
+ private Rect mFrom;
+ private long mPrevTime;
+ private float mFriction;
+
+ private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+
+ public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
+ long startTime, float friction) {
+ mDragView = dragView;
+ mVelocity = vel;
+ mFrom = from;
+ mPrevTime = startTime;
+ mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = ((Float) animation.getAnimatedValue()).floatValue();
+ long curTime = AnimationUtils.currentAnimationTimeMillis();
+
+ mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
+ mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
+
+ mDragView.setTranslationX(mFrom.left);
+ mDragView.setTranslationY(mFrom.top);
+ mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
+
+ mVelocity.x *= mFriction;
+ mVelocity.y *= mFriction;
+ mPrevTime = curTime;
+ }
+ };
+
+ private static final int ANIM_TAG_KEY = 100;
+
+ private Runnable createPostDeleteAnimationRunnable(final View dragView) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ int dragViewIndex = indexOfChild(dragView);
+
+ // For each of the pages around the drag view, animate them from the previous
+ // position to the new position in the layout (as a result of the drag view moving
+ // in the layout)
+ // NOTE: We can make an assumption here because we have side-bound pages that we
+ // will always have pages to animate in from the left
+ getOverviewModePages(mTempVisiblePagesRange);
+ boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
+ boolean slideFromLeft = (isLastWidgetPage ||
+ dragViewIndex > mTempVisiblePagesRange[0]);
+
+ // Setup the scroll to the correct page before we swap the views
+ if (slideFromLeft) {
+ snapToPageImmediately(dragViewIndex - 1);
+ }
+
+ int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
+ int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
+ int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
+ int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
+ ArrayList<Animator> animations = new ArrayList<Animator>();
+ for (int i = lowerIndex; i <= upperIndex; ++i) {
+ View v = getChildAt(i);
+ // dragViewIndex < pageUnderPointIndex, so after we remove the
+ // drag view all subsequent views to pageUnderPointIndex will
+ // shift down.
+ int oldX = 0;
+ int newX = 0;
+ if (slideFromLeft) {
+ if (i == 0) {
+ // Simulate the page being offscreen with the page spacing
+ oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
+ - mPageSpacing;
+ } else {
+ oldX = getViewportOffsetX() + getChildOffset(i - 1);
+ }
+ newX = getViewportOffsetX() + getChildOffset(i);
+ } else {
+ oldX = getChildOffset(i) - getChildOffset(i - 1);
+ newX = 0;
+ }
+
+ // Animate the view translation from its old position to its new
+ // position
+ AnimatorSet anim = (AnimatorSet) v.getTag();
+ if (anim != null) {
+ anim.cancel();
+ }
+
+ // Note: Hacky, but we want to skip any optimizations to not draw completely
+ // hidden views
+ v.setAlpha(Math.max(v.getAlpha(), 0.01f));
+ v.setTranslationX(oldX - newX);
+ anim = new AnimatorSet();
+ anim.playTogether(
+ ObjectAnimator.ofFloat(v, "translationX", 0f),
+ ObjectAnimator.ofFloat(v, "alpha", 1f));
+ animations.add(anim);
+ v.setTag(ANIM_TAG_KEY, anim);
+ }
+
+ AnimatorSet slideAnimations = new AnimatorSet();
+ slideAnimations.playTogether(animations);
+ slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
+ slideAnimations.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDeferringForDelete = false;
+ onEndReordering();
+ onRemoveViewAnimationCompleted();
+ }
+ });
+ slideAnimations.start();
+
+ removeView(dragView);
+ onRemoveView(dragView, true);
+ }
+ };
+ }
+
+ public void onFlingToDelete(PointF vel) {
+ final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+ // NOTE: Because it takes time for the first frame of animation to actually be
+ // called and we expect the animation to be a continuation of the fling, we have
+ // to account for the time that has elapsed since the fling finished. And since
+ // we don't have a startDelay, we will always get call to update when we call
+ // start() (which we want to ignore).
+ final TimeInterpolator tInterpolator = new TimeInterpolator() {
+ private int mCount = -1;
+ private long mStartTime;
+ private float mOffset;
+ /* Anonymous inner class ctor */ {
+ mStartTime = startTime;
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ if (mCount < 0) {
+ mCount++;
+ } else if (mCount == 0) {
+ mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+ mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
+ mCount++;
+ }
+ return Math.min(1f, mOffset + t);
+ }
+ };
+
+ final Rect from = new Rect();
+ final View dragView = mDragView;
+ from.left = (int) dragView.getTranslationX();
+ from.top = (int) dragView.getTranslationY();
+ AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
+ from, startTime, FLING_TO_DELETE_FRICTION);
+
+ final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
+
+ // Create and start the animation
+ ValueAnimator mDropAnim = new ValueAnimator();
+ mDropAnim.setInterpolator(tInterpolator);
+ mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
+ mDropAnim.setFloatValues(0f, 1f);
+ mDropAnim.addUpdateListener(updateCb);
+ mDropAnim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ onAnimationEndRunnable.run();
+ }
+ });
+ mDropAnim.start();
+ mDeferringForDelete = true;
+ }
+
+ /* Drag to delete */
+ private boolean isHoveringOverDeleteDropTarget(int x, int y) {
+ if (mDeleteDropTarget != null) {
+ mAltTmpRect.set(0, 0, 0, 0);
+ View parent = (View) mDeleteDropTarget.getParent();
+ if (parent != null) {
+ parent.getGlobalVisibleRect(mAltTmpRect);
+ }
+ mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
+ mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
+ return mTmpRect.contains(x, y);
+ }
+ return false;
+ }
+
+ protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
+
+ private void onDropToDelete() {
+ final View dragView = mDragView;
+
+ final float toScale = 0f;
+ final float toAlpha = 0f;
+
+ // Create and start the complex animation
+ ArrayList<Animator> animations = new ArrayList<Animator>();
+ AnimatorSet motionAnim = new AnimatorSet();
+ motionAnim.setInterpolator(new DecelerateInterpolator(2));
+ motionAnim.playTogether(
+ ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
+ ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
+ animations.add(motionAnim);
+
+ AnimatorSet alphaAnim = new AnimatorSet();
+ alphaAnim.setInterpolator(new LinearInterpolator());
+ alphaAnim.playTogether(
+ ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
+ animations.add(alphaAnim);
+
+ final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.playTogether(animations);
+ anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ onAnimationEndRunnable.run();
+ }
+ });
+ anim.start();
+
+ mDeferringForDelete = true;
+ }
+
+ /* Accessibility */
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setScrollable(getPageCount() > 1);
+ if (getCurrentPage() < getPageCount() - 1) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ }
+ if (getCurrentPage() > 0) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+ }
+
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ // Don't let the view send real scroll events.
+ if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+ super.sendAccessibilityEvent(eventType);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setScrollable(true);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+ if (getCurrentPage() < getPageCount() - 1) {
+ scrollRight();
+ return true;
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+ if (getCurrentPage() > 0) {
+ scrollLeft();
+ return true;
+ }
+ } break;
+ }
+ return false;
+ }
+
+ protected String getCurrentPageDescription() {
+ return String.format(getContext().getString(R.string.default_scroll_format),
+ getNextPage() + 1, getChildCount());
+ }
+
+ @Override
+ public boolean onHoverEvent(android.view.MotionEvent event) {
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/PagedViewCellLayout.java b/src/com/android/launcher3/PagedViewCellLayout.java
new file mode 100644
index 0000000..2d9e10b
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewCellLayout.java
@@ -0,0 +1,492 @@
+/*
+ * 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.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+
+/**
+ * An abstraction of the original CellLayout which supports laying out items
+ * which span multiple cells into a grid-like layout. Also supports dimming
+ * to give a preview of its contents.
+ */
+public class PagedViewCellLayout extends ViewGroup implements Page {
+ static final String TAG = "PagedViewCellLayout";
+
+ private int mCellCountX;
+ private int mCellCountY;
+ private int mOriginalCellWidth;
+ private int mOriginalCellHeight;
+ private int mCellWidth;
+ private int mCellHeight;
+ private int mOriginalWidthGap;
+ private int mOriginalHeightGap;
+ private int mWidthGap;
+ private int mHeightGap;
+ protected PagedViewCellLayoutChildren mChildren;
+
+ public PagedViewCellLayout(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewCellLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setAlwaysDrawnWithCacheEnabled(false);
+
+ // setup default cell parameters
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mOriginalCellWidth = mCellWidth = grid.cellWidthPx;
+ mOriginalCellHeight = mCellHeight = grid.cellHeightPx;
+ mCellCountX = (int) grid.numColumns;
+ mCellCountY = (int) grid.numRows;
+ mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1;
+
+ mChildren = new PagedViewCellLayoutChildren(context);
+ mChildren.setCellDimensions(mCellWidth, mCellHeight);
+ mChildren.setGap(mWidthGap, mHeightGap);
+
+ addView(mChildren);
+ }
+
+ public int getCellWidth() {
+ return mCellWidth;
+ }
+
+ public int getCellHeight() {
+ return mCellHeight;
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ // Cancel long press for all children
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.cancelLongPress();
+ }
+ }
+
+ public boolean addViewToCellLayout(View child, int index, int childId,
+ PagedViewCellLayout.LayoutParams params) {
+ final PagedViewCellLayout.LayoutParams lp = params;
+
+ // Generate an id for each view, this assumes we have at most 256x256 cells
+ // per workspace screen
+ if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) &&
+ lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) {
+ // If the horizontal or vertical span is set to -1, it is taken to
+ // mean that it spans the extent of the CellLayout
+ if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX;
+ if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY;
+
+ child.setId(childId);
+ mChildren.addView(child, index, lp);
+
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void removeAllViewsOnPage() {
+ mChildren.removeAllViews();
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+
+ @Override
+ public void removeViewOnPageAt(int index) {
+ mChildren.removeViewAt(index);
+ }
+
+ /**
+ * Clears all the key listeners for the individual icons.
+ */
+ public void resetChildrenOnKeyListeners() {
+ int childCount = mChildren.getChildCount();
+ for (int j = 0; j < childCount; ++j) {
+ mChildren.getChildAt(j).setOnKeyListener(null);
+ }
+ }
+
+ @Override
+ public int getPageChildCount() {
+ return mChildren.getChildCount();
+ }
+
+ public PagedViewCellLayoutChildren getChildrenLayout() {
+ return mChildren;
+ }
+
+ @Override
+ public View getChildOnPageAt(int i) {
+ return mChildren.getChildAt(i);
+ }
+
+ @Override
+ public int indexOfChildOnPage(View v) {
+ return mChildren.indexOfChild(v);
+ }
+
+ public int getCellCountX() {
+ return mCellCountX;
+ }
+
+ public int getCellCountY() {
+ return mCellCountY;
+ }
+
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+ }
+
+ int numWidthGaps = mCellCountX - 1;
+ int numHeightGaps = mCellCountY - 1;
+
+ if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
+ int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
+ int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
+ int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth);
+ int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight);
+ mWidthGap = numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0;
+ mHeightGap = numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0;
+
+ mChildren.setGap(mWidthGap, mHeightGap);
+ } else {
+ mWidthGap = mOriginalWidthGap;
+ mHeightGap = mOriginalHeightGap;
+ }
+
+ // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
+ int newWidth = widthSpecSize;
+ int newHeight = heightSpecSize;
+ if (widthSpecMode == MeasureSpec.AT_MOST) {
+ newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) +
+ ((mCellCountX - 1) * mWidthGap);
+ newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) +
+ ((mCellCountY - 1) * mHeightGap);
+ setMeasuredDimension(newWidth, newHeight);
+ }
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
+ getPaddingRight(), MeasureSpec.EXACTLY);
+ int childheightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
+ getPaddingBottom(), MeasureSpec.EXACTLY);
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ }
+
+ setMeasuredDimension(newWidth, newHeight);
+ }
+
+ int getContentWidth() {
+ return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight();
+ }
+
+ int getContentHeight() {
+ if (mCellCountY > 0) {
+ return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap);
+ }
+ return 0;
+ }
+
+ int getWidthBeforeFirstLayout() {
+ if (mCellCountX > 0) {
+ return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap);
+ }
+ return 0;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ child.layout(getPaddingLeft(), getPaddingTop(),
+ r - l - getPaddingRight(), b - t - getPaddingBottom());
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean result = super.onTouchEvent(event);
+ int count = getPageChildCount();
+ if (count > 0) {
+ // We only intercept the touch if we are tapping in empty space after the final row
+ View child = getChildOnPageAt(count - 1);
+ int bottom = child.getBottom();
+ int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX());
+ if (numRows < getCellCountY()) {
+ // Add a little bit of buffer if there is room for another row
+ bottom += mCellHeight / 2;
+ }
+ result = result || (event.getY() < bottom);
+ }
+ return result;
+ }
+
+ public void enableCenteredContent(boolean enabled) {
+ mChildren.enableCenteredContent(enabled);
+ }
+
+ @Override
+ protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+ mChildren.setChildrenDrawingCacheEnabled(enabled);
+ }
+
+ public void setCellCount(int xCount, int yCount) {
+ mCellCountX = xCount;
+ mCellCountY = yCount;
+ requestLayout();
+ }
+
+ public void setGap(int widthGap, int heightGap) {
+ mOriginalWidthGap = mWidthGap = widthGap;
+ mOriginalHeightGap = mHeightGap = heightGap;
+ mChildren.setGap(widthGap, heightGap);
+ }
+
+ public int[] getCellCountForDimensions(int width, int height) {
+ // Always assume we're working with the smallest span to make sure we
+ // reserve enough space in both orientations
+ int smallerSize = Math.min(mCellWidth, mCellHeight);
+
+ // Always round up to next largest cell
+ int spanX = (width + smallerSize) / smallerSize;
+ int spanY = (height + smallerSize) / smallerSize;
+
+ return new int[] { spanX, spanY };
+ }
+
+ /**
+ * Start dragging the specified child
+ *
+ * @param child The child that is being dragged
+ */
+ void onDragChild(View child) {
+ PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+ lp.isDragging = true;
+ }
+
+ /**
+ * Estimates the number of cells that the specified width would take up.
+ */
+ public int estimateCellHSpan(int width) {
+ // We don't show the next/previous pages any more, so we use the full width, minus the
+ // padding
+ int availWidth = width - (getPaddingLeft() + getPaddingRight());
+
+ // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N
+ int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap));
+
+ // We don't do anything fancy to determine if we squeeze another row in.
+ return n;
+ }
+
+ /**
+ * Estimates the number of cells that the specified height would take up.
+ */
+ public int estimateCellVSpan(int height) {
+ // The space for a page is the height - top padding (current page) - bottom padding (current
+ // page)
+ int availHeight = height - (getPaddingTop() + getPaddingBottom());
+
+ // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N
+ int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap));
+
+ // We don't do anything fancy to determine if we squeeze another row in.
+ return n;
+ }
+
+ /** Returns an estimated center position of the cell at the specified index */
+ public int[] estimateCellPosition(int x, int y) {
+ return new int[] {
+ getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2),
+ getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2)
+ };
+ }
+
+ public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) {
+ mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width));
+ mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height));
+ requestLayout();
+ }
+
+ /**
+ * Estimates the width that the number of hSpan cells will take up.
+ */
+ public int estimateCellWidth(int hSpan) {
+ // TODO: we need to take widthGap into effect
+ return hSpan * mCellWidth;
+ }
+
+ /**
+ * Estimates the height that the number of vSpan cells will take up.
+ */
+ public int estimateCellHeight(int vSpan) {
+ // TODO: we need to take heightGap into effect
+ return vSpan * mCellHeight;
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof PagedViewCellLayout.LayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new PagedViewCellLayout.LayoutParams(p);
+ }
+
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ /**
+ * Horizontal location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellX;
+
+ /**
+ * Vertical location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellY;
+
+ /**
+ * Number of cells spanned horizontally by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellHSpan;
+
+ /**
+ * Number of cells spanned vertically by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellVSpan;
+
+ /**
+ * Is this item currently being dragged
+ */
+ public boolean isDragging;
+
+ // a data object that you can bind to this layout params
+ private Object mTag;
+
+ // X coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int x;
+ // Y coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int y;
+
+ public LayoutParams() {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+ this.cellX = source.cellX;
+ this.cellY = source.cellY;
+ this.cellHSpan = source.cellHSpan;
+ this.cellVSpan = source.cellVSpan;
+ }
+
+ public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ this.cellX = cellX;
+ this.cellY = cellY;
+ this.cellHSpan = cellHSpan;
+ this.cellVSpan = cellVSpan;
+ }
+
+ public void setup(Context context,
+ int cellWidth, int cellHeight, int widthGap, int heightGap,
+ int hStartPadding, int vStartPadding) {
+
+ final int myCellHSpan = cellHSpan;
+ final int myCellVSpan = cellVSpan;
+ final int myCellX = cellX;
+ final int myCellY = cellY;
+
+ width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
+ leftMargin - rightMargin;
+ height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
+ topMargin - bottomMargin;
+
+ if (LauncherAppState.getInstance().isScreenLarge()) {
+ x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
+ y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
+ } else {
+ x = myCellX * (cellWidth + widthGap) + leftMargin;
+ y = myCellY * (cellHeight + heightGap) + topMargin;
+ }
+ }
+
+ public Object getTag() {
+ return mTag;
+ }
+
+ public void setTag(Object tag) {
+ mTag = tag;
+ }
+
+ public String toString() {
+ return "(" + this.cellX + ", " + this.cellY + ", " +
+ this.cellHSpan + ", " + this.cellVSpan + ")";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/PagedViewCellLayoutChildren.java b/src/com/android/launcher3/PagedViewCellLayoutChildren.java
new file mode 100644
index 0000000..84d2b1d
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewCellLayoutChildren.java
@@ -0,0 +1,161 @@
+/*
+ * 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.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An abstraction of the original CellLayout which supports laying out items
+ * which span multiple cells into a grid-like layout. Also supports dimming
+ * to give a preview of its contents.
+ */
+public class PagedViewCellLayoutChildren extends ViewGroup {
+ static final String TAG = "PagedViewCellLayout";
+
+ private boolean mCenterContent;
+
+ private int mCellWidth;
+ private int mCellHeight;
+ private int mWidthGap;
+ private int mHeightGap;
+
+ public PagedViewCellLayoutChildren(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ // Cancel long press for all children
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.cancelLongPress();
+ }
+ }
+
+ public void setGap(int widthGap, int heightGap) {
+ mWidthGap = widthGap;
+ mHeightGap = heightGap;
+ requestLayout();
+ }
+
+ public void setCellDimensions(int width, int height) {
+ mCellWidth = width;
+ mCellHeight = height;
+ requestLayout();
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ if (child != null) {
+ Rect r = new Rect();
+ child.getDrawingRect(r);
+ requestRectangleOnScreen(r);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+ }
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ PagedViewCellLayout.LayoutParams lp =
+ (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+ lp.setup(getContext(),
+ mCellWidth, mCellHeight, mWidthGap, mHeightGap,
+ getPaddingLeft(),
+ getPaddingTop());
+
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
+ MeasureSpec.EXACTLY);
+ int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+ MeasureSpec.EXACTLY);
+
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ }
+
+ setMeasuredDimension(widthSpecSize, heightSpecSize);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+
+ int offsetX = 0;
+ if (mCenterContent && count > 0) {
+ // determine the max width of all the rows and center accordingly
+ int maxRowX = 0;
+ int minRowX = Integer.MAX_VALUE;
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ PagedViewCellLayout.LayoutParams lp =
+ (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+ minRowX = Math.min(minRowX, lp.x);
+ maxRowX = Math.max(maxRowX, lp.x + lp.width);
+ }
+ }
+ int maxRowWidth = maxRowX - minRowX;
+ offsetX = (getMeasuredWidth() - maxRowWidth) / 2;
+ }
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ PagedViewCellLayout.LayoutParams lp =
+ (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
+
+ int childLeft = offsetX + lp.x;
+ int childTop = lp.y;
+ child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+ }
+ }
+ }
+
+ public void enableCenteredContent(boolean enabled) {
+ mCenterContent = enabled;
+ }
+
+ @Override
+ protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = getChildAt(i);
+ view.setDrawingCacheEnabled(enabled);
+ // Update the drawing caches
+ if (!view.isHardwareAccelerated()) {
+ view.buildDrawingCache(true);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/PagedViewGridLayout.java b/src/com/android/launcher3/PagedViewGridLayout.java
new file mode 100644
index 0000000..b286861
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewGridLayout.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 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.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.GridLayout;
+
+/**
+ * The grid based layout used strictly for the widget/wallpaper tab of the AppsCustomize pane
+ */
+public class PagedViewGridLayout extends GridLayout implements Page {
+ static final String TAG = "PagedViewGridLayout";
+
+ private int mCellCountX;
+ private int mCellCountY;
+ private Runnable mOnLayoutListener;
+
+ public PagedViewGridLayout(Context context, int cellCountX, int cellCountY) {
+ super(context, null, 0);
+ mCellCountX = cellCountX;
+ mCellCountY = cellCountY;
+ }
+
+ int getCellCountX() {
+ return mCellCountX;
+ }
+
+ int getCellCountY() {
+ return mCellCountY;
+ }
+
+ /**
+ * Clears all the key listeners for the individual widgets.
+ */
+ public void resetChildrenOnKeyListeners() {
+ int childCount = getChildCount();
+ for (int j = 0; j < childCount; ++j) {
+ getChildAt(j).setOnKeyListener(null);
+ }
+ }
+
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // PagedView currently has issues with different-sized pages since it calculates the
+ // offset of each page to scroll to before it updates the actual size of each page
+ // (which can change depending on the content if the contents aren't a fixed size).
+ // We work around this by having a minimum size on each widget page).
+ int widthSpecSize = Math.min(getSuggestedMinimumWidth(),
+ MeasureSpec.getSize(widthMeasureSpec));
+ int widthSpecMode = MeasureSpec.EXACTLY;
+ super.onMeasure(MeasureSpec.makeMeasureSpec(widthSpecSize, widthSpecMode),
+ heightMeasureSpec);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mOnLayoutListener = null;
+ }
+
+ public void setOnLayoutListener(Runnable r) {
+ mOnLayoutListener = r;
+ }
+
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mOnLayoutListener != null) {
+ mOnLayoutListener.run();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean result = super.onTouchEvent(event);
+ int count = getPageChildCount();
+ if (count > 0) {
+ // We only intercept the touch if we are tapping in empty space after the final row
+ View child = getChildOnPageAt(count - 1);
+ int bottom = child.getBottom();
+ result = result || (event.getY() < bottom);
+ }
+ return result;
+ }
+
+ @Override
+ public void removeAllViewsOnPage() {
+ removeAllViews();
+ mOnLayoutListener = null;
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+
+ @Override
+ public void removeViewOnPageAt(int index) {
+ removeViewAt(index);
+ }
+
+ @Override
+ public int getPageChildCount() {
+ return getChildCount();
+ }
+
+ @Override
+ public View getChildOnPageAt(int i) {
+ return getChildAt(i);
+ }
+
+ @Override
+ public int indexOfChildOnPage(View v) {
+ return indexOfChild(v);
+ }
+
+ public static class LayoutParams extends FrameLayout.LayoutParams {
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
new file mode 100644
index 0000000..c6d5e49
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewIcon.java
@@ -0,0 +1,128 @@
+/*
+ * 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.Region.Op;
+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";
+ private 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_SP, grid.iconTextSize);
+ }
+
+ public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
+ PagedViewIcon.PressedCallback cb) {
+ mIcon = info.iconBitmap;
+ mPressedCallback = cb;
+ setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null);
+ 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/PagedViewIconCache.java b/src/com/android/launcher3/PagedViewIconCache.java
new file mode 100644
index 0000000..8d8924b
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewIconCache.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+
+/**
+ * Simple cache mechanism for PagedView outlines.
+ */
+public class PagedViewIconCache {
+ public static class Key {
+ public enum Type {
+ ApplicationInfoKey,
+ AppWidgetProviderInfoKey,
+ ResolveInfoKey
+ }
+ private final ComponentName mComponentName;
+ private final Type mType;
+
+ public Key(AppInfo info) {
+ mComponentName = info.componentName;
+ mType = Type.ApplicationInfoKey;
+ }
+ public Key(ResolveInfo info) {
+ final ComponentInfo ci = info.activityInfo != null ? info.activityInfo :
+ info.serviceInfo;
+ mComponentName = new ComponentName(ci.packageName, ci.name);
+ mType = Type.ResolveInfoKey;
+ }
+ public Key(AppWidgetProviderInfo info) {
+ mComponentName = info.provider;
+ mType = Type.AppWidgetProviderInfoKey;
+ }
+
+ private ComponentName getComponentName() {
+ return mComponentName;
+ }
+ public boolean isKeyType(Type t) {
+ return (mType == t);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Key) {
+ Key k = (Key) o;
+ return mComponentName.equals(k.mComponentName);
+ }
+ return super.equals(o);
+ }
+ @Override
+ public int hashCode() {
+ return getComponentName().hashCode();
+ }
+ }
+
+ private final HashMap<Key, Bitmap> mIconOutlineCache = new HashMap<Key, Bitmap>();
+
+ public void clear() {
+ for (Key key : mIconOutlineCache.keySet()) {
+ mIconOutlineCache.get(key).recycle();
+ }
+ mIconOutlineCache.clear();
+ }
+ private void retainAll(HashSet<Key> keysToKeep, Key.Type t) {
+ HashSet<Key> keysToRemove = new HashSet<Key>(mIconOutlineCache.keySet());
+ keysToRemove.removeAll(keysToKeep);
+ for (Key key : keysToRemove) {
+ if (key.isKeyType(t)) {
+ mIconOutlineCache.get(key).recycle();
+ mIconOutlineCache.remove(key);
+ }
+ }
+ }
+ /** Removes all the keys to applications that aren't in the passed in collection */
+ public void retainAllApps(ArrayList<AppInfo> keys) {
+ HashSet<Key> keysSet = new HashSet<Key>();
+ for (AppInfo info : keys) {
+ keysSet.add(new Key(info));
+ }
+ retainAll(keysSet, Key.Type.ApplicationInfoKey);
+ }
+ /** Removes all the keys to shortcuts that aren't in the passed in collection */
+ public void retainAllShortcuts(List<ResolveInfo> keys) {
+ HashSet<Key> keysSet = new HashSet<Key>();
+ for (ResolveInfo info : keys) {
+ keysSet.add(new Key(info));
+ }
+ retainAll(keysSet, Key.Type.ResolveInfoKey);
+ }
+ /** Removes all the keys to widgets that aren't in the passed in collection */
+ public void retainAllAppWidgets(List<AppWidgetProviderInfo> keys) {
+ HashSet<Key> keysSet = new HashSet<Key>();
+ for (AppWidgetProviderInfo info : keys) {
+ keysSet.add(new Key(info));
+ }
+ retainAll(keysSet, Key.Type.AppWidgetProviderInfoKey);
+ }
+ public void addOutline(Key key, Bitmap b) {
+ mIconOutlineCache.put(key, b);
+ }
+ public void removeOutline(Key key) {
+ if (mIconOutlineCache.containsKey(key)) {
+ mIconOutlineCache.get(key).recycle();
+ mIconOutlineCache.remove(key);
+ }
+ }
+ public Bitmap getOutline(Key key) {
+ return mIconOutlineCache.get(key);
+ }
+}
diff --git a/src/com/android/launcher3/PagedViewWidget.java b/src/com/android/launcher3/PagedViewWidget.java
new file mode 100644
index 0000000..45320a4
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewWidget.java
@@ -0,0 +1,262 @@
+/*
+ * 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.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+/**
+ * The linear layout used strictly for the widget/wallpaper tab of the customization tray
+ */
+public class PagedViewWidget extends LinearLayout {
+ static final String TAG = "PagedViewWidgetLayout";
+
+ private static boolean sDeletePreviewsWhenDetachedFromWindow = true;
+ private static boolean sRecyclePreviewsWhenDetachedFromWindow = true;
+
+ private String mDimensionsFormatString;
+ CheckForShortPress mPendingCheckForShortPress = null;
+ ShortPressListener mShortPressListener = null;
+ boolean mShortPressTriggered = false;
+ static PagedViewWidget sShortpressTarget = null;
+ boolean mIsAppWidget;
+ private final Rect mOriginalImagePadding = new Rect();
+ private Object mInfo;
+ private WidgetPreviewLoader mWidgetPreviewLoader;
+
+ public PagedViewWidget(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewWidget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewWidget(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final Resources r = context.getResources();
+ mDimensionsFormatString = r.getString(R.string.widget_dims_format);
+
+ setWillNotDraw(false);
+ setClipToPadding(false);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ final ImageView image = (ImageView) findViewById(R.id.widget_preview);
+ mOriginalImagePadding.left = image.getPaddingLeft();
+ mOriginalImagePadding.top = image.getPaddingTop();
+ mOriginalImagePadding.right = image.getPaddingRight();
+ mOriginalImagePadding.bottom = image.getPaddingBottom();
+
+ // Ensure we are using the right text size
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ TextView name = (TextView) findViewById(R.id.widget_name);
+ if (name != null) {
+ name.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+ }
+ TextView dims = (TextView) findViewById(R.id.widget_dims);
+ if (dims != null) {
+ dims.setTextSize(TypedValue.COMPLEX_UNIT_SP, grid.iconTextSize);
+ }
+ }
+
+ public static void setDeletePreviewsWhenDetachedFromWindow(boolean value) {
+ sDeletePreviewsWhenDetachedFromWindow = value;
+ }
+
+ public static void setRecyclePreviewsWhenDetachedFromWindow(boolean value) {
+ sRecyclePreviewsWhenDetachedFromWindow = value;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (sDeletePreviewsWhenDetachedFromWindow) {
+ final ImageView image = (ImageView) findViewById(R.id.widget_preview);
+ if (image != null) {
+ FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable();
+ if (sRecyclePreviewsWhenDetachedFromWindow &&
+ mInfo != null && preview != null && preview.getBitmap() != null) {
+ mWidgetPreviewLoader.recycleBitmap(mInfo, preview.getBitmap());
+ }
+ image.setImageDrawable(null);
+ }
+ }
+ }
+
+ public void applyFromAppWidgetProviderInfo(AppWidgetProviderInfo info,
+ int maxWidth, int[] cellSpan, WidgetPreviewLoader loader) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ mIsAppWidget = true;
+ mInfo = info;
+ final ImageView image = (ImageView) findViewById(R.id.widget_preview);
+ if (maxWidth > -1) {
+ image.setMaxWidth(maxWidth);
+ }
+ final TextView name = (TextView) findViewById(R.id.widget_name);
+ name.setText(info.label);
+ final TextView dims = (TextView) findViewById(R.id.widget_dims);
+ if (dims != null) {
+ int hSpan = Math.min(cellSpan[0], (int) grid.numColumns);
+ int vSpan = Math.min(cellSpan[1], (int) grid.numRows);
+ dims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
+ }
+ mWidgetPreviewLoader = loader;
+ }
+
+ public void applyFromResolveInfo(
+ PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) {
+ mIsAppWidget = false;
+ mInfo = info;
+ CharSequence label = info.loadLabel(pm);
+ final TextView name = (TextView) findViewById(R.id.widget_name);
+ name.setText(label);
+ final TextView dims = (TextView) findViewById(R.id.widget_dims);
+ if (dims != null) {
+ dims.setText(String.format(mDimensionsFormatString, 1, 1));
+ }
+ mWidgetPreviewLoader = loader;
+ }
+
+ public int[] getPreviewSize() {
+ final ImageView i = (ImageView) findViewById(R.id.widget_preview);
+ int[] maxSize = new int[2];
+ maxSize[0] = i.getWidth() - mOriginalImagePadding.left - mOriginalImagePadding.right;
+ maxSize[1] = i.getHeight() - mOriginalImagePadding.top;
+ return maxSize;
+ }
+
+ void applyPreview(FastBitmapDrawable preview, int index) {
+ final PagedViewWidgetImageView image =
+ (PagedViewWidgetImageView) findViewById(R.id.widget_preview);
+ if (preview != null) {
+ image.mAllowRequestLayout = false;
+ image.setImageDrawable(preview);
+ if (mIsAppWidget) {
+ // center horizontally
+ int[] imageSize = getPreviewSize();
+ int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2;
+ image.setPadding(mOriginalImagePadding.left + centerAmount,
+ mOriginalImagePadding.top,
+ mOriginalImagePadding.right,
+ mOriginalImagePadding.bottom);
+ }
+ image.setAlpha(1f);
+ image.mAllowRequestLayout = true;
+ }
+ }
+
+ void setShortPressListener(ShortPressListener listener) {
+ mShortPressListener = listener;
+ }
+
+ interface ShortPressListener {
+ void onShortPress(View v);
+ void cleanUpShortPress(View v);
+ }
+
+ class CheckForShortPress implements Runnable {
+ public void run() {
+ if (sShortpressTarget != null) return;
+ if (mShortPressListener != null) {
+ mShortPressListener.onShortPress(PagedViewWidget.this);
+ sShortpressTarget = PagedViewWidget.this;
+ }
+ mShortPressTriggered = true;
+ }
+ }
+
+ private void checkForShortPress() {
+ if (sShortpressTarget != null) return;
+ if (mPendingCheckForShortPress == null) {
+ mPendingCheckForShortPress = new CheckForShortPress();
+ }
+ postDelayed(mPendingCheckForShortPress, 120);
+ }
+
+ /**
+ * Remove the longpress detection timer.
+ */
+ private void removeShortPressCallback() {
+ if (mPendingCheckForShortPress != null) {
+ removeCallbacks(mPendingCheckForShortPress);
+ }
+ }
+
+ private void cleanUpShortPress() {
+ removeShortPressCallback();
+ if (mShortPressTriggered) {
+ if (mShortPressListener != null) {
+ mShortPressListener.cleanUpShortPress(PagedViewWidget.this);
+ }
+ mShortPressTriggered = false;
+ }
+ }
+
+ static void resetShortPressTarget() {
+ sShortpressTarget = null;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ cleanUpShortPress();
+ break;
+ case MotionEvent.ACTION_DOWN:
+ checkForShortPress();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ cleanUpShortPress();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ break;
+ }
+
+ // We eat up the touch events here, since the PagedView (which uses the same swiping
+ // touch code as Workspace previously) uses onInterceptTouchEvent() to determine when
+ // the user is scrolling between pages. This means that if the pages themselves don't
+ // handle touch events, it gets forwarded up to PagedView itself, and it's own
+ // onTouchEvent() handling will prevent further intercept touch events from being called
+ // (it's the same view in that case). This is not ideal, but to prevent more changes,
+ // we just always mark the touch event as handled.
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/PagedViewWidgetImageView.java b/src/com/android/launcher3/PagedViewWidgetImageView.java
new file mode 100644
index 0000000..71f5eea
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewWidgetImageView.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 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.Canvas;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+class PagedViewWidgetImageView extends ImageView {
+ public boolean mAllowRequestLayout = true;
+
+ public PagedViewWidgetImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void requestLayout() {
+ if (mAllowRequestLayout) {
+ super.requestLayout();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.save();
+ canvas.clipRect(getScrollX() + getPaddingLeft(),
+ getScrollY() + getPaddingTop(),
+ getScrollX() + getRight() - getLeft() - getPaddingRight(),
+ getScrollY() + getBottom() - getTop() - getPaddingBottom());
+
+ super.onDraw(canvas);
+ canvas.restore();
+
+ }
+}
diff --git a/src/com/android/launcher3/PagedViewWithDraggableItems.java b/src/com/android/launcher3/PagedViewWithDraggableItems.java
new file mode 100644
index 0000000..2a29c33
--- /dev/null
+++ b/src/com/android/launcher3/PagedViewWithDraggableItems.java
@@ -0,0 +1,170 @@
+/*
+ * 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.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+
+/* Class that does most of the work of enabling dragging items out of a PagedView by performing a
+ * vertical drag. Used by both CustomizePagedView and AllAppsPagedView.
+ * Subclasses must do the following:
+ * * call setDragSlopeThreshold after making an instance of the PagedViewWithDraggableItems
+ * * call child.setOnLongClickListener(this) and child.setOnTouchListener(this) on all children
+ * (good place to do it is in syncPageItems)
+ * * override beginDragging(View) (but be careful to call super.beginDragging(View)
+ *
+ */
+public abstract class PagedViewWithDraggableItems extends PagedView
+ implements View.OnLongClickListener, View.OnTouchListener {
+ private View mLastTouchedItem;
+ private boolean mIsDragging;
+ private boolean mIsDragEnabled;
+ private float mDragSlopeThreshold;
+ private Launcher mLauncher;
+
+ public PagedViewWithDraggableItems(Context context) {
+ this(context, null);
+ }
+
+ public PagedViewWithDraggableItems(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedViewWithDraggableItems(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mLauncher = (Launcher) context;
+ }
+
+ protected boolean beginDragging(View v) {
+ boolean wasDragging = mIsDragging;
+ mIsDragging = true;
+ return !wasDragging;
+ }
+
+ protected void cancelDragging() {
+ mIsDragging = false;
+ mLastTouchedItem = null;
+ mIsDragEnabled = false;
+ }
+
+ private void handleTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ cancelDragging();
+ mIsDragEnabled = true;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTouchState != TOUCH_STATE_SCROLLING && !mIsDragging && mIsDragEnabled) {
+ determineDraggingStart(ev);
+ }
+ break;
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ handleTouchEvent(ev);
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ handleTouchEvent(ev);
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ mLastTouchedItem = v;
+ mIsDragEnabled = true;
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ // Return early if this is not initiated from a touch
+ if (!v.isInTouchMode()) return false;
+ // Return early if we are still animating the pages
+ if (mNextPage != INVALID_PAGE) return false;
+ // When we have exited all apps or are in transition, disregard long clicks
+ if (!mLauncher.isAllAppsVisible() ||
+ mLauncher.getWorkspace().isSwitchingState()) return false;
+ // Return if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return false;
+
+ return beginDragging(v);
+ }
+
+ /*
+ * Determines if we should change the touch state to start scrolling after the
+ * user moves their touch point too far.
+ */
+ protected void determineScrollingStart(MotionEvent ev) {
+ if (!mIsDragging) super.determineScrollingStart(ev);
+ }
+
+ /*
+ * Determines if we should change the touch state to start dragging after the
+ * user moves their touch point far enough.
+ */
+ protected void determineDraggingStart(MotionEvent ev) {
+ /*
+ * Locally do absolute value. mLastMotionX is set to the y value
+ * of the down event.
+ */
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = mTouchSlop;
+ boolean yMoved = yDiff > touchSlop;
+ boolean isUpwardMotion = (yDiff / (float) xDiff) > mDragSlopeThreshold;
+
+ if (isUpwardMotion && yMoved && mLastTouchedItem != null) {
+ // Drag if the user moved far enough along the Y axis
+ beginDragging(mLastTouchedItem);
+
+ // Cancel any pending long press
+ if (mAllowLongPress) {
+ mAllowLongPress = false;
+ // Try canceling the long press. It could also have been scheduled
+ // by a distant descendant, so use the mAllowLongPress flag to block
+ // everything
+ final View currentPage = getPageAt(mCurrentPage);
+ if (currentPage != null) {
+ currentPage.cancelLongPress();
+ }
+ }
+ }
+ }
+
+ public void setDragSlopeThreshold(float dragSlopeThreshold) {
+ mDragSlopeThreshold = dragSlopeThreshold;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ cancelDragging();
+ super.onDetachedFromWindow();
+ }
+}
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
new file mode 100644
index 0000000..967cc92
--- /dev/null
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+/**
+ * We pass this object with a drag from the customization tray
+ */
+class PendingAddItemInfo extends ItemInfo {
+ /**
+ * The component that will be created.
+ */
+ ComponentName componentName;
+}
+
+class PendingAddShortcutInfo extends PendingAddItemInfo {
+
+ ActivityInfo shortcutActivityInfo;
+
+ public PendingAddShortcutInfo(ActivityInfo activityInfo) {
+ shortcutActivityInfo = activityInfo;
+ }
+
+ @Override
+ public String toString() {
+ return "Shortcut: " + shortcutActivityInfo.packageName;
+ }
+}
+
+class PendingAddWidgetInfo extends PendingAddItemInfo {
+ int minWidth;
+ int minHeight;
+ int minResizeWidth;
+ int minResizeHeight;
+ int previewImage;
+ int icon;
+ AppWidgetProviderInfo info;
+ AppWidgetHostView boundWidget;
+ Bundle bindOptions = null;
+
+ // Any configuration data that we want to pass to a configuration activity when
+ // starting up a widget
+ String mimeType;
+ Parcelable configurationData;
+
+ public PendingAddWidgetInfo(AppWidgetProviderInfo i, String dataMimeType, Parcelable data) {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ this.info = i;
+ componentName = i.provider;
+ minWidth = i.minWidth;
+ minHeight = i.minHeight;
+ minResizeWidth = i.minResizeWidth;
+ minResizeHeight = i.minResizeHeight;
+ previewImage = i.previewImage;
+ icon = i.icon;
+ if (dataMimeType != null && data != null) {
+ mimeType = dataMimeType;
+ configurationData = data;
+ }
+ }
+
+ // Copy constructor
+ public PendingAddWidgetInfo(PendingAddWidgetInfo copy) {
+ minWidth = copy.minWidth;
+ minHeight = copy.minHeight;
+ minResizeWidth = copy.minResizeWidth;
+ minResizeHeight = copy.minResizeHeight;
+ previewImage = copy.previewImage;
+ icon = copy.icon;
+ info = copy.info;
+ boundWidget = copy.boundWidget;
+ mimeType = copy.mimeType;
+ configurationData = copy.configurationData;
+ componentName = copy.componentName;
+ itemType = copy.itemType;
+ spanX = copy.spanX;
+ spanY = copy.spanY;
+ minSpanX = copy.minSpanX;
+ minSpanY = copy.minSpanY;
+ bindOptions = copy.bindOptions == null ? null : (Bundle) copy.bindOptions.clone();
+ }
+
+ @Override
+ public String toString() {
+ return "Widget: " + componentName.toShortString();
+ }
+}
diff --git a/src/com/android/launcher3/PreloadReceiver.java b/src/com/android/launcher3/PreloadReceiver.java
new file mode 100644
index 0000000..75e5c98
--- /dev/null
+++ b/src/com/android/launcher3/PreloadReceiver.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class PreloadReceiver extends BroadcastReceiver {
+ private static final String TAG = "Launcher.PreloadReceiver";
+ private static final boolean LOGD = false;
+
+ public static final String EXTRA_WORKSPACE_NAME =
+ "com.android.launcher3.action.EXTRA_WORKSPACE_NAME";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final LauncherProvider provider = LauncherAppState.getLauncherProvider();
+ if (provider != null) {
+ String name = intent.getStringExtra(EXTRA_WORKSPACE_NAME);
+ final int workspaceResId = !TextUtils.isEmpty(name)
+ ? context.getResources().getIdentifier(name, "xml", "com.android.launcher3") : 0;
+ if (LOGD) {
+ Log.d(TAG, "workspace name: " + name + " id: " + workspaceResId);
+ }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ provider.loadDefaultFavoritesIfNecessary(workspaceResId);
+ }
+ }).start();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/RampUpScroller.java b/src/com/android/launcher3/RampUpScroller.java
new file mode 100644
index 0000000..89eb579
--- /dev/null
+++ b/src/com/android/launcher3/RampUpScroller.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 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.view.animation.AccelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * Scroller that gradually reaches a target velocity.
+ */
+class RampUpScroller {
+ private final Interpolator mInterpolator;
+ private final long mRampUpTime;
+
+ private long mStartTime;
+ private long mDeltaTime;
+ private float mTargetVelocityX;
+ private float mTargetVelocityY;
+ private int mDeltaX;
+ private int mDeltaY;
+
+ /**
+ * Creates a new ramp-up scroller that reaches full velocity after a
+ * specified duration.
+ *
+ * @param rampUpTime Duration before the scroller reaches target velocity.
+ */
+ public RampUpScroller(long rampUpTime) {
+ mInterpolator = new AccelerateInterpolator();
+ mRampUpTime = rampUpTime;
+ }
+
+ /**
+ * Starts the scroller at the current animation time.
+ */
+ public void start() {
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDeltaTime = mStartTime;
+ }
+
+ /**
+ * Computes the current scroll deltas. This usually only be called after
+ * starting the scroller with {@link #start()}.
+ *
+ * @see #getDeltaX()
+ * @see #getDeltaY()
+ */
+ public void computeScrollDelta() {
+ final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ final long elapsedSinceStart = currentTime - mStartTime;
+ final float scale;
+ if (elapsedSinceStart < mRampUpTime) {
+ scale = mInterpolator.getInterpolation((float) elapsedSinceStart / mRampUpTime);
+ } else {
+ scale = 1f;
+ }
+
+ final long elapsedSinceDelta = currentTime - mDeltaTime;
+ mDeltaTime = currentTime;
+
+ mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX);
+ mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY);
+ }
+
+ /**
+ * Sets the target velocity for this scroller.
+ *
+ * @param x The target X velocity in pixels per millisecond.
+ * @param y The target Y velocity in pixels per millisecond.
+ */
+ public void setTargetVelocity(float x, float y) {
+ mTargetVelocityX = x;
+ mTargetVelocityY = y;
+ }
+
+ /**
+ * @return The target X velocity for this scroller.
+ */
+ public float getTargetVelocityX() {
+ return mTargetVelocityX;
+ }
+
+ /**
+ * @return The target Y velocity for this scroller.
+ */
+ public float getTargetVelocityY() {
+ return mTargetVelocityY;
+ }
+
+ /**
+ * The distance traveled in the X-coordinate computed by the last call to
+ * {@link #computeScrollDelta()}.
+ */
+ public int getDeltaX() {
+ return mDeltaX;
+ }
+
+ /**
+ * The distance traveled in the Y-coordinate computed by the last call to
+ * {@link #computeScrollDelta()}.
+ */
+ public int getDeltaY() {
+ return mDeltaY;
+ }
+}
diff --git a/src/com/android/launcher3/SavedWallpaperImages.java b/src/com/android/launcher3/SavedWallpaperImages.java
new file mode 100644
index 0000000..8d5b005
--- /dev/null
+++ b/src/com/android/launcher3/SavedWallpaperImages.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2013 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.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import com.android.photos.BitmapRegionTileSource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+public class SavedWallpaperImages extends BaseAdapter implements ListAdapter {
+ private static String TAG = "Launcher3.SavedWallpaperImages";
+ private ImageDb mDb;
+ ArrayList<SavedWallpaperTile> mImages;
+ Context mContext;
+ LayoutInflater mLayoutInflater;
+
+ public static class SavedWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+ private int mDbId;
+ private Drawable mThumb;
+ public SavedWallpaperTile(int dbId, Drawable thumb) {
+ mDbId = dbId;
+ mThumb = thumb;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+ File file = new File(a.getFilesDir(), imageFilename);
+ CropView v = a.getCropView();
+ int rotation = WallpaperCropActivity.getRotationFromExif(file.getAbsolutePath());
+ v.setTileSource(
+ new BitmapRegionTileSource(a, file.getAbsolutePath(), 1024, rotation), null);
+ v.moveToLeft();
+ v.setTouchEnabled(false);
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ String imageFilename = a.getSavedImages().getImageFilename(mDbId);
+ a.setWallpaper(imageFilename, finishActivityWhenDone);
+ }
+ @Override
+ public void onDelete(WallpaperPickerActivity a) {
+ a.getSavedImages().deleteImage(mDbId);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public SavedWallpaperImages(Activity context) {
+ mDb = new ImageDb(context);
+ mContext = context;
+ mLayoutInflater = context.getLayoutInflater();
+ }
+
+ public void loadThumbnailsAndImageIdList() {
+ mImages = new ArrayList<SavedWallpaperTile>();
+ SQLiteDatabase db = mDb.getReadableDatabase();
+ Cursor result = db.query(ImageDb.TABLE_NAME,
+ new String[] { ImageDb.COLUMN_ID,
+ ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME }, // cols to return
+ null, // select query
+ null, // args to select query
+ null,
+ null,
+ ImageDb.COLUMN_ID + " DESC",
+ null);
+
+ while (result.moveToNext()) {
+ String filename = result.getString(1);
+ File file = new File(mContext.getFilesDir(), filename);
+
+ Bitmap thumb = BitmapFactory.decodeFile(file.getAbsolutePath());
+ if (thumb != null) {
+ mImages.add(new SavedWallpaperTile(result.getInt(0), new BitmapDrawable(thumb)));
+ }
+ }
+ result.close();
+ }
+
+ public int getCount() {
+ return mImages.size();
+ }
+
+ public SavedWallpaperTile getItem(int position) {
+ return mImages.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Drawable thumbDrawable = mImages.get(position).mThumb;
+ if (thumbDrawable == null) {
+ Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+ }
+ return WallpaperPickerActivity.createImageTileView(
+ mLayoutInflater, position, convertView, parent, thumbDrawable);
+ }
+
+ public String getImageFilename(int id) {
+ Pair<String, String> filenames = getImageFilenames(id);
+ if (filenames != null) {
+ return filenames.second;
+ }
+ return null;
+ }
+
+ private Pair<String, String> getImageFilenames(int id) {
+ SQLiteDatabase db = mDb.getReadableDatabase();
+ Cursor result = db.query(ImageDb.TABLE_NAME,
+ new String[] { ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME,
+ ImageDb.COLUMN_IMAGE_FILENAME }, // cols to return
+ ImageDb.COLUMN_ID + " = ?", // select query
+ new String[] { Integer.toString(id) }, // args to select query
+ null,
+ null,
+ null,
+ null);
+ if (result.getCount() > 0) {
+ result.moveToFirst();
+ String thumbFilename = result.getString(0);
+ String imageFilename = result.getString(1);
+ result.close();
+ return new Pair<String, String>(thumbFilename, imageFilename);
+ } else {
+ return null;
+ }
+ }
+
+ public void deleteImage(int id) {
+ Pair<String, String> filenames = getImageFilenames(id);
+ File imageFile = new File(mContext.getFilesDir(), filenames.first);
+ imageFile.delete();
+ File thumbFile = new File(mContext.getFilesDir(), filenames.second);
+ thumbFile.delete();
+ SQLiteDatabase db = mDb.getWritableDatabase();
+ db.delete(ImageDb.TABLE_NAME,
+ ImageDb.COLUMN_ID + " = ?", // SELECT query
+ new String[] {
+ Integer.toString(id) // args to SELECT query
+ });
+ }
+
+ public void writeImage(Bitmap thumbnail, byte[] imageBytes) {
+ try {
+ File imageFile = File.createTempFile("wallpaper", "", mContext.getFilesDir());
+ FileOutputStream imageFileStream =
+ mContext.openFileOutput(imageFile.getName(), Context.MODE_PRIVATE);
+ imageFileStream.write(imageBytes);
+ imageFileStream.close();
+
+ File thumbFile = File.createTempFile("wallpaperthumb", "", mContext.getFilesDir());
+ FileOutputStream thumbFileStream =
+ mContext.openFileOutput(thumbFile.getName(), Context.MODE_PRIVATE);
+ thumbnail.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+ thumbFileStream.close();
+
+ SQLiteDatabase db = mDb.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(ImageDb.COLUMN_IMAGE_THUMBNAIL_FILENAME, thumbFile.getName());
+ values.put(ImageDb.COLUMN_IMAGE_FILENAME, imageFile.getName());
+ db.insert(ImageDb.TABLE_NAME, null, values);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed writing images to storage " + e);
+ }
+ }
+
+ static class ImageDb extends SQLiteOpenHelper {
+ final static int DB_VERSION = 1;
+ final static String DB_NAME = "saved_wallpaper_images.db";
+ final static String TABLE_NAME = "saved_wallpaper_images";
+ final static String COLUMN_ID = "id";
+ final static String COLUMN_IMAGE_THUMBNAIL_FILENAME = "image_thumbnail";
+ final static String COLUMN_IMAGE_FILENAME = "image";
+
+ Context mContext;
+
+ public ImageDb(Context context) {
+ super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION);
+ // Store the context for later use
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase database) {
+ database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+ COLUMN_ID + " INTEGER NOT NULL, " +
+ COLUMN_IMAGE_THUMBNAIL_FILENAME + " TEXT NOT NULL, " +
+ COLUMN_IMAGE_FILENAME + " TEXT NOT NULL, " +
+ "PRIMARY KEY (" + COLUMN_ID + " ASC) " +
+ ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ // Delete all the records; they'll be repopulated as this is a cache
+ db.execSQL("DELETE FROM " + TABLE_NAME);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/ScrimView.java b/src/com/android/launcher3/ScrimView.java
new file mode 100644
index 0000000..6831fe3
--- /dev/null
+++ b/src/com/android/launcher3/ScrimView.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 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.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+public class ScrimView extends FrameLayout implements Insettable {
+
+ public ScrimView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ScrimView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ScrimView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ // Do nothing
+ }
+}
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
new file mode 100644
index 0000000..e681aa1
--- /dev/null
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2011 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.FrameLayout;
+
+/*
+ * Ths bar will manage the transition between the QSB search bar and the delete drop
+ * targets so that each of the individual IconDropTargets don't have to.
+ */
+public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
+
+ private static final int sTransitionInDuration = 200;
+ private static final int sTransitionOutDuration = 175;
+
+ private ObjectAnimator mDropTargetBarAnim;
+ private ObjectAnimator mQSBSearchBarAnim;
+ private static final AccelerateInterpolator sAccelerateInterpolator =
+ new AccelerateInterpolator();
+
+ private boolean mIsSearchBarHidden;
+ private View mQSBSearchBar;
+ private View mDropTargetBar;
+ private ButtonDropTarget mInfoDropTarget;
+ private ButtonDropTarget mDeleteDropTarget;
+ private int mBarHeight;
+ private boolean mDeferOnDragEnd = false;
+
+ private Drawable mPreviousBackground;
+ private boolean mEnableDropDownDropTargets;
+
+ public SearchDropTargetBar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SearchDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setup(Launcher launcher, DragController dragController) {
+ dragController.addDragListener(this);
+ dragController.addDragListener(mInfoDropTarget);
+ dragController.addDragListener(mDeleteDropTarget);
+ dragController.addDropTarget(mInfoDropTarget);
+ dragController.addDropTarget(mDeleteDropTarget);
+ dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
+ mInfoDropTarget.setLauncher(launcher);
+ mDeleteDropTarget.setLauncher(launcher);
+ mQSBSearchBar = launcher.getQsbBar();
+ if (mEnableDropDownDropTargets) {
+ mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "translationY", 0,
+ -mBarHeight);
+ } else {
+ mQSBSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
+ }
+ setupAnimation(mQSBSearchBarAnim, mQSBSearchBar);
+ }
+
+ private void prepareStartAnimation(View v) {
+ // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd
+ // callback below)
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ private void setupAnimation(ObjectAnimator anim, final View v) {
+ anim.setInterpolator(sAccelerateInterpolator);
+ anim.setDuration(sTransitionInDuration);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ });
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ // Get the individual components
+ mDropTargetBar = findViewById(R.id.drag_target_bar);
+ mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text);
+ mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text);
+
+ mInfoDropTarget.setSearchDropTargetBar(this);
+ mDeleteDropTarget.setSearchDropTargetBar(this);
+
+ mEnableDropDownDropTargets =
+ getResources().getBoolean(R.bool.config_useDropTargetDownTransition);
+
+ // Create the various fade animations
+ if (mEnableDropDownDropTargets) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mBarHeight = grid.searchBarSpaceHeightPx;
+ mDropTargetBar.setTranslationY(-mBarHeight);
+ mDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "translationY",
+ -mBarHeight, 0f);
+
+ } else {
+ mDropTargetBar.setAlpha(0f);
+ mDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
+ }
+ setupAnimation(mDropTargetBarAnim, mDropTargetBar);
+ }
+
+ public void finishAnimations() {
+ prepareStartAnimation(mDropTargetBar);
+ mDropTargetBarAnim.reverse();
+ prepareStartAnimation(mQSBSearchBar);
+ mQSBSearchBarAnim.reverse();
+ }
+
+ /*
+ * Shows and hides the search bar.
+ */
+ public void showSearchBar(boolean animated) {
+ if (!mIsSearchBarHidden) return;
+ if (animated) {
+ prepareStartAnimation(mQSBSearchBar);
+ mQSBSearchBarAnim.reverse();
+ } else {
+ mQSBSearchBarAnim.cancel();
+ if (mEnableDropDownDropTargets) {
+ mQSBSearchBar.setTranslationY(0);
+ } else {
+ mQSBSearchBar.setAlpha(1f);
+ }
+ }
+ mIsSearchBarHidden = false;
+ }
+ public void hideSearchBar(boolean animated) {
+ if (mIsSearchBarHidden) return;
+ if (animated) {
+ prepareStartAnimation(mQSBSearchBar);
+ mQSBSearchBarAnim.start();
+ } else {
+ mQSBSearchBarAnim.cancel();
+ if (mEnableDropDownDropTargets) {
+ mQSBSearchBar.setTranslationY(-mBarHeight);
+ } else {
+ mQSBSearchBar.setAlpha(0f);
+ }
+ }
+ mIsSearchBarHidden = true;
+ }
+
+ /*
+ * Gets various transition durations.
+ */
+ public int getTransitionInDuration() {
+ return sTransitionInDuration;
+ }
+ public int getTransitionOutDuration() {
+ return sTransitionOutDuration;
+ }
+
+ /*
+ * DragController.DragListener implementation
+ */
+ @Override
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ // Animate out the QSB search bar, and animate in the drop target bar
+ prepareStartAnimation(mDropTargetBar);
+ mDropTargetBarAnim.start();
+ if (!mIsSearchBarHidden) {
+ prepareStartAnimation(mQSBSearchBar);
+ mQSBSearchBarAnim.start();
+ }
+ }
+
+ public void deferOnDragEnd() {
+ mDeferOnDragEnd = true;
+ }
+
+ @Override
+ public void onDragEnd() {
+ if (!mDeferOnDragEnd) {
+ // Restore the QSB search bar, and animate out the drop target bar
+ prepareStartAnimation(mDropTargetBar);
+ mDropTargetBarAnim.reverse();
+ if (!mIsSearchBarHidden) {
+ prepareStartAnimation(mQSBSearchBar);
+ mQSBSearchBarAnim.reverse();
+ }
+ } else {
+ mDeferOnDragEnd = false;
+ }
+ }
+
+ public void onSearchPackagesChanged(boolean searchVisible, boolean voiceVisible) {
+ if (mQSBSearchBar != null) {
+ Drawable bg = mQSBSearchBar.getBackground();
+ if (bg != null && (!searchVisible && !voiceVisible)) {
+ // Save the background and disable it
+ mPreviousBackground = bg;
+ mQSBSearchBar.setBackgroundResource(0);
+ } else if (mPreviousBackground != null && (searchVisible || voiceVisible)) {
+ // Restore the background
+ mQSBSearchBar.setBackground(mPreviousBackground);
+ }
+ }
+ }
+
+ public Rect getSearchBarBounds() {
+ if (mQSBSearchBar != null) {
+ final int[] pos = new int[2];
+ mQSBSearchBar.getLocationOnScreen(pos);
+
+ final Rect rect = new Rect();
+ rect.left = pos[0];
+ rect.top = pos[1];
+ rect.right = pos[0] + mQSBSearchBar.getWidth();
+ rect.bottom = pos[1] + mQSBSearchBar.getHeight();
+ return rect;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
new file mode 100644
index 0000000..bb5601e
--- /dev/null
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class ShortcutAndWidgetContainer extends ViewGroup {
+ static final String TAG = "CellLayoutChildren";
+
+ // These are temporary variables to prevent having to allocate a new object just to
+ // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+ private final int[] mTmpCellXY = new int[2];
+
+ private final WallpaperManager mWallpaperManager;
+
+ private boolean mIsHotseatLayout;
+
+ private int mCellWidth;
+ private int mCellHeight;
+
+ private int mWidthGap;
+ private int mHeightGap;
+
+ private int mCountX;
+ private int mCountY;
+
+ private boolean mInvertIfRtl = false;
+
+ public ShortcutAndWidgetContainer(Context context) {
+ super(context);
+ mWallpaperManager = WallpaperManager.getInstance(context);
+ }
+
+ public void setCellDimensions(int cellWidth, int cellHeight, int widthGap, int heightGap,
+ int countX, int countY) {
+ mCellWidth = cellWidth;
+ mCellHeight = cellHeight;
+ mWidthGap = widthGap;
+ mHeightGap = heightGap;
+ mCountX = countX;
+ mCountY = countY;
+ }
+
+ public View getChildAt(int x, int y) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+ if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
+ (lp.cellY <= y) && (y < lp.cellY + lp.cellVSpan)) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ @SuppressWarnings("all") // suppress dead code warning
+ final boolean debug = false;
+ if (debug) {
+ // Debug drawing for hit space
+ Paint p = new Paint();
+ p.setColor(0x6600FF00);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+ canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p);
+ }
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int count = getChildCount();
+
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(widthSpecSize, heightSpecSize);
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ measureChild(child);
+ }
+ }
+ }
+
+ public void setupLp(CellLayout.LayoutParams lp) {
+ lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
+ mCountX);
+ }
+
+ // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
+ public void setInvertIfRtl(boolean invert) {
+ mInvertIfRtl = invert;
+ }
+
+ public void setIsHotseat(boolean isHotseat) {
+ mIsHotseatLayout = isHotseat;
+ }
+
+ int getCellContentWidth() {
+ final LauncherAppState app = LauncherAppState.getInstance();
+ final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
+ grid.hotseatCellWidthPx: grid.cellWidthPx);
+ }
+
+ int getCellContentHeight() {
+ final LauncherAppState app = LauncherAppState.getInstance();
+ final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ return Math.min(getMeasuredHeight(), mIsHotseatLayout ?
+ grid.hotseatCellHeightPx : grid.cellHeightPx);
+ }
+
+ public void measureChild(View child) {
+ final LauncherAppState app = LauncherAppState.getInstance();
+ final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ final int cellWidth = mCellWidth;
+ final int cellHeight = mCellHeight;
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ if (!lp.isFullscreen) {
+ lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, invertLayoutHorizontally(),
+ mCountX);
+
+ if (child instanceof LauncherAppWidgetHostView) {
+ // Widgets have their own padding, so skip
+ } else {
+ // Otherwise, center the icon
+ int cHeight = getCellContentHeight();
+ int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
+ int cellPaddingX = (int) (grid.edgeMarginPx / 2f);
+ child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
+ }
+ } else {
+ lp.x = 0;
+ lp.y = 0;
+ lp.width = getMeasuredWidth();
+ lp.height = getMeasuredHeight();
+ }
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+ int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
+ MeasureSpec.EXACTLY);
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ }
+
+ private boolean invertLayoutHorizontally() {
+ return mInvertIfRtl && isLayoutRtl();
+ }
+
+ public boolean isLayoutRtl() {
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+ int childLeft = lp.x;
+ int childTop = lp.y;
+ child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+
+ if (lp.dropped) {
+ lp.dropped = false;
+
+ final int[] cellXY = mTmpCellXY;
+ getLocationOnScreen(cellXY);
+ mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+ WallpaperManager.COMMAND_DROP,
+ cellXY[0] + childLeft + lp.width / 2,
+ cellXY[1] + childTop + lp.height / 2, 0, null);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ if (child != null) {
+ Rect r = new Rect();
+ child.getDrawingRect(r);
+ requestRectangleOnScreen(r);
+ }
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+
+ // Cancel long press for all children
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.cancelLongPress();
+ }
+ }
+
+ @Override
+ protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = getChildAt(i);
+ view.setDrawingCacheEnabled(enabled);
+ // Update the drawing caches
+ if (!view.isHardwareAccelerated() && enabled) {
+ view.buildDrawingCache(true);
+ }
+ }
+ }
+
+ @Override
+ protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+ super.setChildrenDrawnWithCacheEnabled(enabled);
+ }
+}
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
new file mode 100644
index 0000000..dafabb8
--- /dev/null
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageInfo;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a launchable icon on the workspaces and in folders.
+ */
+class ShortcutInfo extends ItemInfo {
+
+ /**
+ * The intent used to start the application.
+ */
+ Intent intent;
+
+ /**
+ * Indicates whether the icon comes from an application's resource (if false)
+ * or from a custom Bitmap (if true.)
+ */
+ boolean customIcon;
+
+ /**
+ * Indicates whether we're using the default fallback icon instead of something from the
+ * app.
+ */
+ boolean usingFallbackIcon;
+
+ /**
+ * If isShortcut=true and customIcon=false, this contains a reference to the
+ * shortcut icon as an application's resource.
+ */
+ Intent.ShortcutIconResource iconResource;
+
+ /**
+ * The application icon.
+ */
+ private Bitmap mIcon;
+
+ long firstInstallTime;
+ int flags = 0;
+
+ ShortcutInfo() {
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
+ }
+
+ protected Intent getIntent() {
+ return intent;
+ }
+
+ public ShortcutInfo(Context context, ShortcutInfo info) {
+ super(info);
+ title = info.title.toString();
+ intent = new Intent(info.intent);
+ if (info.iconResource != null) {
+ iconResource = new Intent.ShortcutIconResource();
+ iconResource.packageName = info.iconResource.packageName;
+ iconResource.resourceName = info.iconResource.resourceName;
+ }
+ mIcon = info.mIcon; // TODO: should make a copy here. maybe we don't need this ctor at all
+ customIcon = info.customIcon;
+ initFlagsAndFirstInstallTime(
+ getPackageInfo(context, intent.getComponent().getPackageName()));
+ }
+
+ /** TODO: Remove this. It's only called by ApplicationInfo.makeShortcut. */
+ public ShortcutInfo(AppInfo info) {
+ super(info);
+ title = info.title.toString();
+ intent = new Intent(info.intent);
+ customIcon = false;
+ flags = info.flags;
+ firstInstallTime = info.firstInstallTime;
+ }
+
+ public static PackageInfo getPackageInfo(Context context, String packageName) {
+ PackageInfo pi = null;
+ try {
+ PackageManager pm = context.getPackageManager();
+ pi = pm.getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ Log.d("ShortcutInfo", "PackageManager.getPackageInfo failed for " + packageName);
+ }
+ return pi;
+ }
+
+ void initFlagsAndFirstInstallTime(PackageInfo pi) {
+ flags = AppInfo.initFlags(pi);
+ firstInstallTime = AppInfo.initFirstInstallTime(pi);
+ }
+
+ public void setIcon(Bitmap b) {
+ mIcon = b;
+ }
+
+ public Bitmap getIcon(IconCache iconCache) {
+ if (mIcon == null) {
+ updateIcon(iconCache);
+ }
+ return mIcon;
+ }
+
+ public void updateIcon(IconCache iconCache) {
+ mIcon = iconCache.getIcon(intent);
+ usingFallbackIcon = iconCache.isDefaultIcon(mIcon);
+ }
+
+ /**
+ * Creates the application intent based on a component name and various launch flags.
+ * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
+ *
+ * @param className the class name of the component representing the intent
+ * @param launchFlags the launch flags
+ */
+ final void setActivity(Context context, ComponentName className, int launchFlags) {
+ intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(className);
+ intent.setFlags(launchFlags);
+ itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
+ initFlagsAndFirstInstallTime(
+ getPackageInfo(context, intent.getComponent().getPackageName()));
+ }
+
+ @Override
+ void onAddToDatabase(ContentValues values) {
+ super.onAddToDatabase(values);
+
+ String titleStr = title != null ? title.toString() : null;
+ values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
+
+ String uri = intent != null ? intent.toUri(0) : null;
+ values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
+
+ if (customIcon) {
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+ LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP);
+ writeBitmap(values, mIcon);
+ } else {
+ if (!usingFallbackIcon) {
+ writeBitmap(values, mIcon);
+ }
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+ LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
+ if (iconResource != null) {
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
+ iconResource.packageName);
+ values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+ iconResource.resourceName);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ShortcutInfo(title=" + title.toString() + "intent=" + intent + "id=" + this.id
+ + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId
+ + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY
+ + " dropPos=" + dropPos + ")";
+ }
+
+ public static void dumpShortcutInfoList(String tag, String label,
+ ArrayList<ShortcutInfo> list) {
+ Log.d(tag, label + " size=" + list.size());
+ for (ShortcutInfo info: list) {
+ Log.d(tag, " title=\"" + info.title + " icon=" + info.mIcon
+ + " customIcon=" + info.customIcon);
+ }
+ }
+}
+
diff --git a/src/com/android/launcher3/SmoothPagedView.java b/src/com/android/launcher3/SmoothPagedView.java
new file mode 100644
index 0000000..a45dbbf
--- /dev/null
+++ b/src/com/android/launcher3/SmoothPagedView.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+public abstract class SmoothPagedView extends PagedView {
+ private static final float SMOOTHING_SPEED = 0.75f;
+ private static final float SMOOTHING_CONSTANT = (float) (0.016 / Math.log(SMOOTHING_SPEED));
+
+ private float mBaseLineFlingVelocity;
+ private float mFlingVelocityInfluence;
+
+ static final int DEFAULT_MODE = 0;
+ static final int X_LARGE_MODE = 1;
+
+ int mScrollMode;
+
+ private Interpolator mScrollInterpolator;
+
+ public static class OvershootInterpolator implements Interpolator {
+ private static final float DEFAULT_TENSION = 1.3f;
+ private float mTension;
+
+ public OvershootInterpolator() {
+ mTension = DEFAULT_TENSION;
+ }
+
+ public void setDistance(int distance) {
+ mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
+ }
+
+ public void disableSettle() {
+ mTension = 0.f;
+ }
+
+ public float getInterpolation(float t) {
+ // _o(t) = t * t * ((tension + 1) * t + tension)
+ // o(t) = _o(t - 1) + 1
+ t -= 1.0f;
+ return t * t * ((mTension + 1) * t + mTension) + 1.0f;
+ }
+ }
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ */
+ public SmoothPagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ * @param defStyle Unused.
+ */
+ public SmoothPagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mUsePagingTouchSlop = false;
+
+ // This means that we'll take care of updating the scroll parameter ourselves (we do it
+ // in computeScroll), we only do this in the OVERSHOOT_MODE, ie. on phones
+ mDeferScrollUpdate = mScrollMode != X_LARGE_MODE;
+ }
+
+ protected int getScrollMode() {
+ return X_LARGE_MODE;
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ @Override
+ protected void init() {
+ super.init();
+
+ mScrollMode = getScrollMode();
+ if (mScrollMode == DEFAULT_MODE) {
+ mBaseLineFlingVelocity = 2500.0f;
+ mFlingVelocityInfluence = 0.4f;
+ mScrollInterpolator = new OvershootInterpolator();
+ mScroller = new Scroller(getContext(), mScrollInterpolator);
+ }
+ }
+
+ @Override
+ protected void snapToDestination() {
+ if (mScrollMode == X_LARGE_MODE) {
+ super.snapToDestination();
+ } else {
+ snapToPageWithVelocity(getPageNearestToCenterOfScreen(), 0);
+ }
+ }
+
+ @Override
+ protected void snapToPageWithVelocity(int whichPage, int velocity) {
+ if (mScrollMode == X_LARGE_MODE) {
+ super.snapToPageWithVelocity(whichPage, velocity);
+ } else {
+ snapToPageWithVelocity(whichPage, 0, true);
+ }
+ }
+
+ private void snapToPageWithVelocity(int whichPage, int velocity, boolean settle) {
+ // if (!mScroller.isFinished()) return;
+
+ whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+
+ final int screenDelta = Math.max(1, Math.abs(whichPage - mCurrentPage));
+ final int newX = getScrollForPage(whichPage);
+ final int delta = newX - mUnboundedScrollX;
+ int duration = (screenDelta + 1) * 100;
+
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ if (settle) {
+ ((OvershootInterpolator) mScrollInterpolator).setDistance(screenDelta);
+ } else {
+ ((OvershootInterpolator) mScrollInterpolator).disableSettle();
+ }
+
+ velocity = Math.abs(velocity);
+ if (velocity > 0) {
+ duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
+ } else {
+ duration += 100;
+ }
+
+ snapToPage(whichPage, delta, duration);
+ }
+
+ @Override
+ protected void snapToPage(int whichPage) {
+ if (mScrollMode == X_LARGE_MODE) {
+ super.snapToPage(whichPage);
+ } else {
+ snapToPageWithVelocity(whichPage, 0, false);
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScrollMode == X_LARGE_MODE) {
+ super.computeScroll();
+ } else {
+ boolean scrollComputed = computeScrollHelper();
+
+ if (!scrollComputed && mTouchState == TOUCH_STATE_SCROLLING) {
+ final float now = System.nanoTime() / NANOTIME_DIV;
+ final float e = (float) Math.exp((now - mSmoothingTime) / SMOOTHING_CONSTANT);
+
+ final float dx = mTouchX - mUnboundedScrollX;
+ scrollTo(Math.round(mUnboundedScrollX + dx * e), getScrollY());
+ mSmoothingTime = now;
+
+ // Keep generating points as long as we're more than 1px away from the target
+ if (dx > 1.f || dx < -1.f) {
+ invalidate();
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/SpringLoadedDragController.java b/src/com/android/launcher3/SpringLoadedDragController.java
new file mode 100644
index 0000000..45edaef
--- /dev/null
+++ b/src/com/android/launcher3/SpringLoadedDragController.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+public class SpringLoadedDragController implements OnAlarmListener {
+ // how long the user must hover over a mini-screen before it unshrinks
+ final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
+ final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
+ final long EXIT_SPRING_LOAD_HOVER_TIME = 200;
+
+ Alarm mAlarm;
+
+ // the screen the user is currently hovering over, if any
+ private CellLayout mScreen;
+ private Launcher mLauncher;
+
+ public SpringLoadedDragController(Launcher launcher) {
+ mLauncher = launcher;
+ mAlarm = new Alarm();
+ mAlarm.setOnAlarmListener(this);
+ }
+
+ public void cancel() {
+ mAlarm.cancelAlarm();
+ }
+
+ // Set a new alarm to expire for the screen that we are hovering over now
+ public void setAlarm(CellLayout cl) {
+ mAlarm.cancelAlarm();
+ mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME :
+ ENTER_SPRING_LOAD_HOVER_TIME);
+ mScreen = cl;
+ }
+
+ // this is called when our timer runs out
+ public void onAlarm(Alarm alarm) {
+ if (mScreen != null) {
+ // Snap to the screen that we are hovering over now
+ Workspace w = mLauncher.getWorkspace();
+ int page = w.indexOfChild(mScreen);
+ if (page != w.getCurrentPage()) {
+ w.snapToPage(page);
+ }
+ } else {
+ mLauncher.getDragController().cancelDrag();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java
new file mode 100644
index 0000000..882fb04
--- /dev/null
+++ b/src/com/android/launcher3/Stats.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.io.*;
+import java.util.ArrayList;
+
+public class Stats {
+ private static final boolean DEBUG_BROADCASTS = false;
+ private static final String TAG = "Launcher3/Stats";
+
+ private static final boolean LOCAL_LAUNCH_LOG = true;
+
+ public static final String ACTION_LAUNCH = "com.android.launcher3.action.LAUNCH";
+ public static final String PERM_LAUNCH = "com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS";
+ public static final String EXTRA_INTENT = "intent";
+ public static final String EXTRA_CONTAINER = "container";
+ public static final String EXTRA_SCREEN = "screen";
+ public static final String EXTRA_CELLX = "cellX";
+ public static final String EXTRA_CELLY = "cellY";
+
+ private static final String LOG_FILE_NAME = "launches.log";
+ private static final int LOG_VERSION = 1;
+ private static final int LOG_TAG_VERSION = 0x1;
+ private static final int LOG_TAG_LAUNCH = 0x1000;
+
+ private static final String STATS_FILE_NAME = "stats.log";
+ private static final int STATS_VERSION = 1;
+ private static final int INITIAL_STATS_SIZE = 100;
+
+ // TODO: delayed/batched writes
+ private static final boolean FLUSH_IMMEDIATELY = true;
+
+ private final Launcher mLauncher;
+
+ DataOutputStream mLog;
+
+ ArrayList<String> mIntents;
+ ArrayList<Integer> mHistogram;
+
+ public Stats(Launcher launcher) {
+ mLauncher = launcher;
+
+ loadStats();
+
+ if (LOCAL_LAUNCH_LOG) {
+ try {
+ mLog = new DataOutputStream(mLauncher.openFileOutput(LOG_FILE_NAME, Context.MODE_APPEND));
+ mLog.writeInt(LOG_TAG_VERSION);
+ mLog.writeInt(LOG_VERSION);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "unable to create stats log: " + e);
+ mLog = null;
+ } catch (IOException e) {
+ Log.e(TAG, "unable to write to stats log: " + e);
+ mLog = null;
+ }
+ }
+
+ if (DEBUG_BROADCASTS) {
+ launcher.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ android.util.Log.v("Stats", "got broadcast: " + intent + " for launched intent: "
+ + intent.getStringExtra(EXTRA_INTENT));
+ }
+ },
+ new IntentFilter(ACTION_LAUNCH),
+ PERM_LAUNCH,
+ null
+ );
+ }
+ }
+
+ public void incrementLaunch(String intentStr) {
+ int pos = mIntents.indexOf(intentStr);
+ if (pos < 0) {
+ mIntents.add(intentStr);
+ mHistogram.add(1);
+ } else {
+ mHistogram.set(pos, mHistogram.get(pos) + 1);
+ }
+ }
+
+ public void recordLaunch(Intent intent) {
+ recordLaunch(intent, null);
+ }
+
+ public void recordLaunch(Intent intent, ShortcutInfo shortcut) {
+ intent = new Intent(intent);
+ intent.setSourceBounds(null);
+
+ final String flat = intent.toUri(0);
+
+ Intent broadcastIntent = new Intent(ACTION_LAUNCH).putExtra(EXTRA_INTENT, flat);
+ if (shortcut != null) {
+ broadcastIntent.putExtra(EXTRA_CONTAINER, shortcut.container)
+ .putExtra(EXTRA_SCREEN, shortcut.screenId)
+ .putExtra(EXTRA_CELLX, shortcut.cellX)
+ .putExtra(EXTRA_CELLY, shortcut.cellY);
+ }
+ mLauncher.sendBroadcast(broadcastIntent, PERM_LAUNCH);
+
+ incrementLaunch(flat);
+
+ if (FLUSH_IMMEDIATELY) {
+ saveStats();
+ }
+
+ if (LOCAL_LAUNCH_LOG && mLog != null) {
+ try {
+ mLog.writeInt(LOG_TAG_LAUNCH);
+ mLog.writeLong(System.currentTimeMillis());
+ if (shortcut == null) {
+ mLog.writeShort(0);
+ mLog.writeShort(0);
+ mLog.writeShort(0);
+ mLog.writeShort(0);
+ } else {
+ mLog.writeShort((short) shortcut.container);
+ mLog.writeShort((short) shortcut.screenId);
+ mLog.writeShort((short) shortcut.cellX);
+ mLog.writeShort((short) shortcut.cellY);
+ }
+ mLog.writeUTF(flat);
+ if (FLUSH_IMMEDIATELY) {
+ mLog.flush(); // TODO: delayed writes
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void saveStats() {
+ DataOutputStream stats = null;
+ try {
+ stats = new DataOutputStream(mLauncher.openFileOutput(STATS_FILE_NAME + ".tmp", Context.MODE_PRIVATE));
+ stats.writeInt(STATS_VERSION);
+ final int N = mHistogram.size();
+ stats.writeInt(N);
+ for (int i=0; i<N; i++) {
+ stats.writeUTF(mIntents.get(i));
+ stats.writeInt(mHistogram.get(i));
+ }
+ stats.close();
+ stats = null;
+ mLauncher.getFileStreamPath(STATS_FILE_NAME + ".tmp")
+ .renameTo(mLauncher.getFileStreamPath(STATS_FILE_NAME));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "unable to create stats data: " + e);
+ } catch (IOException e) {
+ Log.e(TAG, "unable to write to stats data: " + e);
+ } finally {
+ if (stats != null) {
+ try {
+ stats.close();
+ } catch (IOException e) { }
+ }
+ }
+ }
+
+ private void loadStats() {
+ mIntents = new ArrayList<String>(INITIAL_STATS_SIZE);
+ mHistogram = new ArrayList<Integer>(INITIAL_STATS_SIZE);
+ DataInputStream stats = null;
+ try {
+ stats = new DataInputStream(mLauncher.openFileInput(STATS_FILE_NAME));
+ final int version = stats.readInt();
+ if (version == STATS_VERSION) {
+ final int N = stats.readInt();
+ for (int i=0; i<N; i++) {
+ final String pkg = stats.readUTF();
+ final int count = stats.readInt();
+ mIntents.add(pkg);
+ mHistogram.add(count);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // not a problem
+ } catch (IOException e) {
+ // more of a problem
+
+ } finally {
+ if (stats != null) {
+ try {
+ stats.close();
+ } catch (IOException e) { }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
new file mode 100644
index 0000000..494694c
--- /dev/null
+++ b/src/com/android/launcher3/ThirdPartyWallpaperPickerListAdapter.java
@@ -0,0 +1,139 @@
+/*
+ * 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ThirdPartyWallpaperPickerListAdapter extends BaseAdapter implements ListAdapter {
+ private static final String LOG_TAG = "LiveWallpaperListAdapter";
+
+ private final LayoutInflater mInflater;
+ private final PackageManager mPackageManager;
+ private final int mIconSize;
+
+ private List<ThirdPartyWallpaperTile> mThirdPartyWallpaperPickers =
+ new ArrayList<ThirdPartyWallpaperTile>();
+
+ public static class ThirdPartyWallpaperTile extends WallpaperPickerActivity.WallpaperTileInfo {
+ private ResolveInfo mResolveInfo;
+ public ThirdPartyWallpaperTile(ResolveInfo resolveInfo) {
+ mResolveInfo = resolveInfo;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ final ComponentName itemComponentName = new ComponentName(
+ mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name);
+ Intent launchIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ launchIntent.setComponent(itemComponentName);
+ Utilities.startActivityForResultSafely(
+ a, launchIntent, WallpaperPickerActivity.PICK_WALLPAPER_THIRD_PARTY_ACTIVITY);
+ }
+ }
+
+ public ThirdPartyWallpaperPickerListAdapter(Context context) {
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mPackageManager = context.getPackageManager();
+ mIconSize = context.getResources().getDimensionPixelSize(R.dimen.wallpaperItemIconSize);
+ final PackageManager pm = mPackageManager;
+
+ final Intent pickWallpaperIntent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ final List<ResolveInfo> apps =
+ pm.queryIntentActivities(pickWallpaperIntent, 0);
+
+ // Get list of image picker intents
+ Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ pickImageIntent.setType("image/*");
+ final List<ResolveInfo> imagePickerActivities =
+ pm.queryIntentActivities(pickImageIntent, 0);
+ final ComponentName[] imageActivities = new ComponentName[imagePickerActivities.size()];
+ for (int i = 0; i < imagePickerActivities.size(); i++) {
+ ActivityInfo activityInfo = imagePickerActivities.get(i).activityInfo;
+ imageActivities[i] = new ComponentName(activityInfo.packageName, activityInfo.name);
+ }
+
+ outerLoop:
+ for (ResolveInfo info : apps) {
+ final ComponentName itemComponentName =
+ new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+ final String itemPackageName = itemComponentName.getPackageName();
+ // Exclude anything from our own package, and the old Launcher,
+ // and live wallpaper picker
+ if (itemPackageName.equals(context.getPackageName()) ||
+ itemPackageName.equals("com.android.launcher") ||
+ itemPackageName.equals("com.android.wallpaper.livepicker")) {
+ continue;
+ }
+ // Exclude any package that already responds to the image picker intent
+ for (ResolveInfo imagePickerActivityInfo : imagePickerActivities) {
+ if (itemPackageName.equals(
+ imagePickerActivityInfo.activityInfo.packageName)) {
+ continue outerLoop;
+ }
+ }
+ mThirdPartyWallpaperPickers.add(new ThirdPartyWallpaperTile(info));
+ }
+ }
+
+ public int getCount() {
+ return mThirdPartyWallpaperPickers.size();
+ }
+
+ public ThirdPartyWallpaperTile getItem(int position) {
+ return mThirdPartyWallpaperPickers.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ view = mInflater.inflate(R.layout.wallpaper_picker_third_party_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ WallpaperPickerActivity.setWallpaperItemPaddingToZero((FrameLayout) view);
+
+ ResolveInfo info = mThirdPartyWallpaperPickers.get(position).mResolveInfo;
+ TextView label = (TextView) view.findViewById(R.id.wallpaper_item_label);
+ label.setText(info.loadLabel(mPackageManager));
+ Drawable icon = info.loadIcon(mPackageManager);
+ icon.setBounds(new Rect(0, 0, mIconSize, mIconSize));
+ label.setCompoundDrawables(null, icon, null, null);
+ return view;
+ }
+}
diff --git a/src/com/android/launcher3/ToggleWeightWatcher.java b/src/com/android/launcher3/ToggleWeightWatcher.java
new file mode 100644
index 0000000..33701a2
--- /dev/null
+++ b/src/com/android/launcher3/ToggleWeightWatcher.java
@@ -0,0 +1,7 @@
+package com.android.launcher3;
+
+import android.app.Activity;
+
+public class ToggleWeightWatcher extends Activity {
+
+}
diff --git a/src/com/android/launcher3/UninstallShortcutReceiver.java b/src/com/android/launcher3/UninstallShortcutReceiver.java
new file mode 100644
index 0000000..ccea4ec
--- /dev/null
+++ b/src/com/android/launcher3/UninstallShortcutReceiver.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.widget.Toast;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class UninstallShortcutReceiver extends BroadcastReceiver {
+ private static final String ACTION_UNINSTALL_SHORTCUT =
+ "com.android.launcher.action.UNINSTALL_SHORTCUT";
+
+ // The set of shortcuts that are pending uninstall
+ private static ArrayList<PendingUninstallShortcutInfo> mUninstallQueue =
+ new ArrayList<PendingUninstallShortcutInfo>();
+
+ // Determines whether to defer uninstalling shortcuts immediately until
+ // disableAndFlushUninstallQueue() is called.
+ private static boolean mUseUninstallQueue = false;
+
+ private static class PendingUninstallShortcutInfo {
+ Intent data;
+
+ public PendingUninstallShortcutInfo(Intent rawData) {
+ data = rawData;
+ }
+ }
+
+ public void onReceive(Context context, Intent data) {
+ if (!ACTION_UNINSTALL_SHORTCUT.equals(data.getAction())) {
+ return;
+ }
+
+ PendingUninstallShortcutInfo info = new PendingUninstallShortcutInfo(data);
+ if (mUseUninstallQueue) {
+ mUninstallQueue.add(info);
+ } else {
+ processUninstallShortcut(context, info);
+ }
+ }
+
+ static void enableUninstallQueue() {
+ mUseUninstallQueue = true;
+ }
+
+ static void disableAndFlushUninstallQueue(Context context) {
+ mUseUninstallQueue = false;
+ Iterator<PendingUninstallShortcutInfo> iter = mUninstallQueue.iterator();
+ while (iter.hasNext()) {
+ processUninstallShortcut(context, iter.next());
+ iter.remove();
+ }
+ }
+
+ private static void processUninstallShortcut(Context context,
+ PendingUninstallShortcutInfo pendingInfo) {
+ final Intent data = pendingInfo.data;
+
+ LauncherAppState.setApplicationContext(context.getApplicationContext());
+ LauncherAppState app = LauncherAppState.getInstance();
+ synchronized (app) { // TODO: make removeShortcut internally threadsafe
+ removeShortcut(context, data);
+ }
+ }
+
+ private static void removeShortcut(Context context, Intent data) {
+ Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+ String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+ boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
+
+ if (intent != null && name != null) {
+ final ContentResolver cr = context.getContentResolver();
+ Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT },
+ LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null);
+
+ final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+ final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+
+ boolean changed = false;
+
+ try {
+ while (c.moveToNext()) {
+ try {
+ if (intent.filterEquals(Intent.parseUri(c.getString(intentIndex), 0))) {
+ final long id = c.getLong(idIndex);
+ final Uri uri = LauncherSettings.Favorites.getContentUri(id, false);
+ cr.delete(uri, null, null);
+ changed = true;
+ if (!duplicate) {
+ break;
+ }
+ }
+ } catch (URISyntaxException e) {
+ // Ignore
+ }
+ }
+ } finally {
+ c.close();
+ }
+
+ if (changed) {
+ cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null);
+ Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/UserInitializeReceiver.java b/src/com/android/launcher3/UserInitializeReceiver.java
new file mode 100644
index 0000000..d8e17b1
--- /dev/null
+++ b/src/com/android/launcher3/UserInitializeReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Takes care of setting initial wallpaper for a user, by selecting the
+ * first wallpaper that is not in use by another user.
+ */
+public class UserInitializeReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // TODO: initial wallpaper now that wallpapers are owned by another app
+ }
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
new file mode 100644
index 0000000..2cb9314
--- /dev/null
+++ b/src/com/android/launcher3/Utilities.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+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.Toast;
+
+import java.util.ArrayList;
+
+/**
+ * Various utilities shared amongst the Launcher's classes.
+ */
+final class Utilities {
+ private static final String TAG = "Launcher.Utilities";
+
+ private static int sIconWidth = -1;
+ private static int sIconHeight = -1;
+ public static int sIconTextureWidth = -1;
+ public static int sIconTextureHeight = -1;
+
+ private static final Paint sBlurPaint = new Paint();
+ private static final Paint sGlowColorPressedPaint = new Paint();
+ private static final Paint sGlowColorFocusedPaint = new Paint();
+ private static final Paint sDisabledPaint = new Paint();
+ private static final Rect sOldBounds = new Rect();
+ private static final Canvas sCanvas = new Canvas();
+
+ static {
+ sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ }
+ static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
+ static int sColorIndex = 0;
+
+ /**
+ * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
+ * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
+ * to the proper size (48dp)
+ */
+ static Bitmap createIconBitmap(Bitmap icon, Context context) {
+ int textureWidth = sIconTextureWidth;
+ int textureHeight = sIconTextureHeight;
+ int sourceWidth = icon.getWidth();
+ int sourceHeight = icon.getHeight();
+ if (sourceWidth > textureWidth && sourceHeight > textureHeight) {
+ // Icon is bigger than it should be; clip it (solves the GB->ICS migration case)
+ return Bitmap.createBitmap(icon,
+ (sourceWidth - textureWidth) / 2,
+ (sourceHeight - textureHeight) / 2,
+ textureWidth, textureHeight);
+ } else if (sourceWidth == textureWidth && sourceHeight == textureHeight) {
+ // Icon is the right size, no need to change it
+ return icon;
+ } else {
+ // Icon is too small, render to a larger bitmap
+ final Resources resources = context.getResources();
+ return createIconBitmap(new BitmapDrawable(resources, icon), context);
+ }
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view.
+ */
+ static Bitmap createIconBitmap(Drawable icon, Context context) {
+ synchronized (sCanvas) { // we share the statics :-(
+ if (sIconWidth == -1) {
+ initStatics(context);
+ }
+
+ int width = sIconWidth;
+ int height = sIconHeight;
+
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
+ }
+ }
+ int sourceWidth = icon.getIntrinsicWidth();
+ int sourceHeight = icon.getIntrinsicHeight();
+ if (sourceWidth > 0 && sourceHeight > 0) {
+ // Scale the icon proportionally to the icon dimensions
+ final float ratio = (float) sourceWidth / sourceHeight;
+ if (sourceWidth > sourceHeight) {
+ height = (int) (width / ratio);
+ } else if (sourceHeight > sourceWidth) {
+ width = (int) (height * ratio);
+ }
+ }
+
+ // no intrinsic size --> use default size
+ int textureWidth = sIconTextureWidth;
+ int textureHeight = sIconTextureHeight;
+
+ final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = sCanvas;
+ canvas.setBitmap(bitmap);
+
+ final int left = (textureWidth-width) / 2;
+ final int top = (textureHeight-height) / 2;
+
+ @SuppressWarnings("all") // suppress dead code warning
+ final boolean debug = false;
+ if (debug) {
+ // draw a big box for the icon for debugging
+ canvas.drawColor(sColors[sColorIndex]);
+ if (++sColorIndex >= sColors.length) sColorIndex = 0;
+ Paint debugPaint = new Paint();
+ debugPaint.setColor(0xffcccc00);
+ canvas.drawRect(left, top, left+width, top+height, debugPaint);
+ }
+
+ sOldBounds.set(icon.getBounds());
+ icon.setBounds(left, top, left+width, top+height);
+ icon.draw(canvas);
+ icon.setBounds(sOldBounds);
+ canvas.setBitmap(null);
+
+ return bitmap;
+ }
+ }
+
+ /**
+ * Returns a Bitmap representing the thumbnail of the specified Bitmap.
+ *
+ * @param bitmap The bitmap to get a thumbnail of.
+ * @param context The application's context.
+ *
+ * @return A thumbnail for the specified bitmap or the bitmap itself if the
+ * thumbnail could not be created.
+ */
+ static Bitmap resampleIconBitmap(Bitmap bitmap, Context context) {
+ synchronized (sCanvas) { // we share the statics :-(
+ if (sIconWidth == -1) {
+ initStatics(context);
+ }
+
+ if (bitmap.getWidth() == sIconWidth && bitmap.getHeight() == sIconHeight) {
+ return bitmap;
+ } else {
+ final Resources resources = context.getResources();
+ return createIconBitmap(new BitmapDrawable(resources, bitmap), context);
+ }
+ }
+ }
+
+ /**
+ * Given a coordinate relative to the descendant, find the coordinate in a parent view's
+ * coordinates.
+ *
+ * @param descendant The descendant to which the passed coordinate is relative.
+ * @param root The root view to make the coordinates relative to.
+ * @param coord The coordinate that we want mapped.
+ * @param includeRootScroll Whether or not to account for the scroll of the descendant:
+ * sometimes this is relevant as in a child's coordinates within the descendant.
+ * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+ * this scale factor is assumed to be equal in X and Y, and so if at any point this
+ * assumption fails, we will need to return a pair of scale factors.
+ */
+ public static float getDescendantCoordRelativeToParent(View descendant, View root,
+ int[] coord, boolean includeRootScroll) {
+ ArrayList<View> ancestorChain = new ArrayList<View>();
+
+ float[] pt = {coord[0], coord[1]};
+
+ View v = descendant;
+ while(v != root && v != null) {
+ ancestorChain.add(v);
+ v = (View) v.getParent();
+ }
+ ancestorChain.add(root);
+
+ float scale = 1.0f;
+ int count = ancestorChain.size();
+ for (int i = 0; i < count; i++) {
+ View v0 = ancestorChain.get(i);
+ // For TextViews, scroll has a meaning which relates to the text position
+ // which is very strange... ignore the scroll.
+ if (v0 != descendant || includeRootScroll) {
+ pt[0] -= v0.getScrollX();
+ pt[1] -= v0.getScrollY();
+ }
+
+ v0.getMatrix().mapPoints(pt);
+ pt[0] += v0.getLeft();
+ pt[1] += v0.getTop();
+ scale *= v0.getScaleX();
+ }
+
+ coord[0] = (int) Math.round(pt[0]);
+ coord[1] = (int) Math.round(pt[1]);
+ return scale;
+ }
+
+ /**
+ * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
+ */
+ public static float mapCoordInSelfToDescendent(View descendant, View root,
+ int[] coord) {
+ ArrayList<View> ancestorChain = new ArrayList<View>();
+
+ float[] pt = {coord[0], coord[1]};
+
+ View v = descendant;
+ while(v != root) {
+ ancestorChain.add(v);
+ v = (View) v.getParent();
+ }
+ ancestorChain.add(root);
+
+ float scale = 1.0f;
+ Matrix inverse = new Matrix();
+ int count = ancestorChain.size();
+ for (int i = count - 1; i >= 0; i--) {
+ View ancestor = ancestorChain.get(i);
+ View next = i > 0 ? ancestorChain.get(i-1) : null;
+
+ pt[0] += ancestor.getScrollX();
+ pt[1] += ancestor.getScrollY();
+
+ if (next != null) {
+ pt[0] -= next.getLeft();
+ pt[1] -= next.getTop();
+ next.getMatrix().invert(inverse);
+ inverse.mapPoints(pt);
+ scale *= next.getScaleX();
+ }
+ }
+
+ coord[0] = (int) Math.round(pt[0]);
+ coord[1] = (int) Math.round(pt[1]);
+ return scale;
+ }
+
+ private static void initStatics(Context context) {
+ final Resources resources = context.getResources();
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ final float density = metrics.density;
+
+ sIconWidth = sIconHeight = (int) resources.getDimension(R.dimen.app_icon_size);
+ sIconTextureWidth = sIconTextureHeight = sIconWidth;
+
+ sBlurPaint.setMaskFilter(new BlurMaskFilter(5 * density, BlurMaskFilter.Blur.NORMAL));
+ sGlowColorPressedPaint.setColor(0xffffc300);
+ sGlowColorFocusedPaint.setColor(0xffff8e00);
+
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(0.2f);
+ sDisabledPaint.setColorFilter(new ColorMatrixColorFilter(cm));
+ sDisabledPaint.setAlpha(0x88);
+ }
+
+ public static void setIconSize(int widthPx) {
+ sIconWidth = sIconHeight = widthPx;
+ sIconTextureWidth = sIconTextureHeight = widthPx;
+ }
+
+ public static void scaleRect(Rect r, float scale) {
+ if (scale != 1.0f) {
+ r.left = (int) (r.left * scale + 0.5f);
+ r.top = (int) (r.top * scale + 0.5f);
+ r.right = (int) (r.right * scale + 0.5f);
+ r.bottom = (int) (r.bottom * scale + 0.5f);
+ }
+ }
+
+ public static void scaleRectAboutCenter(Rect r, float scale) {
+ int cx = r.centerX();
+ int cy = r.centerY();
+ r.offset(-cx, -cy);
+ Utilities.scaleRect(r, scale);
+ r.offset(cx, cy);
+ }
+
+ public static void startActivityForResultSafely(
+ Activity activity, Intent intent, int requestCode) {
+ try {
+ activity.startActivityForResult(intent, requestCode);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ } catch (SecurityException e) {
+ Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Launcher does not have the permission to launch " + intent +
+ ". Make sure to create a MAIN intent-filter for the corresponding activity " +
+ "or use the exported attribute for this activity.", e);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/WallpaperCropActivity.java b/src/com/android/launcher3/WallpaperCropActivity.java
new file mode 100644
index 0000000..79cc88e
--- /dev/null
+++ b/src/com/android/launcher3/WallpaperCropActivity.java
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 2013 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.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.photos.BitmapRegionTileSource;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WallpaperCropActivity extends Activity {
+ private static final String LOGTAG = "Launcher3.CropActivity";
+
+ protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
+ protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
+ private static final int DEFAULT_COMPRESS_QUALITY = 90;
+ /**
+ * The maximum bitmap size we allow to be returned through the intent.
+ * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
+ * have some overhead to hit so that we go way below the limit here to make
+ * sure the intent stays below 1MB.We should consider just returning a byte
+ * array instead of a Bitmap instance to avoid overhead.
+ */
+ public static final int MAX_BMAP_IN_INTENT = 750000;
+ private static final float WALLPAPER_SCREENS_SPAN = 2f;
+
+ protected CropView mCropView;
+ protected Uri mUri;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ init();
+ if (!enableRotation()) {
+ setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
+ }
+ }
+
+ protected void init() {
+ setContentView(R.layout.wallpaper_cropper);
+
+ mCropView = (CropView) findViewById(R.id.cropView);
+
+ Intent cropIntent = this.getIntent();
+ final Uri imageUri = cropIntent.getData();
+
+ int rotation = getRotationFromExif(this, imageUri);
+ mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null);
+ mCropView.setTouchEnabled(true);
+ // Action bar
+ // Show the custom action bar view
+ final ActionBar actionBar = getActionBar();
+ actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
+ actionBar.getCustomView().setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ boolean finishActivityWhenDone = true;
+ cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone);
+ }
+ });
+ }
+
+ public boolean enableRotation() {
+ return getResources().getBoolean(R.bool.allow_rotation);
+ }
+
+ public static String getSharedPreferencesKey() {
+ return WallpaperCropActivity.class.getName();
+ }
+
+ // As a ratio of screen height, the total distance we want the parallax effect to span
+ // horizontally
+ private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
+ float aspectRatio = width / (float) height;
+
+ // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
+ // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
+ // We will use these two data points to extrapolate how much the wallpaper parallax effect
+ // to span (ie travel) at any aspect ratio:
+
+ final float ASPECT_RATIO_LANDSCAPE = 16/10f;
+ final float ASPECT_RATIO_PORTRAIT = 10/16f;
+ final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
+ final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
+
+ // To find out the desired width at different aspect ratios, we use the following two
+ // formulas, where the coefficient on x is the aspect ratio (width/height):
+ // (16/10)x + y = 1.5
+ // (10/16)x + y = 1.2
+ // We solve for x and y and end up with a final formula:
+ final float x =
+ (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
+ (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
+ final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
+ return x * aspectRatio + y;
+ }
+
+ static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
+ Point minDims = new Point();
+ Point maxDims = new Point();
+ windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
+
+ int maxDim = Math.max(maxDims.x, maxDims.y);
+ int minDim = Math.max(minDims.x, minDims.y);
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ Point realSize = new Point();
+ windowManager.getDefaultDisplay().getRealSize(realSize);
+ maxDim = Math.max(realSize.x, realSize.y);
+ minDim = Math.min(realSize.x, realSize.y);
+ }
+
+ // We need to ensure that there is enough extra space in the wallpaper
+ // for the intended
+ // parallax effects
+ final int defaultWidth, defaultHeight;
+ if (isScreenLarge(res)) {
+ defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+ defaultHeight = maxDim;
+ } else {
+ defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+ defaultHeight = maxDim;
+ }
+ return new Point(defaultWidth, defaultHeight);
+ }
+
+ public static int getRotationFromExif(String path) {
+ return getRotationFromExifHelper(path, null, 0, null, null);
+ }
+
+ public static int getRotationFromExif(Context context, Uri uri) {
+ return getRotationFromExifHelper(null, null, 0, context, uri);
+ }
+
+ public static int getRotationFromExif(Resources res, int resId) {
+ return getRotationFromExifHelper(null, res, resId, null, null);
+ }
+
+ private static int getRotationFromExifHelper(
+ String path, Resources res, int resId, Context context, Uri uri) {
+ ExifInterface ei = new ExifInterface();
+ try {
+ if (path != null) {
+ ei.readExif(path);
+ } else if (uri != null) {
+ InputStream is = context.getContentResolver().openInputStream(uri);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ ei.readExif(bis);
+ } else {
+ InputStream is = res.openRawResource(resId);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ ei.readExif(bis);
+ }
+ Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (ori != null) {
+ return ExifInterface.getRotationForOrientationValue(ori.shortValue());
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "Getting exif data failed", e);
+ }
+ return 0;
+ }
+
+ protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) {
+ int rotation = getRotationFromExif(filePath);
+ BitmapCropTask cropTask = new BitmapCropTask(
+ this, filePath, null, rotation, 0, 0, true, false, null);
+ final Point bounds = cropTask.getImageBounds();
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ updateWallpaperDimensions(bounds.x, bounds.y);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ cropTask.setOnEndRunnable(onEndCrop);
+ cropTask.setNoCrop(true);
+ cropTask.execute();
+ }
+
+ protected void cropImageAndSetWallpaper(
+ Resources res, int resId, final boolean finishActivityWhenDone) {
+ // crop this image and scale it down to the default wallpaper size for
+ // this device
+ int rotation = getRotationFromExif(res, resId);
+ Point inSize = mCropView.getSourceDimensions();
+ Point outSize = getDefaultWallpaperSize(getResources(),
+ getWindowManager());
+ RectF crop = getMaxCropRect(
+ inSize.x, inSize.y, outSize.x, outSize.y, false);
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ // Passing 0, 0 will cause launcher to revert to using the
+ // default wallpaper size
+ updateWallpaperDimensions(0, 0);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(this, res, resId,
+ crop, rotation, outSize.x, outSize.y, true, false, onEndCrop);
+ cropTask.execute();
+ }
+
+ private static boolean isScreenLarge(Resources res) {
+ Configuration config = res.getConfiguration();
+ return config.smallestScreenWidthDp >= 720;
+ }
+
+ protected void cropImageAndSetWallpaper(Uri uri,
+ OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) {
+ // Get the crop
+ boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+
+ Point minDims = new Point();
+ Point maxDims = new Point();
+ Display d = getWindowManager().getDefaultDisplay();
+ d.getCurrentSizeRange(minDims, maxDims);
+
+ Point displaySize = new Point();
+ d.getSize(displaySize);
+
+ int maxDim = Math.max(maxDims.x, maxDims.y);
+ final int minDim = Math.min(minDims.x, minDims.y);
+ int defaultWallpaperWidth;
+ if (isScreenLarge(getResources())) {
+ defaultWallpaperWidth = (int) (maxDim *
+ wallpaperTravelToScreenWidthRatio(maxDim, minDim));
+ } else {
+ defaultWallpaperWidth = Math.max((int)
+ (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
+ }
+
+ boolean isPortrait = displaySize.x < displaySize.y;
+ int portraitHeight;
+ if (isPortrait) {
+ portraitHeight = mCropView.getHeight();
+ } else {
+ // TODO: how to actually get the proper portrait height?
+ // This is not quite right:
+ portraitHeight = Math.max(maxDims.x, maxDims.y);
+ }
+ if (android.os.Build.VERSION.SDK_INT >=
+ android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ Point realSize = new Point();
+ d.getRealSize(realSize);
+ portraitHeight = Math.max(realSize.x, realSize.y);
+ }
+ // Get the crop
+ RectF cropRect = mCropView.getCrop();
+ int cropRotation = mCropView.getImageRotation();
+ float cropScale = mCropView.getWidth() / (float) cropRect.width();
+
+ Point inSize = mCropView.getSourceDimensions();
+ Matrix rotateMatrix = new Matrix();
+ rotateMatrix.setRotate(cropRotation);
+ float[] rotatedInSize = new float[] { inSize.x, inSize.y };
+ rotateMatrix.mapPoints(rotatedInSize);
+ rotatedInSize[0] = Math.abs(rotatedInSize[0]);
+ rotatedInSize[1] = Math.abs(rotatedInSize[1]);
+
+ // ADJUST CROP WIDTH
+ // Extend the crop all the way to the right, for parallax
+ // (or all the way to the left, in RTL)
+ float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left;
+ // Cap the amount of extra width
+ float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width();
+ extraSpace = Math.min(extraSpace, maxExtraSpace);
+
+ if (ltr) {
+ cropRect.right += extraSpace;
+ } else {
+ cropRect.left -= extraSpace;
+ }
+
+ // ADJUST CROP HEIGHT
+ if (isPortrait) {
+ cropRect.bottom = cropRect.top + portraitHeight / cropScale;
+ } else { // LANDSCAPE
+ float extraPortraitHeight =
+ portraitHeight / cropScale - cropRect.height();
+ float expandHeight =
+ Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top),
+ extraPortraitHeight / 2);
+ cropRect.top -= expandHeight;
+ cropRect.bottom += expandHeight;
+ }
+ final int outWidth = (int) Math.round(cropRect.width() * cropScale);
+ final int outHeight = (int) Math.round(cropRect.height() * cropScale);
+
+ Runnable onEndCrop = new Runnable() {
+ public void run() {
+ updateWallpaperDimensions(outWidth, outHeight);
+ if (finishActivityWhenDone) {
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ };
+ BitmapCropTask cropTask = new BitmapCropTask(this, uri,
+ cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop);
+ if (onBitmapCroppedHandler != null) {
+ cropTask.setOnBitmapCropped(onBitmapCroppedHandler);
+ }
+ cropTask.execute();
+ }
+
+ public interface OnBitmapCroppedHandler {
+ public void onBitmapCropped(byte[] imageBytes);
+ }
+
+ protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
+ Uri mInUri = null;
+ Context mContext;
+ String mInFilePath;
+ byte[] mInImageBytes;
+ int mInResId = 0;
+ InputStream mInStream;
+ RectF mCropBounds = null;
+ int mOutWidth, mOutHeight;
+ int mRotation;
+ String mOutputFormat = "jpg"; // for now
+ boolean mSetWallpaper;
+ boolean mSaveCroppedBitmap;
+ Bitmap mCroppedBitmap;
+ Runnable mOnEndRunnable;
+ Resources mResources;
+ OnBitmapCroppedHandler mOnBitmapCroppedHandler;
+ boolean mNoCrop;
+
+ public BitmapCropTask(Context c, String filePath,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInFilePath = filePath;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(byte[] imageBytes,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mInImageBytes = imageBytes;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(Context c, Uri inUri,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInUri = inUri;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ public BitmapCropTask(Context c, Resources res, int inResId,
+ RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mContext = c;
+ mInResId = inResId;
+ mResources = res;
+ init(cropBounds, rotation,
+ outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
+ }
+
+ private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
+ boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
+ mCropBounds = cropBounds;
+ mRotation = rotation;
+ mOutWidth = outWidth;
+ mOutHeight = outHeight;
+ mSetWallpaper = setWallpaper;
+ mSaveCroppedBitmap = saveCroppedBitmap;
+ mOnEndRunnable = onEndRunnable;
+ }
+
+ public void setOnBitmapCropped(OnBitmapCroppedHandler handler) {
+ mOnBitmapCroppedHandler = handler;
+ }
+
+ public void setNoCrop(boolean value) {
+ mNoCrop = value;
+ }
+
+ public void setOnEndRunnable(Runnable onEndRunnable) {
+ mOnEndRunnable = onEndRunnable;
+ }
+
+ // Helper to setup input stream
+ private void regenerateInputStream() {
+ if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
+ Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
+ "image byte array given");
+ } else {
+ Utils.closeSilently(mInStream);
+ try {
+ if (mInUri != null) {
+ mInStream = new BufferedInputStream(
+ mContext.getContentResolver().openInputStream(mInUri));
+ } else if (mInFilePath != null) {
+ mInStream = mContext.openFileInput(mInFilePath);
+ } else if (mInImageBytes != null) {
+ mInStream = new BufferedInputStream(
+ new ByteArrayInputStream(mInImageBytes));
+ } else {
+ mInStream = new BufferedInputStream(
+ mResources.openRawResource(mInResId));
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+ }
+ }
+ }
+
+ public Point getImageBounds() {
+ regenerateInputStream();
+ if (mInStream != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(mInStream, null, options);
+ if (options.outWidth != 0 && options.outHeight != 0) {
+ return new Point(options.outWidth, options.outHeight);
+ }
+ }
+ return null;
+ }
+
+ public void setCropBounds(RectF cropBounds) {
+ mCropBounds = cropBounds;
+ }
+
+ public Bitmap getCroppedBitmap() {
+ return mCroppedBitmap;
+ }
+ public boolean cropBitmap() {
+ boolean failure = false;
+
+ regenerateInputStream();
+
+ WallpaperManager wallpaperManager = null;
+ if (mSetWallpaper) {
+ wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
+ }
+ if (mSetWallpaper && mNoCrop && mInStream != null) {
+ try {
+ wallpaperManager.setStream(mInStream);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+ failure = true;
+ }
+ return !failure;
+ }
+ if (mInStream != null) {
+ // Find crop bounds (scaled to original image size)
+ Rect roundedTrueCrop = new Rect();
+ Matrix rotateMatrix = new Matrix();
+ Matrix inverseRotateMatrix = new Matrix();
+ if (mRotation > 0) {
+ rotateMatrix.setRotate(mRotation);
+ inverseRotateMatrix.setRotate(-mRotation);
+
+ mCropBounds.roundOut(roundedTrueCrop);
+ mCropBounds = new RectF(roundedTrueCrop);
+
+ Point bounds = getImageBounds();
+
+ float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+ rotateMatrix.mapPoints(rotatedBounds);
+ rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+ rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+ mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
+ inverseRotateMatrix.mapRect(mCropBounds);
+ mCropBounds.offset(bounds.x/2, bounds.y/2);
+
+ regenerateInputStream();
+ }
+
+ mCropBounds.roundOut(roundedTrueCrop);
+
+ if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+ Log.w(LOGTAG, "crop has bad values for full size image");
+ failure = true;
+ return false;
+ }
+
+ // See how much we're reducing the size of the image
+ int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth,
+ roundedTrueCrop.height() / mOutHeight);
+
+ // Attempt to open a region decoder
+ BitmapRegionDecoder decoder = null;
+ try {
+ decoder = BitmapRegionDecoder.newInstance(mInStream, true);
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+ }
+
+ Bitmap crop = null;
+ if (decoder != null) {
+ // Do region decoding to get crop bitmap
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ crop = decoder.decodeRegion(roundedTrueCrop, options);
+ decoder.recycle();
+ }
+
+ if (crop == null) {
+ // BitmapRegionDecoder has failed, try to crop in-memory
+ regenerateInputStream();
+ Bitmap fullSize = null;
+ if (mInStream != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ if (scaleDownSampleSize > 1) {
+ options.inSampleSize = scaleDownSampleSize;
+ }
+ fullSize = BitmapFactory.decodeStream(mInStream, null, options);
+ }
+ if (fullSize != null) {
+ mCropBounds.left /= scaleDownSampleSize;
+ mCropBounds.top /= scaleDownSampleSize;
+ mCropBounds.bottom /= scaleDownSampleSize;
+ mCropBounds.right /= scaleDownSampleSize;
+ mCropBounds.roundOut(roundedTrueCrop);
+
+ crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+ roundedTrueCrop.top, roundedTrueCrop.width(),
+ roundedTrueCrop.height());
+ }
+ }
+
+ if (crop == null) {
+ Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
+ failure = true;
+ return false;
+ }
+ if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
+ float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
+ rotateMatrix.mapPoints(dimsAfter);
+ dimsAfter[0] = Math.abs(dimsAfter[0]);
+ dimsAfter[1] = Math.abs(dimsAfter[1]);
+
+ if (!(mOutWidth > 0 && mOutHeight > 0)) {
+ mOutWidth = Math.round(dimsAfter[0]);
+ mOutHeight = Math.round(dimsAfter[1]);
+ }
+
+ RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
+ RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
+
+ Matrix m = new Matrix();
+ if (mRotation == 0) {
+ m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+ } else {
+ Matrix m1 = new Matrix();
+ m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
+ Matrix m2 = new Matrix();
+ m2.setRotate(mRotation);
+ Matrix m3 = new Matrix();
+ m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
+ Matrix m4 = new Matrix();
+ m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+
+ Matrix c1 = new Matrix();
+ c1.setConcat(m2, m1);
+ Matrix c2 = new Matrix();
+ c2.setConcat(m4, m3);
+ m.setConcat(c2, c1);
+ }
+
+ Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+ (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+ if (tmp != null) {
+ Canvas c = new Canvas(tmp);
+ Paint p = new Paint();
+ p.setFilterBitmap(true);
+ c.drawBitmap(crop, m, p);
+ crop = tmp;
+ }
+ }
+
+ if (mSaveCroppedBitmap) {
+ mCroppedBitmap = crop;
+ }
+
+ // Get output compression format
+ CompressFormat cf =
+ convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
+
+ // Compress to byte array
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+ if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+ // If we need to set to the wallpaper, set it
+ if (mSetWallpaper && wallpaperManager != null) {
+ try {
+ byte[] outByteArray = tmpOut.toByteArray();
+ wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
+ if (mOnBitmapCroppedHandler != null) {
+ mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
+ }
+ } catch (IOException e) {
+ Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+ failure = true;
+ }
+ }
+ } else {
+ Log.w(LOGTAG, "cannot compress bitmap");
+ failure = true;
+ }
+ }
+ return !failure; // True if any of the operations failed
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ return cropBitmap();
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (mOnEndRunnable != null) {
+ mOnEndRunnable.run();
+ }
+ }
+ }
+
+ protected void updateWallpaperDimensions(int width, int height) {
+ String spKey = getSharedPreferencesKey();
+ SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sp.edit();
+ if (width != 0 && height != 0) {
+ editor.putInt(WALLPAPER_WIDTH_KEY, width);
+ editor.putInt(WALLPAPER_HEIGHT_KEY, height);
+ } else {
+ editor.remove(WALLPAPER_WIDTH_KEY);
+ editor.remove(WALLPAPER_HEIGHT_KEY);
+ }
+ editor.commit();
+
+ suggestWallpaperDimension(getResources(),
+ sp, getWindowManager(), WallpaperManager.getInstance(this));
+ }
+
+ static public void suggestWallpaperDimension(Resources res,
+ final SharedPreferences sharedPrefs,
+ WindowManager windowManager,
+ final WallpaperManager wallpaperManager) {
+ final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
+
+ new Thread("suggestWallpaperDimension") {
+ public void run() {
+ // If we have saved a wallpaper width/height, use that instead
+ int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
+ int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
+ wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
+ }
+ }.start();
+ }
+
+ protected static RectF getMaxCropRect(
+ int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) {
+ RectF cropRect = new RectF();
+ // Get a crop rect that will fit this
+ if (inWidth / (float) inHeight > outWidth / (float) outHeight) {
+ cropRect.top = 0;
+ cropRect.bottom = inHeight;
+ cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2;
+ cropRect.right = inWidth - cropRect.left;
+ if (leftAligned) {
+ cropRect.right -= cropRect.left;
+ cropRect.left = 0;
+ }
+ } else {
+ cropRect.left = 0;
+ cropRect.right = inWidth;
+ cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2;
+ cropRect.bottom = inHeight - cropRect.top;
+ }
+ return cropRect;
+ }
+
+ protected static CompressFormat convertExtensionToCompressFormat(String extension) {
+ return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+ }
+
+ protected static String getFileExtension(String requestFormat) {
+ String outputFormat = (requestFormat == null)
+ ? "jpg"
+ : requestFormat;
+ outputFormat = outputFormat.toLowerCase();
+ return (outputFormat.equals("png") || outputFormat.equals("gif"))
+ ? "png" // We don't support gif compression.
+ : "jpg";
+ }
+}
diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java
new file mode 100644
index 0000000..9bf3c77
--- /dev/null
+++ b/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -0,0 +1,847 @@
+/*
+ * Copyright (C) 2013 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.animation.LayoutTransition;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LevelListDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ActionMode;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.photos.BitmapRegionTileSource;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+public class WallpaperPickerActivity extends WallpaperCropActivity {
+ static final String TAG = "Launcher.WallpaperPickerActivity";
+
+ public static final int IMAGE_PICK = 5;
+ public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6;
+ public static final int PICK_LIVE_WALLPAPER = 7;
+ private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES";
+
+ private View mSelectedThumb;
+ private boolean mIgnoreNextTap;
+ private OnClickListener mThumbnailOnClickListener;
+
+ private LinearLayout mWallpapersView;
+ private View mWallpaperStrip;
+
+ private ActionMode.Callback mActionModeCallback;
+ private ActionMode mActionMode;
+
+ private View.OnLongClickListener mLongClickListener;
+
+ ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>();
+ private SavedWallpaperImages mSavedImages;
+ private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch;
+
+ public static abstract class WallpaperTileInfo {
+ protected View mView;
+ public void setView(View v) {
+ mView = v;
+ }
+ public void onClick(WallpaperPickerActivity a) {}
+ public void onSave(WallpaperPickerActivity a) {}
+ public void onDelete(WallpaperPickerActivity a) {}
+ public boolean isSelectable() { return false; }
+ public boolean isNamelessWallpaper() { return false; }
+ public void onIndexUpdated(CharSequence label) {
+ if (isNamelessWallpaper()) {
+ mView.setContentDescription(label);
+ }
+ }
+ }
+
+ public static class PickImageInfo extends WallpaperTileInfo {
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ Utilities.startActivityForResultSafely(a, intent, IMAGE_PICK);
+ }
+ }
+
+ public static class UriWallpaperInfo extends WallpaperTileInfo {
+ private Uri mUri;
+ public UriWallpaperInfo(Uri uri) {
+ mUri = uri;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ CropView v = a.getCropView();
+ int rotation = WallpaperCropActivity.getRotationFromExif(a, mUri);
+ v.setTileSource(new BitmapRegionTileSource(a, mUri, 1024, rotation), null);
+ v.setTouchEnabled(true);
+ }
+ @Override
+ public void onSave(final WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() {
+ public void onBitmapCropped(byte[] imageBytes) {
+ Point thumbSize = getDefaultThumbnailSize(a.getResources());
+ // rotation is set to 0 since imageBytes has already been correctly rotated
+ Bitmap thumb = createThumbnail(
+ thumbSize, null, null, imageBytes, null, 0, 0, true);
+ a.getSavedImages().writeImage(thumb, imageBytes);
+ }
+ };
+ a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public static class ResourceWallpaperInfo extends WallpaperTileInfo {
+ private Resources mResources;
+ private int mResId;
+ private Drawable mThumb;
+
+ public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) {
+ mResources = res;
+ mResId = resId;
+ mThumb = thumb;
+ }
+ @Override
+ public void onClick(WallpaperPickerActivity a) {
+ int rotation = WallpaperCropActivity.getRotationFromExif(mResources, mResId);
+ BitmapRegionTileSource source = new BitmapRegionTileSource(
+ mResources, a, mResId, 1024, rotation);
+ CropView v = a.getCropView();
+ v.setTileSource(source, null);
+ Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize(
+ a.getResources(), a.getWindowManager());
+ RectF crop = WallpaperCropActivity.getMaxCropRect(
+ source.getImageWidth(), source.getImageHeight(),
+ wallpaperSize.x, wallpaperSize.y, false);
+ v.setScale(wallpaperSize.x / crop.width());
+ v.setTouchEnabled(false);
+ }
+ @Override
+ public void onSave(WallpaperPickerActivity a) {
+ boolean finishActivityWhenDone = true;
+ a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone);
+ }
+ @Override
+ public boolean isSelectable() {
+ return true;
+ }
+ @Override
+ public boolean isNamelessWallpaper() {
+ return true;
+ }
+ }
+
+ public void setWallpaperStripYOffset(float offset) {
+ mWallpaperStrip.setPadding(0, 0, 0, (int) offset);
+ }
+
+ // called by onCreate; this is subclassed to overwrite WallpaperCropActivity
+ protected void init() {
+ setContentView(R.layout.wallpaper_picker);
+
+ mCropView = (CropView) findViewById(R.id.cropView);
+ mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+ mCropView.setTouchCallback(new CropView.TouchCallback() {
+ LauncherViewPropertyAnimator mAnim;
+ @Override
+ public void onTouchDown() {
+ if (mAnim != null) {
+ mAnim.cancel();
+ }
+ if (mWallpaperStrip.getTranslationY() == 0) {
+ mIgnoreNextTap = true;
+ }
+ mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
+ mAnim.translationY(mWallpaperStrip.getHeight()).alpha(0f)
+ .setInterpolator(new DecelerateInterpolator(0.75f));
+ mAnim.start();
+ }
+ @Override
+ public void onTouchUp() {
+ mIgnoreNextTap = false;
+ }
+ @Override
+ public void onTap() {
+ boolean ignoreTap = mIgnoreNextTap;
+ mIgnoreNextTap = false;
+ if (!ignoreTap) {
+ if (mAnim != null) {
+ mAnim.cancel();
+ }
+ mAnim = new LauncherViewPropertyAnimator(mWallpaperStrip);
+ mAnim.translationY(0f).alpha(1f)
+ .setInterpolator(new DecelerateInterpolator(0.75f));
+ mAnim.start();
+ }
+ }
+ });
+
+ mThumbnailOnClickListener = new OnClickListener() {
+ public void onClick(View v) {
+ if (mActionMode != null) {
+ // When CAB is up, clicking toggles the item instead
+ if (v.isLongClickable()) {
+ mLongClickListener.onLongClick(v);
+ }
+ return;
+ }
+ WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
+ if (info.isSelectable()) {
+ if (mSelectedThumb != null) {
+ mSelectedThumb.setSelected(false);
+ mSelectedThumb = null;
+ }
+ mSelectedThumb = v;
+ v.setSelected(true);
+ // TODO: Remove this once the accessibility framework and
+ // services have better support for selection state.
+ v.announceForAccessibility(
+ getString(R.string.announce_selection, v.getContentDescription()));
+ }
+ info.onClick(WallpaperPickerActivity.this);
+ }
+ };
+ mLongClickListener = new View.OnLongClickListener() {
+ // Called when the user long-clicks on someView
+ public boolean onLongClick(View view) {
+ CheckableFrameLayout c = (CheckableFrameLayout) view;
+ c.toggle();
+
+ if (mActionMode != null) {
+ mActionMode.invalidate();
+ } else {
+ // Start the CAB using the ActionMode.Callback defined below
+ mActionMode = startActionMode(mActionModeCallback);
+ int childCount = mWallpapersView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mWallpapersView.getChildAt(i).setSelected(false);
+ }
+ }
+ return true;
+ }
+ };
+
+ // Populate the built-in wallpapers
+ ArrayList<ResourceWallpaperInfo> wallpapers = findBundledWallpapers();
+ mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list);
+ BuiltInWallpapersAdapter ia = new BuiltInWallpapersAdapter(this, wallpapers);
+ populateWallpapersFromAdapter(mWallpapersView, ia, false, true);
+
+ // Populate the saved wallpapers
+ mSavedImages = new SavedWallpaperImages(this);
+ mSavedImages.loadThumbnailsAndImageIdList();
+ populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true, true);
+
+ // Populate the live wallpapers
+ final LinearLayout liveWallpapersView =
+ (LinearLayout) findViewById(R.id.live_wallpaper_list);
+ final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this);
+ a.registerDataSetObserver(new DataSetObserver() {
+ public void onChanged() {
+ liveWallpapersView.removeAllViews();
+ populateWallpapersFromAdapter(liveWallpapersView, a, false, false);
+ initializeScrollForRtl();
+ updateTileIndices();
+ }
+ });
+
+ // Populate the third-party wallpaper pickers
+ final LinearLayout thirdPartyWallpapersView =
+ (LinearLayout) findViewById(R.id.third_party_wallpaper_list);
+ final ThirdPartyWallpaperPickerListAdapter ta =
+ new ThirdPartyWallpaperPickerListAdapter(this);
+ populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false, false);
+
+ // Add a tile for the Gallery
+ LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
+ FrameLayout pickImageTile = (FrameLayout) getLayoutInflater().
+ inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false);
+ setWallpaperItemPaddingToZero(pickImageTile);
+ masterWallpaperList.addView(pickImageTile, 0);
+
+ // Make its background the last photo taken on external storage
+ Bitmap lastPhoto = getThumbnailOfLastPhoto();
+ if (lastPhoto != null) {
+ ImageView galleryThumbnailBg =
+ (ImageView) pickImageTile.findViewById(R.id.wallpaper_image);
+ galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto());
+ int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray);
+ galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP);
+
+ }
+
+ PickImageInfo pickImageInfo = new PickImageInfo();
+ pickImageTile.setTag(pickImageInfo);
+ pickImageInfo.setView(pickImageTile);
+ pickImageTile.setOnClickListener(mThumbnailOnClickListener);
+ pickImageInfo.setView(pickImageTile);
+
+ updateTileIndices();
+
+ // Update the scroll for RTL
+ initializeScrollForRtl();
+
+ // Create smooth layout transitions for when items are deleted
+ final LayoutTransition transitioner = new LayoutTransition();
+ transitioner.setDuration(200);
+ transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
+ transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
+ mWallpapersView.setLayoutTransition(transitioner);
+
+ // Action bar
+ // Show the custom action bar view
+ final ActionBar actionBar = getActionBar();
+ actionBar.setCustomView(R.layout.actionbar_set_wallpaper);
+ actionBar.getCustomView().setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mSelectedThumb != null) {
+ WallpaperTileInfo info = (WallpaperTileInfo) mSelectedThumb.getTag();
+ info.onSave(WallpaperPickerActivity.this);
+ }
+ }
+ });
+
+ // CAB for deleting items
+ mActionModeCallback = new ActionMode.Callback() {
+ // Called when the action mode is created; startActionMode() was called
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // Inflate a menu resource providing context menu items
+ MenuInflater inflater = mode.getMenuInflater();
+ inflater.inflate(R.menu.cab_delete_wallpapers, menu);
+ return true;
+ }
+
+ private int numCheckedItems() {
+ int childCount = mWallpapersView.getChildCount();
+ int numCheckedItems = 0;
+ for (int i = 0; i < childCount; i++) {
+ CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+ if (c.isChecked()) {
+ numCheckedItems++;
+ }
+ }
+ return numCheckedItems;
+ }
+
+ // Called each time the action mode is shown. Always called after onCreateActionMode,
+ // but may be called multiple times if the mode is invalidated.
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ int numCheckedItems = numCheckedItems();
+ if (numCheckedItems == 0) {
+ mode.finish();
+ return true;
+ } else {
+ mode.setTitle(getResources().getQuantityString(
+ R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems));
+ return true;
+ }
+ }
+
+ // Called when the user selects a contextual menu item
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == R.id.menu_delete) {
+ int childCount = mWallpapersView.getChildCount();
+ ArrayList<View> viewsToRemove = new ArrayList<View>();
+ for (int i = 0; i < childCount; i++) {
+ CheckableFrameLayout c =
+ (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+ if (c.isChecked()) {
+ WallpaperTileInfo info = (WallpaperTileInfo) c.getTag();
+ info.onDelete(WallpaperPickerActivity.this);
+ viewsToRemove.add(c);
+ }
+ }
+ for (View v : viewsToRemove) {
+ mWallpapersView.removeView(v);
+ }
+ updateTileIndices();
+ mode.finish(); // Action picked, so close the CAB
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Called when the user exits the action mode
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ int childCount = mWallpapersView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i);
+ c.setChecked(false);
+ }
+ mSelectedThumb.setSelected(true);
+ mActionMode = null;
+ }
+ };
+ }
+
+ private void initializeScrollForRtl() {
+ final HorizontalScrollView scroll =
+ (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
+
+ if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ final ViewTreeObserver observer = scroll.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ public void onGlobalLayout() {
+ LinearLayout masterWallpaperList =
+ (LinearLayout) findViewById(R.id.master_wallpaper_list);
+ scroll.scrollTo(masterWallpaperList.getWidth(), 0);
+ scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+ }
+
+ public boolean enableRotation() {
+ return super.enableRotation() || Launcher.sForceEnableRotation;
+ }
+
+ protected Bitmap getThumbnailOfLastPhoto() {
+ Cursor cursor = MediaStore.Images.Media.query(getContentResolver(),
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[] { MediaStore.Images.ImageColumns._ID,
+ MediaStore.Images.ImageColumns.DATE_TAKEN},
+ null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1");
+ Bitmap thumb = null;
+ if (cursor.moveToNext()) {
+ int id = cursor.getInt(0);
+ thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(),
+ id, MediaStore.Images.Thumbnails.MINI_KIND, null);
+ }
+ cursor.close();
+ return thumb;
+ }
+
+ protected void onStop() {
+ super.onStop();
+ mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+ if (mWallpaperStrip.getTranslationY() > 0f) {
+ mWallpaperStrip.setTranslationY(0f);
+ mWallpaperStrip.setAlpha(1f);
+ }
+ }
+
+ protected void onSaveInstanceState(Bundle outState) {
+ outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles);
+ }
+
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES);
+ for (Uri uri : uris) {
+ addTemporaryWallpaperTile(uri);
+ }
+ }
+
+ private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter,
+ boolean addLongPressHandler, boolean selectFirstTile) {
+ for (int i = 0; i < adapter.getCount(); i++) {
+ FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent);
+ parent.addView(thumbnail, i);
+ WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i);
+ thumbnail.setTag(info);
+ info.setView(thumbnail);
+ if (addLongPressHandler) {
+ addLongPressHandler(thumbnail);
+ }
+ thumbnail.setOnClickListener(mThumbnailOnClickListener);
+ if (i == 0 && selectFirstTile) {
+ mThumbnailOnClickListener.onClick(thumbnail);
+ }
+ }
+ }
+
+ private void updateTileIndices() {
+ LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list);
+ final int childCount = masterWallpaperList.getChildCount();
+ final Resources res = getResources();
+
+ // Do two passes; the first pass gets the total number of tiles
+ int numTiles = 0;
+ for (int passNum = 0; passNum < 2; passNum++) {
+ int tileIndex = 0;
+ for (int i = 0; i < childCount; i++) {
+ View child = masterWallpaperList.getChildAt(i);
+ LinearLayout subList;
+
+ int subListStart;
+ int subListEnd;
+ if (child.getTag() instanceof WallpaperTileInfo) {
+ subList = masterWallpaperList;
+ subListStart = i;
+ subListEnd = i + 1;
+ } else { // if (child instanceof LinearLayout) {
+ subList = (LinearLayout) child;
+ subListStart = 0;
+ subListEnd = subList.getChildCount();
+ }
+
+ for (int j = subListStart; j < subListEnd; j++) {
+ WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag();
+ if (info.isNamelessWallpaper()) {
+ if (passNum == 0) {
+ numTiles++;
+ } else {
+ CharSequence label = res.getString(
+ R.string.wallpaper_accessibility_name, ++tileIndex, numTiles);
+ info.onIndexUpdated(label);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static Point getDefaultThumbnailSize(Resources res) {
+ return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth),
+ res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight));
+
+ }
+
+ private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes,
+ Resources res, int resId, int rotation, boolean leftAligned) {
+ int width = size.x;
+ int height = size.y;
+
+ BitmapCropTask cropTask;
+ if (uri != null) {
+ cropTask = new BitmapCropTask(
+ context, uri, null, rotation, width, height, false, true, null);
+ } else if (imageBytes != null) {
+ cropTask = new BitmapCropTask(
+ imageBytes, null, rotation, width, height, false, true, null);
+ } else {
+ cropTask = new BitmapCropTask(
+ context, res, resId, null, rotation, width, height, false, true, null);
+ }
+ Point bounds = cropTask.getImageBounds();
+ if (bounds == null || bounds.x == 0 || bounds.y == 0) {
+ return null;
+ }
+
+ Matrix rotateMatrix = new Matrix();
+ rotateMatrix.setRotate(rotation);
+ float[] rotatedBounds = new float[] { bounds.x, bounds.y };
+ rotateMatrix.mapPoints(rotatedBounds);
+ rotatedBounds[0] = Math.abs(rotatedBounds[0]);
+ rotatedBounds[1] = Math.abs(rotatedBounds[1]);
+
+ RectF cropRect = WallpaperCropActivity.getMaxCropRect(
+ (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned);
+ cropTask.setCropBounds(cropRect);
+
+ if (cropTask.cropBitmap()) {
+ return cropTask.getCroppedBitmap();
+ } else {
+ return null;
+ }
+ }
+
+ private void addTemporaryWallpaperTile(Uri uri) {
+ mTempWallpaperTiles.add(uri);
+ // Add a tile for the image picked from Gallery
+ FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater().
+ inflate(R.layout.wallpaper_picker_item, mWallpapersView, false);
+ setWallpaperItemPaddingToZero(pickedImageThumbnail);
+
+ // Load the thumbnail
+ ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image);
+ Point defaultSize = getDefaultThumbnailSize(this.getResources());
+ int rotation = WallpaperCropActivity.getRotationFromExif(this, uri);
+ Bitmap thumb = createThumbnail(defaultSize, this, uri, null, null, 0, rotation, false);
+ if (thumb != null) {
+ image.setImageBitmap(thumb);
+ Drawable thumbDrawable = image.getDrawable();
+ thumbDrawable.setDither(true);
+ } else {
+ Log.e(TAG, "Error loading thumbnail for uri=" + uri);
+ }
+ mWallpapersView.addView(pickedImageThumbnail, 0);
+
+ UriWallpaperInfo info = new UriWallpaperInfo(uri);
+ pickedImageThumbnail.setTag(info);
+ info.setView(pickedImageThumbnail);
+ addLongPressHandler(pickedImageThumbnail);
+ updateTileIndices();
+ pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener);
+ mThumbnailOnClickListener.onClick(pickedImageThumbnail);
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) {
+ if (data != null && data.getData() != null) {
+ Uri uri = data.getData();
+ addTemporaryWallpaperTile(uri);
+ }
+ } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) {
+ setResult(RESULT_OK);
+ finish();
+ } else if (requestCode == PICK_LIVE_WALLPAPER) {
+ WallpaperManager wm = WallpaperManager.getInstance(this);
+ final WallpaperInfo oldLiveWallpaper = mLiveWallpaperInfoOnPickerLaunch;
+ WallpaperInfo newLiveWallpaper = wm.getWallpaperInfo();
+ // Try to figure out if a live wallpaper was set;
+ if (newLiveWallpaper != null &&
+ (oldLiveWallpaper == null ||
+ !oldLiveWallpaper.getComponent().equals(newLiveWallpaper.getComponent()))) {
+ // Return if a live wallpaper was set
+ setResult(RESULT_OK);
+ finish();
+ }
+ }
+ }
+
+ static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) {
+ frameLayout.setPadding(0, 0, 0, 0);
+ frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground()));
+ }
+
+ private void addLongPressHandler(View v) {
+ v.setOnLongClickListener(mLongClickListener);
+ }
+
+ private ArrayList<ResourceWallpaperInfo> findBundledWallpapers() {
+ ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+ new ArrayList<ResourceWallpaperInfo>(24);
+
+ Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId();
+ if (r != null) {
+ try {
+ Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first);
+ bundledWallpapers = addWallpapers(wallpaperRes, r.first.packageName, r.second);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+
+ // Add an entry for the default wallpaper (stored in system resources)
+ ResourceWallpaperInfo defaultWallpaperInfo = getDefaultWallpaperInfo();
+ if (defaultWallpaperInfo != null) {
+ bundledWallpapers.add(0, defaultWallpaperInfo);
+ }
+ return bundledWallpapers;
+ }
+
+ private ResourceWallpaperInfo getDefaultWallpaperInfo() {
+ Resources sysRes = Resources.getSystem();
+ int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android");
+
+ File defaultThumbFile = new File(getFilesDir(), "default_thumb.jpg");
+ Bitmap thumb = null;
+ boolean defaultWallpaperExists = false;
+ if (defaultThumbFile.exists()) {
+ thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath());
+ defaultWallpaperExists = true;
+ } else {
+ Resources res = getResources();
+ Point defaultThumbSize = getDefaultThumbnailSize(res);
+ int rotation = WallpaperCropActivity.getRotationFromExif(res, resId);
+ thumb = createThumbnail(
+ defaultThumbSize, this, null, null, sysRes, resId, rotation, false);
+ if (thumb != null) {
+ try {
+ defaultThumbFile.createNewFile();
+ FileOutputStream thumbFileStream =
+ openFileOutput(defaultThumbFile.getName(), Context.MODE_PRIVATE);
+ thumb.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream);
+ thumbFileStream.close();
+ defaultWallpaperExists = true;
+ } catch (IOException e) {
+ Log.e(TAG, "Error while writing default wallpaper thumbnail to file " + e);
+ defaultThumbFile.delete();
+ }
+ }
+ }
+ if (defaultWallpaperExists) {
+ return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb));
+ }
+ return null;
+ }
+
+ public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() {
+ // Context.getPackageName() may return the "original" package name,
+ // com.android.launcher3; Resources needs the real package name,
+ // com.android.launcher3. So we ask Resources for what it thinks the
+ // package name should be.
+ final String packageName = getResources().getResourcePackageName(R.array.wallpapers);
+ try {
+ ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0);
+ return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ private ArrayList<ResourceWallpaperInfo> addWallpapers(
+ Resources res, String packageName, int listResId) {
+ ArrayList<ResourceWallpaperInfo> bundledWallpapers =
+ new ArrayList<ResourceWallpaperInfo>(24);
+ final String[] extras = res.getStringArray(listResId);
+ for (String extra : extras) {
+ int resId = res.getIdentifier(extra, "drawable", packageName);
+ if (resId != 0) {
+ final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName);
+
+ if (thumbRes != 0) {
+ ResourceWallpaperInfo wallpaperInfo =
+ new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes));
+ bundledWallpapers.add(wallpaperInfo);
+ // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")");
+ }
+ } else {
+ Log.e(TAG, "Couldn't find wallpaper " + extra);
+ }
+ }
+ return bundledWallpapers;
+ }
+
+ public CropView getCropView() {
+ return mCropView;
+ }
+
+ public SavedWallpaperImages getSavedImages() {
+ return mSavedImages;
+ }
+
+ public void onLiveWallpaperPickerLaunch() {
+ mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo();
+ }
+
+ static class ZeroPaddingDrawable extends LevelListDrawable {
+ public ZeroPaddingDrawable(Drawable d) {
+ super();
+ addLevel(0, 0, d);
+ setLevel(0);
+ }
+
+ @Override
+ public boolean getPadding(Rect padding) {
+ padding.set(0, 0, 0, 0);
+ return true;
+ }
+ }
+
+ private static class BuiltInWallpapersAdapter extends BaseAdapter implements ListAdapter {
+ private LayoutInflater mLayoutInflater;
+ private ArrayList<ResourceWallpaperInfo> mWallpapers;
+
+ BuiltInWallpapersAdapter(Activity activity, ArrayList<ResourceWallpaperInfo> wallpapers) {
+ mLayoutInflater = activity.getLayoutInflater();
+ mWallpapers = wallpapers;
+ }
+
+ public int getCount() {
+ return mWallpapers.size();
+ }
+
+ public ResourceWallpaperInfo getItem(int position) {
+ return mWallpapers.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Drawable thumb = mWallpapers.get(position).mThumb;
+ if (thumb == null) {
+ Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position);
+ }
+ return createImageTileView(mLayoutInflater, position, convertView, parent, thumb);
+ }
+ }
+
+ public static View createImageTileView(LayoutInflater layoutInflater, int position,
+ View convertView, ViewGroup parent, Drawable thumb) {
+ View view;
+
+ if (convertView == null) {
+ view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ setWallpaperItemPaddingToZero((FrameLayout) view);
+
+ ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image);
+
+ if (thumb != null) {
+ image.setImageDrawable(thumb);
+ thumb.setDither(true);
+ }
+
+ return view;
+ }
+}
diff --git a/src/com/android/launcher3/WallpaperRootView.java b/src/com/android/launcher3/WallpaperRootView.java
new file mode 100644
index 0000000..ceaa043
--- /dev/null
+++ b/src/com/android/launcher3/WallpaperRootView.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 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.Rect;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+
+public class WallpaperRootView extends RelativeLayout {
+ private final WallpaperPickerActivity a;
+ public WallpaperRootView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ a = (WallpaperPickerActivity) context;
+ }
+ public WallpaperRootView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ a = (WallpaperPickerActivity) context;
+ }
+
+ protected boolean fitSystemWindows(Rect insets) {
+ a.setWallpaperStripYOffset(insets.bottom);
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/WeightWatcher.java b/src/com/android/launcher3/WeightWatcher.java
new file mode 100644
index 0000000..70b8afe
--- /dev/null
+++ b/src/com/android/launcher3/WeightWatcher.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2013 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class WeightWatcher extends LinearLayout {
+ private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
+ private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
+ private static final int TEXT_COLOR = 0xFFFFFFFF;
+ private static final int BACKGROUND_COLOR = 0xc0000000;
+
+ private static final int UPDATE_RATE = 5000;
+
+ private static final int MSG_START = 1;
+ private static final int MSG_STOP = 2;
+ private static final int MSG_UPDATE = 3;
+
+ static int indexOf(int[] a, int x) {
+ for (int i=0; i<a.length; i++) {
+ if (a[i] == x) return i;
+ }
+ return -1;
+ }
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case MSG_START:
+ mHandler.sendEmptyMessage(MSG_UPDATE);
+ break;
+ case MSG_STOP:
+ mHandler.removeMessages(MSG_UPDATE);
+ break;
+ case MSG_UPDATE:
+ int[] pids = mMemoryService.getTrackedProcesses();
+
+ final int N = getChildCount();
+ if (pids.length != N) initViews();
+ else for (int i=0; i<N; i++) {
+ ProcessWatcher pw = ((ProcessWatcher) getChildAt(i));
+ if (indexOf(pids, pw.getPid()) < 0) {
+ initViews();
+ break;
+ }
+ pw.update();
+ }
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
+ break;
+ }
+ }
+ };
+ private MemoryTracker mMemoryService;
+
+ public WeightWatcher(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ ServiceConnection connection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
+ initViews();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ mMemoryService = null;
+ }
+ };
+ context.bindService(new Intent(context, MemoryTracker.class),
+ connection, Context.BIND_AUTO_CREATE);
+
+ setOrientation(LinearLayout.VERTICAL);
+
+ setBackgroundColor(BACKGROUND_COLOR);
+ }
+
+ public void initViews() {
+ removeAllViews();
+ int[] processes = mMemoryService.getTrackedProcesses();
+ for (int i=0; i<processes.length; i++) {
+ final ProcessWatcher v = new ProcessWatcher(getContext());
+ v.setPid(processes[i]);
+ addView(v);
+ }
+ }
+
+ public WeightWatcher(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mHandler.sendEmptyMessage(MSG_START);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mHandler.sendEmptyMessage(MSG_STOP);
+ }
+
+ public class ProcessWatcher extends LinearLayout {
+ GraphView mRamGraph;
+ TextView mText;
+ int mPid;
+ private MemoryTracker.ProcessMemInfo mMemInfo;
+
+ public ProcessWatcher(Context context) {
+ this(context, null);
+ }
+
+ public ProcessWatcher(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final float dp = getResources().getDisplayMetrics().density;
+
+ mText = new TextView(getContext());
+ mText.setTextColor(TEXT_COLOR);
+ mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
+ mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+
+ final int p = (int)(2*dp);
+ setPadding(p, 0, p, 0);
+
+ mRamGraph = new GraphView(getContext());
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ 0,
+ (int)(14 * dp),
+ 1f
+ );
+
+ addView(mText, params);
+ params.leftMargin = (int)(4*dp);
+ params.weight = 0f;
+ params.width = (int)(200 * dp);
+ addView(mRamGraph, params);
+ }
+
+ public void setPid(int pid) {
+ mPid = pid;
+ mMemInfo = mMemoryService.getMemInfo(mPid);
+ if (mMemInfo == null) {
+ Log.v("WeightWatcher", "Missing info for pid " + mPid + ", removing view: " + this);
+ initViews();
+ }
+ }
+
+ public int getPid() {
+ return mPid;
+ }
+
+ public String getUptimeString() {
+ long sec = mMemInfo.getUptime() / 1000;
+ StringBuilder sb = new StringBuilder();
+ long days = sec / 86400;
+ if (days > 0) {
+ sec -= days * 86400;
+ sb.append(days);
+ sb.append("d");
+ }
+
+ long hours = sec / 3600;
+ if (hours > 0) {
+ sec -= hours * 3600;
+ sb.append(hours);
+ sb.append("h");
+ }
+
+ long mins = sec / 60;
+ if (mins > 0) {
+ sec -= mins * 60;
+ sb.append(mins);
+ sb.append("m");
+ }
+
+ sb.append(sec);
+ sb.append("s");
+ return sb.toString();
+ }
+
+ public void update() {
+ //Log.v("WeightWatcher.ProcessWatcher",
+ // "MSG_UPDATE pss=" + mMemInfo.currentPss);
+ mText.setText("(" + mPid
+ + (mPid == android.os.Process.myPid()
+ ? "/A" // app
+ : "/S") // service
+ + ") up " + getUptimeString()
+ + " P=" + mMemInfo.currentPss
+ + " U=" + mMemInfo.currentUss
+ );
+ mRamGraph.invalidate();
+ }
+
+ public class GraphView extends View {
+ Paint pssPaint, ussPaint, headPaint;
+
+ public GraphView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ pssPaint = new Paint();
+ pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
+ ussPaint = new Paint();
+ ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
+ headPaint = new Paint();
+ headPaint.setColor(Color.WHITE);
+ }
+
+ public GraphView(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ int w = c.getWidth();
+ int h = c.getHeight();
+
+ if (mMemInfo == null) return;
+
+ final int N = mMemInfo.pss.length;
+ final float barStep = (float) w / N;
+ final float barWidth = Math.max(1, barStep);
+ final float scale = (float) h / mMemInfo.max;
+
+ int i;
+ float x;
+ for (i=0; i<N; i++) {
+ x = i * barStep;
+ c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
+ c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
+ }
+ x = mMemInfo.head * barStep;
+ c.drawRect(x, 0, x + barWidth, h, headPaint);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/WidgetAdder.java b/src/com/android/launcher3/WidgetAdder.java
new file mode 100644
index 0000000..79ac504
--- /dev/null
+++ b/src/com/android/launcher3/WidgetAdder.java
@@ -0,0 +1,7 @@
+package com.android.launcher3;
+
+import android.app.Activity;
+
+public class WidgetAdder extends Activity {
+
+}
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
new file mode 100644
index 0000000..07b4f6f
--- /dev/null
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -0,0 +1,626 @@
+package com.android.launcher3;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+abstract class SoftReferenceThreadLocal<T> {
+ private ThreadLocal<SoftReference<T>> mThreadLocal;
+ public SoftReferenceThreadLocal() {
+ mThreadLocal = new ThreadLocal<SoftReference<T>>();
+ }
+
+ abstract T initialValue();
+
+ public void set(T t) {
+ mThreadLocal.set(new SoftReference<T>(t));
+ }
+
+ public T get() {
+ SoftReference<T> reference = mThreadLocal.get();
+ T obj;
+ if (reference == null) {
+ obj = initialValue();
+ mThreadLocal.set(new SoftReference<T>(obj));
+ return obj;
+ } else {
+ obj = reference.get();
+ if (obj == null) {
+ obj = initialValue();
+ mThreadLocal.set(new SoftReference<T>(obj));
+ }
+ return obj;
+ }
+ }
+}
+
+class CanvasCache extends SoftReferenceThreadLocal<Canvas> {
+ @Override
+ protected Canvas initialValue() {
+ return new Canvas();
+ }
+}
+
+class PaintCache extends SoftReferenceThreadLocal<Paint> {
+ @Override
+ protected Paint initialValue() {
+ return null;
+ }
+}
+
+class BitmapCache extends SoftReferenceThreadLocal<Bitmap> {
+ @Override
+ protected Bitmap initialValue() {
+ return null;
+ }
+}
+
+class RectCache extends SoftReferenceThreadLocal<Rect> {
+ @Override
+ protected Rect initialValue() {
+ return new Rect();
+ }
+}
+
+class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> {
+ @Override
+ protected BitmapFactory.Options initialValue() {
+ return new BitmapFactory.Options();
+ }
+}
+
+public class WidgetPreviewLoader {
+ static final String TAG = "WidgetPreviewLoader";
+
+ private int mPreviewBitmapWidth;
+ private int mPreviewBitmapHeight;
+ private String mSize;
+ private Context mContext;
+ private PackageManager mPackageManager;
+ private PagedViewCellLayout mWidgetSpacingLayout;
+
+ // Used for drawing shortcut previews
+ private BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
+ private PaintCache mCachedShortcutPreviewPaint = new PaintCache();
+ private CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
+
+ // Used for drawing widget previews
+ private CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
+ private RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
+ private RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
+ private PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
+ private String mCachedSelectQuery;
+ private BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache();
+
+ private int mAppIconSize;
+ private IconCache mIconCache;
+
+ private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
+
+ private CacheDb mDb;
+
+ private HashMap<String, WeakReference<Bitmap>> mLoadedPreviews;
+ private ArrayList<SoftReference<Bitmap>> mUnusedBitmaps;
+ private static HashSet<String> sInvalidPackages;
+
+ static {
+ sInvalidPackages = new HashSet<String>();
+ }
+
+ public WidgetPreviewLoader(Context context) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ mContext = context;
+ mPackageManager = mContext.getPackageManager();
+ mAppIconSize = grid.iconSizePx;
+ mIconCache = app.getIconCache();
+ mDb = app.getWidgetPreviewCacheDb();
+ mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>();
+ mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>();
+ }
+
+ public void setPreviewSize(int previewWidth, int previewHeight,
+ PagedViewCellLayout widgetSpacingLayout) {
+ mPreviewBitmapWidth = previewWidth;
+ mPreviewBitmapHeight = previewHeight;
+ mSize = previewWidth + "x" + previewHeight;
+ mWidgetSpacingLayout = widgetSpacingLayout;
+ }
+
+ public Bitmap getPreview(final Object o) {
+ final String name = getObjectName(o);
+ final String packageName = getObjectPackage(o);
+ // check if the package is valid
+ boolean packageValid = true;
+ synchronized(sInvalidPackages) {
+ packageValid = !sInvalidPackages.contains(packageName);
+ }
+ if (!packageValid) {
+ return null;
+ }
+ if (packageValid) {
+ synchronized(mLoadedPreviews) {
+ // check if it exists in our existing cache
+ if (mLoadedPreviews.containsKey(name) && mLoadedPreviews.get(name).get() != null) {
+ return mLoadedPreviews.get(name).get();
+ }
+ }
+ }
+
+ Bitmap unusedBitmap = null;
+ synchronized(mUnusedBitmaps) {
+ // not in cache; we need to load it from the db
+ while ((unusedBitmap == null || !unusedBitmap.isMutable() ||
+ unusedBitmap.getWidth() != mPreviewBitmapWidth ||
+ unusedBitmap.getHeight() != mPreviewBitmapHeight)
+ && mUnusedBitmaps.size() > 0) {
+ unusedBitmap = mUnusedBitmaps.remove(0).get();
+ }
+ if (unusedBitmap != null) {
+ final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+ c.setBitmap(unusedBitmap);
+ c.drawColor(0, PorterDuff.Mode.CLEAR);
+ c.setBitmap(null);
+ }
+ }
+
+ if (unusedBitmap == null) {
+ unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight,
+ Bitmap.Config.ARGB_8888);
+ }
+
+ Bitmap preview = null;
+
+ if (packageValid) {
+ preview = readFromDb(name, unusedBitmap);
+ }
+
+ if (preview != null) {
+ synchronized(mLoadedPreviews) {
+ mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
+ }
+ return preview;
+ } else {
+ // it's not in the db... we need to generate it
+ final Bitmap generatedPreview = generatePreview(o, unusedBitmap);
+ preview = generatedPreview;
+ if (preview != unusedBitmap) {
+ throw new RuntimeException("generatePreview is not recycling the bitmap " + o);
+ }
+
+ synchronized(mLoadedPreviews) {
+ mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
+ }
+
+ // write to db on a thread pool... this can be done lazily and improves the performance
+ // of the first time widget previews are loaded
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
+ writeToDb(o, generatedPreview);
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+
+ return preview;
+ }
+ }
+
+ public void recycleBitmap(Object o, Bitmap bitmapToRecycle) {
+ String name = getObjectName(o);
+ synchronized (mLoadedPreviews) {
+ if (mLoadedPreviews.containsKey(name)) {
+ Bitmap b = mLoadedPreviews.get(name).get();
+ if (b == bitmapToRecycle) {
+ mLoadedPreviews.remove(name);
+ if (bitmapToRecycle.isMutable()) {
+ synchronized (mUnusedBitmaps) {
+ mUnusedBitmaps.add(new SoftReference<Bitmap>(b));
+ }
+ }
+ } else {
+ throw new RuntimeException("Bitmap passed in doesn't match up");
+ }
+ }
+ }
+ }
+
+ static class CacheDb extends SQLiteOpenHelper {
+ final static int DB_VERSION = 2;
+ final static String DB_NAME = "widgetpreviews.db";
+ final static String TABLE_NAME = "shortcut_and_widget_previews";
+ final static String COLUMN_NAME = "name";
+ final static String COLUMN_SIZE = "size";
+ final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
+ Context mContext;
+
+ public CacheDb(Context context) {
+ super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION);
+ // Store the context for later use
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase database) {
+ database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
+ COLUMN_NAME + " TEXT NOT NULL, " +
+ COLUMN_SIZE + " TEXT NOT NULL, " +
+ COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " +
+ "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " +
+ ");");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion != newVersion) {
+ // Delete all the records; they'll be repopulated as this is a cache
+ db.execSQL("DELETE FROM " + TABLE_NAME);
+ }
+ }
+ }
+
+ private static final String WIDGET_PREFIX = "Widget:";
+ private static final String SHORTCUT_PREFIX = "Shortcut:";
+
+ private static String getObjectName(Object o) {
+ // should cache the string builder
+ StringBuilder sb = new StringBuilder();
+ String output;
+ if (o instanceof AppWidgetProviderInfo) {
+ sb.append(WIDGET_PREFIX);
+ sb.append(((AppWidgetProviderInfo) o).provider.flattenToString());
+ output = sb.toString();
+ sb.setLength(0);
+ } else {
+ sb.append(SHORTCUT_PREFIX);
+
+ ResolveInfo info = (ResolveInfo) o;
+ sb.append(new ComponentName(info.activityInfo.packageName,
+ info.activityInfo.name).flattenToString());
+ output = sb.toString();
+ sb.setLength(0);
+ }
+ return output;
+ }
+
+ private String getObjectPackage(Object o) {
+ if (o instanceof AppWidgetProviderInfo) {
+ return ((AppWidgetProviderInfo) o).provider.getPackageName();
+ } else {
+ ResolveInfo info = (ResolveInfo) o;
+ return info.activityInfo.packageName;
+ }
+ }
+
+ private void writeToDb(Object o, Bitmap preview) {
+ String name = getObjectName(o);
+ SQLiteDatabase db = mDb.getWritableDatabase();
+ ContentValues values = new ContentValues();
+
+ values.put(CacheDb.COLUMN_NAME, name);
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ preview.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray());
+ values.put(CacheDb.COLUMN_SIZE, mSize);
+ db.insert(CacheDb.TABLE_NAME, null, values);
+ }
+
+ public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) {
+ synchronized(sInvalidPackages) {
+ sInvalidPackages.add(packageName);
+ }
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
+ SQLiteDatabase db = cacheDb.getWritableDatabase();
+ db.delete(CacheDb.TABLE_NAME,
+ CacheDb.COLUMN_NAME + " LIKE ? OR " +
+ CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
+ new String[] {
+ WIDGET_PREFIX + packageName + "/%",
+ SHORTCUT_PREFIX + packageName + "/%"} // args to SELECT query
+ );
+ synchronized(sInvalidPackages) {
+ sInvalidPackages.remove(packageName);
+ }
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+ }
+
+ public static void removeItemFromDb(final CacheDb cacheDb, final String objectName) {
+ new AsyncTask<Void, Void, Void>() {
+ public Void doInBackground(Void ... args) {
+ SQLiteDatabase db = cacheDb.getWritableDatabase();
+ db.delete(CacheDb.TABLE_NAME,
+ CacheDb.COLUMN_NAME + " = ? ", // SELECT query
+ new String[] { objectName }); // args to SELECT query
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
+ }
+
+ private Bitmap readFromDb(String name, Bitmap b) {
+ if (mCachedSelectQuery == null) {
+ mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " +
+ CacheDb.COLUMN_SIZE + " = ?";
+ }
+ SQLiteDatabase db = mDb.getReadableDatabase();
+ Cursor result = db.query(CacheDb.TABLE_NAME,
+ new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
+ mCachedSelectQuery, // select query
+ new String[] { name, mSize }, // args to select query
+ null,
+ null,
+ null,
+ null);
+ if (result.getCount() > 0) {
+ result.moveToFirst();
+ byte[] blob = result.getBlob(0);
+ result.close();
+ final BitmapFactory.Options opts = mCachedBitmapFactoryOptions.get();
+ opts.inBitmap = b;
+ opts.inSampleSize = 1;
+ try {
+ return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
+ } catch (IllegalArgumentException e) {
+ removeItemFromDb(mDb, name);
+ return null;
+ }
+ } else {
+ result.close();
+ return null;
+ }
+ }
+
+ public Bitmap generatePreview(Object info, Bitmap preview) {
+ if (preview != null &&
+ (preview.getWidth() != mPreviewBitmapWidth ||
+ preview.getHeight() != mPreviewBitmapHeight)) {
+ throw new RuntimeException("Improperly sized bitmap passed as argument");
+ }
+ if (info instanceof AppWidgetProviderInfo) {
+ return generateWidgetPreview((AppWidgetProviderInfo) info, preview);
+ } else {
+ return generateShortcutPreview(
+ (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview);
+ }
+ }
+
+ public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview) {
+ int[] cellSpans = Launcher.getSpanForWidget(mContext, info);
+ int maxWidth = maxWidthForWidgetPreview(cellSpans[0]);
+ int maxHeight = maxHeightForWidgetPreview(cellSpans[1]);
+ return generateWidgetPreview(info.provider, info.previewImage, info.icon,
+ cellSpans[0], cellSpans[1], maxWidth, maxHeight, preview, null);
+ }
+
+ public int maxWidthForWidgetPreview(int spanX) {
+ return Math.min(mPreviewBitmapWidth,
+ mWidgetSpacingLayout.estimateCellWidth(spanX));
+ }
+
+ public int maxHeightForWidgetPreview(int spanY) {
+ return Math.min(mPreviewBitmapHeight,
+ mWidgetSpacingLayout.estimateCellHeight(spanY));
+ }
+
+ public Bitmap generateWidgetPreview(ComponentName provider, int previewImage,
+ int iconId, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight,
+ Bitmap preview, int[] preScaledWidthOut) {
+ // Load the preview image if possible
+ String packageName = provider.getPackageName();
+ if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
+ if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE;
+
+ Drawable drawable = null;
+ if (previewImage != 0) {
+ drawable = mPackageManager.getDrawable(packageName, previewImage, null);
+ if (drawable == null) {
+ Log.w(TAG, "Can't load widget preview drawable 0x" +
+ Integer.toHexString(previewImage) + " for provider: " + provider);
+ }
+ }
+
+ int previewWidth;
+ int previewHeight;
+ Bitmap defaultPreview = null;
+ boolean widgetPreviewExists = (drawable != null);
+ if (widgetPreviewExists) {
+ previewWidth = drawable.getIntrinsicWidth();
+ previewHeight = drawable.getIntrinsicHeight();
+ } else {
+ // Generate a preview image if we couldn't load one
+ if (cellHSpan < 1) cellHSpan = 1;
+ if (cellVSpan < 1) cellVSpan = 1;
+
+ BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
+ .getDrawable(R.drawable.widget_tile);
+ final int previewDrawableWidth = previewDrawable
+ .getIntrinsicWidth();
+ final int previewDrawableHeight = previewDrawable
+ .getIntrinsicHeight();
+ previewWidth = previewDrawableWidth * cellHSpan;
+ previewHeight = previewDrawableHeight * cellVSpan;
+
+ defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight,
+ Config.ARGB_8888);
+ final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+ c.setBitmap(defaultPreview);
+ previewDrawable.setBounds(0, 0, previewWidth, previewHeight);
+ previewDrawable.setTileModeXY(Shader.TileMode.REPEAT,
+ Shader.TileMode.REPEAT);
+ previewDrawable.draw(c);
+ c.setBitmap(null);
+
+ // Draw the icon in the top left corner
+ int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
+ int smallestSide = Math.min(previewWidth, previewHeight);
+ float iconScale = Math.min((float) smallestSide
+ / (mAppIconSize + 2 * minOffset), 1f);
+
+ try {
+ Drawable icon = null;
+ int hoffset =
+ (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
+ int yoffset =
+ (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
+ if (iconId > 0)
+ icon = mIconCache.getFullResIcon(packageName, iconId);
+ if (icon != null) {
+ renderDrawableToBitmap(icon, defaultPreview, hoffset,
+ yoffset, (int) (mAppIconSize * iconScale),
+ (int) (mAppIconSize * iconScale));
+ }
+ } catch (Resources.NotFoundException e) {
+ }
+ }
+
+ // Scale to fit width only - let the widget preview be clipped in the
+ // vertical dimension
+ float scale = 1f;
+ if (preScaledWidthOut != null) {
+ preScaledWidthOut[0] = previewWidth;
+ }
+ if (previewWidth > maxPreviewWidth) {
+ scale = maxPreviewWidth / (float) previewWidth;
+ }
+ if (scale != 1f) {
+ previewWidth = (int) (scale * previewWidth);
+ previewHeight = (int) (scale * previewHeight);
+ }
+
+ // If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size
+ if (preview == null) {
+ preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
+ }
+
+ // Draw the scaled preview into the final bitmap
+ int x = (preview.getWidth() - previewWidth) / 2;
+ if (widgetPreviewExists) {
+ renderDrawableToBitmap(drawable, preview, x, 0, previewWidth,
+ previewHeight);
+ } else {
+ final Canvas c = mCachedAppWidgetPreviewCanvas.get();
+ final Rect src = mCachedAppWidgetPreviewSrcRect.get();
+ final Rect dest = mCachedAppWidgetPreviewDestRect.get();
+ c.setBitmap(preview);
+ src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
+ dest.set(x, 0, x + previewWidth, previewHeight);
+
+ Paint p = mCachedAppWidgetPreviewPaint.get();
+ if (p == null) {
+ p = new Paint();
+ p.setFilterBitmap(true);
+ mCachedAppWidgetPreviewPaint.set(p);
+ }
+ c.drawBitmap(defaultPreview, src, dest, p);
+ c.setBitmap(null);
+ }
+ return preview;
+ }
+
+ private Bitmap generateShortcutPreview(
+ ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview) {
+ Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get();
+ final Canvas c = mCachedShortcutPreviewCanvas.get();
+ if (tempBitmap == null ||
+ tempBitmap.getWidth() != maxWidth ||
+ tempBitmap.getHeight() != maxHeight) {
+ tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+ mCachedShortcutPreviewBitmap.set(tempBitmap);
+ } else {
+ c.setBitmap(tempBitmap);
+ c.drawColor(0, PorterDuff.Mode.CLEAR);
+ c.setBitmap(null);
+ }
+ // Render the icon
+ Drawable icon = mIconCache.getFullResIcon(info);
+
+ int paddingTop = mContext.
+ getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
+ int paddingLeft = mContext.
+ getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
+ int paddingRight = mContext.
+ getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
+
+ int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
+
+ renderDrawableToBitmap(
+ icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth);
+
+ if (preview != null &&
+ (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight)) {
+ throw new RuntimeException("Improperly sized bitmap passed as argument");
+ } else if (preview == null) {
+ preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
+ }
+
+ c.setBitmap(preview);
+ // Draw a desaturated/scaled version of the icon in the background as a watermark
+ Paint p = mCachedShortcutPreviewPaint.get();
+ if (p == null) {
+ p = new Paint();
+ ColorMatrix colorMatrix = new ColorMatrix();
+ colorMatrix.setSaturation(0);
+ p.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+ p.setAlpha((int) (255 * 0.06f));
+ mCachedShortcutPreviewPaint.set(p);
+ }
+ c.drawBitmap(tempBitmap, 0, 0, p);
+ c.setBitmap(null);
+
+ renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize);
+
+ return preview;
+ }
+
+
+ public static void renderDrawableToBitmap(
+ Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
+ renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
+ }
+
+ private static void renderDrawableToBitmap(
+ Drawable d, Bitmap bitmap, int x, int y, int w, int h,
+ float scale) {
+ if (bitmap != null) {
+ Canvas c = new Canvas(bitmap);
+ c.scale(scale, scale);
+ Rect oldBounds = d.copyBounds();
+ d.setBounds(x, y, x + w, y + h);
+ d.draw(c);
+ d.setBounds(oldBounds); // Restore the bounds
+ c.setBitmap(null);
+ }
+ }
+
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
new file mode 100644
index 0000000..1d5b5ac
--- /dev/null
+++ b/src/com/android/launcher3/Workspace.java
@@ -0,0 +1,4477 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.WallpaperManager;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.TextView;
+
+import com.android.launcher3.FolderIcon.FolderRingAnimator;
+import com.android.launcher3.Launcher.CustomContentCallbacks;
+import com.android.launcher3.LauncherSettings.Favorites;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * The workspace is a wide area with a wallpaper and a finite number of pages.
+ * Each page contains a number of icons, folders or widgets the user can
+ * interact with. A workspace is meant to be used with a fixed width only.
+ */
+public class Workspace extends SmoothPagedView
+ implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
+ DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
+ Insettable {
+ private static final String TAG = "Launcher.Workspace";
+
+ // Y rotation to apply to the workspace screens
+ private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
+
+ private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
+ private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
+ private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
+
+ private static final int BACKGROUND_FADE_OUT_DURATION = 350;
+ private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
+ private static final int FLING_THRESHOLD_VELOCITY = 500;
+
+ private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
+
+ // These animators are used to fade the children's outlines
+ private ObjectAnimator mChildrenOutlineFadeInAnimation;
+ private ObjectAnimator mChildrenOutlineFadeOutAnimation;
+ private float mChildrenOutlineAlpha = 0;
+
+ // These properties refer to the background protection gradient used for AllApps and Customize
+ private ValueAnimator mBackgroundFadeInAnimation;
+ private ValueAnimator mBackgroundFadeOutAnimation;
+ private Drawable mBackground;
+ boolean mDrawBackground = true;
+ private float mBackgroundAlpha = 0;
+
+ private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
+ private long mTouchDownTime = -1;
+ private long mCustomContentShowTime = -1;
+
+ private LayoutTransition mLayoutTransition;
+ private final WallpaperManager mWallpaperManager;
+ private IBinder mWindowToken;
+
+ private int mOriginalDefaultPage;
+ private int mDefaultPage;
+
+ private ShortcutAndWidgetContainer mDragSourceInternal;
+ private static boolean sAccessibilityEnabled;
+
+ // The screen id used for the empty screen always present to the right.
+ private final static long EXTRA_EMPTY_SCREEN_ID = -201;
+ private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
+
+ private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
+ private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
+
+ /**
+ * CellInfo for the cell that is currently being dragged
+ */
+ private CellLayout.CellInfo mDragInfo;
+
+ /**
+ * Target drop area calculated during last acceptDrop call.
+ */
+ private int[] mTargetCell = new int[2];
+ private int mDragOverX = -1;
+ private int mDragOverY = -1;
+
+ static Rect mLandscapeCellLayoutMetrics = null;
+ static Rect mPortraitCellLayoutMetrics = null;
+
+ CustomContentCallbacks mCustomContentCallbacks;
+ boolean mCustomContentShowing;
+ private float mLastCustomContentScrollProgress = -1f;
+ private String mCustomContentDescription = "";
+
+ /**
+ * The CellLayout that is currently being dragged over
+ */
+ private CellLayout mDragTargetLayout = null;
+ /**
+ * The CellLayout that we will show as glowing
+ */
+ private CellLayout mDragOverlappingLayout = null;
+
+ /**
+ * The CellLayout which will be dropped to
+ */
+ private CellLayout mDropToLayout = null;
+
+ private Launcher mLauncher;
+ private IconCache mIconCache;
+ private DragController mDragController;
+
+ // These are temporary variables to prevent having to allocate a new object just to
+ // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
+ private int[] mTempCell = new int[2];
+ private int[] mTempPt = new int[2];
+ private int[] mTempEstimate = new int[2];
+ private float[] mDragViewVisualCenter = new float[2];
+ private float[] mTempCellLayoutCenterCoordinates = new float[2];
+ private Matrix mTempInverseMatrix = new Matrix();
+
+ private SpringLoadedDragController mSpringLoadedDragController;
+ private float mSpringLoadedShrinkFactor;
+ private float mOverviewModeShrinkFactor;
+ private int mOverviewModePageOffset;
+
+ // State variable that indicates whether the pages are small (ie when you're
+ // in all apps or customize mode)
+
+ enum State { NORMAL, SPRING_LOADED, SMALL, OVERVIEW};
+ private State mState = State.NORMAL;
+ private boolean mIsSwitchingState = false;
+
+ boolean mAnimatingViewIntoPlace = false;
+ boolean mIsDragOccuring = false;
+ boolean mChildrenLayersEnabled = true;
+
+ private boolean mStripScreensOnPageStopMoving = false;
+
+ /** Is the user is dragging an item near the edge of a page? */
+ private boolean mInScrollArea = false;
+
+ private HolographicOutlineHelper mOutlineHelper;
+ private Bitmap mDragOutline = null;
+ private final Rect mTempRect = new Rect();
+ private final int[] mTempXY = new int[2];
+ private int[] mTempVisiblePagesRange = new int[2];
+ private boolean mOverscrollTransformsSet;
+ private float mLastOverscrollPivotX;
+ public static final int DRAG_BITMAP_PADDING = 2;
+ private boolean mWorkspaceFadeInAdjacentScreens;
+
+ WallpaperOffsetInterpolator mWallpaperOffset;
+ private Runnable mDelayedResizeRunnable;
+ private Runnable mDelayedSnapToPageRunnable;
+ private Point mDisplaySize = new Point();
+ private int mCameraDistance;
+
+ // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
+ private static final int FOLDER_CREATION_TIMEOUT = 0;
+ private static final int REORDER_TIMEOUT = 250;
+ private final Alarm mFolderCreationAlarm = new Alarm();
+ private final Alarm mReorderAlarm = new Alarm();
+ private FolderRingAnimator mDragFolderRingAnimator = null;
+ private FolderIcon mDragOverFolderIcon = null;
+ private boolean mCreateUserFolderOnDrop = false;
+ private boolean mAddToExistingFolderOnDrop = false;
+ private DropTarget.DragEnforcer mDragEnforcer;
+ private float mMaxDistanceForFolderCreation;
+
+ // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
+ private float mXDown;
+ private float mYDown;
+ final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
+ final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
+ final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
+
+ // Relating to the animation of items being dropped externally
+ public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
+ public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
+ public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
+ public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
+ public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
+
+ // Related to dragging, folder creation and reordering
+ private static final int DRAG_MODE_NONE = 0;
+ private static final int DRAG_MODE_CREATE_FOLDER = 1;
+ private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
+ private static final int DRAG_MODE_REORDER = 3;
+ private int mDragMode = DRAG_MODE_NONE;
+ private int mLastReorderX = -1;
+ private int mLastReorderY = -1;
+
+ private SparseArray<Parcelable> mSavedStates;
+ private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
+
+ // These variables are used for storing the initial and final values during workspace animations
+ private int mSavedScrollX;
+ private float mSavedRotationY;
+ private float mSavedTranslationX;
+
+ private float mCurrentScale;
+ private float mNewScale;
+ private float[] mOldBackgroundAlphas;
+ private float[] mOldAlphas;
+ private float[] mNewBackgroundAlphas;
+ private float[] mNewAlphas;
+ private int mLastChildCount = -1;
+ private float mTransitionProgress;
+
+ private Runnable mDeferredAction;
+ private boolean mDeferDropAfterUninstall;
+ private boolean mUninstallSuccessful;
+
+ private final Runnable mBindPages = new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.getModel().bindRemainingSynchronousPages();
+ }
+ };
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ */
+ public Workspace(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Used to inflate the Workspace from XML.
+ *
+ * @param context The application's context.
+ * @param attrs The attributes set containing the Workspace's customization values.
+ * @param defStyle Unused.
+ */
+ public Workspace(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mContentIsRefreshable = false;
+
+ mOutlineHelper = HolographicOutlineHelper.obtain(context);
+
+ mDragEnforcer = new DropTarget.DragEnforcer(context);
+ // With workspace, data is available straight from the get-go
+ setDataIsReady();
+
+ mLauncher = (Launcher) context;
+ final Resources res = getResources();
+ mWorkspaceFadeInAdjacentScreens = res.getBoolean(R.bool.config_workspaceFadeAdjacentScreens);
+ mFadeInAdjacentScreens = false;
+ mWallpaperManager = WallpaperManager.getInstance(context);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.Workspace, defStyle, 0);
+ mSpringLoadedShrinkFactor =
+ res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+ mOverviewModeShrinkFactor =
+ res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100.0f;
+ mOverviewModePageOffset = res.getDimensionPixelSize(R.dimen.overview_mode_page_offset);
+ mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
+ mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
+ a.recycle();
+
+ setOnHierarchyChangeListener(this);
+ setHapticFeedbackEnabled(false);
+
+ initWorkspace();
+
+ // Disable multitouch across the workspace/all apps/customize tray
+ setMotionEventSplittingEnabled(true);
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ }
+
+ // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
+ // dimension if unsuccessful
+ public int[] estimateItemSize(int hSpan, int vSpan,
+ ItemInfo itemInfo, boolean springLoaded) {
+ int[] size = new int[2];
+ if (getChildCount() > 0) {
+ // Use the first non-custom page to estimate the child position
+ CellLayout cl = (CellLayout) getChildAt(numCustomPages());
+ Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
+ size[0] = r.width();
+ size[1] = r.height();
+ if (springLoaded) {
+ size[0] *= mSpringLoadedShrinkFactor;
+ size[1] *= mSpringLoadedShrinkFactor;
+ }
+ return size;
+ } else {
+ size[0] = Integer.MAX_VALUE;
+ size[1] = Integer.MAX_VALUE;
+ return size;
+ }
+ }
+
+ public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
+ int hCell, int vCell, int hSpan, int vSpan) {
+ Rect r = new Rect();
+ cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
+ return r;
+ }
+
+ public void onDragStart(final DragSource source, Object info, int dragAction) {
+ mIsDragOccuring = true;
+ updateChildrenLayersEnabled(false);
+ mLauncher.lockScreenOrientation();
+ mLauncher.onInteractionBegin();
+ setChildrenBackgroundAlphaMultipliers(1f);
+ // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
+ InstallShortcutReceiver.enableInstallQueue();
+ UninstallShortcutReceiver.enableUninstallQueue();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mIsDragOccuring) {
+ addExtraEmptyScreenOnDrag();
+ }
+ }
+ });
+ }
+
+ public void onDragEnd() {
+ mIsDragOccuring = false;
+ updateChildrenLayersEnabled(false);
+ mLauncher.unlockScreenOrientation(false);
+
+ // Re-enable any Un/InstallShortcutReceiver and now process any queued items
+ InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
+ UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
+
+ removeExtraEmptyScreen();
+ mDragSourceInternal = null;
+ mLauncher.onInteractionEnd();
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ protected void initWorkspace() {
+ Context context = getContext();
+ mCurrentPage = mDefaultPage;
+ Launcher.setScreen(mCurrentPage);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mIconCache = app.getIconCache();
+ setWillNotDraw(false);
+ setClipChildren(false);
+ setClipToPadding(false);
+ setChildrenDrawnWithCacheEnabled(true);
+
+ // This is a bit of a hack to account for the fact that we translate the workspace
+ // up a bit, and still need to draw the background covering the whole screen.
+ setMinScale(mOverviewModeShrinkFactor - 0.2f);
+ setupLayoutTransition();
+
+ final Resources res = getResources();
+ try {
+ mBackground = res.getDrawable(R.drawable.apps_customize_bg);
+ } catch (Resources.NotFoundException e) {
+ // In this case, we will skip drawing background protection
+ }
+
+ mWallpaperOffset = new WallpaperOffsetInterpolator();
+ Display display = mLauncher.getWindowManager().getDefaultDisplay();
+ display.getSize(mDisplaySize);
+
+ mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
+ mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+ }
+
+ private void setupLayoutTransition() {
+ // We want to show layout transitions when pages are deleted, to close the gap.
+ mLayoutTransition = new LayoutTransition();
+ mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
+ mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+ mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+ setLayoutTransition(mLayoutTransition);
+ }
+
+ void enableLayoutTransitions() {
+ setLayoutTransition(mLayoutTransition);
+ }
+ void disableLayoutTransitions() {
+ setLayoutTransition(null);
+ }
+
+ @Override
+ protected int getScrollMode() {
+ return SmoothPagedView.X_LARGE_MODE;
+ }
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ if (!(child instanceof CellLayout)) {
+ throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
+ }
+ CellLayout cl = ((CellLayout) child);
+ cl.setOnInterceptTouchListener(this);
+ cl.setClickable(true);
+ cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ super.onChildViewAdded(parent, child);
+ }
+
+ protected boolean shouldDrawChild(View child) {
+ final CellLayout cl = (CellLayout) child;
+ return super.shouldDrawChild(child) &&
+ (mIsSwitchingState ||
+ cl.getShortcutsAndWidgets().getAlpha() > 0 ||
+ cl.getBackgroundAlpha() > 0);
+ }
+
+ /**
+ * @return The open folder on the current screen, or null if there is none
+ */
+ Folder getOpenFolder() {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ int count = dragLayer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = dragLayer.getChildAt(i);
+ if (child instanceof Folder) {
+ Folder folder = (Folder) child;
+ if (folder.getInfo().opened)
+ return folder;
+ }
+ }
+ return null;
+ }
+
+ boolean isTouchActive() {
+ return mTouchState != TOUCH_STATE_REST;
+ }
+
+ public void removeAllWorkspaceScreens() {
+ // Disable all layout transitions before removing all pages to ensure that we don't get the
+ // transition animations competing with us changing the scroll when we add pages or the
+ // custom content screen
+ disableLayoutTransitions();
+
+ // Since we increment the current page when we call addCustomContentPage via bindScreens
+ // (and other places), we need to adjust the current page back when we clear the pages
+ if (hasCustomContent()) {
+ removeCustomContentPage();
+ }
+
+ // Remove the pages and clear the screen models
+ removeAllViews();
+ mScreenOrder.clear();
+ mWorkspaceScreens.clear();
+
+ // Re-enable the layout transitions
+ enableLayoutTransitions();
+ }
+
+ public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
+ // Find the index to insert this view into. If the empty screen exists, then
+ // insert it before that.
+ int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
+ if (insertIndex < 0) {
+ insertIndex = mScreenOrder.size();
+ }
+ return insertNewWorkspaceScreen(screenId, insertIndex);
+ }
+
+ public long insertNewWorkspaceScreen(long screenId) {
+ return insertNewWorkspaceScreen(screenId, getChildCount());
+ }
+
+ public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
+ if (mWorkspaceScreens.containsKey(screenId)) {
+ throw new RuntimeException("Screen id " + screenId + " already exists!");
+ }
+
+ CellLayout newScreen = (CellLayout)
+ mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
+
+ newScreen.setOnLongClickListener(mLongClickListener);
+ newScreen.setOnClickListener(mLauncher);
+ newScreen.setSoundEffectsEnabled(false);
+ mWorkspaceScreens.put(screenId, newScreen);
+ mScreenOrder.add(insertIndex, screenId);
+ addView(newScreen, insertIndex);
+ return screenId;
+ }
+
+ public void createCustomContentPage() {
+ CellLayout customScreen = (CellLayout)
+ mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
+
+ mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
+ mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
+
+ // We want no padding on the custom content
+ customScreen.setPadding(0, 0, 0, 0);
+
+ addFullScreenPage(customScreen);
+
+ // Ensure that the current page and default page are maintained.
+ mDefaultPage = mOriginalDefaultPage + 1;
+
+ // Update the custom content hint
+ mLauncher.updateCustomContentHintVisibility();
+ if (mRestorePage != INVALID_RESTORE_PAGE) {
+ mRestorePage = mRestorePage + 1;
+ } else {
+ setCurrentPage(getCurrentPage() + 1);
+ }
+ }
+
+ public void removeCustomContentPage() {
+ CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
+ if (customScreen == null) {
+ throw new RuntimeException("Expected custom content screen to exist");
+ }
+
+ mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
+ mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
+ removeView(customScreen);
+
+ if (mCustomContentCallbacks != null) {
+ mCustomContentCallbacks.onScrollProgressChanged(0);
+ mCustomContentCallbacks.onHide();
+ }
+
+ mCustomContentCallbacks = null;
+
+ // Ensure that the current page and default page are maintained.
+ mDefaultPage = mOriginalDefaultPage - 1;
+
+ // Update the custom content hint
+ mLauncher.updateCustomContentHintVisibility();
+ if (mRestorePage != INVALID_RESTORE_PAGE) {
+ mRestorePage = mRestorePage - 1;
+ } else {
+ setCurrentPage(getCurrentPage() - 1);
+ }
+ }
+
+ public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
+ String description) {
+ if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
+ throw new RuntimeException("Expected custom content screen to exist");
+ }
+
+ // Add the custom content to the full screen custom page
+ CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
+ int spanX = customScreen.getCountX();
+ int spanY = customScreen.getCountY();
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
+ lp.canReorder = false;
+ lp.isFullscreen = true;
+ if (customContent instanceof Insettable) {
+ ((Insettable)customContent).setInsets(mInsets);
+ }
+ customScreen.removeAllViews();
+ customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
+ mCustomContentDescription = description;
+
+ mCustomContentCallbacks = callbacks;
+ }
+
+ public void addExtraEmptyScreenOnDrag() {
+ boolean lastChildOnScreen = false;
+ boolean childOnFinalScreen = false;
+
+ if (mDragSourceInternal != null) {
+ if (mDragSourceInternal.getChildCount() == 1) {
+ lastChildOnScreen = true;
+ }
+ CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
+ if (indexOfChild(cl) == getChildCount() - 1) {
+ childOnFinalScreen = true;
+ }
+ }
+
+ // If this is the last item on the final screen
+ if (lastChildOnScreen && childOnFinalScreen) {
+ return;
+ }
+ if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
+ insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
+ }
+ }
+
+ public boolean addExtraEmptyScreen() {
+ if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
+ insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
+ return true;
+ }
+ return false;
+ }
+
+ public void removeExtraEmptyScreen() {
+ if (hasExtraEmptyScreen()) {
+ CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+ mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+ mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
+ removeView(cl);
+ }
+ }
+
+ public boolean hasExtraEmptyScreen() {
+ int nScreens = getChildCount();
+ nScreens = nScreens - numCustomPages();
+ return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
+ }
+
+ public long commitExtraEmptyScreen() {
+ int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
+ CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
+ mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
+ mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
+
+ long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
+ mWorkspaceScreens.put(newId, cl);
+ mScreenOrder.add(newId);
+
+ // Update the page indicator marker
+ if (getPageIndicator() != null) {
+ getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
+ }
+
+ // Update the model for the new screen
+ mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+
+ return newId;
+ }
+
+ public CellLayout getScreenWithId(long screenId) {
+ CellLayout layout = mWorkspaceScreens.get(screenId);
+ return layout;
+ }
+
+ public long getIdForScreen(CellLayout layout) {
+ Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
+ while (iter.hasNext()) {
+ long id = iter.next();
+ if (mWorkspaceScreens.get(id) == layout) {
+ return id;
+ }
+ }
+ return -1;
+ }
+
+ public int getPageIndexForScreenId(long screenId) {
+ return indexOfChild(mWorkspaceScreens.get(screenId));
+ }
+
+ public long getScreenIdForPageIndex(int index) {
+ if (0 <= index && index < mScreenOrder.size()) {
+ return mScreenOrder.get(index);
+ }
+ return -1;
+ }
+
+ ArrayList<Long> getScreenOrder() {
+ return mScreenOrder;
+ }
+
+ public void stripEmptyScreens() {
+ if (isPageMoving()) {
+ mStripScreensOnPageStopMoving = true;
+ return;
+ }
+
+ int currentPage = getNextPage();
+ ArrayList<Long> removeScreens = new ArrayList<Long>();
+ for (Long id: mWorkspaceScreens.keySet()) {
+ CellLayout cl = mWorkspaceScreens.get(id);
+ if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
+ removeScreens.add(id);
+ }
+ }
+
+ // We enforce at least one page to add new items to. In the case that we remove the last
+ // such screen, we convert the last screen to the empty screen
+ int minScreens = 1 + numCustomPages();
+
+ int pageShift = 0;
+ for (Long id: removeScreens) {
+ CellLayout cl = mWorkspaceScreens.get(id);
+ mWorkspaceScreens.remove(id);
+ mScreenOrder.remove(id);
+
+ if (getChildCount() > minScreens) {
+ if (indexOfChild(cl) < currentPage) {
+ pageShift++;
+ }
+ removeView(cl);
+ } else {
+ // if this is the last non-custom content screen, convert it to the empty screen
+ mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
+ mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
+ }
+ }
+
+ if (!removeScreens.isEmpty()) {
+ // Update the model if we have changed any screens
+ mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+ }
+
+ if (pageShift >= 0) {
+ setCurrentPage(currentPage - pageShift);
+ }
+ }
+
+ // See implementation for parameter definition.
+ void addInScreen(View child, long container, long screenId,
+ int x, int y, int spanX, int spanY) {
+ addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
+ }
+
+ // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
+ // See implementation for parameter definition.
+ void addInScreenFromBind(View child, long container, long screenId, int x, int y,
+ int spanX, int spanY) {
+ addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
+ }
+
+ // See implementation for parameter definition.
+ void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
+ boolean insert) {
+ addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
+ }
+
+ /**
+ * Adds the specified child in the specified screen. The position and dimension of
+ * the child are defined by x, y, spanX and spanY.
+ *
+ * @param child The child to add in one of the workspace's screens.
+ * @param screenId The screen in which to add the child.
+ * @param x The X position of the child in the screen's grid.
+ * @param y The Y position of the child in the screen's grid.
+ * @param spanX The number of cells spanned horizontally by the child.
+ * @param spanY The number of cells spanned vertically by the child.
+ * @param insert When true, the child is inserted at the beginning of the children list.
+ * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
+ * the x and y position in which to place hotseat items. Otherwise
+ * we use the x and y position to compute the rank.
+ */
+ void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
+ boolean insert, boolean computeXYFromRank) {
+ if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ if (getScreenWithId(screenId) == null) {
+ Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
+ // DEBUGGING - Print out the stack trace to see where we are adding from
+ new Throwable().printStackTrace();
+ return;
+ }
+ }
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+ // This should never happen
+ throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
+ }
+
+ final CellLayout layout;
+ if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+ layout = mLauncher.getHotseat().getLayout();
+ child.setOnKeyListener(null);
+
+ // Hide folder title in the hotseat
+ if (child instanceof FolderIcon) {
+ ((FolderIcon) child).setTextVisible(false);
+ }
+
+ if (computeXYFromRank) {
+ x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
+ y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
+ } else {
+ screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
+ }
+ } else {
+ // Show folder title if not in the hotseat
+ if (child instanceof FolderIcon) {
+ ((FolderIcon) child).setTextVisible(true);
+ }
+ layout = getScreenWithId(screenId);
+ child.setOnKeyListener(new IconKeyEventListener());
+ }
+
+ ViewGroup.LayoutParams genericLp = child.getLayoutParams();
+ CellLayout.LayoutParams lp;
+ if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
+ lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
+ } else {
+ lp = (CellLayout.LayoutParams) genericLp;
+ lp.cellX = x;
+ lp.cellY = y;
+ lp.cellHSpan = spanX;
+ lp.cellVSpan = spanY;
+ }
+
+ if (spanX < 0 && spanY < 0) {
+ lp.isLockedToGrid = false;
+ }
+
+ // Get the canonical child id to uniquely represent this view in this screen
+ int childId = LauncherModel.getCellLayoutChildId(container, screenId, x, y, spanX, spanY);
+ boolean markCellsAsOccupied = !(child instanceof Folder);
+ if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
+ // TODO: This branch occurs when the workspace is adding views
+ // outside of the defined grid
+ // maybe we should be deleting these items from the LauncherModel?
+ Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true);
+ }
+
+ if (!(child instanceof Folder)) {
+ child.setHapticFeedbackEnabled(false);
+ child.setOnLongClickListener(mLongClickListener);
+ }
+ if (child instanceof DropTarget) {
+ mDragController.addDropTarget((DropTarget) child);
+ }
+ }
+
+ /**
+ * Called directly from a CellLayout (not by the framework), after we've been added as a
+ * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
+ * that it should intercept touch events, which is not something that is normally supported.
+ */
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return (isSmall() || !isFinishedSwitchingState())
+ || (!isSmall() && indexOfChild(v) != mCurrentPage);
+ }
+
+ public boolean isSwitchingState() {
+ return mIsSwitchingState;
+ }
+
+ /** This differs from isSwitchingState in that we take into account how far the transition
+ * has completed. */
+ public boolean isFinishedSwitchingState() {
+ return !mIsSwitchingState || (mTransitionProgress > 0.5f);
+ }
+
+ protected void onWindowVisibilityChanged (int visibility) {
+ mLauncher.onWindowVisibilityChanged(visibility);
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ if (isSmall() || !isFinishedSwitchingState()) {
+ // when the home screens are shrunken, shouldn't allow side-scrolling
+ return false;
+ }
+ return super.dispatchUnhandledMove(focused, direction);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ switch (ev.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mXDown = ev.getX();
+ mYDown = ev.getY();
+ mTouchDownTime = System.currentTimeMillis();
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_UP:
+ if (mTouchState == TOUCH_STATE_REST) {
+ final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
+ if (!currentPage.lastDownOnOccupiedCell()) {
+ onWallpaperTap(ev);
+ }
+ }
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ protected void reinflateWidgetsIfNecessary() {
+ final int clCount = getChildCount();
+ for (int i = 0; i < clCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
+ final int itemCount = swc.getChildCount();
+ for (int j = 0; j < itemCount; j++) {
+ View v = swc.getChildAt(j);
+
+ if (v.getTag() instanceof LauncherAppWidgetInfo) {
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
+ LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
+ if (lahv != null && lahv.orientationChangedSincedInflation()) {
+ mLauncher.removeAppWidget(info);
+ // Remove the current widget which is inflated with the wrong orientation
+ cl.removeView(lahv);
+ mLauncher.bindAppWidget(info);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void determineScrollingStart(MotionEvent ev) {
+ if (!isFinishedSwitchingState()) return;
+
+ float deltaX = ev.getX() - mXDown;
+ float absDeltaX = Math.abs(deltaX);
+ float absDeltaY = Math.abs(ev.getY() - mYDown);
+
+ if (Float.compare(absDeltaX, 0f) == 0) return;
+
+ float slope = absDeltaY / absDeltaX;
+ float theta = (float) Math.atan(slope);
+
+ if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
+ cancelCurrentPageLongPress();
+ }
+
+ boolean passRightSwipesToCustomContent =
+ (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
+
+ boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
+ if (swipeInIgnoreDirection && getScreenIdForPageIndex(getCurrentPage()) ==
+ CUSTOM_CONTENT_SCREEN_ID && passRightSwipesToCustomContent) {
+ // Pass swipes to the right to the custom content page.
+ return;
+ }
+
+ if (theta > MAX_SWIPE_ANGLE) {
+ // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
+ return;
+ } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
+ // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
+ // increase the touch slop to make it harder to begin scrolling the workspace. This
+ // results in vertically scrolling widgets to more easily. The higher the angle, the
+ // more we increase touch slop.
+ theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
+ float extraRatio = (float)
+ Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
+ super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
+ } else {
+ // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
+ super.determineScrollingStart(ev);
+ }
+ }
+
+ protected void onPageBeginMoving() {
+ super.onPageBeginMoving();
+
+ if (isHardwareAccelerated()) {
+ updateChildrenLayersEnabled(false);
+ } else {
+ if (mNextPage != INVALID_PAGE) {
+ // we're snapping to a particular screen
+ enableChildrenCache(mCurrentPage, mNextPage);
+ } else {
+ // this is when user is actively dragging a particular screen, they might
+ // swipe it either left or right (but we won't advance by more than one screen)
+ enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
+ }
+ }
+
+ // Only show page outlines as we pan if we are on large screen
+ if (LauncherAppState.getInstance().isScreenLarge()) {
+ showOutlines();
+ }
+
+ // If we are not fading in adjacent screens, we still need to restore the alpha in case the
+ // user scrolls while we are transitioning (should not affect dispatchDraw optimizations)
+ if (!mWorkspaceFadeInAdjacentScreens) {
+ for (int i = 0; i < getChildCount(); ++i) {
+ ((CellLayout) getPageAt(i)).setShortcutAndWidgetAlpha(1f);
+ }
+ }
+ }
+
+ protected void onPageEndMoving() {
+ super.onPageEndMoving();
+
+ if (isHardwareAccelerated()) {
+ updateChildrenLayersEnabled(false);
+ } else {
+ clearChildrenCache();
+ }
+
+ if (mDragController.isDragging()) {
+ if (isSmall()) {
+ // If we are in springloaded mode, then force an event to check if the current touch
+ // is under a new page (to scroll to)
+ mDragController.forceTouchMove();
+ }
+ } else {
+ // If we are not mid-dragging, hide the page outlines if we are on a large screen
+ if (LauncherAppState.getInstance().isScreenLarge()) {
+ hideOutlines();
+ }
+ }
+
+ if (mDelayedResizeRunnable != null) {
+ mDelayedResizeRunnable.run();
+ mDelayedResizeRunnable = null;
+ }
+
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ mDelayedSnapToPageRunnable = null;
+ }
+ if (mStripScreensOnPageStopMoving) {
+ stripEmptyScreens();
+ mStripScreensOnPageStopMoving = false;
+ }
+ }
+
+ @Override
+ protected void notifyPageSwitchListener() {
+ super.notifyPageSwitchListener();
+ Launcher.setScreen(mCurrentPage);
+
+ if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
+ mCustomContentShowing = true;
+ if (mCustomContentCallbacks != null) {
+ mCustomContentCallbacks.onShow();
+ mCustomContentShowTime = System.currentTimeMillis();
+ mLauncher.updateVoiceButtonProxyVisible(false);
+ }
+ } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
+ mCustomContentShowing = false;
+ if (mCustomContentCallbacks != null) {
+ mCustomContentCallbacks.onHide();
+ mLauncher.resetQSBScroll();
+ mLauncher.updateVoiceButtonProxyVisible(false);
+ }
+ }
+ if (getPageIndicator() != null) {
+ getPageIndicator().setContentDescription(getPageIndicatorDescription());
+ }
+ }
+
+ protected CustomContentCallbacks getCustomContentCallbacks() {
+ return mCustomContentCallbacks;
+ }
+
+ protected void setWallpaperDimension() {
+ String spKey = WallpaperCropActivity.getSharedPreferencesKey();
+ SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_PRIVATE);
+ WallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
+ sp, mLauncher.getWindowManager(), mWallpaperManager);
+ }
+
+ protected void snapToPage(int whichPage, Runnable r) {
+ if (mDelayedSnapToPageRunnable != null) {
+ mDelayedSnapToPageRunnable.run();
+ }
+ mDelayedSnapToPageRunnable = r;
+ snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ protected void snapToScreenId(long screenId, Runnable r) {
+ snapToPage(getPageIndexForScreenId(screenId), r);
+ }
+
+ class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
+ float mFinalOffset = 0.0f;
+ float mCurrentOffset = 0.5f; // to force an initial update
+ boolean mWaitingForUpdate;
+ Choreographer mChoreographer;
+ Interpolator mInterpolator;
+ boolean mAnimating;
+ long mAnimationStartTime;
+ float mAnimationStartOffset;
+ private final int ANIMATION_DURATION = 250;
+ // Don't use all the wallpaper for parallax until you have at least this many pages
+ private final int MIN_PARALLAX_PAGE_SPAN = 3;
+ int mNumScreens;
+
+ public WallpaperOffsetInterpolator() {
+ mChoreographer = Choreographer.getInstance();
+ mInterpolator = new DecelerateInterpolator(1.5f);
+ }
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ updateOffset(false);
+ }
+
+ private void updateOffset(boolean force) {
+ if (mWaitingForUpdate || force) {
+ mWaitingForUpdate = false;
+ if (computeScrollOffset() && mWindowToken != null) {
+ try {
+ mWallpaperManager.setWallpaperOffsets(mWindowToken,
+ mWallpaperOffset.getCurrX(), 0.5f);
+ setWallpaperOffsetSteps();
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Error updating wallpaper offset: " + e);
+ }
+ }
+ }
+ }
+
+ public boolean computeScrollOffset() {
+ final float oldOffset = mCurrentOffset;
+ if (mAnimating) {
+ long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
+ float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
+ float t1 = mInterpolator.getInterpolation(t0);
+ mCurrentOffset = mAnimationStartOffset +
+ (mFinalOffset - mAnimationStartOffset) * t1;
+ mAnimating = durationSinceAnimation < ANIMATION_DURATION;
+ } else {
+ mCurrentOffset = mFinalOffset;
+ }
+
+ if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
+ scheduleUpdate();
+ }
+ if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
+ return true;
+ }
+ return false;
+ }
+
+ private float wallpaperOffsetForCurrentScroll() {
+ if (getChildCount() <= 1) {
+ return 0;
+ }
+
+ // Exclude the leftmost page
+ int emptyExtraPages = numEmptyScreensToIgnore();
+ int firstIndex = numCustomPages();
+ // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
+ int lastIndex = getChildCount() - 1 - emptyExtraPages;
+ if (isLayoutRtl()) {
+ int temp = firstIndex;
+ firstIndex = lastIndex;
+ lastIndex = temp;
+ }
+
+ int firstPageScrollX = getScrollForPage(firstIndex);
+ int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
+ if (scrollRange == 0) {
+ return 0;
+ } else {
+ // TODO: do different behavior if it's a live wallpaper?
+ // Sometimes the left parameter of the pages is animated during a layout transition;
+ // this parameter offsets it to keep the wallpaper from animating as well
+ int offsetForLayoutTransitionAnimation = isLayoutRtl() ?
+ getPageAt(getChildCount() - 1).getLeft() - getFirstChildLeft() : 0;
+ int adjustedScroll =
+ getScrollX() - firstPageScrollX - offsetForLayoutTransitionAnimation;
+ float offset = Math.min(1, adjustedScroll / (float) scrollRange);
+ offset = Math.max(0, offset);
+ // Don't use up all the wallpaper parallax until you have at least
+ // MIN_PARALLAX_PAGE_SPAN pages
+ int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
+ int parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
+ // On RTL devices, push the wallpaper offset to the right if we don't have enough
+ // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
+ int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
+ return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
+ }
+ }
+
+ private int numEmptyScreensToIgnore() {
+ int numScrollingPages = getChildCount() - numCustomPages();
+ if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ private int getNumScreensExcludingEmptyAndCustom() {
+ int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
+ return numScrollingPages;
+ }
+
+ public void syncWithScroll() {
+ float offset = wallpaperOffsetForCurrentScroll();
+ mWallpaperOffset.setFinalX(offset);
+ updateOffset(true);
+ }
+
+ public float getCurrX() {
+ return mCurrentOffset;
+ }
+
+ public float getFinalX() {
+ return mFinalOffset;
+ }
+
+ private void animateToFinal() {
+ mAnimating = true;
+ mAnimationStartOffset = mCurrentOffset;
+ mAnimationStartTime = System.currentTimeMillis();
+ }
+
+ private void setWallpaperOffsetSteps() {
+ // Set wallpaper offset steps (1 / (number of screens - 1))
+ mWallpaperManager.setWallpaperOffsetSteps(1.0f / (getChildCount() - 1), 1.0f);
+ }
+
+ public void setFinalX(float x) {
+ scheduleUpdate();
+ mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
+ if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
+ if (mNumScreens > 0) {
+ // Don't animate if we're going from 0 screens
+ animateToFinal();
+ }
+ mNumScreens = getNumScreensExcludingEmptyAndCustom();
+ }
+ }
+
+ private void scheduleUpdate() {
+ if (!mWaitingForUpdate) {
+ mChoreographer.postFrameCallback(this);
+ mWaitingForUpdate = true;
+ }
+ }
+
+ public void jumpToFinal() {
+ mCurrentOffset = mFinalOffset;
+ }
+ }
+
+ @Override
+ public void computeScroll() {
+ super.computeScroll();
+ mWallpaperOffset.syncWithScroll();
+ }
+
+ void showOutlines() {
+ if (!isSmall() && !mIsSwitchingState) {
+ if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+ if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+ mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
+ mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
+ mChildrenOutlineFadeInAnimation.start();
+ }
+ }
+
+ void hideOutlines() {
+ if (!isSmall() && !mIsSwitchingState) {
+ if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+ if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+ mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
+ mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
+ mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
+ mChildrenOutlineFadeOutAnimation.start();
+ }
+ }
+
+ public void showOutlinesTemporarily() {
+ if (!mIsPageMoving && !isTouchActive()) {
+ snapToPage(mCurrentPage);
+ }
+ }
+
+ public void setChildrenOutlineAlpha(float alpha) {
+ mChildrenOutlineAlpha = alpha;
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setBackgroundAlpha(alpha);
+ }
+ }
+
+ public float getChildrenOutlineAlpha() {
+ return mChildrenOutlineAlpha;
+ }
+
+ void disableBackground() {
+ mDrawBackground = false;
+ }
+ void enableBackground() {
+ mDrawBackground = true;
+ }
+
+ private void animateBackgroundGradient(float finalAlpha, boolean animated) {
+ if (mBackground == null) return;
+ if (mBackgroundFadeInAnimation != null) {
+ mBackgroundFadeInAnimation.cancel();
+ mBackgroundFadeInAnimation = null;
+ }
+ if (mBackgroundFadeOutAnimation != null) {
+ mBackgroundFadeOutAnimation.cancel();
+ mBackgroundFadeOutAnimation = null;
+ }
+ float startAlpha = getBackgroundAlpha();
+ if (finalAlpha != startAlpha) {
+ if (animated) {
+ mBackgroundFadeOutAnimation =
+ LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
+ mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setBackgroundAlpha(((Float) animation.getAnimatedValue()).floatValue());
+ }
+ });
+ mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
+ mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
+ mBackgroundFadeOutAnimation.start();
+ } else {
+ setBackgroundAlpha(finalAlpha);
+ }
+ }
+ }
+
+ public void setBackgroundAlpha(float alpha) {
+ if (alpha != mBackgroundAlpha) {
+ mBackgroundAlpha = alpha;
+ invalidate();
+ }
+ }
+
+ public float getBackgroundAlpha() {
+ return mBackgroundAlpha;
+ }
+
+ float backgroundAlphaInterpolator(float r) {
+ float pivotA = 0.1f;
+ float pivotB = 0.4f;
+ if (r < pivotA) {
+ return 0;
+ } else if (r > pivotB) {
+ return 1.0f;
+ } else {
+ return (r - pivotA)/(pivotB - pivotA);
+ }
+ }
+
+ private void updatePageAlphaValues(int screenCenter) {
+ boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+ if (mWorkspaceFadeInAdjacentScreens &&
+ mState == State.NORMAL &&
+ !mIsSwitchingState &&
+ !isInOverscroll) {
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout child = (CellLayout) getChildAt(i);
+ if (child != null) {
+ float scrollProgress = getScrollProgress(screenCenter, child, i);
+ float alpha = 1 - Math.abs(scrollProgress);
+ child.getShortcutsAndWidgets().setAlpha(alpha);
+ if (!mIsDragOccuring) {
+ child.setBackgroundAlphaMultiplier(
+ backgroundAlphaInterpolator(Math.abs(scrollProgress)));
+ } else {
+ child.setBackgroundAlphaMultiplier(1f);
+ }
+ }
+ }
+ }
+ }
+
+ private void setChildrenBackgroundAlphaMultipliers(float a) {
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout child = (CellLayout) getChildAt(i);
+ child.setBackgroundAlphaMultiplier(a);
+ }
+ }
+
+ public boolean hasCustomContent() {
+ return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
+ }
+
+ public int numCustomPages() {
+ return hasCustomContent() ? 1 : 0;
+ }
+
+ public boolean isOnOrMovingToCustomContent() {
+ return hasCustomContent() && getNextPage() == 0;
+ }
+
+ private void updateStateForCustomContent(int screenCenter) {
+ float translationX = 0;
+ float progress = 0;
+ if (hasCustomContent()) {
+ int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
+
+ int scrollDelta = getScrollX() - getScrollForPage(index) -
+ getLayoutTransitionOffsetForPage(index);
+ float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
+ translationX = scrollRange - scrollDelta;
+ progress = (scrollRange - scrollDelta) / scrollRange;
+
+ if (isLayoutRtl()) {
+ translationX = Math.min(0, translationX);
+ } else {
+ translationX = Math.max(0, translationX);
+ }
+ progress = Math.max(0, progress);
+ }
+
+ if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
+ mLastCustomContentScrollProgress = progress;
+
+ setBackgroundAlpha(progress * 0.8f);
+
+ if (mLauncher.getHotseat() != null) {
+ mLauncher.getHotseat().setTranslationX(translationX);
+ }
+
+ if (getPageIndicator() != null) {
+ getPageIndicator().setTranslationX(translationX);
+ }
+
+ if (mCustomContentCallbacks != null) {
+ mCustomContentCallbacks.onScrollProgressChanged(progress);
+ }
+ }
+
+ @Override
+ protected OnClickListener getPageIndicatorClickListener() {
+ AccessibilityManager am = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (!am.isTouchExplorationEnabled()) {
+ return null;
+ }
+ OnClickListener listener = new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ enterOverviewMode();
+ }
+ };
+ return listener;
+ }
+
+ @Override
+ protected void screenScrolled(int screenCenter) {
+ final boolean isRtl = isLayoutRtl();
+ super.screenScrolled(screenCenter);
+
+ updatePageAlphaValues(screenCenter);
+ updateStateForCustomContent(screenCenter);
+ enableHwLayersOnVisiblePages();
+
+ boolean shouldOverScroll = (mOverScrollX < 0 && (!hasCustomContent() || isLayoutRtl())) ||
+ (mOverScrollX > mMaxScrollX && (!hasCustomContent() || !isLayoutRtl()));
+
+ if (shouldOverScroll) {
+ int index = 0;
+ float pivotX = 0f;
+ final float leftBiasedPivot = 0.25f;
+ final float rightBiasedPivot = 0.75f;
+ final int lowerIndex = 0;
+ final int upperIndex = getChildCount() - 1;
+
+ final boolean isLeftPage = mOverScrollX < 0;
+ index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
+ pivotX = isLeftPage ? rightBiasedPivot : leftBiasedPivot;
+
+ CellLayout cl = (CellLayout) getChildAt(index);
+ float scrollProgress = getScrollProgress(screenCenter, cl, index);
+ cl.setOverScrollAmount(Math.abs(scrollProgress), isLeftPage);
+ float rotation = -WORKSPACE_OVERSCROLL_ROTATION * scrollProgress;
+ cl.setRotationY(rotation);
+
+ if (!mOverscrollTransformsSet || Float.compare(mLastOverscrollPivotX, pivotX) != 0) {
+ mOverscrollTransformsSet = true;
+ mLastOverscrollPivotX = pivotX;
+ cl.setCameraDistance(mDensity * mCameraDistance);
+ cl.setPivotX(cl.getMeasuredWidth() * pivotX);
+ cl.setPivotY(cl.getMeasuredHeight() * 0.5f);
+ cl.setOverscrollTransformsDirty(true);
+ }
+ } else {
+ if (mOverscrollTransformsSet) {
+ mOverscrollTransformsSet = false;
+ ((CellLayout) getChildAt(0)).resetOverscrollTransforms();
+ ((CellLayout) getChildAt(getChildCount() - 1)).resetOverscrollTransforms();
+ }
+ }
+ }
+
+ @Override
+ protected void overScroll(float amount) {
+ acceleratedOverScroll(amount);
+ }
+
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mWindowToken = getWindowToken();
+ computeScroll();
+ mDragController.setWindowToken(mWindowToken);
+ }
+
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mWindowToken = null;
+ }
+
+ protected void onResume() {
+ if (getPageIndicator() != null) {
+ // In case accessibility state has changed, we need to perform this on every
+ // attach to window
+ OnClickListener listener = getPageIndicatorClickListener();
+ if (listener != null) {
+ getPageIndicator().setOnClickListener(listener);
+ }
+ }
+ AccessibilityManager am = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ sAccessibilityEnabled = am.isEnabled();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+ mWallpaperOffset.syncWithScroll();
+ mWallpaperOffset.jumpToFinal();
+ }
+ super.onLayout(changed, left, top, right, bottom);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // Draw the background gradient if necessary
+ if (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground) {
+ int alpha = (int) (mBackgroundAlpha * 255);
+ mBackground.setAlpha(alpha);
+ mBackground.setBounds(getScrollX(), 0, getScrollX() + getMeasuredWidth(),
+ getMeasuredHeight());
+ mBackground.draw(canvas);
+ }
+
+ super.onDraw(canvas);
+
+ // Call back to LauncherModel to finish binding after the first draw
+ post(mBindPages);
+ }
+
+ boolean isDrawingBackgroundGradient() {
+ return (mBackground != null && mBackgroundAlpha > 0.0f && mDrawBackground);
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (!mLauncher.isAllAppsVisible()) {
+ final Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ return openFolder.requestFocus(direction, previouslyFocusedRect);
+ } else {
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getDescendantFocusability() {
+ if (isSmall()) {
+ return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
+ }
+ return super.getDescendantFocusability();
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (!mLauncher.isAllAppsVisible()) {
+ final Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ openFolder.addFocusables(views, direction);
+ } else {
+ super.addFocusables(views, direction, focusableMode);
+ }
+ }
+ }
+
+ public boolean isSmall() {
+ return mState == State.SMALL || mState == State.SPRING_LOADED || mState == State.OVERVIEW;
+ }
+
+ void enableChildrenCache(int fromPage, int toPage) {
+ if (fromPage > toPage) {
+ final int temp = fromPage;
+ fromPage = toPage;
+ toPage = temp;
+ }
+
+ final int screenCount = getChildCount();
+
+ fromPage = Math.max(fromPage, 0);
+ toPage = Math.min(toPage, screenCount - 1);
+
+ for (int i = fromPage; i <= toPage; i++) {
+ final CellLayout layout = (CellLayout) getChildAt(i);
+ layout.setChildrenDrawnWithCacheEnabled(true);
+ layout.setChildrenDrawingCacheEnabled(true);
+ }
+ }
+
+ void clearChildrenCache() {
+ final int screenCount = getChildCount();
+ for (int i = 0; i < screenCount; i++) {
+ final CellLayout layout = (CellLayout) getChildAt(i);
+ layout.setChildrenDrawnWithCacheEnabled(false);
+ // In software mode, we don't want the items to continue to be drawn into bitmaps
+ if (!isHardwareAccelerated()) {
+ layout.setChildrenDrawingCacheEnabled(false);
+ }
+ }
+ }
+
+ private void updateChildrenLayersEnabled(boolean force) {
+ boolean small = mState == State.SMALL || mState == State.OVERVIEW || mIsSwitchingState;
+ boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
+
+ if (enableChildrenLayers != mChildrenLayersEnabled) {
+ mChildrenLayersEnabled = enableChildrenLayers;
+ if (mChildrenLayersEnabled) {
+ enableHwLayersOnVisiblePages();
+ } else {
+ for (int i = 0; i < getPageCount(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ cl.enableHardwareLayer(false);
+ }
+ }
+ }
+ }
+
+ private void enableHwLayersOnVisiblePages() {
+ if (mChildrenLayersEnabled) {
+ final int screenCount = getChildCount();
+ getVisiblePages(mTempVisiblePagesRange);
+ int leftScreen = mTempVisiblePagesRange[0];
+ int rightScreen = mTempVisiblePagesRange[1];
+ if (leftScreen == rightScreen) {
+ // make sure we're caching at least two pages always
+ if (rightScreen < screenCount - 1) {
+ rightScreen++;
+ } else if (leftScreen > 0) {
+ leftScreen--;
+ }
+ }
+
+ final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
+ for (int i = 0; i < screenCount; i++) {
+ final CellLayout layout = (CellLayout) getPageAt(i);
+
+ // enable layers between left and right screen inclusive, except for the
+ // customScreen, which may animate its content during transitions.
+ boolean enableLayer = layout != customScreen &&
+ leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
+ layout.enableHardwareLayer(enableLayer);
+ }
+ }
+ }
+
+ public void buildPageHardwareLayers() {
+ // force layers to be enabled just for the call to buildLayer
+ updateChildrenLayersEnabled(true);
+ if (getWindowToken() != null) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ cl.buildHardwareLayer();
+ }
+ }
+ updateChildrenLayersEnabled(false);
+ }
+
+ protected void onWallpaperTap(MotionEvent ev) {
+ final int[] position = mTempCell;
+ getLocationOnScreen(position);
+
+ int pointerIndex = ev.getActionIndex();
+ position[0] += (int) ev.getX(pointerIndex);
+ position[1] += (int) ev.getY(pointerIndex);
+
+ mWallpaperManager.sendWallpaperCommand(getWindowToken(),
+ ev.getAction() == MotionEvent.ACTION_UP
+ ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
+ position[0], position[1], 0, null);
+ }
+
+ /*
+ * This interpolator emulates the rate at which the perceived scale of an object changes
+ * as its distance from a camera increases. When this interpolator is applied to a scale
+ * animation on a view, it evokes the sense that the object is shrinking due to moving away
+ * from the camera.
+ */
+ static class ZInterpolator implements TimeInterpolator {
+ private float focalLength;
+
+ public ZInterpolator(float foc) {
+ focalLength = foc;
+ }
+
+ public float getInterpolation(float input) {
+ return (1.0f - focalLength / (focalLength + input)) /
+ (1.0f - focalLength / (focalLength + 1.0f));
+ }
+ }
+
+ /*
+ * The exact reverse of ZInterpolator.
+ */
+ static class InverseZInterpolator implements TimeInterpolator {
+ private ZInterpolator zInterpolator;
+ public InverseZInterpolator(float foc) {
+ zInterpolator = new ZInterpolator(foc);
+ }
+ public float getInterpolation(float input) {
+ return 1 - zInterpolator.getInterpolation(1 - input);
+ }
+ }
+
+ /*
+ * ZInterpolator compounded with an ease-out.
+ */
+ static class ZoomOutInterpolator implements TimeInterpolator {
+ private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
+ private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
+
+ public float getInterpolation(float input) {
+ return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
+ }
+ }
+
+ /*
+ * InvereZInterpolator compounded with an ease-out.
+ */
+ static class ZoomInInterpolator implements TimeInterpolator {
+ private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
+ private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
+
+ public float getInterpolation(float input) {
+ return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
+ }
+ }
+
+ private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
+
+ /*
+ *
+ * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
+ * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
+ *
+ * These methods mark the appropriate pages as accepting drops (which alters their visual
+ * appearance).
+ *
+ */
+ public void onDragStartedWithItem(View v) {
+ final Canvas canvas = new Canvas();
+
+ // The outline is used to visualize where the item will land if dropped
+ mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
+ }
+
+ public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
+ final Canvas canvas = new Canvas();
+
+ int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
+
+ // The outline is used to visualize where the item will land if dropped
+ mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
+ size[1], clipAlpha);
+ }
+
+ public void exitWidgetResizeMode() {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ dragLayer.clearAllResizeFrames();
+ }
+
+ private void initAnimationArrays() {
+ final int childCount = getChildCount();
+ if (mLastChildCount == childCount) return;
+
+ mOldBackgroundAlphas = new float[childCount];
+ mOldAlphas = new float[childCount];
+ mNewBackgroundAlphas = new float[childCount];
+ mNewAlphas = new float[childCount];
+ }
+
+ Animator getChangeStateAnimation(final State state, boolean animated) {
+ return getChangeStateAnimation(state, animated, 0, -1);
+ }
+
+ @Override
+ protected void getOverviewModePages(int[] range) {
+ int start = numCustomPages();
+ int end = getChildCount() - 1;
+
+ range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
+ range[1] = Math.max(0, end);
+ }
+
+ protected void onStartReordering() {
+ super.onStartReordering();
+ showOutlines();
+ // Reordering handles its own animations, disable the automatic ones.
+ disableLayoutTransitions();
+ }
+
+ protected void onEndReordering() {
+ super.onEndReordering();
+
+ hideOutlines();
+ mScreenOrder.clear();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ CellLayout cl = ((CellLayout) getChildAt(i));
+ mScreenOrder.add(getIdForScreen(cl));
+ }
+
+ mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
+
+ // Re-enable auto layout transitions for page deletion.
+ enableLayoutTransitions();
+ }
+
+ public boolean isInOverviewMode() {
+ return mState == State.OVERVIEW;
+ }
+
+ public boolean enterOverviewMode() {
+ if (mTouchState != TOUCH_STATE_REST) {
+ return false;
+ }
+ enableOverviewMode(true, -1, true);
+ return true;
+ }
+
+ public void exitOverviewMode(boolean animated) {
+ exitOverviewMode(-1, animated);
+ }
+
+ public void exitOverviewMode(int snapPage, boolean animated) {
+ enableOverviewMode(false, snapPage, animated);
+ }
+
+ private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
+ State finalState = Workspace.State.OVERVIEW;
+ if (!enable) {
+ finalState = Workspace.State.NORMAL;
+ }
+
+ Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
+ if (workspaceAnim != null) {
+ onTransitionPrepare();
+ workspaceAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ onTransitionEnd();
+ }
+ });
+ workspaceAnim.start();
+ }
+ }
+
+ int getOverviewModeTranslationY() {
+ int childHeight = getNormalChildHeight();
+ int viewPortHeight = getViewportHeight();
+ int scaledChildHeight = (int) (mOverviewModeShrinkFactor * childHeight);
+
+ int offset = (viewPortHeight - scaledChildHeight) / 2;
+ int offsetDelta = mOverviewModePageOffset - offset + mInsets.top;
+
+ return offsetDelta;
+ }
+
+ boolean shouldVoiceButtonProxyBeVisible() {
+ if (isOnOrMovingToCustomContent()) {
+ return false;
+ }
+ if (mState != State.NORMAL) {
+ return false;
+ }
+ return true;
+ }
+
+ public void updateInteractionForState() {
+ if (mState != State.NORMAL) {
+ mLauncher.onInteractionBegin();
+ } else {
+ mLauncher.onInteractionEnd();
+ }
+ }
+
+ private void setState(State state) {
+ mState = state;
+ updateInteractionForState();
+ updateAccessibilityFlags();
+ }
+
+ private void updateAccessibilityFlags() {
+ int accessible = mState == State.NORMAL ?
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+ setImportantForAccessibility(accessible);
+ }
+
+ Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
+ if (mState == state) {
+ return null;
+ }
+
+ // Initialize animation arrays for the first time if necessary
+ initAnimationArrays();
+
+ AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
+
+ final State oldState = mState;
+ final boolean oldStateIsNormal = (oldState == State.NORMAL);
+ final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
+ final boolean oldStateIsSmall = (oldState == State.SMALL);
+ final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
+ setState(state);
+ final boolean stateIsNormal = (state == State.NORMAL);
+ final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
+ final boolean stateIsSmall = (state == State.SMALL);
+ final boolean stateIsOverview = (state == State.OVERVIEW);
+ float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
+ float finalHotseatAndPageIndicatorAlpha = (stateIsOverview || stateIsSmall) ? 0f : 1f;
+ float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
+ float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
+ float finalWorkspaceTranslationY = stateIsOverview ? getOverviewModeTranslationY() : 0;
+
+ boolean workspaceToAllApps = (oldStateIsNormal && stateIsSmall);
+ boolean allAppsToWorkspace = (oldStateIsSmall && stateIsNormal);
+ boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
+ boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
+
+ mNewScale = 1.0f;
+
+ if (oldStateIsOverview) {
+ disableFreeScroll(snapPage);
+ } else if (stateIsOverview) {
+ enableFreeScroll();
+ }
+
+ if (state != State.NORMAL) {
+ if (stateIsSpringLoaded) {
+ mNewScale = mSpringLoadedShrinkFactor;
+ } else if (stateIsOverview) {
+ mNewScale = mOverviewModeShrinkFactor;
+ } else if (stateIsSmall){
+ mNewScale = mOverviewModeShrinkFactor - 0.3f;
+ }
+ if (workspaceToAllApps) {
+ updateChildrenLayersEnabled(false);
+ }
+ }
+
+ final int duration;
+ if (workspaceToAllApps) {
+ duration = getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
+ } else if (workspaceToOverview || overviewToWorkspace) {
+ duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
+ } else {
+ duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
+ }
+
+ for (int i = 0; i < getChildCount(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ boolean isCurrentPage = (i == getNextPage());
+ float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
+ float finalAlpha = stateIsSmall ? 0f : 1f;
+
+ // If we are animating to/from the small state, then hide the side pages and fade the
+ // current page in
+ if (!mIsSwitchingState) {
+ if (workspaceToAllApps || allAppsToWorkspace) {
+ if (allAppsToWorkspace && isCurrentPage) {
+ initialAlpha = 0f;
+ } else if (!isCurrentPage) {
+ initialAlpha = finalAlpha = 0f;
+ }
+ cl.setShortcutAndWidgetAlpha(initialAlpha);
+ }
+ }
+
+ mOldAlphas[i] = initialAlpha;
+ mNewAlphas[i] = finalAlpha;
+ if (animated) {
+ mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
+ mNewBackgroundAlphas[i] = finalBackgroundAlpha;
+ } else {
+ cl.setBackgroundAlpha(finalBackgroundAlpha);
+ cl.setShortcutAndWidgetAlpha(finalAlpha);
+ }
+ }
+
+ final View searchBar = mLauncher.getQsbBar();
+ final View overviewPanel = mLauncher.getOverviewPanel();
+ final View hotseat = mLauncher.getHotseat();
+ if (animated) {
+ anim.setDuration(duration);
+ LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
+ scale.scaleX(mNewScale)
+ .scaleY(mNewScale)
+ .translationY(finalWorkspaceTranslationY)
+ .setInterpolator(mZoomInInterpolator);
+ anim.play(scale);
+ for (int index = 0; index < getChildCount(); index++) {
+ final int i = index;
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
+ if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
+ cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
+ cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
+ } else {
+ if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
+ LauncherViewPropertyAnimator alphaAnim =
+ new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
+ alphaAnim.alpha(mNewAlphas[i])
+ .setInterpolator(mZoomInInterpolator);
+ anim.play(alphaAnim);
+ }
+ if (mOldBackgroundAlphas[i] != 0 ||
+ mNewBackgroundAlphas[i] != 0) {
+ ValueAnimator bgAnim =
+ LauncherAnimUtils.ofFloat(cl, 0f, 1f);
+ bgAnim.setInterpolator(mZoomInInterpolator);
+ bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
+ public void onAnimationUpdate(float a, float b) {
+ cl.setBackgroundAlpha(
+ a * mOldBackgroundAlphas[i] +
+ b * mNewBackgroundAlphas[i]);
+ }
+ });
+ anim.play(bgAnim);
+ }
+ }
+ }
+ ObjectAnimator pageIndicatorAlpha = null;
+ if (getPageIndicator() != null) {
+ pageIndicatorAlpha = ObjectAnimator.ofFloat(getPageIndicator(), "alpha",
+ finalHotseatAndPageIndicatorAlpha);
+ }
+ ObjectAnimator hotseatAlpha = ObjectAnimator.ofFloat(hotseat, "alpha",
+ finalHotseatAndPageIndicatorAlpha);
+ ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(searchBar,
+ "alpha", finalSearchBarAlpha);
+ ObjectAnimator overviewPanelAlpha = ObjectAnimator.ofFloat(overviewPanel,
+ "alpha", finalOverviewPanelAlpha);
+
+ overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
+ hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
+ searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
+
+ if (workspaceToOverview) {
+ hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
+ } else if (overviewToWorkspace) {
+ overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
+ }
+
+ if (getPageIndicator() != null) {
+ pageIndicatorAlpha.addListener(new AlphaUpdateListener(getPageIndicator()));
+ }
+
+ anim.play(overviewPanelAlpha);
+ anim.play(hotseatAlpha);
+ anim.play(searchBarAlpha);
+ anim.play(pageIndicatorAlpha);
+ anim.setStartDelay(delay);
+ } else {
+ overviewPanel.setAlpha(finalOverviewPanelAlpha);
+ AlphaUpdateListener.updateVisibility(overviewPanel);
+ hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
+ AlphaUpdateListener.updateVisibility(hotseat);
+ if (getPageIndicator() != null) {
+ getPageIndicator().setAlpha(finalHotseatAndPageIndicatorAlpha);
+ AlphaUpdateListener.updateVisibility(getPageIndicator());
+ }
+ searchBar.setAlpha(finalSearchBarAlpha);
+ AlphaUpdateListener.updateVisibility(searchBar);
+ updateCustomContentVisibility();
+ setScaleX(mNewScale);
+ setScaleY(mNewScale);
+ setTranslationY(finalWorkspaceTranslationY);
+ }
+ mLauncher.updateVoiceButtonProxyVisible(false);
+
+ if (stateIsSpringLoaded) {
+ // Right now we're covered by Apps Customize
+ // Show the background gradient immediately, so the gradient will
+ // be showing once AppsCustomize disappears
+ animateBackgroundGradient(getResources().getInteger(
+ R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, false);
+ } else if (stateIsOverview) {
+ animateBackgroundGradient(getResources().getInteger(
+ R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f, true);
+ } else {
+ // Fade the background gradient away
+ animateBackgroundGradient(0f, animated);
+ }
+ return anim;
+ }
+
+ static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
+ View view;
+ public AlphaUpdateListener(View v) {
+ view = v;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator arg0) {
+ updateVisibility(view);
+ }
+
+ public static void updateVisibility(View view) {
+ // We want to avoid the extra layout pass by setting the views to GONE unless
+ // accessibility is on, in which case not setting them to GONE causes a glitch.
+ int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
+ if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
+ view.setVisibility(invisibleState);
+ } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
+ && view.getVisibility() != VISIBLE) {
+ view.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator arg0) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ updateVisibility(view);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator arg0) {
+ }
+
+ @Override
+ public void onAnimationStart(Animator arg0) {
+ // We want the views to be visible for animation, so fade-in/out is visible
+ view.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
+ onTransitionPrepare();
+ }
+
+ @Override
+ public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
+ }
+
+ @Override
+ public void onLauncherTransitionStep(Launcher l, float t) {
+ mTransitionProgress = t;
+ }
+
+ @Override
+ public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
+ onTransitionEnd();
+ }
+
+ private void onTransitionPrepare() {
+ mIsSwitchingState = true;
+
+ // Invalidate here to ensure that the pages are rendered during the state change transition.
+ invalidate();
+
+ updateChildrenLayersEnabled(false);
+ hideCustomContentIfNecessary();
+ }
+
+ void updateCustomContentVisibility() {
+ int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
+ if (hasCustomContent()) {
+ mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
+ }
+ }
+
+ void showCustomContentIfNecessary() {
+ boolean show = mState == Workspace.State.NORMAL;
+ if (show && hasCustomContent()) {
+ mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
+ }
+ }
+
+ void hideCustomContentIfNecessary() {
+ boolean hide = mState != Workspace.State.NORMAL;
+ if (hide && hasCustomContent()) {
+ mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
+ }
+ }
+
+ private void onTransitionEnd() {
+ mIsSwitchingState = false;
+ updateChildrenLayersEnabled(false);
+ // The code in getChangeStateAnimation to determine initialAlpha and finalAlpha will ensure
+ // ensure that only the current page is visible during (and subsequently, after) the
+ // transition animation. If fade adjacent pages is disabled, then re-enable the page
+ // visibility after the transition animation.
+ if (!mWorkspaceFadeInAdjacentScreens) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final CellLayout cl = (CellLayout) getChildAt(i);
+ cl.setShortcutAndWidgetAlpha(1f);
+ }
+ }
+ showCustomContentIfNecessary();
+ }
+
+ @Override
+ public View getContent() {
+ return this;
+ }
+
+ /**
+ * Draw the View v into the given Canvas.
+ *
+ * @param v the view to draw
+ * @param destCanvas the canvas to draw on
+ * @param padding the horizontal and vertical padding to use when drawing
+ */
+ private void drawDragView(View v, Canvas destCanvas, int padding, boolean pruneToDrawable) {
+ final Rect clipRect = mTempRect;
+ v.getDrawingRect(clipRect);
+
+ boolean textVisible = false;
+
+ destCanvas.save();
+ if (v instanceof TextView && pruneToDrawable) {
+ Drawable d = ((TextView) v).getCompoundDrawables()[1];
+ clipRect.set(0, 0, d.getIntrinsicWidth() + padding, d.getIntrinsicHeight() + padding);
+ destCanvas.translate(padding / 2, padding / 2);
+ d.draw(destCanvas);
+ } else {
+ if (v instanceof FolderIcon) {
+ // For FolderIcons the text can bleed into the icon area, and so we need to
+ // hide the text completely (which can't be achieved by clipping).
+ if (((FolderIcon) v).getTextVisible()) {
+ ((FolderIcon) v).setTextVisible(false);
+ textVisible = true;
+ }
+ } else if (v instanceof BubbleTextView) {
+ final BubbleTextView tv = (BubbleTextView) v;
+ clipRect.bottom = tv.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V +
+ tv.getLayout().getLineTop(0);
+ } else if (v instanceof TextView) {
+ final TextView tv = (TextView) v;
+ clipRect.bottom = tv.getExtendedPaddingTop() - tv.getCompoundDrawablePadding() +
+ tv.getLayout().getLineTop(0);
+ }
+ destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
+ destCanvas.clipRect(clipRect, Op.REPLACE);
+ v.draw(destCanvas);
+
+ // Restore text visibility of FolderIcon if necessary
+ if (textVisible) {
+ ((FolderIcon) v).setTextVisible(true);
+ }
+ }
+ destCanvas.restore();
+ }
+
+ /**
+ * Returns a new bitmap to show when the given View is being dragged around.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ public Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
+ Bitmap b;
+
+ if (v instanceof TextView) {
+ Drawable d = ((TextView) v).getCompoundDrawables()[1];
+ b = Bitmap.createBitmap(d.getIntrinsicWidth() + padding,
+ d.getIntrinsicHeight() + padding, Bitmap.Config.ARGB_8888);
+ } else {
+ b = Bitmap.createBitmap(
+ v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+ }
+
+ canvas.setBitmap(b);
+ drawDragView(v, canvas, padding, true);
+ canvas.setBitmap(null);
+
+ return b;
+ }
+
+ /**
+ * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
+ final int outlineColor = getResources().getColor(R.color.outline_color);
+ final Bitmap b = Bitmap.createBitmap(
+ v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+ canvas.setBitmap(b);
+ drawDragView(v, canvas, padding, true);
+ mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
+ canvas.setBitmap(null);
+ return b;
+ }
+
+ /**
+ * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+ * Responsibility for the bitmap is transferred to the caller.
+ */
+ private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
+ boolean clipAlpha) {
+ final int outlineColor = getResources().getColor(R.color.outline_color);
+ final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ canvas.setBitmap(b);
+
+ Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
+ float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
+ (h - padding) / (float) orig.getHeight());
+ int scaledWidth = (int) (scaleFactor * orig.getWidth());
+ int scaledHeight = (int) (scaleFactor * orig.getHeight());
+ Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
+
+ // center the image
+ dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
+
+ canvas.drawBitmap(orig, src, dst, null);
+ mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
+ clipAlpha);
+ canvas.setBitmap(null);
+
+ return b;
+ }
+
+ void startDrag(CellLayout.CellInfo cellInfo) {
+ View child = cellInfo.cell;
+
+ // Make sure the drag was started by a long press as opposed to a long click.
+ if (!child.isInTouchMode()) {
+ return;
+ }
+
+ mDragInfo = cellInfo;
+ child.setVisibility(INVISIBLE);
+ CellLayout layout = (CellLayout) child.getParent().getParent();
+ layout.prepareChildForDrag(child);
+
+ child.clearFocus();
+ child.setPressed(false);
+
+ final Canvas canvas = new Canvas();
+
+ // The outline is used to visualize where the item will land if dropped
+ mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
+ beginDragShared(child, this);
+ }
+
+ public void beginDragShared(View child, DragSource source) {
+ // The drag bitmap follows the touch point around on the screen
+ final Bitmap b = createDragBitmap(child, new Canvas(), DRAG_BITMAP_PADDING);
+
+ final int bmpWidth = b.getWidth();
+ final int bmpHeight = b.getHeight();
+
+ float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
+ int dragLayerX =
+ Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
+ int dragLayerY =
+ Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
+ - DRAG_BITMAP_PADDING / 2);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ Point dragVisualizeOffset = null;
+ Rect dragRect = null;
+ if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
+ int iconSize = grid.iconSizePx;
+ int top = child.getPaddingTop();
+ int left = (bmpWidth - iconSize) / 2;
+ int right = left + iconSize;
+ int bottom = top + iconSize;
+ dragLayerY += top;
+ // Note: The drag region is used to calculate drag layer offsets, but the
+ // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
+ dragVisualizeOffset = new Point(-DRAG_BITMAP_PADDING / 2, DRAG_BITMAP_PADDING / 2);
+ dragRect = new Rect(left, top, right, bottom);
+ } else if (child instanceof FolderIcon) {
+ int previewSize = grid.folderIconSizePx;
+ dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
+ }
+
+ // Clear the pressed state if necessary
+ if (child instanceof BubbleTextView) {
+ BubbleTextView icon = (BubbleTextView) child;
+ icon.clearPressedOrFocusedBackground();
+ }
+
+ mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+
+ if (child.getParent() instanceof ShortcutAndWidgetContainer) {
+ mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
+ }
+
+ b.recycle();
+ }
+
+ void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
+ int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
+ View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
+
+ final int[] cellXY = new int[2];
+ target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
+ addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
+
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
+ cellXY[1]);
+ }
+
+ public boolean transitionStateShouldAllowDrop() {
+ return ((!isSwitchingState() || mTransitionProgress > 0.5f) && mState != State.SMALL);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean acceptDrop(DragObject d) {
+ // If it's an external drop (e.g. from All Apps), check if it should be accepted
+ CellLayout dropTargetLayout = mDropToLayout;
+ if (d.dragSource != this) {
+ // Don't accept the drop if we're not over a screen at time of drop
+ if (dropTargetLayout == null) {
+ return false;
+ }
+ if (!transitionStateShouldAllowDrop()) return false;
+
+ mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
+ d.dragView, mDragViewVisualCenter);
+
+ // We want the point to be mapped to the dragTarget.
+ if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+ mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+ } else {
+ mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+ }
+
+ int spanX = 1;
+ int spanY = 1;
+ if (mDragInfo != null) {
+ final CellLayout.CellInfo dragCellInfo = mDragInfo;
+ spanX = dragCellInfo.spanX;
+ spanY = dragCellInfo.spanY;
+ } else {
+ final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
+ spanX = dragInfo.spanX;
+ spanY = dragInfo.spanY;
+ }
+
+ int minSpanX = spanX;
+ int minSpanY = spanY;
+ if (d.dragInfo instanceof PendingAddWidgetInfo) {
+ minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
+ minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
+ }
+
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
+ mTargetCell);
+ float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+ if (willCreateUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
+ mTargetCell, distance, true)) {
+ return true;
+ }
+ if (willAddToExistingUserFolder((ItemInfo) d.dragInfo, dropTargetLayout,
+ mTargetCell, distance)) {
+ return true;
+ }
+
+ int[] resultSpan = new int[2];
+ mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+ null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
+ boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+
+ // Don't accept the drop if there's no room for the item
+ if (!foundCell) {
+ // Don't show the message if we are dropping on the AllApps button and the hotseat
+ // is full
+ boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
+ if (mTargetCell != null && isHotseat) {
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (hotseat.isAllAppsButtonRank(
+ hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
+ return false;
+ }
+ }
+
+ mLauncher.showOutOfSpaceMessage(isHotseat);
+ return false;
+ }
+ }
+
+ long screenId = getIdForScreen(dropTargetLayout);
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+ commitExtraEmptyScreen();
+ }
+
+ return true;
+ }
+
+ boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
+ distance, boolean considerTimeout) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+ View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+
+ if (dropOverView != null) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+ if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+ return false;
+ }
+ }
+
+ boolean hasntMoved = false;
+ if (mDragInfo != null) {
+ hasntMoved = dropOverView == mDragInfo.cell;
+ }
+
+ if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
+ return false;
+ }
+
+ boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
+ boolean willBecomeShortcut =
+ (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
+ info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
+
+ return (aboveShortcut && willBecomeShortcut);
+ }
+
+ boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
+ float distance) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+ View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+
+ if (dropOverView != null) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
+ if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
+ return false;
+ }
+ }
+
+ if (dropOverView instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) dropOverView;
+ if (fi.acceptDrop(dragInfo)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
+ int[] targetCell, float distance, boolean external, DragView dragView,
+ Runnable postAnimationRunnable) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+ View v = target.getChildAt(targetCell[0], targetCell[1]);
+
+ boolean hasntMoved = false;
+ if (mDragInfo != null) {
+ CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
+ hasntMoved = (mDragInfo.cellX == targetCell[0] &&
+ mDragInfo.cellY == targetCell[1]) && (cellParent == target);
+ }
+
+ if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
+ mCreateUserFolderOnDrop = false;
+ final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
+
+ boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
+ boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
+
+ if (aboveShortcut && willBecomeShortcut) {
+ ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
+ ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
+ // if the drag started here, we need to remove it from the workspace
+ if (!external) {
+ getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
+ }
+
+ Rect folderLocation = new Rect();
+ float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
+ target.removeView(v);
+
+ FolderIcon fi =
+ mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
+ destInfo.cellX = -1;
+ destInfo.cellY = -1;
+ sourceInfo.cellX = -1;
+ sourceInfo.cellY = -1;
+
+ // If the dragView is null, we can't animate
+ boolean animate = dragView != null;
+ if (animate) {
+ fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
+ postAnimationRunnable);
+ } else {
+ fi.addItem(destInfo);
+ fi.addItem(sourceInfo);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
+ float distance, DragObject d, boolean external) {
+ if (distance > mMaxDistanceForFolderCreation) return false;
+
+ View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
+ if (!mAddToExistingFolderOnDrop) return false;
+ mAddToExistingFolderOnDrop = false;
+
+ if (dropOverView instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) dropOverView;
+ if (fi.acceptDrop(d.dragInfo)) {
+ fi.onDrop(d);
+
+ // if the drag started here, we need to remove it from the workspace
+ if (!external) {
+ getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void onDrop(final DragObject d) {
+ mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
+ mDragViewVisualCenter);
+
+ CellLayout dropTargetLayout = mDropToLayout;
+
+ // We want the point to be mapped to the dragTarget.
+ if (dropTargetLayout != null) {
+ if (mLauncher.isHotseatLayout(dropTargetLayout)) {
+ mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+ } else {
+ mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
+ }
+ }
+
+ int snapScreen = -1;
+ boolean resizeOnDrop = false;
+ if (d.dragSource != this) {
+ final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1] };
+ onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
+ } else if (mDragInfo != null) {
+ final View cell = mDragInfo.cell;
+
+ Runnable resizeRunnable = null;
+ if (dropTargetLayout != null && !d.cancelled) {
+ // Move internally
+ boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
+ boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
+ long container = hasMovedIntoHotseat ?
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT :
+ LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ long screenId = (mTargetCell[0] < 0) ?
+ mDragInfo.screenId : getIdForScreen(dropTargetLayout);
+ int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
+ int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
+ // First we find the cell nearest to point at which the item is
+ // dropped, without any consideration to whether there is an item there.
+
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
+ mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
+ float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+
+ // If the item being dropped is a shortcut and the nearest drop
+ // cell also contains a shortcut, then create a folder with the two shortcuts.
+ if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
+ dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
+ stripEmptyScreens();
+ return;
+ }
+
+ if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
+ distance, d, false)) {
+ stripEmptyScreens();
+ return;
+ }
+
+ // Aside from the special case where we're dropping a shortcut onto a shortcut,
+ // we need to find the nearest cell location that is vacant
+ ItemInfo item = (ItemInfo) d.dragInfo;
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+
+ int[] resultSpan = new int[2];
+ mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
+ mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
+
+ boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
+
+ // if the widget resizes on drop
+ if (foundCell && (cell instanceof AppWidgetHostView) &&
+ (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
+ resizeOnDrop = true;
+ item.spanX = resultSpan[0];
+ item.spanY = resultSpan[1];
+ AppWidgetHostView awhv = (AppWidgetHostView) cell;
+ AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
+ resultSpan[1]);
+ }
+
+ if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
+ snapScreen = getPageIndexForScreenId(screenId);
+ snapToPage(snapScreen);
+ }
+
+ if (foundCell) {
+ final ItemInfo info = (ItemInfo) cell.getTag();
+ if (hasMovedLayouts) {
+ // Reparent the view
+ getParentCellLayoutForView(cell).removeView(cell);
+ addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
+ info.spanX, info.spanY);
+ }
+
+ // update the item's position after drop
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+ lp.cellX = lp.tmpCellX = mTargetCell[0];
+ lp.cellY = lp.tmpCellY = mTargetCell[1];
+ lp.cellHSpan = item.spanX;
+ lp.cellVSpan = item.spanY;
+ lp.isLockedToGrid = true;
+ cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screenId,
+ mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
+
+ if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
+ cell instanceof LauncherAppWidgetHostView) {
+ final CellLayout cellLayout = dropTargetLayout;
+ // We post this call so that the widget has a chance to be placed
+ // in its final location
+
+ final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
+ AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
+ if (pinfo != null &&
+ pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
+ final Runnable addResizeFrame = new Runnable() {
+ public void run() {
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ dragLayer.addResizeFrame(info, hostView, cellLayout);
+ }
+ };
+ resizeRunnable = (new Runnable() {
+ public void run() {
+ if (!isPageMoving()) {
+ addResizeFrame.run();
+ } else {
+ mDelayedResizeRunnable = addResizeFrame;
+ }
+ }
+ });
+ }
+ }
+
+ LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
+ lp.cellY, item.spanX, item.spanY);
+ } else {
+ // If we can't find a drop location, we return the item to its original position
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+ mTargetCell[0] = lp.cellX;
+ mTargetCell[1] = lp.cellY;
+ CellLayout layout = (CellLayout) cell.getParent().getParent();
+ layout.markCellsAsOccupiedForView(cell);
+ }
+ }
+
+ final CellLayout parent = (CellLayout) cell.getParent().getParent();
+ final Runnable finalResizeRunnable = resizeRunnable;
+ // Prepare it to be animated into its new position
+ // This must be called after the view has been re-parented
+ final Runnable onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mAnimatingViewIntoPlace = false;
+ updateChildrenLayersEnabled(false);
+ if (finalResizeRunnable != null) {
+ finalResizeRunnable.run();
+ }
+ stripEmptyScreens();
+ }
+ };
+ mAnimatingViewIntoPlace = true;
+ if (d.dragView.hasDrawn()) {
+ final ItemInfo info = (ItemInfo) cell.getTag();
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
+ int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
+ ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+ animateWidgetDrop(info, parent, d.dragView,
+ onCompleteRunnable, animationType, cell, false);
+ } else {
+ int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
+ mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
+ onCompleteRunnable, this);
+ }
+ } else {
+ d.deferDragViewCleanupPostAnimation = false;
+ cell.setVisibility(VISIBLE);
+ }
+ parent.onDropChild(cell);
+ }
+ }
+
+ public void setFinalScrollForPageChange(int pageIndex) {
+ CellLayout cl = (CellLayout) getChildAt(pageIndex);
+ if (cl != null) {
+ mSavedScrollX = getScrollX();
+ mSavedTranslationX = cl.getTranslationX();
+ mSavedRotationY = cl.getRotationY();
+ final int newX = getScrollForPage(pageIndex);
+ setScrollX(newX);
+ cl.setTranslationX(0f);
+ cl.setRotationY(0f);
+ }
+ }
+
+ public void resetFinalScrollForPageChange(int pageIndex) {
+ if (pageIndex >= 0) {
+ CellLayout cl = (CellLayout) getChildAt(pageIndex);
+ setScrollX(mSavedScrollX);
+ cl.setTranslationX(mSavedTranslationX);
+ cl.setRotationY(mSavedRotationY);
+ }
+ }
+
+ public void getViewLocationRelativeToSelf(View v, int[] location) {
+ getLocationInWindow(location);
+ int x = location[0];
+ int y = location[1];
+
+ v.getLocationInWindow(location);
+ int vX = location[0];
+ int vY = location[1];
+
+ location[0] = vX - x;
+ location[1] = vY - y;
+ }
+
+ public void onDragEnter(DragObject d) {
+ mDragEnforcer.onDragEnter();
+ mCreateUserFolderOnDrop = false;
+ mAddToExistingFolderOnDrop = false;
+
+ mDropToLayout = null;
+ CellLayout layout = getCurrentDropLayout();
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+
+ // Because we don't have space in the Phone UI (the CellLayouts run to the edge) we
+ // don't need to show the outlines
+ if (LauncherAppState.getInstance().isScreenLarge()) {
+ showOutlines();
+ }
+ }
+
+ /** Return a rect that has the cellWidth/cellHeight (left, top), and
+ * widthGap/heightGap (right, bottom) */
+ static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ Resources res = launcher.getResources();
+ Display display = launcher.getWindowManager().getDefaultDisplay();
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+ int countX = (int) grid.numColumns;
+ int countY = (int) grid.numRows;
+ int constrainedLongEdge = largestSize.y;
+ int constrainedShortEdge = smallestSize.y;
+ if (orientation == CellLayout.LANDSCAPE) {
+ if (mLandscapeCellLayoutMetrics == null) {
+ Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
+ int width = constrainedLongEdge - padding.left - padding.right;
+ int height = constrainedShortEdge - padding.top - padding.bottom;
+ mLandscapeCellLayoutMetrics = new Rect();
+ mLandscapeCellLayoutMetrics.set(
+ grid.calculateCellWidth(width, countX),
+ grid.calculateCellHeight(height, countY), 0, 0);
+ }
+ return mLandscapeCellLayoutMetrics;
+ } else if (orientation == CellLayout.PORTRAIT) {
+ if (mPortraitCellLayoutMetrics == null) {
+ Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
+ int width = constrainedShortEdge - padding.left - padding.right;
+ int height = constrainedLongEdge - padding.top - padding.bottom;
+ mPortraitCellLayoutMetrics = new Rect();
+ mPortraitCellLayoutMetrics.set(
+ grid.calculateCellWidth(width, countX),
+ grid.calculateCellHeight(height, countY), 0, 0);
+ }
+ return mPortraitCellLayoutMetrics;
+ }
+ return null;
+ }
+
+ public void onDragExit(DragObject d) {
+ mDragEnforcer.onDragExit();
+
+ // Here we store the final page that will be dropped to, if the workspace in fact
+ // receives the drop
+ if (mInScrollArea) {
+ if (isPageMoving()) {
+ // If the user drops while the page is scrolling, we should use that page as the
+ // destination instead of the page that is being hovered over.
+ mDropToLayout = (CellLayout) getPageAt(getNextPage());
+ } else {
+ mDropToLayout = mDragOverlappingLayout;
+ }
+ } else {
+ mDropToLayout = mDragTargetLayout;
+ }
+
+ if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
+ mCreateUserFolderOnDrop = true;
+ } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
+ mAddToExistingFolderOnDrop = true;
+ }
+
+ // Reset the scroll area and previous drag target
+ onResetScrollArea();
+ setCurrentDropLayout(null);
+ setCurrentDragOverlappingLayout(null);
+
+ mSpringLoadedDragController.cancel();
+
+ if (!mIsPageMoving) {
+ hideOutlines();
+ }
+ }
+
+ void setCurrentDropLayout(CellLayout layout) {
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.revertTempState();
+ mDragTargetLayout.onDragExit();
+ }
+ mDragTargetLayout = layout;
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.onDragEnter();
+ }
+ cleanupReorder(true);
+ cleanupFolderCreation();
+ setCurrentDropOverCell(-1, -1);
+ }
+
+ void setCurrentDragOverlappingLayout(CellLayout layout) {
+ if (mDragOverlappingLayout != null) {
+ mDragOverlappingLayout.setIsDragOverlapping(false);
+ }
+ mDragOverlappingLayout = layout;
+ if (mDragOverlappingLayout != null) {
+ mDragOverlappingLayout.setIsDragOverlapping(true);
+ }
+ invalidate();
+ }
+
+ void setCurrentDropOverCell(int x, int y) {
+ if (x != mDragOverX || y != mDragOverY) {
+ mDragOverX = x;
+ mDragOverY = y;
+ setDragMode(DRAG_MODE_NONE);
+ }
+ }
+
+ void setDragMode(int dragMode) {
+ if (dragMode != mDragMode) {
+ if (dragMode == DRAG_MODE_NONE) {
+ cleanupAddToFolder();
+ // We don't want to cancel the re-order alarm every time the target cell changes
+ // as this feels to slow / unresponsive.
+ cleanupReorder(false);
+ cleanupFolderCreation();
+ } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
+ cleanupReorder(true);
+ cleanupFolderCreation();
+ } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
+ cleanupAddToFolder();
+ cleanupReorder(true);
+ } else if (dragMode == DRAG_MODE_REORDER) {
+ cleanupAddToFolder();
+ cleanupFolderCreation();
+ }
+ mDragMode = dragMode;
+ }
+ }
+
+ private void cleanupFolderCreation() {
+ if (mDragFolderRingAnimator != null) {
+ mDragFolderRingAnimator.animateToNaturalState();
+ }
+ mFolderCreationAlarm.cancelAlarm();
+ }
+
+ private void cleanupAddToFolder() {
+ if (mDragOverFolderIcon != null) {
+ mDragOverFolderIcon.onDragExit(null);
+ mDragOverFolderIcon = null;
+ }
+ }
+
+ private void cleanupReorder(boolean cancelAlarm) {
+ // Any pending reorders are canceled
+ if (cancelAlarm) {
+ mReorderAlarm.cancelAlarm();
+ }
+ mLastReorderX = -1;
+ mLastReorderY = -1;
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+ * coordinate space. The argument xy is modified with the return result.
+ *
+ * if cachedInverseMatrix is not null, this method will just use that matrix instead of
+ * computing it itself; we use this to avoid redundant matrix inversions in
+ * findMatchingPageForDragOver
+ *
+ */
+ void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
+ xy[0] = xy[0] - v.getLeft();
+ xy[1] = xy[1] - v.getTop();
+ }
+
+ boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
+ if (r == null) {
+ r = new Rect();
+ }
+ mTempPt[0] = x;
+ mTempPt[1] = y;
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ r = grid.getHotseatRect();
+ if (r.contains(mTempPt[0], mTempPt[1])) {
+ return true;
+ }
+ return false;
+ }
+
+ void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
+ mTempPt[0] = (int) xy[0];
+ mTempPt[1] = (int) xy[1];
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
+ mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
+
+ xy[0] = mTempPt[0];
+ xy[1] = mTempPt[1];
+ }
+
+ /*
+ *
+ * Convert the 2D coordinate xy from this CellLayout's coordinate space to
+ * the parent View's coordinate space. The argument xy is modified with the return result.
+ *
+ */
+ void mapPointFromChildToSelf(View v, float[] xy) {
+ xy[0] += v.getLeft();
+ xy[1] += v.getTop();
+ }
+
+ static private float squaredDistance(float[] point1, float[] point2) {
+ float distanceX = point1[0] - point2[0];
+ float distanceY = point2[1] - point2[1];
+ return distanceX * distanceX + distanceY * distanceY;
+ }
+
+ /*
+ *
+ * This method returns the CellLayout that is currently being dragged to. In order to drag
+ * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
+ * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
+ *
+ * Return null if no CellLayout is currently being dragged over
+ *
+ */
+ private CellLayout findMatchingPageForDragOver(
+ DragView dragView, float originX, float originY, boolean exact) {
+ // We loop through all the screens (ie CellLayouts) and see which ones overlap
+ // with the item being dragged and then choose the one that's closest to the touch point
+ final int screenCount = getChildCount();
+ CellLayout bestMatchingScreen = null;
+ float smallestDistSoFar = Float.MAX_VALUE;
+
+ for (int i = 0; i < screenCount; i++) {
+ // The custom content screen is not a valid drag over option
+ if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
+ continue;
+ }
+
+ CellLayout cl = (CellLayout) getChildAt(i);
+
+ final float[] touchXy = {originX, originY};
+ // Transform the touch coordinates to the CellLayout's local coordinates
+ // If the touch point is within the bounds of the cell layout, we can return immediately
+ cl.getMatrix().invert(mTempInverseMatrix);
+ mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
+
+ if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
+ touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
+ return cl;
+ }
+
+ if (!exact) {
+ // Get the center of the cell layout in screen coordinates
+ final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
+ cellLayoutCenter[0] = cl.getWidth()/2;
+ cellLayoutCenter[1] = cl.getHeight()/2;
+ mapPointFromChildToSelf(cl, cellLayoutCenter);
+
+ touchXy[0] = originX;
+ touchXy[1] = originY;
+
+ // Calculate the distance between the center of the CellLayout
+ // and the touch point
+ float dist = squaredDistance(touchXy, cellLayoutCenter);
+
+ if (dist < smallestDistSoFar) {
+ smallestDistSoFar = dist;
+ bestMatchingScreen = cl;
+ }
+ }
+ }
+ return bestMatchingScreen;
+ }
+
+ // This is used to compute the visual center of the dragView. This point is then
+ // used to visualize drop locations and determine where to drop an item. The idea is that
+ // the visual center represents the user's interpretation of where the item is, and hence
+ // is the appropriate point to use when determining drop location.
+ private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
+ DragView dragView, float[] recycle) {
+ float res[];
+ if (recycle == null) {
+ res = new float[2];
+ } else {
+ res = recycle;
+ }
+
+ // First off, the drag view has been shifted in a way that is not represented in the
+ // x and y values or the x/yOffsets. Here we account for that shift.
+ x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
+ y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
+
+ // These represent the visual top and left of drag view if a dragRect was provided.
+ // If a dragRect was not provided, then they correspond to the actual view left and
+ // top, as the dragRect is in that case taken to be the entire dragView.
+ // R.dimen.dragViewOffsetY.
+ int left = x - xOffset;
+ int top = y - yOffset;
+
+ // In order to find the visual center, we shift by half the dragRect
+ res[0] = left + dragView.getDragRegion().width() / 2;
+ res[1] = top + dragView.getDragRegion().height() / 2;
+
+ return res;
+ }
+
+ private boolean isDragWidget(DragObject d) {
+ return (d.dragInfo instanceof LauncherAppWidgetInfo ||
+ d.dragInfo instanceof PendingAddWidgetInfo);
+ }
+ private boolean isExternalDragWidget(DragObject d) {
+ return d.dragSource != this && isDragWidget(d);
+ }
+
+ public void onDragOver(DragObject d) {
+ // Skip drag over events while we are dragging over side pages
+ if (mInScrollArea || mIsSwitchingState || mState == State.SMALL) return;
+
+ Rect r = new Rect();
+ CellLayout layout = null;
+ ItemInfo item = (ItemInfo) d.dragInfo;
+
+ // Ensure that we have proper spans for the item that we are dropping
+ if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
+ mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
+ d.dragView, mDragViewVisualCenter);
+
+ final View child = (mDragInfo == null) ? null : mDragInfo.cell;
+ // Identify whether we have dragged over a side page
+ if (isSmall()) {
+ if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
+ if (isPointInSelfOverHotseat(d.x, d.y, r)) {
+ layout = mLauncher.getHotseat().getLayout();
+ }
+ }
+ if (layout == null) {
+ layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
+ }
+ if (layout != mDragTargetLayout) {
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+
+ boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
+ if (isInSpringLoadedMode) {
+ if (mLauncher.isHotseatLayout(layout)) {
+ mSpringLoadedDragController.cancel();
+ } else {
+ mSpringLoadedDragController.setAlarm(mDragTargetLayout);
+ }
+ }
+ }
+ } else {
+ // Test to see if we are over the hotseat otherwise just use the current page
+ if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
+ if (isPointInSelfOverHotseat(d.x, d.y, r)) {
+ layout = mLauncher.getHotseat().getLayout();
+ }
+ }
+ if (layout == null) {
+ layout = getCurrentDropLayout();
+ }
+ if (layout != mDragTargetLayout) {
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+ }
+ }
+
+ // Handle the drag over
+ if (mDragTargetLayout != null) {
+ // We want the point to be mapped to the dragTarget.
+ if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
+ mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
+ } else {
+ mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
+ }
+
+ ItemInfo info = (ItemInfo) d.dragInfo;
+
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
+ mDragTargetLayout, mTargetCell);
+ int reorderX = mTargetCell[0];
+ int reorderY = mTargetCell[1];
+
+ setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
+
+ float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
+ mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
+
+ final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
+ mTargetCell[1]);
+
+ manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
+ targetCellDistance, dragOverView);
+
+ boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
+ mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
+ item.spanY, child, mTargetCell);
+
+ if (!nearestDropOccupied) {
+ mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+ (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
+ mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
+ d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
+ } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
+ && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
+ mLastReorderY != reorderY)) {
+
+ // Otherwise, if we aren't adding to or creating a folder and there's no pending
+ // reorder, then we schedule a reorder
+ ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
+ minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
+ mReorderAlarm.setOnAlarmListener(listener);
+ mReorderAlarm.setAlarm(REORDER_TIMEOUT);
+ }
+
+ if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
+ !nearestDropOccupied) {
+ if (mDragTargetLayout != null) {
+ mDragTargetLayout.revertTempState();
+ }
+ }
+ }
+ }
+
+ private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
+ int[] targetCell, float distance, View dragOverView) {
+ boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
+ false);
+
+ if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
+ !mFolderCreationAlarm.alarmPending()) {
+ mFolderCreationAlarm.setOnAlarmListener(new
+ FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
+ mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+ return;
+ }
+
+ boolean willAddToFolder =
+ willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
+
+ if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
+ mDragOverFolderIcon = ((FolderIcon) dragOverView);
+ mDragOverFolderIcon.onDragEnter(info);
+ if (targetLayout != null) {
+ targetLayout.clearDragOutlines();
+ }
+ setDragMode(DRAG_MODE_ADD_TO_FOLDER);
+ return;
+ }
+
+ if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
+ setDragMode(DRAG_MODE_NONE);
+ }
+ if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
+ setDragMode(DRAG_MODE_NONE);
+ }
+
+ return;
+ }
+
+ class FolderCreationAlarmListener implements OnAlarmListener {
+ CellLayout layout;
+ int cellX;
+ int cellY;
+
+ public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
+ this.layout = layout;
+ this.cellX = cellX;
+ this.cellY = cellY;
+ }
+
+ public void onAlarm(Alarm alarm) {
+ if (mDragFolderRingAnimator == null) {
+ mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
+ }
+ mDragFolderRingAnimator.setCell(cellX, cellY);
+ mDragFolderRingAnimator.setCellLayout(layout);
+ mDragFolderRingAnimator.animateToAcceptState();
+ layout.showFolderAccept(mDragFolderRingAnimator);
+ layout.clearDragOutlines();
+ setDragMode(DRAG_MODE_CREATE_FOLDER);
+ }
+ }
+
+ class ReorderAlarmListener implements OnAlarmListener {
+ float[] dragViewCenter;
+ int minSpanX, minSpanY, spanX, spanY;
+ DragView dragView;
+ View child;
+
+ public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
+ int spanY, DragView dragView, View child) {
+ this.dragViewCenter = dragViewCenter;
+ this.minSpanX = minSpanX;
+ this.minSpanY = minSpanY;
+ this.spanX = spanX;
+ this.spanY = spanY;
+ this.child = child;
+ this.dragView = dragView;
+ }
+
+ public void onAlarm(Alarm alarm) {
+ int[] resultSpan = new int[2];
+ mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
+ mTargetCell);
+ mLastReorderX = mTargetCell[0];
+ mLastReorderY = mTargetCell[1];
+
+ mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
+ child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
+
+ if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
+ mDragTargetLayout.revertTempState();
+ } else {
+ setDragMode(DRAG_MODE_REORDER);
+ }
+
+ boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
+ mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
+ (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
+ mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
+ dragView.getDragVisualizeOffset(), dragView.getDragRegion());
+ }
+ }
+
+ @Override
+ public void getHitRectRelativeToDragLayer(Rect outRect) {
+ // We want the workspace to have the whole area of the display (it will find the correct
+ // cell layout to drop to in the existing drag/drop logic.
+ mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
+ }
+
+ /**
+ * Add the item specified by dragInfo to the given layout.
+ * @return true if successful
+ */
+ public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
+ if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
+ onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
+ return true;
+ }
+ mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
+ return false;
+ }
+
+ private void onDropExternal(int[] touchXY, Object dragInfo,
+ CellLayout cellLayout, boolean insertAtFirst) {
+ onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
+ }
+
+ /**
+ * Drop an item that didn't originate on one of the workspace screens.
+ * It may have come from Launcher (e.g. from all apps or customize), or it may have
+ * come from another app altogether.
+ *
+ * NOTE: This can also be called when we are outside of a drag event, when we want
+ * to add an item to one of the workspace screens.
+ */
+ private void onDropExternal(final int[] touchXY, final Object dragInfo,
+ final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
+ final Runnable exitSpringLoadedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLauncher.exitSpringLoadedDragModeDelayed(true, false, null);
+ }
+ };
+
+ ItemInfo info = (ItemInfo) dragInfo;
+ int spanX = info.spanX;
+ int spanY = info.spanY;
+ if (mDragInfo != null) {
+ spanX = mDragInfo.spanX;
+ spanY = mDragInfo.spanY;
+ }
+
+ final long container = mLauncher.isHotseatLayout(cellLayout) ?
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT :
+ LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ final long screenId = getIdForScreen(cellLayout);
+ if (!mLauncher.isHotseatLayout(cellLayout)
+ && screenId != getScreenIdForPageIndex(mCurrentPage)
+ && mState != State.SPRING_LOADED) {
+ snapToScreenId(screenId, null);
+ }
+
+ if (info instanceof PendingAddItemInfo) {
+ final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
+
+ boolean findNearestVacantCell = true;
+ if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+ mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
+ cellLayout, mTargetCell);
+ float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+ if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
+ distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
+ cellLayout, mTargetCell, distance)) {
+ findNearestVacantCell = false;
+ }
+ }
+
+ final ItemInfo item = (ItemInfo) d.dragInfo;
+ boolean updateWidgetSize = false;
+ if (findNearestVacantCell) {
+ int minSpanX = item.spanX;
+ int minSpanY = item.spanY;
+ if (item.minSpanX > 0 && item.minSpanY > 0) {
+ minSpanX = item.minSpanX;
+ minSpanY = item.minSpanY;
+ }
+ int[] resultSpan = new int[2];
+ mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
+ null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
+
+ if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
+ updateWidgetSize = true;
+ }
+ item.spanX = resultSpan[0];
+ item.spanY = resultSpan[1];
+ }
+
+ Runnable onAnimationCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // When dragging and dropping from customization tray, we deal with creating
+ // widgets/shortcuts/folders in a slightly different way
+ switch (pendingInfo.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+ int span[] = new int[2];
+ span[0] = item.spanX;
+ span[1] = item.spanY;
+ mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
+ container, screenId, mTargetCell, span, null);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ mLauncher.processShortcutFromDrop(pendingInfo.componentName,
+ container, screenId, mTargetCell, null);
+ break;
+ default:
+ throw new IllegalStateException("Unknown item type: " +
+ pendingInfo.itemType);
+ }
+ }
+ };
+ View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+ ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
+
+ if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
+ AppWidgetHostView awhv = (AppWidgetHostView) finalView;
+ AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
+ item.spanY);
+ }
+
+ int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
+ if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
+ ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
+ animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
+ }
+ animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
+ animationStyle, finalView, true);
+ } else {
+ // This is for other drag/drop cases, like dragging from All Apps
+ View view = null;
+
+ switch (info.itemType) {
+ case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+ case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+ if (info.container == NO_ID && info instanceof AppInfo) {
+ // Came from all apps -- make a copy
+ info = new ShortcutInfo((AppInfo) info);
+ }
+ view = mLauncher.createShortcut(R.layout.application, cellLayout,
+ (ShortcutInfo) info);
+ break;
+ case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
+ (FolderInfo) info, mIconCache);
+ break;
+ default:
+ throw new IllegalStateException("Unknown item type: " + info.itemType);
+ }
+
+ // First we find the cell nearest to point at which the item is
+ // dropped, without any consideration to whether there is an item there.
+ if (touchXY != null) {
+ mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
+ cellLayout, mTargetCell);
+ float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
+ mDragViewVisualCenter[1], mTargetCell);
+ d.postAnimationRunnable = exitSpringLoadedRunnable;
+ if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
+ true, d.dragView, d.postAnimationRunnable)) {
+ return;
+ }
+ if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
+ true)) {
+ return;
+ }
+ }
+
+ if (touchXY != null) {
+ // when dragging and dropping, just find the closest free spot
+ mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0],
+ (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
+ null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
+ } else {
+ cellLayout.findCellForSpan(mTargetCell, 1, 1);
+ }
+ addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
+ info.spanY, insertAtFirst);
+ cellLayout.onDropChild(view);
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+ cellLayout.getShortcutsAndWidgets().measureChild(view);
+
+ LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
+ lp.cellX, lp.cellY);
+
+ if (d.dragView != null) {
+ // We wrap the animation call in the temporary set and reset of the current
+ // cellLayout to its final transform -- this means we animate the drag view to
+ // the correct final location.
+ setFinalTransitionTransform(cellLayout);
+ mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
+ exitSpringLoadedRunnable);
+ resetTransitionTransform(cellLayout);
+ }
+ }
+ }
+
+ public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+ int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
+ widgetInfo.spanY, widgetInfo, false);
+ int visibility = layout.getVisibility();
+ layout.setVisibility(VISIBLE);
+
+ int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
+ int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
+ Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+
+ layout.measure(width, height);
+ layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
+ layout.draw(c);
+ c.setBitmap(null);
+ layout.setVisibility(visibility);
+ return b;
+ }
+
+ private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
+ DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
+ boolean external, boolean scale) {
+ // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
+ // location and size on the home screen.
+ int spanX = info.spanX;
+ int spanY = info.spanY;
+
+ Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
+ loc[0] = r.left;
+ loc[1] = r.top;
+
+ setFinalTransitionTransform(layout);
+ float cellLayoutScale =
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
+ resetTransitionTransform(layout);
+
+ float dragViewScaleX;
+ float dragViewScaleY;
+ if (scale) {
+ dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
+ dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
+ } else {
+ dragViewScaleX = 1f;
+ dragViewScaleY = 1f;
+ }
+
+ // The animation will scale the dragView about its center, so we need to center about
+ // the final location.
+ loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
+ loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
+
+ scaleXY[0] = dragViewScaleX * cellLayoutScale;
+ scaleXY[1] = dragViewScaleY * cellLayoutScale;
+ }
+
+ public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
+ final Runnable onCompleteRunnable, int animationType, final View finalView,
+ boolean external) {
+ Rect from = new Rect();
+ mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
+
+ int[] finalPos = new int[2];
+ float scaleXY[] = new float[2];
+ boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
+ getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
+ external, scalePreview);
+
+ Resources res = mLauncher.getResources();
+ int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
+
+ // In the case where we've prebound the widget, we remove it from the DragLayer
+ if (finalView instanceof AppWidgetHostView && external) {
+ Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
+ mLauncher.getDragLayer().removeView(finalView);
+ }
+ if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
+ Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
+ dragView.setCrossFadeBitmap(crossFadeBitmap);
+ dragView.crossFade((int) (duration * 0.8f));
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
+ scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
+ }
+
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
+ mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
+ DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+ } else {
+ int endStyle;
+ if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
+ endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
+ } else {
+ endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
+ }
+
+ Runnable onComplete = new Runnable() {
+ @Override
+ public void run() {
+ if (finalView != null) {
+ finalView.setVisibility(VISIBLE);
+ }
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ }
+ };
+ dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
+ finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
+ duration, this);
+ }
+ }
+
+ public void setFinalTransitionTransform(CellLayout layout) {
+ if (isSwitchingState()) {
+ mCurrentScale = getScaleX();
+ setScaleX(mNewScale);
+ setScaleY(mNewScale);
+ }
+ }
+ public void resetTransitionTransform(CellLayout layout) {
+ if (isSwitchingState()) {
+ setScaleX(mCurrentScale);
+ setScaleY(mCurrentScale);
+ }
+ }
+
+ /**
+ * Return the current {@link CellLayout}, correctly picking the destination
+ * screen while a scroll is in progress.
+ */
+ public CellLayout getCurrentDropLayout() {
+ return (CellLayout) getChildAt(getNextPage());
+ }
+
+ /**
+ * Return the current CellInfo describing our current drag; this method exists
+ * so that Launcher can sync this object with the correct info when the activity is created/
+ * destroyed
+ *
+ */
+ public CellLayout.CellInfo getDragInfo() {
+ return mDragInfo;
+ }
+
+ public int getRestorePage() {
+ return getNextPage() - numCustomPages();
+ }
+
+ /**
+ * Calculate the nearest cell where the given object would be dropped.
+ *
+ * pixelX and pixelY should be in the coordinate system of layout
+ */
+ private int[] findNearestArea(int pixelX, int pixelY,
+ int spanX, int spanY, CellLayout layout, int[] recycle) {
+ return layout.findNearestArea(
+ pixelX, pixelY, spanX, spanY, recycle);
+ }
+
+ void setup(DragController dragController) {
+ mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
+ mDragController = dragController;
+
+ // hardware layers on children are enabled on startup, but should be disabled until
+ // needed
+ updateChildrenLayersEnabled(false);
+ setWallpaperDimension();
+ }
+
+ /**
+ * Called at the end of a drag which originated on the workspace.
+ */
+ public void onDropCompleted(final View target, final DragObject d,
+ final boolean isFlingToDelete, final boolean success) {
+ if (mDeferDropAfterUninstall) {
+ mDeferredAction = new Runnable() {
+ public void run() {
+ onDropCompleted(target, d, isFlingToDelete, success);
+ mDeferredAction = null;
+ }
+ };
+ return;
+ }
+
+ boolean beingCalledAfterUninstall = mDeferredAction != null;
+
+ if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
+ if (target != this && mDragInfo != null) {
+ CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
+ if (parentCell != null) {
+ parentCell.removeView(mDragInfo.cell);
+ }
+ if (mDragInfo.cell instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
+ }
+ // If we move the item to anything not on the Workspace, check if any empty
+ // screens need to be removed. If we dropped back on the workspace, this will
+ // be done post drop animation.
+ stripEmptyScreens();
+ }
+ } else if (mDragInfo != null) {
+ CellLayout cellLayout;
+ if (mLauncher.isHotseatLayout(target)) {
+ cellLayout = mLauncher.getHotseat().getLayout();
+ } else {
+ cellLayout = getScreenWithId(mDragInfo.screenId);
+ }
+ cellLayout.onDropChild(mDragInfo.cell);
+ }
+ if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
+ && mDragInfo.cell != null) {
+ mDragInfo.cell.setVisibility(VISIBLE);
+ }
+ mDragOutline = null;
+ mDragInfo = null;
+ }
+
+ public void deferCompleteDropAfterUninstallActivity() {
+ mDeferDropAfterUninstall = true;
+ }
+
+ /// maybe move this into a smaller part
+ public void onUninstallActivityReturned(boolean success) {
+ mDeferDropAfterUninstall = false;
+ mUninstallSuccessful = success;
+ if (mDeferredAction != null) {
+ mDeferredAction.run();
+ }
+ }
+
+ void updateItemLocationsInDatabase(CellLayout cl) {
+ int count = cl.getShortcutsAndWidgets().getChildCount();
+
+ long screenId = getIdForScreen(cl);
+ int container = Favorites.CONTAINER_DESKTOP;
+
+ if (mLauncher.isHotseatLayout(cl)) {
+ screenId = -1;
+ container = Favorites.CONTAINER_HOTSEAT;
+ }
+
+ for (int i = 0; i < count; i++) {
+ View v = cl.getShortcutsAndWidgets().getChildAt(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ // Null check required as the AllApps button doesn't have an item info
+ if (info != null && info.requiresDbUpdate) {
+ info.requiresDbUpdate = false;
+ LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
+ info.cellY, info.spanX, info.spanY);
+ }
+ }
+ }
+
+ ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplicates) {
+ ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
+ getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, false);
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ getUniqueIntents(cl, uniqueIntents, duplicates, false);
+ }
+ return uniqueIntents;
+ }
+
+ void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
+ ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
+ int count = cl.getShortcutsAndWidgets().getChildCount();
+
+ ArrayList<View> children = new ArrayList<View>();
+ for (int i = 0; i < count; i++) {
+ View v = cl.getShortcutsAndWidgets().getChildAt(i);
+ children.add(v);
+ }
+
+ for (int i = 0; i < count; i++) {
+ View v = children.get(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ // Null check required as the AllApps button doesn't have an item info
+ if (info instanceof ShortcutInfo) {
+ ShortcutInfo si = (ShortcutInfo) info;
+ ComponentName cn = si.intent.getComponent();
+
+ Uri dataUri = si.intent.getData();
+ // If dataUri is not null / empty or if this component isn't one that would
+ // have previously showed up in the AllApps list, then this is a widget-type
+ // shortcut, so ignore it.
+ if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
+ continue;
+ }
+
+ if (!uniqueIntents.contains(cn)) {
+ uniqueIntents.add(cn);
+ } else {
+ if (stripDuplicates) {
+ cl.removeViewInLayout(v);
+ LauncherModel.deleteItemFromDatabase(mLauncher, si);
+ }
+ if (duplicates != null) {
+ duplicates.add(cn);
+ }
+ }
+ }
+ if (v instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) v;
+ ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
+ for (int j = 0; j < items.size(); j++) {
+ if (items.get(j).getTag() instanceof ShortcutInfo) {
+ ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
+ ComponentName cn = si.intent.getComponent();
+
+ Uri dataUri = si.intent.getData();
+ // If dataUri is not null / empty or if this component isn't one that would
+ // have previously showed up in the AllApps list, then this is a widget-type
+ // shortcut, so ignore it.
+ if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
+ continue;
+ }
+
+ if (!uniqueIntents.contains(cn)) {
+ uniqueIntents.add(cn);
+ } else {
+ if (stripDuplicates) {
+ fi.getFolderInfo().remove(si);
+ LauncherModel.deleteItemFromDatabase(mLauncher, si);
+ }
+ if (duplicates != null) {
+ duplicates.add(cn);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void saveWorkspaceToDb() {
+ saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ CellLayout cl = (CellLayout) getChildAt(i);
+ saveWorkspaceScreenToDb(cl);
+ }
+ }
+
+ void saveWorkspaceScreenToDb(CellLayout cl) {
+ int count = cl.getShortcutsAndWidgets().getChildCount();
+
+ long screenId = getIdForScreen(cl);
+ int container = Favorites.CONTAINER_DESKTOP;
+
+ Hotseat hotseat = mLauncher.getHotseat();
+ if (mLauncher.isHotseatLayout(cl)) {
+ screenId = -1;
+ container = Favorites.CONTAINER_HOTSEAT;
+ }
+
+ for (int i = 0; i < count; i++) {
+ View v = cl.getShortcutsAndWidgets().getChildAt(i);
+ ItemInfo info = (ItemInfo) v.getTag();
+ // Null check required as the AllApps button doesn't have an item info
+ if (info != null) {
+ int cellX = info.cellX;
+ int cellY = info.cellY;
+ if (container == Favorites.CONTAINER_HOTSEAT) {
+ cellX = hotseat.getCellXFromOrder((int) info.screenId);
+ cellY = hotseat.getCellYFromOrder((int) info.screenId);
+ }
+ LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
+ cellY, false);
+ }
+ if (v instanceof FolderIcon) {
+ FolderIcon fi = (FolderIcon) v;
+ fi.getFolder().addItemLocationsInDatabase();
+ }
+ }
+ }
+
+ @Override
+ public boolean supportsFlingToDelete() {
+ return true;
+ }
+
+ @Override
+ public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+ // Do nothing
+ }
+
+ @Override
+ public void onFlingToDeleteCompleted() {
+ // Do nothing
+ }
+
+ public boolean isDropEnabled() {
+ return true;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ super.onRestoreInstanceState(state);
+ Launcher.setScreen(mCurrentPage);
+ }
+
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ // We don't dispatch restoreInstanceState to our children using this code path.
+ // Some pages will be restored immediately as their items are bound immediately, and
+ // others we will need to wait until after their items are bound.
+ mSavedStates = container;
+ }
+
+ public void restoreInstanceStateForChild(int child) {
+ if (mSavedStates != null) {
+ mRestoredPages.add(child);
+ CellLayout cl = (CellLayout) getChildAt(child);
+ cl.restoreInstanceState(mSavedStates);
+ }
+ }
+
+ public void restoreInstanceStateForRemainingPages() {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ if (!mRestoredPages.contains(i)) {
+ restoreInstanceStateForChild(i);
+ }
+ }
+ mRestoredPages.clear();
+ }
+
+ @Override
+ public void scrollLeft() {
+ if (!isSmall() && !mIsSwitchingState) {
+ super.scrollLeft();
+ }
+ Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ openFolder.completeDragExit();
+ }
+ }
+
+ @Override
+ public void scrollRight() {
+ if (!isSmall() && !mIsSwitchingState) {
+ super.scrollRight();
+ }
+ Folder openFolder = getOpenFolder();
+ if (openFolder != null) {
+ openFolder.completeDragExit();
+ }
+ }
+
+ @Override
+ public boolean onEnterScrollArea(int x, int y, int direction) {
+ // Ignore the scroll area if we are dragging over the hot seat
+ boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
+ if (mLauncher.getHotseat() != null && isPortrait) {
+ Rect r = new Rect();
+ mLauncher.getHotseat().getHitRect(r);
+ if (r.contains(x, y)) {
+ return false;
+ }
+ }
+
+ boolean result = false;
+ if (!isSmall() && !mIsSwitchingState && getOpenFolder() == null) {
+ mInScrollArea = true;
+
+ final int page = getNextPage() +
+ (direction == DragController.SCROLL_LEFT ? -1 : 1);
+ // We always want to exit the current layout to ensure parity of enter / exit
+ setCurrentDropLayout(null);
+
+ if (0 <= page && page < getChildCount()) {
+ // Ensure that we are not dragging over to the custom content screen
+ if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
+ return false;
+ }
+
+ CellLayout layout = (CellLayout) getChildAt(page);
+ setCurrentDragOverlappingLayout(layout);
+
+ // Workspace is responsible for drawing the edge glow on adjacent pages,
+ // so we need to redraw the workspace when this may have changed.
+ invalidate();
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onExitScrollArea() {
+ boolean result = false;
+ if (mInScrollArea) {
+ invalidate();
+ CellLayout layout = getCurrentDropLayout();
+ setCurrentDropLayout(layout);
+ setCurrentDragOverlappingLayout(layout);
+
+ result = true;
+ mInScrollArea = false;
+ }
+ return result;
+ }
+
+ private void onResetScrollArea() {
+ setCurrentDragOverlappingLayout(null);
+ mInScrollArea = false;
+ }
+
+ /**
+ * Returns a specific CellLayout
+ */
+ CellLayout getParentCellLayoutForView(View v) {
+ ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
+ for (CellLayout layout : layouts) {
+ if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
+ return layout;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of all the CellLayouts in the workspace.
+ */
+ ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
+ ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
+ int screenCount = getChildCount();
+ for (int screen = 0; screen < screenCount; screen++) {
+ layouts.add(((CellLayout) getChildAt(screen)));
+ }
+ if (mLauncher.getHotseat() != null) {
+ layouts.add(mLauncher.getHotseat().getLayout());
+ }
+ return layouts;
+ }
+
+ /**
+ * We should only use this to search for specific children. Do not use this method to modify
+ * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
+ * the hotseat and workspace pages
+ */
+ ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ new ArrayList<ShortcutAndWidgetContainer>();
+ int screenCount = getChildCount();
+ for (int screen = 0; screen < screenCount; screen++) {
+ childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
+ }
+ if (mLauncher.getHotseat() != null) {
+ childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
+ }
+ return childrenLayouts;
+ }
+
+ public Folder getFolderForTag(Object tag) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int count = layout.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = layout.getChildAt(i);
+ if (child instanceof Folder) {
+ Folder f = (Folder) child;
+ if (f.getInfo() == tag && f.getInfo().opened) {
+ return f;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public View getViewForTag(Object tag) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int count = layout.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = layout.getChildAt(i);
+ if (child.getTag() == tag) {
+ return child;
+ }
+ }
+ }
+ return null;
+ }
+
+ void clearDropTargets() {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
+ getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int childCount = layout.getChildCount();
+ for (int j = 0; j < childCount; j++) {
+ View v = layout.getChildAt(j);
+ if (v instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) v);
+ }
+ }
+ }
+ }
+
+ // Removes ALL items that match a given package name, this is usually called when a package
+ // has been removed and we want to remove all components (widgets, shortcuts, apps) that
+ // belong to that package.
+ void removeItemsByPackageName(final ArrayList<String> packages) {
+ final HashSet<String> packageNames = new HashSet<String>();
+ packageNames.addAll(packages);
+
+ // Filter out all the ItemInfos that this is going to affect
+ final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
+ final HashSet<ComponentName> cns = new HashSet<ComponentName>();
+ ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
+ for (CellLayout layoutParent : cellLayouts) {
+ ViewGroup layout = layoutParent.getShortcutsAndWidgets();
+ int childCount = layout.getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View view = layout.getChildAt(i);
+ infos.add((ItemInfo) view.getTag());
+ }
+ }
+ LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
+ @Override
+ public boolean filterItem(ItemInfo parent, ItemInfo info,
+ ComponentName cn) {
+ if (packageNames.contains(cn.getPackageName())) {
+ cns.add(cn);
+ return true;
+ }
+ return false;
+ }
+ };
+ LauncherModel.filterItemInfos(infos, filter);
+
+ // Remove the affected components
+ removeItemsByComponentName(cns);
+ }
+
+ // Removes items that match the application info specified, when applications are removed
+ // as a part of an update, this is called to ensure that other widgets and application
+ // shortcuts are not removed.
+ void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
+ // Just create a hash table of all the specific components that this will affect
+ HashSet<ComponentName> cns = new HashSet<ComponentName>();
+ for (AppInfo info : appInfos) {
+ cns.add(info.componentName);
+ }
+
+ // Remove all the things
+ removeItemsByComponentName(cns);
+ }
+
+ void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
+ ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
+ for (final CellLayout layoutParent: cellLayouts) {
+ final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
+
+ final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
+ for (int j = 0; j < layout.getChildCount(); j++) {
+ final View view = layout.getChildAt(j);
+ children.put((ItemInfo) view.getTag(), view);
+ }
+
+ final ArrayList<View> childrenToRemove = new ArrayList<View>();
+ final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
+ new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
+ LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
+ @Override
+ public boolean filterItem(ItemInfo parent, ItemInfo info,
+ ComponentName cn) {
+ if (parent instanceof FolderInfo) {
+ if (componentNames.contains(cn)) {
+ FolderInfo folder = (FolderInfo) parent;
+ ArrayList<ShortcutInfo> appsToRemove;
+ if (folderAppsToRemove.containsKey(folder)) {
+ appsToRemove = folderAppsToRemove.get(folder);
+ } else {
+ appsToRemove = new ArrayList<ShortcutInfo>();
+ folderAppsToRemove.put(folder, appsToRemove);
+ }
+ appsToRemove.add((ShortcutInfo) info);
+ return true;
+ }
+ } else {
+ if (componentNames.contains(cn)) {
+ childrenToRemove.add(children.get(info));
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ LauncherModel.filterItemInfos(children.keySet(), filter);
+
+ // Remove all the apps from their folders
+ for (FolderInfo folder : folderAppsToRemove.keySet()) {
+ ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
+ for (ShortcutInfo info : appsToRemove) {
+ folder.remove(info);
+ }
+ }
+
+ // Remove all the other children
+ for (View child : childrenToRemove) {
+ // Note: We can not remove the view directly from CellLayoutChildren as this
+ // does not re-mark the spaces as unoccupied.
+ layoutParent.removeViewInLayout(child);
+ if (child instanceof DropTarget) {
+ mDragController.removeDropTarget((DropTarget) child);
+ }
+ }
+
+ if (childrenToRemove.size() > 0) {
+ layout.requestLayout();
+ layout.invalidate();
+ }
+ }
+
+ // Strip all the empty screens
+ stripEmptyScreens();
+ }
+
+ void updateShortcuts(ArrayList<AppInfo> apps) {
+ ArrayList<ShortcutAndWidgetContainer> childrenLayouts = getAllShortcutAndWidgetContainers();
+ for (ShortcutAndWidgetContainer layout: childrenLayouts) {
+ int childCount = layout.getChildCount();
+ for (int j = 0; j < childCount; j++) {
+ final View view = layout.getChildAt(j);
+ Object tag = view.getTag();
+
+ if (LauncherModel.isShortcutInfoUpdateable((ItemInfo) tag)) {
+ ShortcutInfo info = (ShortcutInfo) tag;
+
+ final Intent intent = info.intent;
+ final ComponentName name = intent.getComponent();
+ final int appCount = apps.size();
+ for (int k = 0; k < appCount; k++) {
+ AppInfo app = apps.get(k);
+ if (app.componentName.equals(name)) {
+ BubbleTextView shortcut = (BubbleTextView) view;
+ info.updateIcon(mIconCache);
+ info.title = app.title.toString();
+ shortcut.applyFromShortcutInfo(info, mIconCache);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void moveToScreen(int page, boolean animate) {
+ if (!isSmall()) {
+ if (animate) {
+ snapToPage(page);
+ } else {
+ setCurrentPage(page);
+ }
+ }
+ View child = getChildAt(page);
+ if (child != null) {
+ child.requestFocus();
+ }
+ }
+
+ void moveToDefaultScreen(boolean animate) {
+ moveToScreen(mDefaultPage, animate);
+ }
+
+ void moveToCustomContentScreen(boolean animate) {
+ if (hasCustomContent()) {
+ int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
+ if (animate) {
+ snapToPage(ccIndex);
+ } else {
+ setCurrentPage(ccIndex);
+ }
+ View child = getChildAt(ccIndex);
+ if (child != null) {
+ child.requestFocus();
+ }
+ }
+ }
+
+ @Override
+ protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
+ long screenId = getScreenIdForPageIndex(pageIndex);
+ if (screenId == EXTRA_EMPTY_SCREEN_ID) {
+ int count = mScreenOrder.size() - numCustomPages();
+ if (count > 1) {
+ return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
+ R.drawable.ic_pageindicator_add);
+ }
+ }
+
+ return super.getPageIndicatorMarker(pageIndex);
+ }
+
+ @Override
+ public void syncPages() {
+ }
+
+ @Override
+ public void syncPageItems(int page, boolean immediate) {
+ }
+
+ protected String getPageIndicatorDescription() {
+ String settings = getResources().getString(R.string.settings_button_text);
+ return getCurrentPageDescription() + ", " + settings;
+ }
+
+ protected String getCurrentPageDescription() {
+ int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ int delta = numCustomPages();
+ if (hasCustomContent() && getNextPage() == 0) {
+ return mCustomContentDescription;
+ }
+ return String.format(getContext().getString(R.string.workspace_scroll_format),
+ page + 1 - delta, getChildCount() - delta);
+ }
+
+ public void getLocationInDragLayer(int[] loc) {
+ mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
+ }
+}
diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java
new file mode 100644
index 0000000..db25076
--- /dev/null
+++ b/src/com/android/launcher3/config/ProviderConfig.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 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.config;
+
+public class ProviderConfig {
+
+ public static final String AUTHORITY = "com.android.launcher3.settings";
+}
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
new file mode 100644
index 0000000..5f64018
--- /dev/null
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2013 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.photos;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.util.Log;
+
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.photos.views.TiledImageRenderer;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
+ * {@link BitmapRegionDecoder} to wrap a local file
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
+
+ private static final String TAG = "BitmapRegionTileSource";
+
+ private static final boolean REUSE_BITMAP =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+ private static final int GL_SIZE_LIMIT = 2048;
+ // This must be no larger than half the size of the GL_SIZE_LIMIT
+ // due to decodePreview being allowed to be up to 2x the size of the target
+ private static final int MAX_PREVIEW_SIZE = 1024;
+
+ BitmapRegionDecoder mDecoder;
+ int mWidth;
+ int mHeight;
+ int mTileSize;
+ private BasicTexture mPreview;
+ private final int mRotation;
+
+ // For use only by getTile
+ private Rect mWantRegion = new Rect();
+ private Rect mOverlapRegion = new Rect();
+ private BitmapFactory.Options mOptions;
+ private Canvas mCanvas;
+
+ public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
+ this(null, context, path, null, 0, previewSize, rotation);
+ }
+
+ public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
+ this(null, context, null, uri, 0, previewSize, rotation);
+ }
+
+ public BitmapRegionTileSource(Resources res,
+ Context context, int resId, int previewSize, int rotation) {
+ this(res, context, null, null, resId, previewSize, rotation);
+ }
+
+ private BitmapRegionTileSource(Resources res,
+ Context context, String path, Uri uri, int resId, int previewSize, int rotation) {
+ mTileSize = TiledImageRenderer.suggestedTileSize(context);
+ mRotation = rotation;
+ try {
+ if (path != null) {
+ mDecoder = BitmapRegionDecoder.newInstance(path, true);
+ } else if (uri != null) {
+ InputStream is = context.getContentResolver().openInputStream(uri);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ mDecoder = BitmapRegionDecoder.newInstance(bis, true);
+ } else {
+ InputStream is = res.openRawResource(resId);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ mDecoder = BitmapRegionDecoder.newInstance(bis, true);
+ }
+ mWidth = mDecoder.getWidth();
+ mHeight = mDecoder.getHeight();
+ } catch (IOException e) {
+ Log.w("BitmapRegionTileSource", "ctor failed", e);
+ }
+ mOptions = new BitmapFactory.Options();
+ mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ mOptions.inPreferQualityOverSpeed = true;
+ mOptions.inTempStorage = new byte[16 * 1024];
+ if (previewSize != 0) {
+ previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
+ // Although this is the same size as the Bitmap that is likely already
+ // loaded, the lifecycle is different and interactions are on a different
+ // thread. Thus to simplify, this source will decode its own bitmap.
+ Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize);
+ if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
+ mPreview = new BitmapTexture(preview);
+ } else {
+ Log.w(TAG, String.format(
+ "Failed to create preview of apropriate size! "
+ + " in: %dx%d, out: %dx%d",
+ mWidth, mHeight,
+ preview.getWidth(), preview.getHeight()));
+ }
+ }
+ }
+
+ @Override
+ public int getTileSize() {
+ return mTileSize;
+ }
+
+ @Override
+ public int getImageWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getImageHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public BasicTexture getPreview() {
+ return mPreview;
+ }
+
+ @Override
+ public int getRotation() {
+ return mRotation;
+ }
+
+ @Override
+ public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+ int tileSize = getTileSize();
+ if (!REUSE_BITMAP) {
+ return getTileWithoutReusingBitmap(level, x, y, tileSize);
+ }
+
+ int t = tileSize << level;
+ mWantRegion.set(x, y, x + t, y + t);
+
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+ }
+
+ mOptions.inSampleSize = (1 << level);
+ mOptions.inBitmap = bitmap;
+
+ try {
+ bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
+ } finally {
+ if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
+ mOptions.inBitmap = null;
+ }
+ }
+
+ if (bitmap == null) {
+ Log.w("BitmapRegionTileSource", "fail in decoding region");
+ }
+ return bitmap;
+ }
+
+ private Bitmap getTileWithoutReusingBitmap(
+ int level, int x, int y, int tileSize) {
+
+ int t = tileSize << level;
+ mWantRegion.set(x, y, x + t, y + t);
+
+ mOverlapRegion.set(0, 0, mWidth, mHeight);
+
+ mOptions.inSampleSize = (1 << level);
+ Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
+
+ if (bitmap == null) {
+ Log.w(TAG, "fail in decoding region");
+ }
+
+ if (mWantRegion.equals(mOverlapRegion)) {
+ return bitmap;
+ }
+
+ Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
+ if (mCanvas == null) {
+ mCanvas = new Canvas();
+ }
+ mCanvas.setBitmap(result);
+ mCanvas.drawBitmap(bitmap,
+ (mOverlapRegion.left - mWantRegion.left) >> level,
+ (mOverlapRegion.top - mWantRegion.top) >> level, null);
+ mCanvas.setBitmap(null);
+ return result;
+ }
+
+ /**
+ * Note that the returned bitmap may have a long edge that's longer
+ * than the targetSize, but it will always be less than 2x the targetSize
+ */
+ private Bitmap decodePreview(
+ Resources res, Context context, String file, Uri uri, int resId, int targetSize) {
+ float scale = (float) targetSize / Math.max(mWidth, mHeight);
+ mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+ mOptions.inJustDecodeBounds = false;
+
+ Bitmap result = null;
+ if (file != null) {
+ result = BitmapFactory.decodeFile(file, mOptions);
+ } else if (uri != null) {
+ try {
+ InputStream is = context.getContentResolver().openInputStream(uri);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ result = BitmapFactory.decodeStream(bis, null, mOptions);
+ } catch (IOException e) {
+ Log.w("BitmapRegionTileSource", "getting preview failed", e);
+ }
+ } else {
+ result = BitmapFactory.decodeResource(res, resId, mOptions);
+ }
+ if (result == null) {
+ return null;
+ }
+
+ // We need to resize down if the decoder does not support inSampleSize
+ // or didn't support the specified inSampleSize (some decoders only do powers of 2)
+ scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
+
+ if (scale <= 0.5) {
+ result = BitmapUtils.resizeBitmapByScale(result, scale, true);
+ }
+ return ensureGLCompatibleBitmap(result);
+ }
+
+ private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
+ if (bitmap == null || bitmap.getConfig() != null) {
+ return bitmap;
+ }
+ Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
+ bitmap.recycle();
+ return newBitmap;
+ }
+}
diff --git a/src/com/android/photos/views/BlockingGLTextureView.java b/src/com/android/photos/views/BlockingGLTextureView.java
new file mode 100644
index 0000000..8a05051
--- /dev/null
+++ b/src/com/android/photos/views/BlockingGLTextureView.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2013 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.photos.views;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLSurfaceView.Renderer;
+import android.opengl.GLUtils;
+import android.util.Log;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A TextureView that supports blocking rendering for synchronous drawing
+ */
+public class BlockingGLTextureView extends TextureView
+ implements SurfaceTextureListener {
+
+ private RenderThread mRenderThread;
+
+ public BlockingGLTextureView(Context context) {
+ super(context);
+ setSurfaceTextureListener(this);
+ }
+
+ public void setRenderer(Renderer renderer) {
+ if (mRenderThread != null) {
+ throw new IllegalArgumentException("Renderer already set");
+ }
+ mRenderThread = new RenderThread(renderer);
+ }
+
+ public void render() {
+ mRenderThread.render();
+ }
+
+ public void destroy() {
+ if (mRenderThread != null) {
+ mRenderThread.finish();
+ mRenderThread = null;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
+ int height) {
+ mRenderThread.setSurface(surface);
+ mRenderThread.setSize(width, height);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
+ int height) {
+ mRenderThread.setSize(width, height);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ if (mRenderThread != null) {
+ mRenderThread.setSurface(null);
+ }
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ destroy();
+ } catch (Throwable t) {
+ // Ignore
+ }
+ super.finalize();
+ }
+
+ /**
+ * An EGL helper class.
+ */
+
+ private static class EglHelper {
+ private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ private static final int EGL_OPENGL_ES2_BIT = 4;
+
+ EGL10 mEgl;
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLConfig mEglConfig;
+ EGLContext mEglContext;
+
+ private EGLConfig chooseEglConfig() {
+ int[] configsCount = new int[1];
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] configSpec = getConfig();
+ if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
+ throw new IllegalArgumentException("eglChooseConfig failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ } else if (configsCount[0] > 0) {
+ return configs[0];
+ }
+ return null;
+ }
+
+ private static int[] getConfig() {
+ return new int[] {
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_RED_SIZE, 8,
+ EGL10.EGL_GREEN_SIZE, 8,
+ EGL10.EGL_BLUE_SIZE, 8,
+ EGL10.EGL_ALPHA_SIZE, 8,
+ EGL10.EGL_DEPTH_SIZE, 0,
+ EGL10.EGL_STENCIL_SIZE, 0,
+ EGL10.EGL_NONE
+ };
+ }
+
+ EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+ int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+ return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList);
+ }
+
+ /**
+ * Initialize EGL for a given configuration spec.
+ */
+ public void start() {
+ /*
+ * Get an EGL instance
+ */
+ mEgl = (EGL10) EGLContext.getEGL();
+
+ /*
+ * Get to the default display.
+ */
+ mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+ if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+ throw new RuntimeException("eglGetDisplay failed");
+ }
+
+ /*
+ * We can now initialize EGL for that display
+ */
+ int[] version = new int[2];
+ if (!mEgl.eglInitialize(mEglDisplay, version)) {
+ throw new RuntimeException("eglInitialize failed");
+ }
+ mEglConfig = chooseEglConfig();
+
+ /*
+ * Create an EGL context. We want to do this as rarely as we can, because an
+ * EGL context is a somewhat heavy object.
+ */
+ mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+
+ if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
+ mEglContext = null;
+ throwEglException("createContext");
+ }
+
+ mEglSurface = null;
+ }
+
+ /**
+ * Create an egl surface for the current SurfaceTexture surface. If a surface
+ * already exists, destroy it before creating the new surface.
+ *
+ * @return true if the surface was created successfully.
+ */
+ public boolean createSurface(SurfaceTexture surface) {
+ /*
+ * Check preconditions.
+ */
+ if (mEgl == null) {
+ throw new RuntimeException("egl not initialized");
+ }
+ if (mEglDisplay == null) {
+ throw new RuntimeException("eglDisplay not initialized");
+ }
+ if (mEglConfig == null) {
+ throw new RuntimeException("mEglConfig not initialized");
+ }
+
+ /*
+ * The window size has changed, so we need to create a new
+ * surface.
+ */
+ destroySurfaceImp();
+
+ /*
+ * Create an EGL surface we can render into.
+ */
+ if (surface != null) {
+ mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
+ } else {
+ mEglSurface = null;
+ }
+
+ if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+ int error = mEgl.eglGetError();
+ if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+ Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+ }
+ return false;
+ }
+
+ /*
+ * Before we can issue GL commands, we need to make sure
+ * the context is current and bound to a surface.
+ */
+ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ /*
+ * Could not make the context current, probably because the underlying
+ * SurfaceView surface has been destroyed.
+ */
+ logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a GL object for the current EGL context.
+ */
+ public GL10 createGL() {
+ return (GL10) mEglContext.getGL();
+ }
+
+ /**
+ * Display the current render surface.
+ * @return the EGL error code from eglSwapBuffers.
+ */
+ public int swap() {
+ if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ return mEgl.eglGetError();
+ }
+ return EGL10.EGL_SUCCESS;
+ }
+
+ public void destroySurface() {
+ destroySurfaceImp();
+ }
+
+ private void destroySurfaceImp() {
+ if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
+ mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT);
+ mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = null;
+ }
+ }
+
+ public void finish() {
+ if (mEglContext != null) {
+ mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+ mEglContext = null;
+ }
+ if (mEglDisplay != null) {
+ mEgl.eglTerminate(mEglDisplay);
+ mEglDisplay = null;
+ }
+ }
+
+ private void throwEglException(String function) {
+ throwEglException(function, mEgl.eglGetError());
+ }
+
+ public static void throwEglException(String function, int error) {
+ String message = formatEglError(function, error);
+ throw new RuntimeException(message);
+ }
+
+ public static void logEglErrorAsWarning(String tag, String function, int error) {
+ Log.w(tag, formatEglError(function, error));
+ }
+
+ public static String formatEglError(String function, int error) {
+ return function + " failed: " + error;
+ }
+
+ }
+
+ private static class RenderThread extends Thread {
+ private static final int INVALID = -1;
+ private static final int RENDER = 1;
+ private static final int CHANGE_SURFACE = 2;
+ private static final int RESIZE_SURFACE = 3;
+ private static final int FINISH = 4;
+
+ private EglHelper mEglHelper = new EglHelper();
+
+ private Object mLock = new Object();
+ private int mExecMsgId = INVALID;
+ private SurfaceTexture mSurface;
+ private Renderer mRenderer;
+ private int mWidth, mHeight;
+
+ private boolean mFinished = false;
+ private GL10 mGL;
+
+ public RenderThread(Renderer renderer) {
+ super("RenderThread");
+ mRenderer = renderer;
+ start();
+ }
+
+ private void checkRenderer() {
+ if (mRenderer == null) {
+ throw new IllegalArgumentException("Renderer is null!");
+ }
+ }
+
+ private void checkSurface() {
+ if (mSurface == null) {
+ throw new IllegalArgumentException("surface is null!");
+ }
+ }
+
+ public void setSurface(SurfaceTexture surface) {
+ // If the surface is null we're being torn down, don't need a
+ // renderer then
+ if (surface != null) {
+ checkRenderer();
+ }
+ mSurface = surface;
+ exec(CHANGE_SURFACE);
+ }
+
+ public void setSize(int width, int height) {
+ checkRenderer();
+ checkSurface();
+ mWidth = width;
+ mHeight = height;
+ exec(RESIZE_SURFACE);
+ }
+
+ public void render() {
+ checkRenderer();
+ if (mSurface != null) {
+ exec(RENDER);
+ mSurface.updateTexImage();
+ }
+ }
+
+ public void finish() {
+ mSurface = null;
+ exec(FINISH);
+ try {
+ join();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ private void exec(int msgid) {
+ synchronized (mLock) {
+ if (mExecMsgId != INVALID) {
+ throw new IllegalArgumentException(
+ "Message already set - multithreaded access?");
+ }
+ mExecMsgId = msgid;
+ mLock.notify();
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+
+ private void handleMessageLocked(int what) {
+ switch (what) {
+ case CHANGE_SURFACE:
+ if (mEglHelper.createSurface(mSurface)) {
+ mGL = mEglHelper.createGL();
+ mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
+ }
+ break;
+ case RESIZE_SURFACE:
+ mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
+ break;
+ case RENDER:
+ mRenderer.onDrawFrame(mGL);
+ mEglHelper.swap();
+ break;
+ case FINISH:
+ mEglHelper.destroySurface();
+ mEglHelper.finish();
+ mFinished = true;
+ break;
+ }
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ mEglHelper.start();
+ while (!mFinished) {
+ while (mExecMsgId == INVALID) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ handleMessageLocked(mExecMsgId);
+ mExecMsgId = INVALID;
+ mLock.notify();
+ }
+ mExecMsgId = FINISH;
+ }
+ }
+ }
+}
diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java
new file mode 100644
index 0000000..c4e493b
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageRenderer.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2013 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.photos.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.support.v4.util.LongSparseArray;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SynchronizedPool;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
+
+/**
+ * Handles laying out, decoding, and drawing of tiles in GL
+ */
+public class TiledImageRenderer {
+ public static final int SIZE_UNKNOWN = -1;
+
+ private static final String TAG = "TiledImageRenderer";
+ private static final int UPLOAD_LIMIT = 1;
+
+ /*
+ * This is the tile state in the CPU side.
+ * Life of a Tile:
+ * ACTIVATED (initial state)
+ * --> IN_QUEUE - by queueForDecode()
+ * --> RECYCLED - by recycleTile()
+ * IN_QUEUE --> DECODING - by decodeTile()
+ * --> RECYCLED - by recycleTile)
+ * DECODING --> RECYCLING - by recycleTile()
+ * --> DECODED - by decodeTile()
+ * --> DECODE_FAIL - by decodeTile()
+ * RECYCLING --> RECYCLED - by decodeTile()
+ * DECODED --> ACTIVATED - (after the decoded bitmap is uploaded)
+ * DECODED --> RECYCLED - by recycleTile()
+ * DECODE_FAIL -> RECYCLED - by recycleTile()
+ * RECYCLED --> ACTIVATED - by obtainTile()
+ */
+ private static final int STATE_ACTIVATED = 0x01;
+ private static final int STATE_IN_QUEUE = 0x02;
+ private static final int STATE_DECODING = 0x04;
+ private static final int STATE_DECODED = 0x08;
+ private static final int STATE_DECODE_FAIL = 0x10;
+ private static final int STATE_RECYCLING = 0x20;
+ private static final int STATE_RECYCLED = 0x40;
+
+ private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64);
+
+ // TILE_SIZE must be 2^N
+ private int mTileSize;
+
+ private TileSource mModel;
+ private BasicTexture mPreview;
+ protected int mLevelCount; // cache the value of mScaledBitmaps.length
+
+ // The mLevel variable indicates which level of bitmap we should use.
+ // Level 0 means the original full-sized bitmap, and a larger value means
+ // a smaller scaled bitmap (The width and height of each scaled bitmap is
+ // half size of the previous one). If the value is in [0, mLevelCount), we
+ // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value
+ // is mLevelCount
+ private int mLevel = 0;
+
+ private int mOffsetX;
+ private int mOffsetY;
+
+ private int mUploadQuota;
+ private boolean mRenderComplete;
+
+ private final RectF mSourceRect = new RectF();
+ private final RectF mTargetRect = new RectF();
+
+ private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>();
+
+ // The following three queue are guarded by mQueueLock
+ private final Object mQueueLock = new Object();
+ private final TileQueue mRecycledQueue = new TileQueue();
+ private final TileQueue mUploadQueue = new TileQueue();
+ private final TileQueue mDecodeQueue = new TileQueue();
+
+ // The width and height of the full-sized bitmap
+ protected int mImageWidth = SIZE_UNKNOWN;
+ protected int mImageHeight = SIZE_UNKNOWN;
+
+ protected int mCenterX;
+ protected int mCenterY;
+ protected float mScale;
+ protected int mRotation;
+
+ private boolean mLayoutTiles;
+
+ // Temp variables to avoid memory allocation
+ private final Rect mTileRange = new Rect();
+ private final Rect mActiveRange[] = {new Rect(), new Rect()};
+
+ private TileDecoder mTileDecoder;
+ private boolean mBackgroundTileUploaded;
+
+ private int mViewWidth, mViewHeight;
+ private View mParent;
+
+ /**
+ * Interface for providing tiles to a {@link TiledImageRenderer}
+ */
+ public static interface TileSource {
+
+ /**
+ * If the source does not care about the tile size, it should use
+ * {@link TiledImageRenderer#suggestedTileSize(Context)}
+ */
+ public int getTileSize();
+ public int getImageWidth();
+ public int getImageHeight();
+ public int getRotation();
+
+ /**
+ * Return a Preview image if available. This will be used as the base layer
+ * if higher res tiles are not yet available
+ */
+ public BasicTexture getPreview();
+
+ /**
+ * The tile returned by this method can be specified this way: Assuming
+ * the image size is (width, height), first take the intersection of (0,
+ * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
+ * in extending the region, we found some part of the region is outside
+ * the image, those pixels are filled with black.
+ *
+ * If level > 0, it does the same operation on a down-scaled version of
+ * the original image (down-scaled by a factor of 2^level), but (x, y)
+ * still refers to the coordinate on the original image.
+ *
+ * The method would be called by the decoder thread.
+ */
+ public Bitmap getTile(int level, int x, int y, Bitmap reuse);
+ }
+
+ public static int suggestedTileSize(Context context) {
+ return isHighResolution(context) ? 512 : 256;
+ }
+
+ private static boolean isHighResolution(Context context) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager wm = (WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(metrics);
+ return metrics.heightPixels > 2048 || metrics.widthPixels > 2048;
+ }
+
+ public TiledImageRenderer(View parent) {
+ mParent = parent;
+ mTileDecoder = new TileDecoder();
+ mTileDecoder.start();
+ }
+
+ public int getViewWidth() {
+ return mViewWidth;
+ }
+
+ public int getViewHeight() {
+ return mViewHeight;
+ }
+
+ private void invalidate() {
+ mParent.postInvalidate();
+ }
+
+ public void setModel(TileSource model, int rotation) {
+ if (mModel != model) {
+ mModel = model;
+ notifyModelInvalidated();
+ }
+ if (mRotation != rotation) {
+ mRotation = rotation;
+ mLayoutTiles = true;
+ }
+ }
+
+ private void calculateLevelCount() {
+ if (mPreview != null) {
+ mLevelCount = Math.max(0, Utils.ceilLog2(
+ mImageWidth / (float) mPreview.getWidth()));
+ } else {
+ int levels = 1;
+ int maxDim = Math.max(mImageWidth, mImageHeight);
+ int t = mTileSize;
+ while (t < maxDim) {
+ t <<= 1;
+ levels++;
+ }
+ mLevelCount = levels;
+ }
+ }
+
+ public void notifyModelInvalidated() {
+ invalidateTiles();
+ if (mModel == null) {
+ mImageWidth = 0;
+ mImageHeight = 0;
+ mLevelCount = 0;
+ mPreview = null;
+ } else {
+ mImageWidth = mModel.getImageWidth();
+ mImageHeight = mModel.getImageHeight();
+ mPreview = mModel.getPreview();
+ mTileSize = mModel.getTileSize();
+ calculateLevelCount();
+ }
+ mLayoutTiles = true;
+ }
+
+ public void setViewSize(int width, int height) {
+ mViewWidth = width;
+ mViewHeight = height;
+ }
+
+ public void setPosition(int centerX, int centerY, float scale) {
+ if (mCenterX == centerX && mCenterY == centerY
+ && mScale == scale) {
+ return;
+ }
+ mCenterX = centerX;
+ mCenterY = centerY;
+ mScale = scale;
+ mLayoutTiles = true;
+ }
+
+ // Prepare the tiles we want to use for display.
+ //
+ // 1. Decide the tile level we want to use for display.
+ // 2. Decide the tile levels we want to keep as texture (in addition to
+ // the one we use for display).
+ // 3. Recycle unused tiles.
+ // 4. Activate the tiles we want.
+ private void layoutTiles() {
+ if (mViewWidth == 0 || mViewHeight == 0 || !mLayoutTiles) {
+ return;
+ }
+ mLayoutTiles = false;
+
+ // The tile levels we want to keep as texture is in the range
+ // [fromLevel, endLevel).
+ int fromLevel;
+ int endLevel;
+
+ // We want to use a texture larger than or equal to the display size.
+ mLevel = Utils.clamp(Utils.floorLog2(1f / mScale), 0, mLevelCount);
+
+ // We want to keep one more tile level as texture in addition to what
+ // we use for display. So it can be faster when the scale moves to the
+ // next level. We choose the level closest to the current scale.
+ if (mLevel != mLevelCount) {
+ Rect range = mTileRange;
+ getRange(range, mCenterX, mCenterY, mLevel, mScale, mRotation);
+ mOffsetX = Math.round(mViewWidth / 2f + (range.left - mCenterX) * mScale);
+ mOffsetY = Math.round(mViewHeight / 2f + (range.top - mCenterY) * mScale);
+ fromLevel = mScale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel;
+ } else {
+ // Activate the tiles of the smallest two levels.
+ fromLevel = mLevel - 2;
+ mOffsetX = Math.round(mViewWidth / 2f - mCenterX * mScale);
+ mOffsetY = Math.round(mViewHeight / 2f - mCenterY * mScale);
+ }
+
+ fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2));
+ endLevel = Math.min(fromLevel + 2, mLevelCount);
+
+ Rect range[] = mActiveRange;
+ for (int i = fromLevel; i < endLevel; ++i) {
+ getRange(range[i - fromLevel], mCenterX, mCenterY, i, mRotation);
+ }
+
+ // If rotation is transient, don't update the tile.
+ if (mRotation % 90 != 0) {
+ return;
+ }
+
+ synchronized (mQueueLock) {
+ mDecodeQueue.clean();
+ mUploadQueue.clean();
+ mBackgroundTileUploaded = false;
+
+ // Recycle unused tiles: if the level of the active tile is outside the
+ // range [fromLevel, endLevel) or not in the visible range.
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ int level = tile.mTileLevel;
+ if (level < fromLevel || level >= endLevel
+ || !range[level - fromLevel].contains(tile.mX, tile.mY)) {
+ mActiveTiles.removeAt(i);
+ i--;
+ n--;
+ recycleTile(tile);
+ }
+ }
+ }
+
+ for (int i = fromLevel; i < endLevel; ++i) {
+ int size = mTileSize << i;
+ Rect r = range[i - fromLevel];
+ for (int y = r.top, bottom = r.bottom; y < bottom; y += size) {
+ for (int x = r.left, right = r.right; x < right; x += size) {
+ activateTile(x, y, i);
+ }
+ }
+ }
+ invalidate();
+ }
+
+ private void invalidateTiles() {
+ synchronized (mQueueLock) {
+ mDecodeQueue.clean();
+ mUploadQueue.clean();
+
+ // TODO(xx): disable decoder
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ recycleTile(tile);
+ }
+ mActiveTiles.clear();
+ }
+ }
+
+ private void getRange(Rect out, int cX, int cY, int level, int rotation) {
+ getRange(out, cX, cY, level, 1f / (1 << (level + 1)), rotation);
+ }
+
+ // If the bitmap is scaled by the given factor "scale", return the
+ // rectangle containing visible range. The left-top coordinate returned is
+ // aligned to the tile boundary.
+ //
+ // (cX, cY) is the point on the original bitmap which will be put in the
+ // center of the ImageViewer.
+ private void getRange(Rect out,
+ int cX, int cY, int level, float scale, int rotation) {
+
+ double radians = Math.toRadians(-rotation);
+ double w = mViewWidth;
+ double h = mViewHeight;
+
+ double cos = Math.cos(radians);
+ double sin = Math.sin(radians);
+ int width = (int) Math.ceil(Math.max(
+ Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h)));
+ int height = (int) Math.ceil(Math.max(
+ Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h)));
+
+ int left = (int) Math.floor(cX - width / (2f * scale));
+ int top = (int) Math.floor(cY - height / (2f * scale));
+ int right = (int) Math.ceil(left + width / scale);
+ int bottom = (int) Math.ceil(top + height / scale);
+
+ // align the rectangle to tile boundary
+ int size = mTileSize << level;
+ left = Math.max(0, size * (left / size));
+ top = Math.max(0, size * (top / size));
+ right = Math.min(mImageWidth, right);
+ bottom = Math.min(mImageHeight, bottom);
+
+ out.set(left, top, right, bottom);
+ }
+
+ public void freeTextures() {
+ mLayoutTiles = true;
+
+ mTileDecoder.finishAndWait();
+ synchronized (mQueueLock) {
+ mUploadQueue.clean();
+ mDecodeQueue.clean();
+ Tile tile = mRecycledQueue.pop();
+ while (tile != null) {
+ tile.recycle();
+ tile = mRecycledQueue.pop();
+ }
+ }
+
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile texture = mActiveTiles.valueAt(i);
+ texture.recycle();
+ }
+ mActiveTiles.clear();
+ mTileRange.set(0, 0, 0, 0);
+
+ while (sTilePool.acquire() != null) {}
+ }
+
+ public boolean draw(GLCanvas canvas) {
+ layoutTiles();
+ uploadTiles(canvas);
+
+ mUploadQuota = UPLOAD_LIMIT;
+ mRenderComplete = true;
+
+ int level = mLevel;
+ int rotation = mRotation;
+ int flags = 0;
+ if (rotation != 0) {
+ flags |= GLCanvas.SAVE_FLAG_MATRIX;
+ }
+
+ if (flags != 0) {
+ canvas.save(flags);
+ if (rotation != 0) {
+ int centerX = mViewWidth / 2, centerY = mViewHeight / 2;
+ canvas.translate(centerX, centerY);
+ canvas.rotate(rotation, 0, 0, 1);
+ canvas.translate(-centerX, -centerY);
+ }
+ }
+ try {
+ if (level != mLevelCount) {
+ int size = (mTileSize << level);
+ float length = size * mScale;
+ Rect r = mTileRange;
+
+ for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) {
+ float y = mOffsetY + i * length;
+ for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) {
+ float x = mOffsetX + j * length;
+ drawTile(canvas, tx, ty, level, x, y, length);
+ }
+ }
+ } else if (mPreview != null) {
+ mPreview.draw(canvas, mOffsetX, mOffsetY,
+ Math.round(mImageWidth * mScale),
+ Math.round(mImageHeight * mScale));
+ }
+ } finally {
+ if (flags != 0) {
+ canvas.restore();
+ }
+ }
+
+ if (mRenderComplete) {
+ if (!mBackgroundTileUploaded) {
+ uploadBackgroundTiles(canvas);
+ }
+ } else {
+ invalidate();
+ }
+ return mRenderComplete || mPreview != null;
+ }
+
+ private void uploadBackgroundTiles(GLCanvas canvas) {
+ mBackgroundTileUploaded = true;
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ if (!tile.isContentValid()) {
+ queueForDecode(tile);
+ }
+ }
+ }
+
+ private void queueForDecode(Tile tile) {
+ synchronized (mQueueLock) {
+ if (tile.mTileState == STATE_ACTIVATED) {
+ tile.mTileState = STATE_IN_QUEUE;
+ if (mDecodeQueue.push(tile)) {
+ mQueueLock.notifyAll();
+ }
+ }
+ }
+ }
+
+ private void decodeTile(Tile tile) {
+ synchronized (mQueueLock) {
+ if (tile.mTileState != STATE_IN_QUEUE) {
+ return;
+ }
+ tile.mTileState = STATE_DECODING;
+ }
+ boolean decodeComplete = tile.decode();
+ synchronized (mQueueLock) {
+ if (tile.mTileState == STATE_RECYCLING) {
+ tile.mTileState = STATE_RECYCLED;
+ if (tile.mDecodedTile != null) {
+ sTilePool.release(tile.mDecodedTile);
+ tile.mDecodedTile = null;
+ }
+ mRecycledQueue.push(tile);
+ return;
+ }
+ tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL;
+ if (!decodeComplete) {
+ return;
+ }
+ mUploadQueue.push(tile);
+ }
+ invalidate();
+ }
+
+ private Tile obtainTile(int x, int y, int level) {
+ synchronized (mQueueLock) {
+ Tile tile = mRecycledQueue.pop();
+ if (tile != null) {
+ tile.mTileState = STATE_ACTIVATED;
+ tile.update(x, y, level);
+ return tile;
+ }
+ return new Tile(x, y, level);
+ }
+ }
+
+ private void recycleTile(Tile tile) {
+ synchronized (mQueueLock) {
+ if (tile.mTileState == STATE_DECODING) {
+ tile.mTileState = STATE_RECYCLING;
+ return;
+ }
+ tile.mTileState = STATE_RECYCLED;
+ if (tile.mDecodedTile != null) {
+ sTilePool.release(tile.mDecodedTile);
+ tile.mDecodedTile = null;
+ }
+ mRecycledQueue.push(tile);
+ }
+ }
+
+ private void activateTile(int x, int y, int level) {
+ long key = makeTileKey(x, y, level);
+ Tile tile = mActiveTiles.get(key);
+ if (tile != null) {
+ if (tile.mTileState == STATE_IN_QUEUE) {
+ tile.mTileState = STATE_ACTIVATED;
+ }
+ return;
+ }
+ tile = obtainTile(x, y, level);
+ mActiveTiles.put(key, tile);
+ }
+
+ private Tile getTile(int x, int y, int level) {
+ return mActiveTiles.get(makeTileKey(x, y, level));
+ }
+
+ private static long makeTileKey(int x, int y, int level) {
+ long result = x;
+ result = (result << 16) | y;
+ result = (result << 16) | level;
+ return result;
+ }
+
+ private void uploadTiles(GLCanvas canvas) {
+ int quota = UPLOAD_LIMIT;
+ Tile tile = null;
+ while (quota > 0) {
+ synchronized (mQueueLock) {
+ tile = mUploadQueue.pop();
+ }
+ if (tile == null) {
+ break;
+ }
+ if (!tile.isContentValid()) {
+ if (tile.mTileState == STATE_DECODED) {
+ tile.updateContent(canvas);
+ --quota;
+ } else {
+ Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState);
+ }
+ }
+ }
+ if (tile != null) {
+ invalidate();
+ }
+ }
+
+ // Draw the tile to a square at canvas that locates at (x, y) and
+ // has a side length of length.
+ private void drawTile(GLCanvas canvas,
+ int tx, int ty, int level, float x, float y, float length) {
+ RectF source = mSourceRect;
+ RectF target = mTargetRect;
+ target.set(x, y, x + length, y + length);
+ source.set(0, 0, mTileSize, mTileSize);
+
+ Tile tile = getTile(tx, ty, level);
+ if (tile != null) {
+ if (!tile.isContentValid()) {
+ if (tile.mTileState == STATE_DECODED) {
+ if (mUploadQuota > 0) {
+ --mUploadQuota;
+ tile.updateContent(canvas);
+ } else {
+ mRenderComplete = false;
+ }
+ } else if (tile.mTileState != STATE_DECODE_FAIL){
+ mRenderComplete = false;
+ queueForDecode(tile);
+ }
+ }
+ if (drawTile(tile, canvas, source, target)) {
+ return;
+ }
+ }
+ if (mPreview != null) {
+ int size = mTileSize << level;
+ float scaleX = (float) mPreview.getWidth() / mImageWidth;
+ float scaleY = (float) mPreview.getHeight() / mImageHeight;
+ source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX,
+ (ty + size) * scaleY);
+ canvas.drawTexture(mPreview, source, target);
+ }
+ }
+
+ private boolean drawTile(
+ Tile tile, GLCanvas canvas, RectF source, RectF target) {
+ while (true) {
+ if (tile.isContentValid()) {
+ canvas.drawTexture(tile, source, target);
+ return true;
+ }
+
+ // Parent can be divided to four quads and tile is one of the four.
+ Tile parent = tile.getParentTile();
+ if (parent == null) {
+ return false;
+ }
+ if (tile.mX == parent.mX) {
+ source.left /= 2f;
+ source.right /= 2f;
+ } else {
+ source.left = (mTileSize + source.left) / 2f;
+ source.right = (mTileSize + source.right) / 2f;
+ }
+ if (tile.mY == parent.mY) {
+ source.top /= 2f;
+ source.bottom /= 2f;
+ } else {
+ source.top = (mTileSize + source.top) / 2f;
+ source.bottom = (mTileSize + source.bottom) / 2f;
+ }
+ tile = parent;
+ }
+ }
+
+ private class Tile extends UploadedTexture {
+ public int mX;
+ public int mY;
+ public int mTileLevel;
+ public Tile mNext;
+ public Bitmap mDecodedTile;
+ public volatile int mTileState = STATE_ACTIVATED;
+
+ public Tile(int x, int y, int level) {
+ mX = x;
+ mY = y;
+ mTileLevel = level;
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ sTilePool.release(bitmap);
+ }
+
+ boolean decode() {
+ // Get a tile from the original image. The tile is down-scaled
+ // by (1 << mTilelevel) from a region in the original image.
+ try {
+ Bitmap reuse = sTilePool.acquire();
+ if (reuse != null && reuse.getWidth() != mTileSize) {
+ reuse = null;
+ }
+ mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse);
+ } catch (Throwable t) {
+ Log.w(TAG, "fail to decode tile", t);
+ }
+ return mDecodedTile != null;
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ Utils.assertTrue(mTileState == STATE_DECODED);
+
+ // We need to override the width and height, so that we won't
+ // draw beyond the boundaries.
+ int rightEdge = ((mImageWidth - mX) >> mTileLevel);
+ int bottomEdge = ((mImageHeight - mY) >> mTileLevel);
+ setSize(Math.min(mTileSize, rightEdge), Math.min(mTileSize, bottomEdge));
+
+ Bitmap bitmap = mDecodedTile;
+ mDecodedTile = null;
+ mTileState = STATE_ACTIVATED;
+ return bitmap;
+ }
+
+ // We override getTextureWidth() and getTextureHeight() here, so the
+ // texture can be re-used for different tiles regardless of the actual
+ // size of the tile (which may be small because it is a tile at the
+ // boundary).
+ @Override
+ public int getTextureWidth() {
+ return mTileSize;
+ }
+
+ @Override
+ public int getTextureHeight() {
+ return mTileSize;
+ }
+
+ public void update(int x, int y, int level) {
+ mX = x;
+ mY = y;
+ mTileLevel = level;
+ invalidateContent();
+ }
+
+ public Tile getParentTile() {
+ if (mTileLevel + 1 == mLevelCount) {
+ return null;
+ }
+ int size = mTileSize << (mTileLevel + 1);
+ int x = size * (mX / size);
+ int y = size * (mY / size);
+ return getTile(x, y, mTileLevel + 1);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("tile(%s, %s, %s / %s)",
+ mX / mTileSize, mY / mTileSize, mLevel, mLevelCount);
+ }
+ }
+
+ private static class TileQueue {
+ private Tile mHead;
+
+ public Tile pop() {
+ Tile tile = mHead;
+ if (tile != null) {
+ mHead = tile.mNext;
+ }
+ return tile;
+ }
+
+ public boolean push(Tile tile) {
+ if (contains(tile)) {
+ Log.w(TAG, "Attempting to add a tile already in the queue!");
+ return false;
+ }
+ boolean wasEmpty = mHead == null;
+ tile.mNext = mHead;
+ mHead = tile;
+ return wasEmpty;
+ }
+
+ private boolean contains(Tile tile) {
+ Tile other = mHead;
+ while (other != null) {
+ if (other == tile) {
+ return true;
+ }
+ other = other.mNext;
+ }
+ return false;
+ }
+
+ public void clean() {
+ mHead = null;
+ }
+ }
+
+ private class TileDecoder extends Thread {
+
+ public void finishAndWait() {
+ interrupt();
+ try {
+ join();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting for TileDecoder thread to finish!");
+ }
+ }
+
+ private Tile waitForTile() throws InterruptedException {
+ synchronized (mQueueLock) {
+ while (true) {
+ Tile tile = mDecodeQueue.pop();
+ if (tile != null) {
+ return tile;
+ }
+ mQueueLock.wait();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!isInterrupted()) {
+ Tile tile = waitForTile();
+ decodeTile(tile);
+ }
+ } catch (InterruptedException ex) {
+ // We were finished
+ }
+ }
+
+ }
+}
diff --git a/src/com/android/photos/views/TiledImageView.java b/src/com/android/photos/views/TiledImageView.java
new file mode 100644
index 0000000..af4199c
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageView.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2013 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.photos.views;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.RectF;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLSurfaceView.Renderer;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Choreographer;
+import android.view.Choreographer.FrameCallback;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLES20Canvas;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
+ * or {@link BlockingGLTextureView}.
+ */
+public class TiledImageView extends FrameLayout {
+
+ private static final boolean USE_TEXTURE_VIEW = false;
+ private static final boolean IS_SUPPORTED =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+ private static final boolean USE_CHOREOGRAPHER =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+
+ private BlockingGLTextureView mTextureView;
+ private GLSurfaceView mGLSurfaceView;
+ private boolean mInvalPending = false;
+ private FrameCallback mFrameCallback;
+
+ protected static class ImageRendererWrapper {
+ // Guarded by locks
+ public float scale;
+ public int centerX, centerY;
+ public int rotation;
+ public TileSource source;
+ Runnable isReadyCallback;
+
+ // GL thread only
+ TiledImageRenderer image;
+ }
+
+ private float[] mValues = new float[9];
+
+ // -------------------------
+ // Guarded by mLock
+ // -------------------------
+ protected Object mLock = new Object();
+ protected ImageRendererWrapper mRenderer;
+
+ public static boolean isTilingSupported() {
+ return IS_SUPPORTED;
+ }
+
+ public TiledImageView(Context context) {
+ this(context, null);
+ }
+
+ public TiledImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (!IS_SUPPORTED) {
+ return;
+ }
+
+ mRenderer = new ImageRendererWrapper();
+ mRenderer.image = new TiledImageRenderer(this);
+ View view;
+ if (USE_TEXTURE_VIEW) {
+ mTextureView = new BlockingGLTextureView(context);
+ mTextureView.setRenderer(new TileRenderer());
+ view = mTextureView;
+ } else {
+ mGLSurfaceView = new GLSurfaceView(context);
+ mGLSurfaceView.setEGLContextClientVersion(2);
+ mGLSurfaceView.setRenderer(new TileRenderer());
+ mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ view = mGLSurfaceView;
+ }
+ addView(view, new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ //setTileSource(new ColoredTiles());
+ }
+
+ public void destroy() {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (USE_TEXTURE_VIEW) {
+ mTextureView.destroy();
+ } else {
+ mGLSurfaceView.queueEvent(mFreeTextures);
+ }
+ }
+
+ private Runnable mFreeTextures = new Runnable() {
+
+ @Override
+ public void run() {
+ mRenderer.image.freeTextures();
+ }
+ };
+
+ public void onPause() {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (!USE_TEXTURE_VIEW) {
+ mGLSurfaceView.onPause();
+ }
+ }
+
+ public void onResume() {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (!USE_TEXTURE_VIEW) {
+ mGLSurfaceView.onResume();
+ }
+ }
+
+ public void setTileSource(TileSource source, Runnable isReadyCallback) {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ synchronized (mLock) {
+ mRenderer.source = source;
+ mRenderer.isReadyCallback = isReadyCallback;
+ mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
+ mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
+ mRenderer.rotation = source != null ? source.getRotation() : 0;
+ mRenderer.scale = 0;
+ updateScaleIfNecessaryLocked(mRenderer);
+ }
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right,
+ int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ synchronized (mLock) {
+ updateScaleIfNecessaryLocked(mRenderer);
+ }
+ }
+
+ private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
+ if (renderer == null || renderer.source == null
+ || renderer.scale > 0 || getWidth() == 0) {
+ return;
+ }
+ renderer.scale = Math.min(
+ (float) getWidth() / (float) renderer.source.getImageWidth(),
+ (float) getHeight() / (float) renderer.source.getImageHeight());
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (USE_TEXTURE_VIEW) {
+ mTextureView.render();
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void setTranslationX(float translationX) {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ super.setTranslationX(translationX);
+ }
+
+ @Override
+ public void invalidate() {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (USE_TEXTURE_VIEW) {
+ super.invalidate();
+ mTextureView.invalidate();
+ } else {
+ if (USE_CHOREOGRAPHER) {
+ invalOnVsync();
+ } else {
+ mGLSurfaceView.requestRender();
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private void invalOnVsync() {
+ if (!mInvalPending) {
+ mInvalPending = true;
+ if (mFrameCallback == null) {
+ mFrameCallback = new FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ mInvalPending = false;
+ mGLSurfaceView.requestRender();
+ }
+ };
+ }
+ Choreographer.getInstance().postFrameCallback(mFrameCallback);
+ }
+ }
+
+ private RectF mTempRectF = new RectF();
+ public void positionFromMatrix(Matrix matrix) {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (mRenderer.source != null) {
+ final int rotation = mRenderer.source.getRotation();
+ final boolean swap = !(rotation % 180 == 0);
+ final int width = swap ? mRenderer.source.getImageHeight()
+ : mRenderer.source.getImageWidth();
+ final int height = swap ? mRenderer.source.getImageWidth()
+ : mRenderer.source.getImageHeight();
+ mTempRectF.set(0, 0, width, height);
+ matrix.mapRect(mTempRectF);
+ matrix.getValues(mValues);
+ int cx = width / 2;
+ int cy = height / 2;
+ float scale = mValues[Matrix.MSCALE_X];
+ int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
+ int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
+ if (rotation == 90 || rotation == 180) {
+ cx += (mTempRectF.left / scale) - xoffset;
+ } else {
+ cx -= (mTempRectF.left / scale) - xoffset;
+ }
+ if (rotation == 180 || rotation == 270) {
+ cy += (mTempRectF.top / scale) - yoffset;
+ } else {
+ cy -= (mTempRectF.top / scale) - yoffset;
+ }
+ mRenderer.scale = scale;
+ mRenderer.centerX = swap ? cy : cx;
+ mRenderer.centerY = swap ? cx : cy;
+ invalidate();
+ }
+ }
+
+ private class TileRenderer implements Renderer {
+
+ private GLES20Canvas mCanvas;
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ mCanvas = new GLES20Canvas();
+ BasicTexture.invalidateAllTextures();
+ mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ mCanvas.setSize(width, height);
+ mRenderer.image.setViewSize(width, height);
+ }
+
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ mCanvas.clearBuffer();
+ Runnable readyCallback;
+ synchronized (mLock) {
+ readyCallback = mRenderer.isReadyCallback;
+ mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
+ mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
+ mRenderer.scale);
+ }
+ boolean complete = mRenderer.image.draw(mCanvas);
+ if (complete && readyCallback != null) {
+ synchronized (mLock) {
+ // Make sure we don't trample on a newly set callback/source
+ // if it changed while we were rendering
+ if (mRenderer.isReadyCallback == readyCallback) {
+ mRenderer.isReadyCallback = null;
+ }
+ }
+ if (readyCallback != null) {
+ post(readyCallback);
+ }
+ }
+ }
+
+ }
+
+ @SuppressWarnings("unused")
+ private static class ColoredTiles implements TileSource {
+ private static final int[] COLORS = new int[] {
+ Color.RED,
+ Color.BLUE,
+ Color.YELLOW,
+ Color.GREEN,
+ Color.CYAN,
+ Color.MAGENTA,
+ Color.WHITE,
+ };
+
+ private Paint mPaint = new Paint();
+ private Canvas mCanvas = new Canvas();
+
+ @Override
+ public int getTileSize() {
+ return 256;
+ }
+
+ @Override
+ public int getImageWidth() {
+ return 16384;
+ }
+
+ @Override
+ public int getImageHeight() {
+ return 8192;
+ }
+
+ @Override
+ public int getRotation() {
+ return 0;
+ }
+
+ @Override
+ public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+ int tileSize = getTileSize();
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(tileSize, tileSize,
+ Bitmap.Config.ARGB_8888);
+ }
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawColor(COLORS[level]);
+ mPaint.setColor(Color.BLACK);
+ mPaint.setTextSize(20);
+ mPaint.setTextAlign(Align.CENTER);
+ mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
+ tileSize <<= level;
+ x /= tileSize;
+ y /= tileSize;
+ mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
+ mCanvas.setBitmap(null);
+ return bitmap;
+ }
+
+ @Override
+ public BasicTexture getPreview() {
+ return null;
+ }
+ }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..762a52b
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2011 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.
+#
+#LOCAL_PATH := $(call my-dir)
+#include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/stress/Android.mk b/tests/stress/Android.mk
new file mode 100644
index 0000000..68289bd
--- /dev/null
+++ b/tests/stress/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2011 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := LauncherRotationStressTest
+
+LOCAL_CERTIFICATE := shared
+
+LOCAL_INSTRUMENTATION_FOR := Launcher2
+
+include $(BUILD_PACKAGE)
diff --git a/tests/stress/AndroidManifest.xml b/tests/stress/AndroidManifest.xml
new file mode 100644
index 0000000..bcca1ff
--- /dev/null
+++ b/tests/stress/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="2.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.launcher3.stress.launcherrotation">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.launcher3"
+ android:label="Rotation stress test using Launcher2">
+ </instrumentation>
+</manifest>
diff --git a/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java b/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java
new file mode 100644
index 0000000..a5b85eb
--- /dev/null
+++ b/tests/stress/src/com/android/launcher3/stress/LauncherRotationStressTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 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.stress;
+
+
+import com.android.launcher3.Launcher;
+
+import android.content.pm.ActivityInfo;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.RepetitiveTest;
+import android.util.Log;
+
+/**
+ * Run rotation stress test using Launcher2 for 50 iterations.
+ */
+public class LauncherRotationStressTest extends ActivityInstrumentationTestCase2<Launcher> {
+
+ private static final int NUM_ITERATIONS = 50;
+ private static final int WAIT_TIME_MS = 500;
+ private static final String LOG_TAG = "LauncherRotationStressTest";
+
+ public LauncherRotationStressTest() {
+ super(Launcher.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @RepetitiveTest(numIterations=NUM_ITERATIONS)
+ public void testLauncherRotationStress() throws Exception {
+ Launcher launcher = getActivity();
+ getInstrumentation().waitForIdleSync();
+ SystemClock.sleep(WAIT_TIME_MS);
+ launcher.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ getInstrumentation().waitForIdleSync();
+ SystemClock.sleep(WAIT_TIME_MS);
+ launcher.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ }
+}
diff --git a/update_gallery_files.py b/update_gallery_files.py
new file mode 100644
index 0000000..ef4e8c9
--- /dev/null
+++ b/update_gallery_files.py
@@ -0,0 +1,54 @@
+# This script is used to pull the most up-to-date files from
+# Gallery into Launcher (we use some code from the Gallery
+# source). The Launcher versions have some small modifications
+# so do this with care, and be sure you are pulling from the
+# latest version of Gallery
+import os
+import sys
+files = """
+src/android/util/Pools.java
+src/com/android/gallery3d/util/IntArray.java
+src/com/android/gallery3d/common/Utils.java
+src/com/android/gallery3d/exif/ByteBufferInputStream.java
+src/com/android/gallery3d/exif/CountedDataInputStream.java
+src/com/android/gallery3d/exif/ExifData.java
+src/com/android/gallery3d/exif/ExifInterface.java
+src/com/android/gallery3d/exif/ExifInvalidFormatException.java
+src/com/android/gallery3d/exif/ExifModifier.java
+src/com/android/gallery3d/exif/ExifOutputStream.java
+src/com/android/gallery3d/exif/ExifParser.java
+src/com/android/gallery3d/exif/ExifReader.java
+src/com/android/gallery3d/exif/ExifTag.java
+src/com/android/gallery3d/exif/IfdData.java
+src/com/android/gallery3d/exif/IfdId.java
+src/com/android/gallery3d/exif/JpegHeader.java
+src/com/android/gallery3d/exif/OrderedDataOutputStream.java
+src/com/android/gallery3d/exif/Rational.java
+src/com/android/gallery3d/glrenderer/BasicTexture.java
+src/com/android/gallery3d/glrenderer/BitmapTexture.java
+src/com/android/gallery3d/glrenderer/GLCanvas.java
+src/com/android/gallery3d/glrenderer/GLES20Canvas.java
+src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
+src/com/android/gallery3d/glrenderer/GLId.java
+src/com/android/gallery3d/glrenderer/GLPaint.java
+src/com/android/gallery3d/glrenderer/RawTexture.java
+src/com/android/gallery3d/glrenderer/Texture.java
+src/com/android/gallery3d/glrenderer/UploadedTexture.java
+src/com/android/photos/BitmapRegionTileSource.java
+src/com/android/photos/views/BlockingGLTextureView.java
+src/com/android/photos/views/TiledImageRenderer.java
+src/com/android/photos/views/TiledImageView.java
+src/com/android/gallery3d/common/BitmapUtils.java
+"""
+
+if len(sys.argv) != 2:
+ print "Usage: python update_gallery_files.py <gallery_dir>"
+ exit()
+gallery_dir = sys.argv[1]
+for file_path in files.split():
+ dir = os.path.dirname(file_path)
+ if file_path.find('exif') != -1 or file_path.find('common') != -1:
+ file_path = 'gallerycommon/' + file_path
+ cmd = 'cp %s/%s %s/' % (gallery_dir, file_path, dir)
+ print cmd
+ os.system(cmd)
diff --git a/util/com/android/launcher3/DecoderRing.java b/util/com/android/launcher3/DecoderRing.java
new file mode 100644
index 0000000..1d9e0de
--- /dev/null
+++ b/util/com/android/launcher3/DecoderRing.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2013 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 com.android.launcher3.backup.BackupProtos.CheckedMessage;
+import com.android.launcher3.backup.BackupProtos.Favorite;
+import com.android.launcher3.backup.BackupProtos.Key;
+import com.android.launcher3.backup.BackupProtos.Journal;
+import com.android.launcher3.backup.BackupProtos.Resource;
+import com.android.launcher3.backup.BackupProtos.Screen;
+import com.android.launcher3.backup.BackupProtos.Widget;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.System;
+import java.util.zip.CRC32;
+
+/**
+ * Commandline utility for decoding protos written to the android logs during debugging.
+ *
+ * base64 -D icon.log > icon.bin
+ * java -classpath $ANDROID_HOST_OUT/framework/protoutil.jar:$ANDROID_HOST_OUT/../common/obj/JAVA_LIBRARIES/host-libprotobuf-java-2.3.0-nano_intermediates/javalib.jar \
+ * com.android.launcher3.DecoderRing -i icon.bin
+ *
+ * TODO: write a wrapper to setup the classpath
+ */
+class DecoderRing {
+ public static void main(String[ ] args)
+ throws Exception {
+ File source = null;
+ Class type = Key.class;
+ int skip = 0;
+
+ for (int i = 0; i < args.length; i++) {
+ if ("-k".equals(args[i])) {
+ type = Key.class;
+ } else if ("-f".equals(args[i])) {
+ type = Favorite.class;
+ } else if ("-j".equals(args[i])) {
+ type = Journal.class;
+ } else if ("-i".equals(args[i])) {
+ type = Resource.class;
+ } else if ("-s".equals(args[i])) {
+ type = Screen.class;
+ } else if ("-w".equals(args[i])) {
+ type = Widget.class;
+ } else if ("-S".equals(args[i])) {
+ if ((i + 1) < args.length) {
+ skip = Integer.valueOf(args[++i]);
+ } else {
+ usage(args);
+ }
+ } else if (args[i] != null && !args[i].startsWith("-")) {
+ source = new File(args[i]);
+ } else {
+ System.err.println("Unsupported flag: " + args[i]);
+ usage(args);
+ }
+ }
+
+
+ // read in the bytes
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ BufferedInputStream input = null;
+ if (source == null) {
+ input = new BufferedInputStream(System.in);
+ } else {
+ try {
+ input = new BufferedInputStream(new FileInputStream(source));
+ } catch (FileNotFoundException e) {
+ System.err.println("failed to open file: " + source + ", " + e);
+ System.exit(1);
+ }
+ }
+ byte[] buffer = new byte[1024];
+ try {
+ while (input.available() > 0) {
+ int n = input.read(buffer);
+ int offset = 0;
+ if (skip > 0) {
+ offset = Math.min(skip, n);
+ n -= offset;
+ skip -= offset;
+ }
+ if (n > 0) {
+ byteStream.write(buffer, offset, n);
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("failed to read input: " + e);
+ System.exit(1);
+ }
+ System.err.println("read this many bytes: " + byteStream.size());
+
+ MessageNano proto = null;
+ if (type == Key.class) {
+ Key key = new Key();
+ try {
+ key = Key.parseFrom(byteStream.toByteArray());
+ } catch (InvalidProtocolBufferNanoException e) {
+ System.err.println("failed to parse proto: " + e);
+ System.exit(1);
+ }
+ // keys are self-checked
+ if (key.checksum != checkKey(key)) {
+ System.err.println("key ckecksum failed");
+ System.exit(1);
+ }
+ proto = key;
+ } else {
+ // other types are wrapped in a checksum message
+ CheckedMessage wrapper = new CheckedMessage();
+ try {
+ MessageNano.mergeFrom(wrapper, byteStream.toByteArray());
+ } catch (InvalidProtocolBufferNanoException e) {
+ System.err.println("failed to parse wrapper: " + e);
+ System.exit(1);
+ }
+ CRC32 checksum = new CRC32();
+ checksum.update(wrapper.payload);
+ if (wrapper.checksum != checksum.getValue()) {
+ System.err.println("wrapper ckecksum failed");
+ System.exit(1);
+ }
+ // decode the actual message
+ proto = (MessageNano) type.newInstance();
+ try {
+ MessageNano.mergeFrom(proto, wrapper.payload);
+ } catch (InvalidProtocolBufferNanoException e) {
+ System.err.println("failed to parse proto: " + e);
+ System.exit(1);
+ }
+ }
+
+ // Generic string output
+ System.out.println(proto.toString());
+
+ // save off the icon bits in a file for inspection
+ if (proto instanceof Resource) {
+ Resource icon = (Resource) proto;
+ final String path = "icon.webp";
+ FileOutputStream iconFile = new FileOutputStream(path);
+ iconFile.write(icon.data);
+ iconFile.close();
+ System.err.println("wrote " + path);
+ }
+
+ // save off the widget icon and preview bits in files for inspection
+ if (proto instanceof Widget) {
+ Widget widget = (Widget) proto;
+ if (widget.icon != null) {
+ final String path = "widget_icon.webp";
+ FileOutputStream iconFile = new FileOutputStream(path);
+ iconFile.write(widget.icon.data);
+ iconFile.close();
+ System.err.println("wrote " + path);
+ }
+ if (widget.preview != null) {
+ final String path = "widget_preview.webp";
+ FileOutputStream iconFile = new FileOutputStream(path);
+ iconFile.write(widget.preview.data);
+ iconFile.close();
+ System.err.println("wrote " + path);
+ }
+ }
+
+ // success
+ System.exit(0);
+ }
+
+ private static long checkKey(Key key) {
+ CRC32 checksum = new CRC32();
+ checksum.update(key.type);
+ checksum.update((int) (key.id & 0xffff));
+ checksum.update((int) ((key.id >> 32) & 0xffff));
+ if (key.name != null && key.name.length() > 0) {
+ checksum.update(key.name.getBytes());
+ }
+ return checksum.getValue();
+ }
+
+ private static void usage(String[] args) {
+ System.err.println("DecoderRing type [input]");
+ System.err.println("\t-k\tdecode a key");
+ System.err.println("\t-f\tdecode a favorite");
+ System.err.println("\t-i\tdecode a icon");
+ System.err.println("\t-s\tdecode a screen");
+ System.err.println("\t-w\tdecode a widget");
+ System.err.println("\t-s b\tskip b bytes");
+ System.err.println("\tfilename\tread from filename, not stdin");
+ System.exit(1);
+ }
+}
\ No newline at end of file