diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..f830c66eb2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+db_files
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000000..3bd20a7cd1
--- /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 0000000000..901b63838d
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000000..c1323951be
--- /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 0000000000..e69de29bb2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000000..c5b1efa7aa
--- /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 0000000000..a88779261e
--- /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 0000000000..05237d0b99
--- /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 -- prints a launcher.db"
+ print "usage: print_db.py -- 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("""shortcut""" % (
+ cgi.escape(cell, True)
+ ))
+
+
+def print_icon(out, id, i, cell):
+ if cell:
+ icon_fn = "icon_%d.png" % id
+ out.write("""""" % ( 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("
")
+
+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("""
+
+
+
+
+""")
+
+ # Data table
+ out.write("Favorites table \n")
+ out.write("""
+
+
+""")
+ 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("""
%s
+""" % ( col ))
+ out.write("""
+
+""")
+
+ for row in rows:
+ out.write("""
+""")
+ for i in range(0,len(row)):
+ cell = row[i]
+ # row[0] is always _id
+ out.write("""
\n")
+ for screen in screenData:
+ render_screen_info(out, screen)
+ out.write("
\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(" Hotseat \n")
+ out.write("
\n")
+ for cell in hotseat:
+ render_cell_info(out, cell, None)
+ out.write("
\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(" Screen %d \n" % i)
+ out.write("
\n")
+ for m in screen:
+ out.write("
\n")
+ for cell in m:
+ render_cell_info(out, cell, occupied)
+ out.write("
\n")
+ out.write("
\n")
+ i=i+1
+
+ out.write("""
+
+
+""")
+
+ 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 0000000000..9b59b217ec
--- /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 0000000000..7ba293702b
--- /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 0000000000..4fa9847aa8
--- /dev/null
+++ b/res/anim/fade_in_fast.xml
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml
new file mode 100644
index 0000000000..a061a6ca9a
--- /dev/null
+++ b/res/anim/fade_out_fast.xml
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/res/drawable-hdpi/apps_customize_bg.png b/res/drawable-hdpi/apps_customize_bg.png
new file mode 100644
index 0000000000..284746796c
Binary files /dev/null and b/res/drawable-hdpi/apps_customize_bg.png 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 0000000000..4da3195d46
Binary files /dev/null and b/res/drawable-hdpi/bg_appwidget_error.9.png differ
diff --git a/res/drawable-hdpi/bg_cling1.png b/res/drawable-hdpi/bg_cling1.png
new file mode 100644
index 0000000000..0e15532992
Binary files /dev/null and b/res/drawable-hdpi/bg_cling1.png differ
diff --git a/res/drawable-hdpi/bg_cling2.png b/res/drawable-hdpi/bg_cling2.png
new file mode 100644
index 0000000000..e65d9a21cc
Binary files /dev/null and b/res/drawable-hdpi/bg_cling2.png differ
diff --git a/res/drawable-hdpi/bg_cling3.png b/res/drawable-hdpi/bg_cling3.png
new file mode 100644
index 0000000000..ea71fbdb31
Binary files /dev/null and b/res/drawable-hdpi/bg_cling3.png differ
diff --git a/res/drawable-hdpi/bg_cling4.png b/res/drawable-hdpi/bg_cling4.png
new file mode 100644
index 0000000000..94036677eb
Binary files /dev/null and b/res/drawable-hdpi/bg_cling4.png differ
diff --git a/res/drawable-hdpi/cling.9.png b/res/drawable-hdpi/cling.9.png
new file mode 100644
index 0000000000..36fbfc8b6d
Binary files /dev/null and b/res/drawable-hdpi/cling.9.png differ
diff --git a/res/drawable-hdpi/cling_arrow_down.png b/res/drawable-hdpi/cling_arrow_down.png
new file mode 100644
index 0000000000..4f521eadc5
Binary files /dev/null and b/res/drawable-hdpi/cling_arrow_down.png differ
diff --git a/res/drawable-hdpi/cling_arrow_left.png b/res/drawable-hdpi/cling_arrow_left.png
new file mode 100644
index 0000000000..13764c9e2c
Binary files /dev/null and b/res/drawable-hdpi/cling_arrow_left.png differ
diff --git a/res/drawable-hdpi/cling_arrow_right.png b/res/drawable-hdpi/cling_arrow_right.png
new file mode 100644
index 0000000000..be522441d0
Binary files /dev/null and b/res/drawable-hdpi/cling_arrow_right.png differ
diff --git a/res/drawable-hdpi/cling_arrow_up.png b/res/drawable-hdpi/cling_arrow_up.png
new file mode 100644
index 0000000000..83b5b3783f
Binary files /dev/null and b/res/drawable-hdpi/cling_arrow_up.png differ
diff --git a/res/drawable-hdpi/cling_button.9.png b/res/drawable-hdpi/cling_button.9.png
new file mode 100644
index 0000000000..e30838241d
Binary files /dev/null and b/res/drawable-hdpi/cling_button.9.png 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 0000000000..4f9ca6f7a6
Binary files /dev/null and b/res/drawable-hdpi/cling_button_pressed.9.png differ
diff --git a/res/drawable-hdpi/custom_content_page.png b/res/drawable-hdpi/custom_content_page.png
new file mode 100644
index 0000000000..9eef50c926
Binary files /dev/null and b/res/drawable-hdpi/custom_content_page.png differ
diff --git a/res/drawable-hdpi/focused_bg.9.png b/res/drawable-hdpi/focused_bg.9.png
new file mode 100644
index 0000000000..2925ae8586
Binary files /dev/null and b/res/drawable-hdpi/focused_bg.9.png differ
diff --git a/res/drawable-hdpi/hand.png b/res/drawable-hdpi/hand.png
new file mode 100644
index 0000000000..bd4f6dfbc0
Binary files /dev/null and b/res/drawable-hdpi/hand.png differ
diff --git a/res/drawable-hdpi/home_press.9.png b/res/drawable-hdpi/home_press.9.png
new file mode 100644
index 0000000000..ef07011f82
Binary files /dev/null and b/res/drawable-hdpi/home_press.9.png differ
diff --git a/res/drawable-hdpi/ic_actionbar_accept.png b/res/drawable-hdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000000..53cf6877eb
Binary files /dev/null and b/res/drawable-hdpi/ic_actionbar_accept.png differ
diff --git a/res/drawable-hdpi/ic_allapps.png b/res/drawable-hdpi/ic_allapps.png
new file mode 100644
index 0000000000..e7677d5d22
Binary files /dev/null and b/res/drawable-hdpi/ic_allapps.png differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed.png b/res/drawable-hdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000000..863eebaa52
Binary files /dev/null and b/res/drawable-hdpi/ic_allapps_pressed.png 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 0000000000..1dc02d5e41
Binary files /dev/null and b/res/drawable-hdpi/ic_home_all_apps_holo_dark.png 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 0000000000..3f64d68234
Binary files /dev/null and b/res/drawable-hdpi/ic_home_search_normal_holo.png 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 0000000000..dae54464a8
Binary files /dev/null and b/res/drawable-hdpi/ic_home_voice_search_holo.png differ
diff --git a/res/drawable-hdpi/ic_images.png b/res/drawable-hdpi/ic_images.png
new file mode 100644
index 0000000000..0003c6c65e
Binary files /dev/null and b/res/drawable-hdpi/ic_images.png 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 0000000000..cdd0052b31
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_clear_active_holo.png 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 0000000000..84549ff946
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_clear_normal_holo.png 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 0000000000..c534e56702
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_info_active_holo.png 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 0000000000..c9bcd7f374
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_info_normal_holo.png 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 0000000000..dc7825114d
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_market_holo.png 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 0000000000..82b1b59172
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_trashcan_active_holo.png 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 0000000000..3fc2e8347b
Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_trashcan_normal_holo.png differ
diff --git a/res/drawable-hdpi/ic_pageindicator_add.png b/res/drawable-hdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000000..c37d622e59
Binary files /dev/null and b/res/drawable-hdpi/ic_pageindicator_add.png differ
diff --git a/res/drawable-hdpi/ic_pageindicator_current.png b/res/drawable-hdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000000..aac8d406d2
Binary files /dev/null and b/res/drawable-hdpi/ic_pageindicator_current.png differ
diff --git a/res/drawable-hdpi/ic_pageindicator_default.png b/res/drawable-hdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000000..bafd94b87b
Binary files /dev/null and b/res/drawable-hdpi/ic_pageindicator_default.png differ
diff --git a/res/drawable-hdpi/ic_setting.png b/res/drawable-hdpi/ic_setting.png
new file mode 100644
index 0000000000..c617154f10
Binary files /dev/null and b/res/drawable-hdpi/ic_setting.png differ
diff --git a/res/drawable-hdpi/ic_setting_pressed.png b/res/drawable-hdpi/ic_setting_pressed.png
new file mode 100644
index 0000000000..fb58a4b12a
Binary files /dev/null and b/res/drawable-hdpi/ic_setting_pressed.png differ
diff --git a/res/drawable-hdpi/ic_wallpaper.png b/res/drawable-hdpi/ic_wallpaper.png
new file mode 100644
index 0000000000..5e5d1186bc
Binary files /dev/null and b/res/drawable-hdpi/ic_wallpaper.png differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed.png b/res/drawable-hdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000000..d104e57707
Binary files /dev/null and b/res/drawable-hdpi/ic_wallpaper_pressed.png differ
diff --git a/res/drawable-hdpi/ic_widget.png b/res/drawable-hdpi/ic_widget.png
new file mode 100644
index 0000000000..8c57af0de7
Binary files /dev/null and b/res/drawable-hdpi/ic_widget.png differ
diff --git a/res/drawable-hdpi/ic_widget_pressed.png b/res/drawable-hdpi/ic_widget_pressed.png
new file mode 100644
index 0000000000..081f9f9ce7
Binary files /dev/null and b/res/drawable-hdpi/ic_widget_pressed.png 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 0000000000..aaf43c73ab
Binary files /dev/null and b/res/drawable-hdpi/overscroll_glow_left.9.png 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 0000000000..d03486441a
Binary files /dev/null and b/res/drawable-hdpi/overscroll_glow_right.9.png 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 0000000000..8a1aa5fe85
Binary files /dev/null and b/res/drawable-hdpi/page_hover_left_holo.9.png 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 0000000000..abf8f51731
Binary files /dev/null and b/res/drawable-hdpi/page_hover_right_holo.9.png 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 0000000000..5b47f44771
Binary files /dev/null and b/res/drawable-hdpi/paged_view_indicator.9.png 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 0000000000..ba8a4a8cd0
Binary files /dev/null and b/res/drawable-hdpi/portal_container_holo.9.png 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 0000000000..857a01ec47
Binary files /dev/null and b/res/drawable-hdpi/portal_ring_inner_holo.png 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 0000000000..53df36a43d
Binary files /dev/null and b/res/drawable-hdpi/portal_ring_inner_nolip_holo.png 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 0000000000..b711cf3502
Binary files /dev/null and b/res/drawable-hdpi/portal_ring_outer_holo.png differ
diff --git a/res/drawable-hdpi/portal_ring_rest.png b/res/drawable-hdpi/portal_ring_rest.png
new file mode 100644
index 0000000000..2979b736bc
Binary files /dev/null and b/res/drawable-hdpi/portal_ring_rest.png differ
diff --git a/res/drawable-hdpi/screenpanel.9.png b/res/drawable-hdpi/screenpanel.9.png
new file mode 100644
index 0000000000..36e7dfd9cf
Binary files /dev/null and b/res/drawable-hdpi/screenpanel.9.png differ
diff --git a/res/drawable-hdpi/screenpanel_hover.9.png b/res/drawable-hdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000000..3321fc9255
Binary files /dev/null and b/res/drawable-hdpi/screenpanel_hover.9.png differ
diff --git a/res/drawable-hdpi/search_frame.9.png b/res/drawable-hdpi/search_frame.9.png
new file mode 100644
index 0000000000..15ca1f4e65
Binary files /dev/null and b/res/drawable-hdpi/search_frame.9.png 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 0000000000..673e3bf10d
Binary files /dev/null and b/res/drawable-hdpi/tab_selected_focused_holo.9.png 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 0000000000..d57df98b50
Binary files /dev/null and b/res/drawable-hdpi/tab_selected_holo.9.png 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 0000000000..4b312d9e34
Binary files /dev/null and b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png 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 0000000000..6278eef472
Binary files /dev/null and b/res/drawable-hdpi/tab_selected_pressed_holo.9.png 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 0000000000..294991d793
Binary files /dev/null and b/res/drawable-hdpi/tab_unselected_focused_holo.9.png 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 0000000000..19532ab10d
Binary files /dev/null and b/res/drawable-hdpi/tab_unselected_holo.9.png 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 0000000000..5140b35237
Binary files /dev/null and b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png 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 0000000000..aadc6f87b2
Binary files /dev/null and b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png 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 0000000000..c72d6a2c62
Binary files /dev/null and b/res/drawable-hdpi/tile_picker_focused.9.png 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 0000000000..44c65ac3ca
Binary files /dev/null and b/res/drawable-hdpi/tile_picker_pressed.9.png 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 0000000000..461bacbcb1
Binary files /dev/null and b/res/drawable-hdpi/tile_picker_selected.9.png 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 0000000000..e80558badb
Binary files /dev/null and b/res/drawable-hdpi/tile_shadow_bottom.9.png 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 0000000000..7e93865e74
Binary files /dev/null and b/res/drawable-hdpi/tile_shadow_top.9.png 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 0000000000..8c15a7c86e
Binary files /dev/null and b/res/drawable-hdpi/widget_container_holo.9.png 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 0000000000..2d6fcf51f4
Binary files /dev/null and b/res/drawable-hdpi/widget_resize_frame_holo.9.png 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 0000000000..f0afd6172b
Binary files /dev/null and b/res/drawable-hdpi/widget_resize_handle_bottom.png 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 0000000000..47613b29f9
Binary files /dev/null and b/res/drawable-hdpi/widget_resize_handle_left.png 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 0000000000..acc28be839
Binary files /dev/null and b/res/drawable-hdpi/widget_resize_handle_right.png 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 0000000000..2c60be00c8
Binary files /dev/null and b/res/drawable-hdpi/widget_resize_handle_top.png differ
diff --git a/res/drawable-hdpi/widget_tile.png b/res/drawable-hdpi/widget_tile.png
new file mode 100644
index 0000000000..310ff8b3df
Binary files /dev/null and b/res/drawable-hdpi/widget_tile.png differ
diff --git a/res/drawable-hdpi/workspace_bg.9.png b/res/drawable-hdpi/workspace_bg.9.png
new file mode 100644
index 0000000000..5bbfa4fff6
Binary files /dev/null and b/res/drawable-hdpi/workspace_bg.9.png differ
diff --git a/res/drawable-land-hdpi/bg_cling1.png b/res/drawable-land-hdpi/bg_cling1.png
new file mode 100644
index 0000000000..7123c5ca7f
Binary files /dev/null and b/res/drawable-land-hdpi/bg_cling1.png differ
diff --git a/res/drawable-land-hdpi/bg_cling2.png b/res/drawable-land-hdpi/bg_cling2.png
new file mode 100644
index 0000000000..889b6274ee
Binary files /dev/null and b/res/drawable-land-hdpi/bg_cling2.png differ
diff --git a/res/drawable-land-hdpi/bg_cling3.png b/res/drawable-land-hdpi/bg_cling3.png
new file mode 100644
index 0000000000..4ff338c13d
Binary files /dev/null and b/res/drawable-land-hdpi/bg_cling3.png 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 0000000000..5a7fc99e45
Binary files /dev/null and b/res/drawable-land-hdpi/ic_home_voice_search_holo.png 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 0000000000..1a58144aaf
Binary files /dev/null and b/res/drawable-land-hdpi/workspace_bg.9.png differ
diff --git a/res/drawable-land-mdpi/bg_cling1.png b/res/drawable-land-mdpi/bg_cling1.png
new file mode 100644
index 0000000000..f5faeb4396
Binary files /dev/null and b/res/drawable-land-mdpi/bg_cling1.png differ
diff --git a/res/drawable-land-mdpi/bg_cling2.png b/res/drawable-land-mdpi/bg_cling2.png
new file mode 100644
index 0000000000..963967d305
Binary files /dev/null and b/res/drawable-land-mdpi/bg_cling2.png differ
diff --git a/res/drawable-land-mdpi/bg_cling3.png b/res/drawable-land-mdpi/bg_cling3.png
new file mode 100644
index 0000000000..921831af94
Binary files /dev/null and b/res/drawable-land-mdpi/bg_cling3.png 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 0000000000..ee7dde52cf
Binary files /dev/null and b/res/drawable-land-mdpi/ic_home_voice_search_holo.png 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 0000000000..a12519ebdc
Binary files /dev/null and b/res/drawable-land-mdpi/workspace_bg.9.png differ
diff --git a/res/drawable-land-xhdpi/bg_cling1.png b/res/drawable-land-xhdpi/bg_cling1.png
new file mode 100644
index 0000000000..2282117ca1
Binary files /dev/null and b/res/drawable-land-xhdpi/bg_cling1.png differ
diff --git a/res/drawable-land-xhdpi/bg_cling2.png b/res/drawable-land-xhdpi/bg_cling2.png
new file mode 100644
index 0000000000..5243889be4
Binary files /dev/null and b/res/drawable-land-xhdpi/bg_cling2.png differ
diff --git a/res/drawable-land-xhdpi/bg_cling3.png b/res/drawable-land-xhdpi/bg_cling3.png
new file mode 100644
index 0000000000..08475f732e
Binary files /dev/null and b/res/drawable-land-xhdpi/bg_cling3.png 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 0000000000..56bbbbb06d
Binary files /dev/null and b/res/drawable-land-xhdpi/ic_home_voice_search_holo.png 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 0000000000..ce414549a0
Binary files /dev/null and b/res/drawable-land-xhdpi/workspace_bg.9.png differ
diff --git a/res/drawable-land-xxhdpi/bg_cling1.png b/res/drawable-land-xxhdpi/bg_cling1.png
new file mode 100644
index 0000000000..78943f01c0
Binary files /dev/null and b/res/drawable-land-xxhdpi/bg_cling1.png differ
diff --git a/res/drawable-land-xxhdpi/bg_cling2.png b/res/drawable-land-xxhdpi/bg_cling2.png
new file mode 100644
index 0000000000..98b65682b9
Binary files /dev/null and b/res/drawable-land-xxhdpi/bg_cling2.png differ
diff --git a/res/drawable-land-xxhdpi/bg_cling3.png b/res/drawable-land-xxhdpi/bg_cling3.png
new file mode 100644
index 0000000000..e249fe598b
Binary files /dev/null and b/res/drawable-land-xxhdpi/bg_cling3.png 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 0000000000..6ea7368bec
Binary files /dev/null and b/res/drawable-land-xxhdpi/ic_home_voice_search_holo.png 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 0000000000..b0b456198c
Binary files /dev/null and b/res/drawable-land-xxhdpi/workspace_bg.9.png differ
diff --git a/res/drawable-mdpi/apps_customize_bg.png b/res/drawable-mdpi/apps_customize_bg.png
new file mode 100644
index 0000000000..284746796c
Binary files /dev/null and b/res/drawable-mdpi/apps_customize_bg.png 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 0000000000..493c0d454c
Binary files /dev/null and b/res/drawable-mdpi/bg_appwidget_error.9.png differ
diff --git a/res/drawable-mdpi/bg_cling1.png b/res/drawable-mdpi/bg_cling1.png
new file mode 100644
index 0000000000..f284412a78
Binary files /dev/null and b/res/drawable-mdpi/bg_cling1.png differ
diff --git a/res/drawable-mdpi/bg_cling2.png b/res/drawable-mdpi/bg_cling2.png
new file mode 100644
index 0000000000..0052dc29db
Binary files /dev/null and b/res/drawable-mdpi/bg_cling2.png differ
diff --git a/res/drawable-mdpi/bg_cling3.png b/res/drawable-mdpi/bg_cling3.png
new file mode 100644
index 0000000000..fabdf7a0d8
Binary files /dev/null and b/res/drawable-mdpi/bg_cling3.png differ
diff --git a/res/drawable-mdpi/bg_cling4.png b/res/drawable-mdpi/bg_cling4.png
new file mode 100644
index 0000000000..2f152f4dbc
Binary files /dev/null and b/res/drawable-mdpi/bg_cling4.png differ
diff --git a/res/drawable-mdpi/bg_cling5.png b/res/drawable-mdpi/bg_cling5.png
new file mode 100644
index 0000000000..e094809b41
Binary files /dev/null and b/res/drawable-mdpi/bg_cling5.png differ
diff --git a/res/drawable-mdpi/cling.9.png b/res/drawable-mdpi/cling.9.png
new file mode 100644
index 0000000000..4c0f139eeb
Binary files /dev/null and b/res/drawable-mdpi/cling.9.png differ
diff --git a/res/drawable-mdpi/cling_arrow_down.png b/res/drawable-mdpi/cling_arrow_down.png
new file mode 100644
index 0000000000..58e66fbb2e
Binary files /dev/null and b/res/drawable-mdpi/cling_arrow_down.png differ
diff --git a/res/drawable-mdpi/cling_arrow_left.png b/res/drawable-mdpi/cling_arrow_left.png
new file mode 100644
index 0000000000..023c71705b
Binary files /dev/null and b/res/drawable-mdpi/cling_arrow_left.png differ
diff --git a/res/drawable-mdpi/cling_arrow_right.png b/res/drawable-mdpi/cling_arrow_right.png
new file mode 100644
index 0000000000..cf0eb1047c
Binary files /dev/null and b/res/drawable-mdpi/cling_arrow_right.png differ
diff --git a/res/drawable-mdpi/cling_arrow_up.png b/res/drawable-mdpi/cling_arrow_up.png
new file mode 100644
index 0000000000..9b0e6b7ae4
Binary files /dev/null and b/res/drawable-mdpi/cling_arrow_up.png differ
diff --git a/res/drawable-mdpi/cling_button.9.png b/res/drawable-mdpi/cling_button.9.png
new file mode 100644
index 0000000000..a0b6f97352
Binary files /dev/null and b/res/drawable-mdpi/cling_button.9.png 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 0000000000..986e669443
Binary files /dev/null and b/res/drawable-mdpi/cling_button_pressed.9.png differ
diff --git a/res/drawable-mdpi/custom_content_page.png b/res/drawable-mdpi/custom_content_page.png
new file mode 100644
index 0000000000..cc4005ded6
Binary files /dev/null and b/res/drawable-mdpi/custom_content_page.png differ
diff --git a/res/drawable-mdpi/focused_bg.9.png b/res/drawable-mdpi/focused_bg.9.png
new file mode 100644
index 0000000000..89c29ac516
Binary files /dev/null and b/res/drawable-mdpi/focused_bg.9.png differ
diff --git a/res/drawable-mdpi/hand.png b/res/drawable-mdpi/hand.png
new file mode 100644
index 0000000000..fe5a035dee
Binary files /dev/null and b/res/drawable-mdpi/hand.png differ
diff --git a/res/drawable-mdpi/home_press.9.png b/res/drawable-mdpi/home_press.9.png
new file mode 100644
index 0000000000..679a1f6269
Binary files /dev/null and b/res/drawable-mdpi/home_press.9.png differ
diff --git a/res/drawable-mdpi/ic_actionbar_accept.png b/res/drawable-mdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000000..35cda8e112
Binary files /dev/null and b/res/drawable-mdpi/ic_actionbar_accept.png differ
diff --git a/res/drawable-mdpi/ic_allapps.png b/res/drawable-mdpi/ic_allapps.png
new file mode 100644
index 0000000000..e0fd9c07a7
Binary files /dev/null and b/res/drawable-mdpi/ic_allapps.png differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed.png b/res/drawable-mdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000000..3bd87b187f
Binary files /dev/null and b/res/drawable-mdpi/ic_allapps_pressed.png 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 0000000000..84fa5947da
Binary files /dev/null and b/res/drawable-mdpi/ic_home_all_apps_holo_dark.png 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 0000000000..7367c380c1
Binary files /dev/null and b/res/drawable-mdpi/ic_home_search_normal_holo.png 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 0000000000..f211a7b9c2
Binary files /dev/null and b/res/drawable-mdpi/ic_home_voice_search_holo.png differ
diff --git a/res/drawable-mdpi/ic_images.png b/res/drawable-mdpi/ic_images.png
new file mode 100644
index 0000000000..aabc1237a6
Binary files /dev/null and b/res/drawable-mdpi/ic_images.png 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 0000000000..2683beaa3b
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_clear_active_holo.png 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 0000000000..219f3e5cfd
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_clear_normal_holo.png 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 0000000000..f84b4a6ba6
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_info_active_holo.png 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 0000000000..eac578f5ce
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_info_normal_holo.png 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 0000000000..cacb374841
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_market_holo.png 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 0000000000..0350e558d9
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_trashcan_active_holo.png 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 0000000000..799b62f8bc
Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_trashcan_normal_holo.png differ
diff --git a/res/drawable-mdpi/ic_pageindicator_add.png b/res/drawable-mdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000000..8e05e64f06
Binary files /dev/null and b/res/drawable-mdpi/ic_pageindicator_add.png differ
diff --git a/res/drawable-mdpi/ic_pageindicator_current.png b/res/drawable-mdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000000..ab5f4c82f3
Binary files /dev/null and b/res/drawable-mdpi/ic_pageindicator_current.png differ
diff --git a/res/drawable-mdpi/ic_pageindicator_default.png b/res/drawable-mdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000000..c919ee8359
Binary files /dev/null and b/res/drawable-mdpi/ic_pageindicator_default.png differ
diff --git a/res/drawable-mdpi/ic_setting.png b/res/drawable-mdpi/ic_setting.png
new file mode 100644
index 0000000000..0c8ae9d65d
Binary files /dev/null and b/res/drawable-mdpi/ic_setting.png differ
diff --git a/res/drawable-mdpi/ic_setting_icn.png b/res/drawable-mdpi/ic_setting_icn.png
new file mode 100644
index 0000000000..5c32c54da9
Binary files /dev/null and b/res/drawable-mdpi/ic_setting_icn.png differ
diff --git a/res/drawable-mdpi/ic_setting_pressed.png b/res/drawable-mdpi/ic_setting_pressed.png
new file mode 100644
index 0000000000..846091f58b
Binary files /dev/null and b/res/drawable-mdpi/ic_setting_pressed.png differ
diff --git a/res/drawable-mdpi/ic_wallpaper.png b/res/drawable-mdpi/ic_wallpaper.png
new file mode 100644
index 0000000000..333a206e2f
Binary files /dev/null and b/res/drawable-mdpi/ic_wallpaper.png differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed.png b/res/drawable-mdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000000..273c48b3a2
Binary files /dev/null and b/res/drawable-mdpi/ic_wallpaper_pressed.png differ
diff --git a/res/drawable-mdpi/ic_widget.png b/res/drawable-mdpi/ic_widget.png
new file mode 100644
index 0000000000..5f974c28c1
Binary files /dev/null and b/res/drawable-mdpi/ic_widget.png differ
diff --git a/res/drawable-mdpi/ic_widget_pressed.png b/res/drawable-mdpi/ic_widget_pressed.png
new file mode 100644
index 0000000000..0a3e8838f6
Binary files /dev/null and b/res/drawable-mdpi/ic_widget_pressed.png 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 0000000000..b79cdcdc2d
Binary files /dev/null and b/res/drawable-mdpi/overscroll_glow_left.9.png 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 0000000000..1321303e13
Binary files /dev/null and b/res/drawable-mdpi/overscroll_glow_right.9.png 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 0000000000..561d3cd31b
Binary files /dev/null and b/res/drawable-mdpi/page_hover_left_holo.9.png 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 0000000000..2681f23f09
Binary files /dev/null and b/res/drawable-mdpi/page_hover_right_holo.9.png 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 0000000000..647b60f376
Binary files /dev/null and b/res/drawable-mdpi/paged_view_indicator.9.png 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 0000000000..1e4afae764
Binary files /dev/null and b/res/drawable-mdpi/portal_container_holo.9.png 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 0000000000..72e0af8f8d
Binary files /dev/null and b/res/drawable-mdpi/portal_ring_inner_holo.png 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 0000000000..483f0ebec8
Binary files /dev/null and b/res/drawable-mdpi/portal_ring_inner_nolip_holo.png 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 0000000000..e9b35f3f36
Binary files /dev/null and b/res/drawable-mdpi/portal_ring_outer_holo.png differ
diff --git a/res/drawable-mdpi/portal_ring_rest.png b/res/drawable-mdpi/portal_ring_rest.png
new file mode 100644
index 0000000000..d0a976e55d
Binary files /dev/null and b/res/drawable-mdpi/portal_ring_rest.png differ
diff --git a/res/drawable-mdpi/screenpanel.9.png b/res/drawable-mdpi/screenpanel.9.png
new file mode 100644
index 0000000000..4de3017f5d
Binary files /dev/null and b/res/drawable-mdpi/screenpanel.9.png differ
diff --git a/res/drawable-mdpi/screenpanel_hover.9.png b/res/drawable-mdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000000..dd7740665d
Binary files /dev/null and b/res/drawable-mdpi/screenpanel_hover.9.png differ
diff --git a/res/drawable-mdpi/search_frame.9.png b/res/drawable-mdpi/search_frame.9.png
new file mode 100644
index 0000000000..058905bdb4
Binary files /dev/null and b/res/drawable-mdpi/search_frame.9.png 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 0000000000..c9972e74bb
Binary files /dev/null and b/res/drawable-mdpi/tab_selected_focused_holo.9.png 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 0000000000..587337caf7
Binary files /dev/null and b/res/drawable-mdpi/tab_selected_holo.9.png 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 0000000000..284f5344a2
Binary files /dev/null and b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png 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 0000000000..155c4fc753
Binary files /dev/null and b/res/drawable-mdpi/tab_selected_pressed_holo.9.png 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 0000000000..f0cecd183a
Binary files /dev/null and b/res/drawable-mdpi/tab_unselected_focused_holo.9.png 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 0000000000..a2dbf42b74
Binary files /dev/null and b/res/drawable-mdpi/tab_unselected_holo.9.png 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 0000000000..f1a2819c50
Binary files /dev/null and b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png 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 0000000000..b1223fe3c4
Binary files /dev/null and b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png 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 0000000000..13b325b87c
Binary files /dev/null and b/res/drawable-mdpi/tile_picker_focused.9.png 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 0000000000..4e8196d369
Binary files /dev/null and b/res/drawable-mdpi/tile_picker_pressed.9.png 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 0000000000..eee69ec6da
Binary files /dev/null and b/res/drawable-mdpi/tile_picker_selected.9.png 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 0000000000..d95787bac2
Binary files /dev/null and b/res/drawable-mdpi/tile_shadow_bottom.9.png 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 0000000000..8da913cc48
Binary files /dev/null and b/res/drawable-mdpi/tile_shadow_top.9.png 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 0000000000..db24457d68
Binary files /dev/null and b/res/drawable-mdpi/widget_container_holo.9.png 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 0000000000..028bd62489
Binary files /dev/null and b/res/drawable-mdpi/widget_resize_frame_holo.9.png 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 0000000000..c838bf405a
Binary files /dev/null and b/res/drawable-mdpi/widget_resize_handle_bottom.png 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 0000000000..ff0b0d3570
Binary files /dev/null and b/res/drawable-mdpi/widget_resize_handle_left.png 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 0000000000..fc4808e3a9
Binary files /dev/null and b/res/drawable-mdpi/widget_resize_handle_right.png 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 0000000000..3b1df01702
Binary files /dev/null and b/res/drawable-mdpi/widget_resize_handle_top.png differ
diff --git a/res/drawable-mdpi/widget_tile.png b/res/drawable-mdpi/widget_tile.png
new file mode 100644
index 0000000000..1ba559d6c8
Binary files /dev/null and b/res/drawable-mdpi/widget_tile.png differ
diff --git a/res/drawable-mdpi/workspace_bg.9.png b/res/drawable-mdpi/workspace_bg.9.png
new file mode 100644
index 0000000000..2856e09851
Binary files /dev/null and b/res/drawable-mdpi/workspace_bg.9.png 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 0000000000..98f7ac8a74
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/homescreen_blue_normal_holo.9.png 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 0000000000..0a511f9df0
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/homescreen_blue_strong_holo.9.png differ
diff --git a/res/drawable-sw600dp-hdpi/ic_allapps.png b/res/drawable-sw600dp-hdpi/ic_allapps.png
new file mode 100644
index 0000000000..8bda43569b
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/ic_allapps.png 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 0000000000..07ff331837
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/ic_allapps_pressed.png 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 0000000000..3928e2c59c
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/overscroll_glow_left.9.png 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 0000000000..e34de34ac9
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/overscroll_glow_right.9.png 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 0000000000..b3be4720a6
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/portal_ring_inner_holo.png 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 0000000000..96b981c50f
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/portal_ring_inner_nolip_holo.png 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 0000000000..bc13a26cde
Binary files /dev/null and b/res/drawable-sw600dp-hdpi/portal_ring_outer_holo.png 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 0000000000..3ae4affb05
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/homescreen_blue_normal_holo.9.png 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 0000000000..31148dd527
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/homescreen_blue_strong_holo.9.png differ
diff --git a/res/drawable-sw600dp-mdpi/ic_allapps.png b/res/drawable-sw600dp-mdpi/ic_allapps.png
new file mode 100644
index 0000000000..e2afea5f39
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/ic_allapps.png 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 0000000000..d409c7ed31
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/ic_allapps_pressed.png 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 0000000000..58ec10c435
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/overscroll_glow_left.9.png 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 0000000000..a986fd3014
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/overscroll_glow_right.9.png 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 0000000000..319c074273
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/portal_ring_inner_holo.png 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 0000000000..8537714aa5
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/portal_ring_inner_nolip_holo.png 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 0000000000..365dcfce98
Binary files /dev/null and b/res/drawable-sw600dp-mdpi/portal_ring_outer_holo.png 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 0000000000..c25b590a98
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/homescreen_blue_normal_holo.9.png 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 0000000000..24ac880136
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/homescreen_blue_strong_holo.9.png differ
diff --git a/res/drawable-sw600dp-xhdpi/ic_allapps.png b/res/drawable-sw600dp-xhdpi/ic_allapps.png
new file mode 100644
index 0000000000..8fed2903ac
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/ic_allapps.png 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 0000000000..786b676ffa
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/ic_allapps_pressed.png 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 0000000000..b66dd2fe6b
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/overscroll_glow_left.9.png 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 0000000000..3ccce33021
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/overscroll_glow_right.9.png 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 0000000000..d4ce45f128
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/portal_ring_inner_holo.png 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 0000000000..9aa13c9878
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/portal_ring_inner_nolip_holo.png 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 0000000000..0106cd6e09
Binary files /dev/null and b/res/drawable-sw600dp-xhdpi/portal_ring_outer_holo.png 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 0000000000..c661f68fb9
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/homescreen_blue_normal_holo.9.png 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 0000000000..bf6ab97c8f
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/homescreen_blue_strong_holo.9.png differ
diff --git a/res/drawable-sw600dp-xxhdpi/ic_allapps.png b/res/drawable-sw600dp-xxhdpi/ic_allapps.png
new file mode 100644
index 0000000000..242965605a
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/ic_allapps.png 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 0000000000..b93a51bead
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/ic_allapps_pressed.png 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 0000000000..472c3f9215
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/overscroll_glow_left.9.png 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 0000000000..e30cc9705e
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/overscroll_glow_right.9.png 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 0000000000..65b2541974
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_holo.png 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 0000000000..506864643d
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/portal_ring_inner_nolip_holo.png 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 0000000000..662842560d
Binary files /dev/null and b/res/drawable-sw600dp-xxhdpi/portal_ring_outer_holo.png 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 0000000000..5bbfa4fff6
Binary files /dev/null and b/res/drawable-sw720dp-hdpi/workspace_bg.9.png 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 0000000000..2856e09851
Binary files /dev/null and b/res/drawable-sw720dp-mdpi/workspace_bg.9.png 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 0000000000..72269f207a
Binary files /dev/null and b/res/drawable-sw720dp-xhdpi/workspace_bg.9.png differ
diff --git a/res/drawable-xhdpi/apps_customize_bg.png b/res/drawable-xhdpi/apps_customize_bg.png
new file mode 100644
index 0000000000..a51cc112b6
Binary files /dev/null and b/res/drawable-xhdpi/apps_customize_bg.png 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 0000000000..b792cc8474
Binary files /dev/null and b/res/drawable-xhdpi/bg_appwidget_error.9.png differ
diff --git a/res/drawable-xhdpi/bg_cling1.png b/res/drawable-xhdpi/bg_cling1.png
new file mode 100644
index 0000000000..b71351a323
Binary files /dev/null and b/res/drawable-xhdpi/bg_cling1.png differ
diff --git a/res/drawable-xhdpi/bg_cling2.png b/res/drawable-xhdpi/bg_cling2.png
new file mode 100644
index 0000000000..ad78dfeb06
Binary files /dev/null and b/res/drawable-xhdpi/bg_cling2.png differ
diff --git a/res/drawable-xhdpi/bg_cling3.png b/res/drawable-xhdpi/bg_cling3.png
new file mode 100644
index 0000000000..ae04195c41
Binary files /dev/null and b/res/drawable-xhdpi/bg_cling3.png differ
diff --git a/res/drawable-xhdpi/bg_cling4.png b/res/drawable-xhdpi/bg_cling4.png
new file mode 100644
index 0000000000..f4bb83e390
Binary files /dev/null and b/res/drawable-xhdpi/bg_cling4.png differ
diff --git a/res/drawable-xhdpi/cling.9.png b/res/drawable-xhdpi/cling.9.png
new file mode 100644
index 0000000000..1cb468159f
Binary files /dev/null and b/res/drawable-xhdpi/cling.9.png differ
diff --git a/res/drawable-xhdpi/cling_arrow_down.png b/res/drawable-xhdpi/cling_arrow_down.png
new file mode 100644
index 0000000000..ee1093340b
Binary files /dev/null and b/res/drawable-xhdpi/cling_arrow_down.png differ
diff --git a/res/drawable-xhdpi/cling_arrow_left.png b/res/drawable-xhdpi/cling_arrow_left.png
new file mode 100644
index 0000000000..cffbcf3ce8
Binary files /dev/null and b/res/drawable-xhdpi/cling_arrow_left.png differ
diff --git a/res/drawable-xhdpi/cling_arrow_right.png b/res/drawable-xhdpi/cling_arrow_right.png
new file mode 100644
index 0000000000..d880d67f0a
Binary files /dev/null and b/res/drawable-xhdpi/cling_arrow_right.png differ
diff --git a/res/drawable-xhdpi/cling_arrow_up.png b/res/drawable-xhdpi/cling_arrow_up.png
new file mode 100644
index 0000000000..fd2c60c31a
Binary files /dev/null and b/res/drawable-xhdpi/cling_arrow_up.png differ
diff --git a/res/drawable-xhdpi/cling_button.9.png b/res/drawable-xhdpi/cling_button.9.png
new file mode 100644
index 0000000000..4192563b50
Binary files /dev/null and b/res/drawable-xhdpi/cling_button.9.png 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 0000000000..d3ce46977d
Binary files /dev/null and b/res/drawable-xhdpi/cling_button_pressed.9.png differ
diff --git a/res/drawable-xhdpi/custom_content_page.png b/res/drawable-xhdpi/custom_content_page.png
new file mode 100644
index 0000000000..e1da91c61b
Binary files /dev/null and b/res/drawable-xhdpi/custom_content_page.png differ
diff --git a/res/drawable-xhdpi/focused_bg.9.png b/res/drawable-xhdpi/focused_bg.9.png
new file mode 100644
index 0000000000..197a26988f
Binary files /dev/null and b/res/drawable-xhdpi/focused_bg.9.png differ
diff --git a/res/drawable-xhdpi/hand.png b/res/drawable-xhdpi/hand.png
new file mode 100644
index 0000000000..35b678c89b
Binary files /dev/null and b/res/drawable-xhdpi/hand.png differ
diff --git a/res/drawable-xhdpi/home_press.9.png b/res/drawable-xhdpi/home_press.9.png
new file mode 100644
index 0000000000..d9abfd358d
Binary files /dev/null and b/res/drawable-xhdpi/home_press.9.png differ
diff --git a/res/drawable-xhdpi/ic_actionbar_accept.png b/res/drawable-xhdpi/ic_actionbar_accept.png
new file mode 100755
index 0000000000..b52dc37012
Binary files /dev/null and b/res/drawable-xhdpi/ic_actionbar_accept.png differ
diff --git a/res/drawable-xhdpi/ic_allapps.png b/res/drawable-xhdpi/ic_allapps.png
new file mode 100644
index 0000000000..f71964c2de
Binary files /dev/null and b/res/drawable-xhdpi/ic_allapps.png differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed.png b/res/drawable-xhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000000..d678f027ef
Binary files /dev/null and b/res/drawable-xhdpi/ic_allapps_pressed.png 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 0000000000..81228d4028
Binary files /dev/null and b/res/drawable-xhdpi/ic_home_all_apps_holo_dark.png 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 0000000000..0fe1cd1332
Binary files /dev/null and b/res/drawable-xhdpi/ic_home_search_normal_holo.png 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 0000000000..1fc5cc8bb1
Binary files /dev/null and b/res/drawable-xhdpi/ic_home_voice_search_holo.png differ
diff --git a/res/drawable-xhdpi/ic_images.png b/res/drawable-xhdpi/ic_images.png
new file mode 100644
index 0000000000..3f21faf8f0
Binary files /dev/null and b/res/drawable-xhdpi/ic_images.png 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 0000000000..1a7e53ddbb
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher_clear_active_holo.png 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 0000000000..d4965d9f80
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher_clear_normal_holo.png 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 0000000000..b8cdbc4f8a
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher_info_active_holo.png 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 0000000000..f503fb82d7
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher_info_normal_holo.png 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 0000000000..958f0de3c1
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher_market_holo.png 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 0000000000..c155274bd3
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher_trashcan_active_holo.png 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 0000000000..2ec7ad9b4e
Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher_trashcan_normal_holo.png differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_add.png b/res/drawable-xhdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000000..28e164b234
Binary files /dev/null and b/res/drawable-xhdpi/ic_pageindicator_add.png differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_current.png b/res/drawable-xhdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000000..aed3d71728
Binary files /dev/null and b/res/drawable-xhdpi/ic_pageindicator_current.png differ
diff --git a/res/drawable-xhdpi/ic_pageindicator_default.png b/res/drawable-xhdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000000..0887416f03
Binary files /dev/null and b/res/drawable-xhdpi/ic_pageindicator_default.png differ
diff --git a/res/drawable-xhdpi/ic_setting.png b/res/drawable-xhdpi/ic_setting.png
new file mode 100644
index 0000000000..91ba98c557
Binary files /dev/null and b/res/drawable-xhdpi/ic_setting.png differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed.png b/res/drawable-xhdpi/ic_setting_pressed.png
new file mode 100644
index 0000000000..08aafc4054
Binary files /dev/null and b/res/drawable-xhdpi/ic_setting_pressed.png differ
diff --git a/res/drawable-xhdpi/ic_wallpaper.png b/res/drawable-xhdpi/ic_wallpaper.png
new file mode 100644
index 0000000000..41dc000fdd
Binary files /dev/null and b/res/drawable-xhdpi/ic_wallpaper.png differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed.png b/res/drawable-xhdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000000..ffff0531da
Binary files /dev/null and b/res/drawable-xhdpi/ic_wallpaper_pressed.png differ
diff --git a/res/drawable-xhdpi/ic_widget.png b/res/drawable-xhdpi/ic_widget.png
new file mode 100644
index 0000000000..47dcdd14f0
Binary files /dev/null and b/res/drawable-xhdpi/ic_widget.png differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed.png b/res/drawable-xhdpi/ic_widget_pressed.png
new file mode 100644
index 0000000000..8bb387b88e
Binary files /dev/null and b/res/drawable-xhdpi/ic_widget_pressed.png 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 0000000000..4f248f70b3
Binary files /dev/null and b/res/drawable-xhdpi/overscroll_glow_left.9.png 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 0000000000..818a70db8b
Binary files /dev/null and b/res/drawable-xhdpi/overscroll_glow_right.9.png 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 0000000000..4972a2eebd
Binary files /dev/null and b/res/drawable-xhdpi/page_hover_left_holo.9.png 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 0000000000..b99461f697
Binary files /dev/null and b/res/drawable-xhdpi/page_hover_right_holo.9.png 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 0000000000..fb8a228203
Binary files /dev/null and b/res/drawable-xhdpi/paged_view_indicator.9.png 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 0000000000..cedbdc401e
Binary files /dev/null and b/res/drawable-xhdpi/portal_container_holo.9.png 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 0000000000..f9acfa2847
Binary files /dev/null and b/res/drawable-xhdpi/portal_ring_inner_holo.png 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 0000000000..eb2cf5f8b1
Binary files /dev/null and b/res/drawable-xhdpi/portal_ring_inner_nolip_holo.png 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 0000000000..f32051dcf6
Binary files /dev/null and b/res/drawable-xhdpi/portal_ring_outer_holo.png differ
diff --git a/res/drawable-xhdpi/portal_ring_rest.png b/res/drawable-xhdpi/portal_ring_rest.png
new file mode 100644
index 0000000000..ff0369a26e
Binary files /dev/null and b/res/drawable-xhdpi/portal_ring_rest.png differ
diff --git a/res/drawable-xhdpi/screenpanel.9.png b/res/drawable-xhdpi/screenpanel.9.png
new file mode 100644
index 0000000000..b4b828d3e9
Binary files /dev/null and b/res/drawable-xhdpi/screenpanel.9.png differ
diff --git a/res/drawable-xhdpi/screenpanel_hover.9.png b/res/drawable-xhdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000000..a44dc113fb
Binary files /dev/null and b/res/drawable-xhdpi/screenpanel_hover.9.png differ
diff --git a/res/drawable-xhdpi/search_frame.9.png b/res/drawable-xhdpi/search_frame.9.png
new file mode 100644
index 0000000000..32a07143ea
Binary files /dev/null and b/res/drawable-xhdpi/search_frame.9.png 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 0000000000..03cfb0945d
Binary files /dev/null and b/res/drawable-xhdpi/tab_selected_focused_holo.9.png 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 0000000000..e4229f26b2
Binary files /dev/null and b/res/drawable-xhdpi/tab_selected_holo.9.png 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 0000000000..24127117f9
Binary files /dev/null and b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png 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 0000000000..e862cb1215
Binary files /dev/null and b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png 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 0000000000..f3a5cbde81
Binary files /dev/null and b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png 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 0000000000..9465173781
Binary files /dev/null and b/res/drawable-xhdpi/tab_unselected_holo.9.png 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 0000000000..16536003fc
Binary files /dev/null and b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png 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 0000000000..f1eb67323a
Binary files /dev/null and b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png 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 0000000000..279e859630
Binary files /dev/null and b/res/drawable-xhdpi/tile_picker_focused.9.png 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 0000000000..abe0e00801
Binary files /dev/null and b/res/drawable-xhdpi/tile_picker_pressed.9.png 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 0000000000..b047591ac8
Binary files /dev/null and b/res/drawable-xhdpi/tile_picker_selected.9.png 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 0000000000..81571f3b7d
Binary files /dev/null and b/res/drawable-xhdpi/tile_shadow_bottom.9.png 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 0000000000..8503a59fef
Binary files /dev/null and b/res/drawable-xhdpi/tile_shadow_top.9.png 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 0000000000..1313fe70dc
Binary files /dev/null and b/res/drawable-xhdpi/widget_container_holo.9.png 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 0000000000..76cec606df
Binary files /dev/null and b/res/drawable-xhdpi/widget_resize_frame_holo.9.png 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 0000000000..19437d7dc7
Binary files /dev/null and b/res/drawable-xhdpi/widget_resize_handle_bottom.png 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 0000000000..28c5487ec1
Binary files /dev/null and b/res/drawable-xhdpi/widget_resize_handle_left.png 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 0000000000..4f672a602b
Binary files /dev/null and b/res/drawable-xhdpi/widget_resize_handle_right.png 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 0000000000..e866c008c2
Binary files /dev/null and b/res/drawable-xhdpi/widget_resize_handle_top.png differ
diff --git a/res/drawable-xhdpi/widget_tile.png b/res/drawable-xhdpi/widget_tile.png
new file mode 100644
index 0000000000..9730f35db3
Binary files /dev/null and b/res/drawable-xhdpi/widget_tile.png differ
diff --git a/res/drawable-xhdpi/workspace_bg.9.png b/res/drawable-xhdpi/workspace_bg.9.png
new file mode 100644
index 0000000000..72269f207a
Binary files /dev/null and b/res/drawable-xhdpi/workspace_bg.9.png differ
diff --git a/res/drawable-xxhdpi/arrow_dashed.png b/res/drawable-xxhdpi/arrow_dashed.png
new file mode 100644
index 0000000000..b64f4d0fef
Binary files /dev/null and b/res/drawable-xxhdpi/arrow_dashed.png differ
diff --git a/res/drawable-xxhdpi/bg_cling1.png b/res/drawable-xxhdpi/bg_cling1.png
new file mode 100644
index 0000000000..0777856006
Binary files /dev/null and b/res/drawable-xxhdpi/bg_cling1.png differ
diff --git a/res/drawable-xxhdpi/bg_cling2.png b/res/drawable-xxhdpi/bg_cling2.png
new file mode 100644
index 0000000000..1797a1b6c8
Binary files /dev/null and b/res/drawable-xxhdpi/bg_cling2.png differ
diff --git a/res/drawable-xxhdpi/bg_cling3.png b/res/drawable-xxhdpi/bg_cling3.png
new file mode 100644
index 0000000000..a87be630e6
Binary files /dev/null and b/res/drawable-xxhdpi/bg_cling3.png differ
diff --git a/res/drawable-xxhdpi/bg_cling4.png b/res/drawable-xxhdpi/bg_cling4.png
new file mode 100644
index 0000000000..cabe919d51
Binary files /dev/null and b/res/drawable-xxhdpi/bg_cling4.png differ
diff --git a/res/drawable-xxhdpi/bg_cling_home.png b/res/drawable-xxhdpi/bg_cling_home.png
new file mode 100644
index 0000000000..1ae93e7fe0
Binary files /dev/null and b/res/drawable-xxhdpi/bg_cling_home.png differ
diff --git a/res/drawable-xxhdpi/bg_cling_nakasi3.png b/res/drawable-xxhdpi/bg_cling_nakasi3.png
new file mode 100644
index 0000000000..f47236cac8
Binary files /dev/null and b/res/drawable-xxhdpi/bg_cling_nakasi3.png differ
diff --git a/res/drawable-xxhdpi/cling.9.png b/res/drawable-xxhdpi/cling.9.png
new file mode 100644
index 0000000000..7beae03bf4
Binary files /dev/null and b/res/drawable-xxhdpi/cling.9.png differ
diff --git a/res/drawable-xxhdpi/cling_arrow_down.png b/res/drawable-xxhdpi/cling_arrow_down.png
new file mode 100644
index 0000000000..48c4f06faa
Binary files /dev/null and b/res/drawable-xxhdpi/cling_arrow_down.png differ
diff --git a/res/drawable-xxhdpi/cling_arrow_left.png b/res/drawable-xxhdpi/cling_arrow_left.png
new file mode 100644
index 0000000000..8760d05da3
Binary files /dev/null and b/res/drawable-xxhdpi/cling_arrow_left.png differ
diff --git a/res/drawable-xxhdpi/cling_arrow_right.png b/res/drawable-xxhdpi/cling_arrow_right.png
new file mode 100644
index 0000000000..356ba178cc
Binary files /dev/null and b/res/drawable-xxhdpi/cling_arrow_right.png differ
diff --git a/res/drawable-xxhdpi/cling_arrow_up.png b/res/drawable-xxhdpi/cling_arrow_up.png
new file mode 100644
index 0000000000..4cb805f453
Binary files /dev/null and b/res/drawable-xxhdpi/cling_arrow_up.png differ
diff --git a/res/drawable-xxhdpi/cling_button.9.png b/res/drawable-xxhdpi/cling_button.9.png
new file mode 100644
index 0000000000..e41287613e
Binary files /dev/null and b/res/drawable-xxhdpi/cling_button.9.png 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 0000000000..55e89da08c
Binary files /dev/null and b/res/drawable-xxhdpi/cling_button_pressed.9.png 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 0000000000..0f62097a81
Binary files /dev/null and b/res/drawable-xxhdpi/default_widget_preview_holo.9.png differ
diff --git a/res/drawable-xxhdpi/focused_bg.9.png b/res/drawable-xxhdpi/focused_bg.9.png
new file mode 100644
index 0000000000..84d3062f1d
Binary files /dev/null and b/res/drawable-xxhdpi/focused_bg.9.png differ
diff --git a/res/drawable-xxhdpi/hand.png b/res/drawable-xxhdpi/hand.png
new file mode 100644
index 0000000000..88c2a882cb
Binary files /dev/null and b/res/drawable-xxhdpi/hand.png 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 0000000000..40fc076ff9
Binary files /dev/null and b/res/drawable-xxhdpi/hotseat_bg_panel.9.png 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 0000000000..8a77536beb
Binary files /dev/null and b/res/drawable-xxhdpi/hotseat_scrubber_holo.9.png 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 0000000000..dd2216fb71
Binary files /dev/null and b/res/drawable-xxhdpi/hotseat_track_holo.9.png differ
diff --git a/res/drawable-xxhdpi/ic_allapps.png b/res/drawable-xxhdpi/ic_allapps.png
new file mode 100644
index 0000000000..624e0ef443
Binary files /dev/null and b/res/drawable-xxhdpi/ic_allapps.png differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed.png b/res/drawable-xxhdpi/ic_allapps_pressed.png
new file mode 100644
index 0000000000..77b45aefb8
Binary files /dev/null and b/res/drawable-xxhdpi/ic_allapps_pressed.png 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 0000000000..0b8e88c169
Binary files /dev/null and b/res/drawable-xxhdpi/ic_home_all_apps_holo_dark.png 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 0000000000..a47b2ba990
Binary files /dev/null and b/res/drawable-xxhdpi/ic_home_google_logo_normal_holo.png 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 0000000000..75625d120b
Binary files /dev/null and b/res/drawable-xxhdpi/ic_home_google_logo_pressed_holo.png 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 0000000000..a9523d3cca
Binary files /dev/null and b/res/drawable-xxhdpi/ic_home_search_normal_holo.png 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 0000000000..800d99430c
Binary files /dev/null and b/res/drawable-xxhdpi/ic_home_search_pressed_holo.png 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 0000000000..c9c0b50bd0
Binary files /dev/null and b/res/drawable-xxhdpi/ic_home_voice_search_holo.png 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 0000000000..27a5897823
Binary files /dev/null and b/res/drawable-xxhdpi/ic_home_voice_search_pressed_holo.png differ
diff --git a/res/drawable-xxhdpi/ic_images.png b/res/drawable-xxhdpi/ic_images.png
new file mode 100644
index 0000000000..d00db9a00a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_images.png 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 0000000000..95cf84115a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher_clear_active_holo.png 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 0000000000..b0f5a2702a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher_clear_normal_holo.png 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 0000000000..57f332a94e
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher_info_active_holo.png 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 0000000000..94f0955d64
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher_info_normal_holo.png 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 0000000000..3bb098c2be
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher_trashcan_active_holo.png 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 0000000000..550cc5bb82
Binary files /dev/null and b/res/drawable-xxhdpi/ic_launcher_trashcan_normal_holo.png differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_add.png b/res/drawable-xxhdpi/ic_pageindicator_add.png
new file mode 100644
index 0000000000..fd8a662e1f
Binary files /dev/null and b/res/drawable-xxhdpi/ic_pageindicator_add.png differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current.png b/res/drawable-xxhdpi/ic_pageindicator_current.png
new file mode 100644
index 0000000000..08615f3714
Binary files /dev/null and b/res/drawable-xxhdpi/ic_pageindicator_current.png differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default.png b/res/drawable-xxhdpi/ic_pageindicator_default.png
new file mode 100644
index 0000000000..9d4fbf8208
Binary files /dev/null and b/res/drawable-xxhdpi/ic_pageindicator_default.png 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 0000000000..25ea9fab22
Binary files /dev/null and b/res/drawable-xxhdpi/ic_qs_remote_display.png 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 0000000000..33a8d2d5ad
Binary files /dev/null and b/res/drawable-xxhdpi/ic_qs_remote_display_connected.png differ
diff --git a/res/drawable-xxhdpi/ic_setting.png b/res/drawable-xxhdpi/ic_setting.png
new file mode 100644
index 0000000000..6e1e6627cf
Binary files /dev/null and b/res/drawable-xxhdpi/ic_setting.png differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed.png b/res/drawable-xxhdpi/ic_setting_pressed.png
new file mode 100644
index 0000000000..a202a40fea
Binary files /dev/null and b/res/drawable-xxhdpi/ic_setting_pressed.png differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper.png b/res/drawable-xxhdpi/ic_wallpaper.png
new file mode 100644
index 0000000000..c718444100
Binary files /dev/null and b/res/drawable-xxhdpi/ic_wallpaper.png differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed.png b/res/drawable-xxhdpi/ic_wallpaper_pressed.png
new file mode 100644
index 0000000000..03324aa6cc
Binary files /dev/null and b/res/drawable-xxhdpi/ic_wallpaper_pressed.png differ
diff --git a/res/drawable-xxhdpi/ic_widget.png b/res/drawable-xxhdpi/ic_widget.png
new file mode 100644
index 0000000000..fddfecaa34
Binary files /dev/null and b/res/drawable-xxhdpi/ic_widget.png differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed.png b/res/drawable-xxhdpi/ic_widget_pressed.png
new file mode 100644
index 0000000000..3d3670ed49
Binary files /dev/null and b/res/drawable-xxhdpi/ic_widget_pressed.png 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 0000000000..1a895cdc88
Binary files /dev/null and b/res/drawable-xxhdpi/overscroll_glow_left.9.png 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 0000000000..5766761452
Binary files /dev/null and b/res/drawable-xxhdpi/overscroll_glow_right.9.png 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 0000000000..626aafb55d
Binary files /dev/null and b/res/drawable-xxhdpi/page_hover_left_holo.9.png 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 0000000000..66257dc8ed
Binary files /dev/null and b/res/drawable-xxhdpi/page_hover_right_holo.9.png 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 0000000000..599a076d8b
Binary files /dev/null and b/res/drawable-xxhdpi/portal_container_holo.9.png 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 0000000000..8cd6a59309
Binary files /dev/null and b/res/drawable-xxhdpi/portal_ring_inner_holo.png 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 0000000000..0fad65629b
Binary files /dev/null and b/res/drawable-xxhdpi/portal_ring_inner_nolip_holo.png 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 0000000000..0aee4f0273
Binary files /dev/null and b/res/drawable-xxhdpi/portal_ring_outer_holo.png differ
diff --git a/res/drawable-xxhdpi/portal_ring_rest.png b/res/drawable-xxhdpi/portal_ring_rest.png
new file mode 100644
index 0000000000..6fa6a53c78
Binary files /dev/null and b/res/drawable-xxhdpi/portal_ring_rest.png differ
diff --git a/res/drawable-xxhdpi/screenpanel.9.png b/res/drawable-xxhdpi/screenpanel.9.png
new file mode 100644
index 0000000000..c44f3b8981
Binary files /dev/null and b/res/drawable-xxhdpi/screenpanel.9.png differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover.9.png b/res/drawable-xxhdpi/screenpanel_hover.9.png
new file mode 100644
index 0000000000..1ab18da6a2
Binary files /dev/null and b/res/drawable-xxhdpi/screenpanel_hover.9.png 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 0000000000..85cae17bcb
Binary files /dev/null and b/res/drawable-xxhdpi/search_bg_panel.9.png differ
diff --git a/res/drawable-xxhdpi/search_frame.9.png b/res/drawable-xxhdpi/search_frame.9.png
new file mode 100644
index 0000000000..f297bf19c2
Binary files /dev/null and b/res/drawable-xxhdpi/search_frame.9.png 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 0000000000..2400c65d59
Binary files /dev/null and b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png 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 0000000000..5067cbbfe5
Binary files /dev/null and b/res/drawable-xxhdpi/tab_selected_holo.9.png 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 0000000000..84c246da4e
Binary files /dev/null and b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png 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 0000000000..939e0c3e09
Binary files /dev/null and b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png 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 0000000000..62ca6cf841
Binary files /dev/null and b/res/drawable-xxhdpi/tab_unselected_holo.9.png 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 0000000000..58ac0d649e
Binary files /dev/null and b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png 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 0000000000..1004c14b84
Binary files /dev/null and b/res/drawable-xxhdpi/tile_picker_focused.9.png 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 0000000000..9658444a10
Binary files /dev/null and b/res/drawable-xxhdpi/tile_picker_pressed.9.png 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 0000000000..a3cd303695
Binary files /dev/null and b/res/drawable-xxhdpi/tile_picker_selected.9.png 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 0000000000..55250f0413
Binary files /dev/null and b/res/drawable-xxhdpi/tile_shadow_bottom.9.png 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 0000000000..3f22633645
Binary files /dev/null and b/res/drawable-xxhdpi/tile_shadow_top.9.png 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 0000000000..8f79920c44
Binary files /dev/null and b/res/drawable-xxhdpi/widget_container_holo.9.png 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 0000000000..1681387a13
Binary files /dev/null and b/res/drawable-xxhdpi/widget_resize_frame_holo.9.png 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 0000000000..d549fcd918
Binary files /dev/null and b/res/drawable-xxhdpi/widget_resize_handle_bottom.png 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 0000000000..dd56dad15e
Binary files /dev/null and b/res/drawable-xxhdpi/widget_resize_handle_left.png 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 0000000000..296a1c166c
Binary files /dev/null and b/res/drawable-xxhdpi/widget_resize_handle_right.png 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 0000000000..e86270ad11
Binary files /dev/null and b/res/drawable-xxhdpi/widget_resize_handle_top.png differ
diff --git a/res/drawable-xxhdpi/widget_tile.png b/res/drawable-xxhdpi/widget_tile.png
new file mode 100644
index 0000000000..3a3790dfc0
Binary files /dev/null and b/res/drawable-xxhdpi/widget_tile.png differ
diff --git a/res/drawable-xxhdpi/workspace_bg.9.png b/res/drawable-xxhdpi/workspace_bg.9.png
new file mode 100644
index 0000000000..efc9b04658
Binary files /dev/null and b/res/drawable-xxhdpi/workspace_bg.9.png differ
diff --git a/res/drawable/all_apps_button_icon.xml b/res/drawable/all_apps_button_icon.xml
new file mode 100644
index 0000000000..7c69cad3ac
--- /dev/null
+++ b/res/drawable/all_apps_button_icon.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/res/drawable/cling_arrow_end.xml b/res/drawable/cling_arrow_end.xml
new file mode 100644
index 0000000000..3f63c7d242
--- /dev/null
+++ b/res/drawable/cling_arrow_end.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/res/drawable/cling_arrow_start.xml b/res/drawable/cling_arrow_start.xml
new file mode 100644
index 0000000000..ebe91830d7
--- /dev/null
+++ b/res/drawable/cling_arrow_start.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/res/drawable/cling_button_bg.xml b/res/drawable/cling_button_bg.xml
new file mode 100644
index 0000000000..7bf6ce79f8
--- /dev/null
+++ b/res/drawable/cling_button_bg.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/focusable_view_bg.xml b/res/drawable/focusable_view_bg.xml
new file mode 100644
index 0000000000..66661e28bd
--- /dev/null
+++ b/res/drawable/focusable_view_bg.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/res/drawable/info_target_selector.xml b/res/drawable/info_target_selector.xml
new file mode 100644
index 0000000000..f3a7016c38
--- /dev/null
+++ b/res/drawable/info_target_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/remove_target_selector.xml b/res/drawable/remove_target_selector.xml
new file mode 100644
index 0000000000..5e071fbfaf
--- /dev/null
+++ b/res/drawable/remove_target_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/setting_button.xml b/res/drawable/setting_button.xml
new file mode 100644
index 0000000000..4d66a1ac7d
--- /dev/null
+++ b/res/drawable/setting_button.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/res/drawable/tab_widget_indicator_selector.xml b/res/drawable/tab_widget_indicator_selector.xml
new file mode 100644
index 0000000000..d06f757ceb
--- /dev/null
+++ b/res/drawable/tab_widget_indicator_selector.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/uninstall_target_selector.xml b/res/drawable/uninstall_target_selector.xml
new file mode 100644
index 0000000000..229942e3ad
--- /dev/null
+++ b/res/drawable/uninstall_target_selector.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/wallpaper_button.xml b/res/drawable/wallpaper_button.xml
new file mode 100644
index 0000000000..72da99d050
--- /dev/null
+++ b/res/drawable/wallpaper_button.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/res/drawable/wallpaper_tile_fg.xml b/res/drawable/wallpaper_tile_fg.xml
new file mode 100644
index 0000000000..c299f32c6d
--- /dev/null
+++ b/res/drawable/wallpaper_tile_fg.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/widget_button.xml b/res/drawable/widget_button.xml
new file mode 100644
index 0000000000..6936c87a0f
--- /dev/null
+++ b/res/drawable/widget_button.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout-land/all_apps_cling.xml b/res/layout-land/all_apps_cling.xml
new file mode 100644
index 0000000000..820f00a6d2
--- /dev/null
+++ b/res/layout-land/all_apps_cling.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-land/first_run_cling.xml b/res/layout-land/first_run_cling.xml
new file mode 100644
index 0000000000..9baee64b70
--- /dev/null
+++ b/res/layout-land/first_run_cling.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-land/folder_cling.xml b/res/layout-land/folder_cling.xml
new file mode 100644
index 0000000000..86286d71ba
--- /dev/null
+++ b/res/layout-land/folder_cling.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
new file mode 100644
index 0000000000..77ea2e9aa8
--- /dev/null
+++ b/res/layout-land/launcher.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-land/search_bar.xml b/res/layout-land/search_bar.xml
new file mode 100644
index 0000000000..d56e380d0d
--- /dev/null
+++ b/res/layout-land/search_bar.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-land/workspace_cling.xml b/res/layout-land/workspace_cling.xml
new file mode 100644
index 0000000000..db33db06f3
--- /dev/null
+++ b/res/layout-land/workspace_cling.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-port/all_apps_cling.xml b/res/layout-port/all_apps_cling.xml
new file mode 100644
index 0000000000..62284ec733
--- /dev/null
+++ b/res/layout-port/all_apps_cling.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-port/first_run_cling.xml b/res/layout-port/first_run_cling.xml
new file mode 100644
index 0000000000..4830e5df8d
--- /dev/null
+++ b/res/layout-port/first_run_cling.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-port/folder_cling.xml b/res/layout-port/folder_cling.xml
new file mode 100644
index 0000000000..86286d71ba
--- /dev/null
+++ b/res/layout-port/folder_cling.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
new file mode 100644
index 0000000000..6fbf7c717d
--- /dev/null
+++ b/res/layout-port/launcher.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-port/search_bar.xml b/res/layout-port/search_bar.xml
new file mode 100644
index 0000000000..1c96ca380a
--- /dev/null
+++ b/res/layout-port/search_bar.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-port/workspace_cling.xml b/res/layout-port/workspace_cling.xml
new file mode 100644
index 0000000000..9c000cb543
--- /dev/null
+++ b/res/layout-port/workspace_cling.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 0000000000..cf65e410db
--- /dev/null
+++ b/res/layout-sw600dp-port/all_apps_cling.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-sw600dp-port/folder_cling.xml b/res/layout-sw600dp-port/folder_cling.xml
new file mode 100644
index 0000000000..87086cb62d
--- /dev/null
+++ b/res/layout-sw600dp-port/folder_cling.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-sw720dp-port/folder_cling.xml b/res/layout-sw720dp-port/folder_cling.xml
new file mode 100644
index 0000000000..40d4e20a5d
--- /dev/null
+++ b/res/layout-sw720dp-port/folder_cling.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-sw720dp/all_apps_cling.xml b/res/layout-sw720dp/all_apps_cling.xml
new file mode 100644
index 0000000000..824d84f0dd
--- /dev/null
+++ b/res/layout-sw720dp/all_apps_cling.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
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 0000000000..48e333b3ac
--- /dev/null
+++ b/res/layout-sw720dp/external_widget_drop_list_item.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
new file mode 100644
index 0000000000..951e63a399
--- /dev/null
+++ b/res/layout-sw720dp/launcher.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout-sw720dp/market_button.xml b/res/layout-sw720dp/market_button.xml
new file mode 100644
index 0000000000..f1e99599e3
--- /dev/null
+++ b/res/layout-sw720dp/market_button.xml
@@ -0,0 +1,36 @@
+
+
+
diff --git a/res/layout-sw720dp/search_bar.xml b/res/layout-sw720dp/search_bar.xml
new file mode 100644
index 0000000000..69dd61aa2e
--- /dev/null
+++ b/res/layout-sw720dp/search_bar.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/actionbar_set_wallpaper.xml b/res/layout/actionbar_set_wallpaper.xml
new file mode 100644
index 0000000000..1622742a52
--- /dev/null
+++ b/res/layout/actionbar_set_wallpaper.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
diff --git a/res/layout/add_list_item.xml b/res/layout/add_list_item.xml
new file mode 100644
index 0000000000..0ae0113302
--- /dev/null
+++ b/res/layout/add_list_item.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
new file mode 100644
index 0000000000..1b9ea082f6
--- /dev/null
+++ b/res/layout/all_apps_button.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/res/layout/application.xml b/res/layout/application.xml
new file mode 100644
index 0000000000..e4909ddadd
--- /dev/null
+++ b/res/layout/application.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/res/layout/apps_customize_application.xml b/res/layout/apps_customize_application.xml
new file mode 100644
index 0000000000..3b0fa6f4e1
--- /dev/null
+++ b/res/layout/apps_customize_application.xml
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
new file mode 100644
index 0000000000..11a938fbb4
--- /dev/null
+++ b/res/layout/apps_customize_pane.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/apps_customize_progressbar.xml b/res/layout/apps_customize_progressbar.xml
new file mode 100644
index 0000000000..6aa90999cb
--- /dev/null
+++ b/res/layout/apps_customize_progressbar.xml
@@ -0,0 +1,22 @@
+
+
+
diff --git a/res/layout/apps_customize_widget.xml b/res/layout/apps_customize_widget.xml
new file mode 100644
index 0000000000..f2d2342662
--- /dev/null
+++ b/res/layout/apps_customize_widget.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/appwidget_error.xml b/res/layout/appwidget_error.xml
new file mode 100644
index 0000000000..f5a9148196
--- /dev/null
+++ b/res/layout/appwidget_error.xml
@@ -0,0 +1,29 @@
+
+
+
+
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 0000000000..8fe3f8fdf2
--- /dev/null
+++ b/res/layout/custom_content_page_indicator_marker.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
diff --git a/res/layout/custom_workspace_cling.xml b/res/layout/custom_workspace_cling.xml
new file mode 100644
index 0000000000..3744f2e4bc
--- /dev/null
+++ b/res/layout/custom_workspace_cling.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/res/layout/drop_target_bar.xml b/res/layout/drop_target_bar.xml
new file mode 100644
index 0000000000..f38a500144
--- /dev/null
+++ b/res/layout/drop_target_bar.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
new file mode 100644
index 0000000000..44056828cf
--- /dev/null
+++ b/res/layout/folder_icon.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml
new file mode 100644
index 0000000000..dc9ed2a6fc
--- /dev/null
+++ b/res/layout/hotseat.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/res/layout/market_button.xml b/res/layout/market_button.xml
new file mode 100644
index 0000000000..4a718c3d61
--- /dev/null
+++ b/res/layout/market_button.xml
@@ -0,0 +1,30 @@
+
+
+
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
new file mode 100644
index 0000000000..e36004c4b9
--- /dev/null
+++ b/res/layout/overview_panel.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/page_indicator.xml b/res/layout/page_indicator.xml
new file mode 100644
index 0000000000..14eff75d0f
--- /dev/null
+++ b/res/layout/page_indicator.xml
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/res/layout/page_indicator_marker.xml b/res/layout/page_indicator_marker.xml
new file mode 100644
index 0000000000..7c0c389207
--- /dev/null
+++ b/res/layout/page_indicator_marker.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
diff --git a/res/layout/qsb_bar.xml b/res/layout/qsb_bar.xml
new file mode 100644
index 0000000000..030acf6be4
--- /dev/null
+++ b/res/layout/qsb_bar.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/rename_folder.xml b/res/layout/rename_folder.xml
new file mode 100644
index 0000000000..21a335c4ab
--- /dev/null
+++ b/res/layout/rename_folder.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/tab_widget_indicator.xml b/res/layout/tab_widget_indicator.xml
new file mode 100644
index 0000000000..de7c50ec45
--- /dev/null
+++ b/res/layout/tab_widget_indicator.xml
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
new file mode 100644
index 0000000000..5d5f33b1f9
--- /dev/null
+++ b/res/layout/user_folder.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/wallpaper_cropper.xml b/res/layout/wallpaper_cropper.xml
new file mode 100644
index 0000000000..3a3d98a69c
--- /dev/null
+++ b/res/layout/wallpaper_cropper.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
diff --git a/res/layout/wallpaper_picker.xml b/res/layout/wallpaper_picker.xml
new file mode 100644
index 0000000000..c91cc7e6a2
--- /dev/null
+++ b/res/layout/wallpaper_picker.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 0000000000..ae3c43d8eb
--- /dev/null
+++ b/res/layout/wallpaper_picker_image_picker_item.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
diff --git a/res/layout/wallpaper_picker_item.xml b/res/layout/wallpaper_picker_item.xml
new file mode 100644
index 0000000000..0ac8f97fb8
--- /dev/null
+++ b/res/layout/wallpaper_picker_item.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
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 0000000000..29fdb1b823
--- /dev/null
+++ b/res/layout/wallpaper_picker_live_wallpaper_item.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
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 0000000000..68661bc000
--- /dev/null
+++ b/res/layout/wallpaper_picker_third_party_item.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
diff --git a/res/layout/workspace_screen.xml b/res/layout/workspace_screen.xml
new file mode 100644
index 0000000000..855cf39f50
--- /dev/null
+++ b/res/layout/workspace_screen.xml
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/res/menu/cab_delete_wallpapers.xml b/res/menu/cab_delete_wallpapers.xml
new file mode 100644
index 0000000000..38ac5c4d6f
--- /dev/null
+++ b/res/menu/cab_delete_wallpapers.xml
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/res/mipmap-hdpi/ic_launcher_application.png b/res/mipmap-hdpi/ic_launcher_application.png
new file mode 100644
index 0000000000..b9aa101ad5
Binary files /dev/null and b/res/mipmap-hdpi/ic_launcher_application.png differ
diff --git a/res/mipmap-hdpi/ic_launcher_home.png b/res/mipmap-hdpi/ic_launcher_home.png
new file mode 100644
index 0000000000..b556d7abad
Binary files /dev/null and b/res/mipmap-hdpi/ic_launcher_home.png differ
diff --git a/res/mipmap-hdpi/ic_launcher_wallpaper.png b/res/mipmap-hdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000000..affee851d7
Binary files /dev/null and b/res/mipmap-hdpi/ic_launcher_wallpaper.png differ
diff --git a/res/mipmap-mdpi/ic_launcher_application.png b/res/mipmap-mdpi/ic_launcher_application.png
new file mode 100644
index 0000000000..4771b855f3
Binary files /dev/null and b/res/mipmap-mdpi/ic_launcher_application.png differ
diff --git a/res/mipmap-mdpi/ic_launcher_home.png b/res/mipmap-mdpi/ic_launcher_home.png
new file mode 100644
index 0000000000..961bb7dbd5
Binary files /dev/null and b/res/mipmap-mdpi/ic_launcher_home.png differ
diff --git a/res/mipmap-mdpi/ic_launcher_wallpaper.png b/res/mipmap-mdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000000..cb4443bdb9
Binary files /dev/null and b/res/mipmap-mdpi/ic_launcher_wallpaper.png differ
diff --git a/res/mipmap-xhdpi/ic_launcher_application.png b/res/mipmap-xhdpi/ic_launcher_application.png
new file mode 100644
index 0000000000..932f0f791e
Binary files /dev/null and b/res/mipmap-xhdpi/ic_launcher_application.png differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home.png b/res/mipmap-xhdpi/ic_launcher_home.png
new file mode 100644
index 0000000000..46ec2b7753
Binary files /dev/null and b/res/mipmap-xhdpi/ic_launcher_home.png differ
diff --git a/res/mipmap-xhdpi/ic_launcher_wallpaper.png b/res/mipmap-xhdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000000..60f8dceec7
Binary files /dev/null and b/res/mipmap-xhdpi/ic_launcher_wallpaper.png differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_application.png b/res/mipmap-xxhdpi/ic_launcher_application.png
new file mode 100644
index 0000000000..7fc739aaf5
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_launcher_application.png differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home.png b/res/mipmap-xxhdpi/ic_launcher_home.png
new file mode 100644
index 0000000000..d2975a39f0
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_launcher_home.png differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_wallpaper.png b/res/mipmap-xxhdpi/ic_launcher_wallpaper.png
new file mode 100644
index 0000000000..023fb58869
Binary files /dev/null and b/res/mipmap-xxhdpi/ic_launcher_wallpaper.png differ
diff --git a/res/values-af-land/strings.xml b/res/values-af-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-af-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000000..e16718942b
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Tuis"
+ "Android-kernprogramme"
+
+ "Stel muurpapier"
+
+ "%1$d gekies"
+ "%1$d gekies"
+ "%1$d gekies"
+
+ "Muurpapier %1$d van %2$d"
+ "Het %1$s gekies"
+ "Vee uit"
+ "Kies prent"
+ "Muurpapiere"
+ "Snoei muurpapier"
+ "Program is nie geïnstalleer nie."
+ "Legstukke"
+ "Legstukke"
+ "Wys Mem"
+ "Raak en hou om \'n legstuk op te tel."
+ "Winkel"
+ "%1$d × %2$d"
+ "Kan nie item op hierdie Tuisskerm laat los nie."
+ "Kies legstuk om te skep"
+ "Vouernaam"
+ "Hernoem vouer"
+ "OK"
+ "Kanselleer"
+ "Voeg by Tuisskerm"
+ "Programme"
+ "Kortpaaie"
+ "Legstukke"
+ "Niks meer spasie op jou Tuisskerms nie."
+ "Niks meer spasie op die tuisskerm nie."
+ "Niks meer plek op die warmlaai nie."
+ "Hierdie legstuk is te groot vir die warmlaai."
+ "Kortpad \"%s\" is geskep."
+ "Kortpad \"%s\" is verwyder."
+ "Kortpad \"%s\" bestaan reeds."
+ "Kies kortpad"
+ "Kies program"
+ "Programme"
+ "Tuis"
+ "Verwyder"
+ "Deïnstalleer"
+ "Verwyder"
+ "Deïnstalleer"
+ "Programinligting"
+ "Soek"
+ "Stemsoektog"
+ "Programme"
+ "Verwyder"
+ "Deïnstalleer opdatering"
+ "Deïnstalleer program"
+ "Programbesonderhede"
+ "1 program gekies"
+ "1 legstuk gekies"
+ "1 vouer gekies"
+ "1 kortpad gekies"
+ "installeer kortpaaie"
+ "Laat \'n program toe om kortpaaie by te voeg sonder gebruikerinmenging."
+ "deïnstalleer kortpaaie"
+ "Laat die program toe om kortpaaie te verwyder sonder gebruikerinmenging."
+ "lees Tuis-instellings en -kortpaaie"
+ "Laat die program toe om die instellings en kortpaaie in Tuis te lees."
+ "skryf Tuis-instellings en -kortpaaie"
+ "Laat die program toe om die instellings en kortpaaie in Tuis te verander."
+ "Kon nie legstuk laai nie"
+ "Dit is \'n stelselprogram en kan nie gedeïnstalleer word nie."
+ "Vuurpyllanseerder"
+ "Naamlose vouer"
+ "Tuisskerm %1$d"
+ "Bladsy %1$d van %2$d"
+ "Tuisskerm %1$d van %2$d"
+ "Programme-bladsy %1$d van %2$d"
+ "Legstukke-bladsy %1$d van %2$d"
+ "Welkom!"
+ "Maak jouself tuis."
+
+
+ "Skep meer skerms vir programme en vouers"
+ "Organiseer jou spasie"
+ "Raak en hou agtergrond om muurpapier, legstukke en instellings te bestuur."
+ "Kies \'n paar programme"
+ "Om \'n program by jou Tuisskerm te voeg, raak en hou dit."
+ "Hier\'s \'n vouer"
+ "Om een soos dié te skep, raak en hou \'n program en skuif dit dan oor \'n ander een."
+ "OK"
+ "Vouer oopgemaak, %1$d by %2$d"
+ "Raak om vouer toe te maak"
+ "Raak om hernoem te stoor"
+ "Vouer is gesluit"
+ "Vouer hernoem na %1$s"
+ "Vouer: %1$s"
+
+
+
+
+ "Legstukke"
+ "Muurpapiere"
+ "Instellings"
+
diff --git a/res/values-am-land/strings.xml b/res/values-am-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-am-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000000..6e8aaa49eb
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "ማስጀመሪያ3"
+ "መነሻ"
+ "Android ዋና መተግበሪያዎች"
+
+ "የግድግዳ ወረቀት አዘጋጅ"
+
+ "%1$d ተመርጧል"
+ "%1$d ተመርጧል"
+ "%1$d ተመርጧል"
+
+ "የግድግዳ ወረቀት %1$d ከ%2$d"
+ "%1$s ተመርጧል"
+ "ሰርዝ"
+ "ምስል ምረጥ"
+ "የግድግዳ ወረቀቶች"
+ "የግድግዳ ወረቀት ከርክም"
+ "መተግበሪያ አልተጫነም።"
+ "ፍርግሞች"
+ "ፍርግሞች"
+ "ማህደረ ማስታወሻ አሳይ"
+ "ፍርግም ለማንሳት ይንኩ እና ይያዙት"
+ "ግዛ"
+ "%1$d × %2$d"
+ "ንጥሉን እዚህ የመነሻ ማያ ገጽ ላይ ማኖር አልተቻለም።"
+ "ለመፍጠር መግብር ይምረጡ"
+ "አቃፊ ስም"
+ "አቃፊ ዳግም ሰይም"
+ "እሺ"
+ "ተው"
+ "ወደ መነሻ ማያ ገጽ ያክሉ"
+ "መተግበሪያዎች"
+ "አቋራጮች"
+ "ፍርግሞች"
+ "የመነሻ ማያ ገጾችዎ ላይ ተጨማሪ ቦታ የለም።"
+ "በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"
+ "በመትከያ ቦታው ላይ ተጨማሪ ቦታ የለም።"
+ "ይህ ፍርግም ለመትከያ ቦታው በጣም ትልቅ ነው።"
+ "አቋራጭ «%s» ተፈጥሯል።"
+ "አቋራጭ «%s» ተወግዶ ነበር።"
+ "አቋራጭ «%s» አስቀድሞ አለ።"
+ "አቋራጭ ይምረጡ"
+ "መተግበሪያ ይምረጡ"
+ "መተግበሪያዎች"
+ "መነሻ"
+ "አስወግድ"
+ "አራግፍ"
+ "አስወግድ"
+ "አራግፍ"
+ "የመተግበሪያ መረጃ"
+ "ፍለጋ"
+ "የድምፅ ፍለጋ"
+ "መተግበሪያዎች"
+ "አስወግድ"
+ "ዝማኔ አራግፍ"
+ "መተግበሪያ አራግፍ"
+ "የመተግበሪያ ዝርዝሮች"
+ "1 መተግበሪያ ተመርጧል"
+ "1 ፍርግም ተመርጧል"
+ "1 አቃፊ ተመርጧል"
+ "1 አቋራጭ ተመርጧል"
+ "አቋራጮችን ይጭናል"
+ "መተግበሪያው ያለተጠቃሚ ጣልቃ ገብነት አቋራጭ እንዲያክል ያስችለዋል።"
+ "አቋራጮችን ያራግፋል"
+ "መተግበሪያው አቋራጮችን ያለተጠቃሚ ጣልቃ ገብነት እንዲያስወግድ ያስችለዋል።"
+ "የመነሻ ቅንብሮች እና አቋራጮችን ያነባል"
+ "መተግበሪያው በመነሻ ውስጥ ያሉ ቅንብሮችን እና አቋራጮችን እንዲያነብ ያስችለዋል።"
+ "የመነሻ ቅንብሮችን እና አቋራጮችን ይጽፋል"
+ "መተግብሪያው ቅንብሮችን እና አቋራጮችን በመነሻ ውስጥ እንዲቀይራቸው ያስችለዋል።"
+ "ፍርግም የመጫን ችግር"
+ "ይህ የስርዓት መተግበሪያ ነው እና ማራገፍ አይቻልም።"
+ "የሮኬት ማስጀመሪያ"
+ "ስም-አልባ አቃፊ"
+ "መነሻ ማያ ገጽ %1$d"
+ "ገጽ %1$d ከ%2$d"
+ "መነሻ ማያ ገጽ %1$d ከ%2$d"
+ "የመተግበሪያዎች ገጽ %1$d ከ%2$d"
+ "የመግብሮች ገጽ %1$d ከ%2$d"
+ "እንኳን ደህና መጡ!"
+ "ልክ እቤትዎ እንዳሉ ሆነው ዘና ይበሉ።"
+
+
+ "ለመተግበሪያዎች እና አቃፊዎች ተጨማሪ ማያ ገጾችን ይፍጠሩ"
+ "ቦታዎን ያደራጁ"
+ "የግድግዳ ወረቀት፣ ምግብሮችን እና ቅንብሮችን ለማቀናበር ጀርባውን ይንኩ እና ይያዙት።"
+ "አንዳንድ መተግበሪያዎችን ይምረጡ"
+ "አንድ መተግበሪያ ወደ መነሻ ማያ ገጽዎ ለማከል ይንኩት እና ይያዙት።"
+ "አንድ አቃፊ እነሆ"
+ "አንድ እንደዚህ አይነት ለመፍጠር መተግበሪያውን ነክተው ይያዙት እና ወደ ሌላ ያንቀሳቅሱት።"
+ "እሺ"
+ "አቃፊ ተከፍቷል፣ %1$d በ%2$d"
+ "አቃፊን ለመዝጋት ይንኩ"
+ "ዳግም የተሰየመውን ለማስቀመጥ ይንኩ"
+ "አቃፊ ተዘግቷል"
+ "አቃፊ %1$s ተብሎ ዳግም ተሰይሟል"
+ "አቃፊ፦ %1$s"
+
+
+
+
+ "ፍርግሞች"
+ "የግድግዳ ወረቀቶች"
+ "ቅንብሮች"
+
diff --git a/res/values-ar-land/strings.xml b/res/values-ar-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-ar-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000000..73ee690f99
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "الرئيسية"
+ "تطبيقات Android الأساسية"
+
+ "تعيين خلفية"
+
+ "تم تحديد %1$d"
+ "تم تحديد %1$d"
+ "تم تحديد %1$d"
+
+ "الخلفية %1$d من %2$d"
+ "تم تحديد %1$s"
+ "حذف"
+ "اختيار صورة"
+ "الخلفيات"
+ "اقتصاص الخلفية"
+ "لم يتم تثبيت التطبيق."
+ "الأدوات"
+ "الأدوات"
+ "عرض الذاكرة"
+ "المس مع الاستمرار لاختيار إحدى الأدوات."
+ "تسوق"
+ "%1$d × %2$d"
+ "تعذر إسقاط العنصر على هذه الشاشة الرئيسية."
+ "اختيار أداة لإنشائها"
+ "اسم المجلد"
+ "إعادة تسمية المجلد"
+ "موافق"
+ "إلغاء"
+ "إضافة إلى الشاشة الرئيسية"
+ "التطبيقات"
+ "الاختصارات"
+ "الأدوات"
+ "ليس هناك مساحة أخرى في الشاشات الرئيسية."
+ "ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."
+ "ليست هناك مساحة أخرى في منطقة الإرساء القابلة للتخصيص."
+ "حجم هذه الأداة كبير للغاية بحيث لا تتسع له منطقة الإرساء القابلة للتخصيص."
+ "تم إنشاء الاختصار \"%s\"."
+ "تمت إزالة الاختصار \"%s\"."
+ "الاختصار \"%s\" موجود من قبل."
+ "اختيار اختصار"
+ "اختيار تطبيق"
+ "التطبيقات"
+ "الرئيسية"
+ "إزالة"
+ "إزالة"
+ "إزالة"
+ "إزالة"
+ "معلومات عن التطبيق"
+ "بحث"
+ "البحث الصوتي"
+ "التطبيقات"
+ "إزالة"
+ "إزالة التحديث"
+ "إزالة التطبيق"
+ "تفاصيل التطبيق"
+ "تم تحديد تطبيق واحد"
+ "تم تحديد أداة واحدة"
+ "تم تحديد مجلد واحد"
+ "تم تحديد اختصار واحد"
+ "تثبيت اختصارات"
+ "للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم."
+ "إزالة الاختصارات"
+ "للسماح للتطبيق بإزالة الاختصارات بدون تدخل المستخدم."
+ "قراءة إعدادات واختصارات الشاشة الرئيسية"
+ "للسماح للتطبيق بقراءة الإعدادات والاختصارات في الشاشة الرئيسية."
+ "كتابة إعدادات واختصارات الشاشة الرئيسية"
+ "للسماح للتطبيق بتغيير الإعدادات والاختصارات في الشاشة الرئيسية."
+ "حدثت مشكلة أثناء تحميل الأداة"
+ "هذا تطبيق نظام وتتعذر إزالته."
+ "قاذفة صواريخ"
+ "مجلد بدون اسم"
+ "الشاشة الرئيسية %1$d"
+ "الصفحة %1$d من %2$d"
+ "الشاشة الرئيسية %1$d من %2$d"
+ "صفحة التطبيقات %1$d من %2$d"
+ "صفحة الأدوات %1$d من %2$d"
+ "مرحبًا!"
+ "تصرف على راحتك."
+
+
+ "إنشاء المزيد من الشاشات للتطبيقات والمجلدات"
+ "تنظيم مساحتك"
+ "المس مع الاستمرار الجزء الخلفي من صورة الشاشة لإدارة الخلفية والأدوات والإعدادات."
+ "اختيار بعض التطبيقات"
+ "لإضافة تطبيق إلى الشاشة الرئيسية، المسه مع الاستمرار."
+ "إليك المجلد"
+ "لإنشاء مجلد مثل هذا، المس أحد التطبيقات مع استمرار اللمس، ثم حركه فوق آخر."
+ "موافق"
+ "تم فتح المجلد، بمقاس %1$d في %2$d"
+ "المس لإغلاق المجلد"
+ "المس لحفظ إعادة التسمية"
+ "تم إغلاق المجلد"
+ "تمت إعادة تسمية المجلد إلى %1$s"
+ "المجلد: %1$s"
+
+
+
+
+ "الأدوات"
+ "الخلفيات"
+ "الإعدادات"
+
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
new file mode 100644
index 0000000000..ddcf40442e
--- /dev/null
+++ b/res/values-be/strings.xml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values-bg-land/strings.xml b/res/values-bg-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-bg-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000000..2f53fa3b7b
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Начало"
+ "Основни приложения на Android"
+
+ "Задаване на тапета"
+
+ "Избрахте %1$d"
+ "Избрахте %1$d"
+ "Избрахте %1$d"
+
+ "Тапет %1$d от %2$d"
+ "Избрахте %1$s"
+ "Изтриване"
+ "Избиране на изображение"
+ "Тапети"
+ "Подрязване на тапета"
+ "Приложението не е инсталирано."
+ "Приспособления"
+ "Приспособления"
+ "Показване на паметта"
+ "Докоснете и задръжте за избор на приспособление."
+ "Пазаруване"
+ "%1$d × %2$d"
+ "Не можа да се премести на този начален екран."
+ "Избор на приспособл. за създаване"
+ "Име на папката"
+ "Преименуване на папка"
+ "ОK"
+ "Отказ"
+ "Добавяне към началния екран"
+ "Приложения"
+ "Преки пътища"
+ "Приспособления"
+ "На началните ви екрани няма повече място."
+ "На този начален екран няма повече място."
+ "В трамплина няма повече място."
+ "Това приспособление е твърде голямо за трамплина."
+ "Прекият път към %s е създаден."
+ "Прекият път към %s бе премахнат."
+ "Прекият път към %s вече съществува."
+ "Избор на пряк път"
+ "Избор на приложение"
+ "Приложения"
+ "Начало"
+ "Премахване"
+ "Деинсталиране"
+ "Премахване"
+ "Деинсталиране"
+ "Информация за приложението"
+ "Търсене"
+ "Гласово търсене"
+ "Приложения"
+ "Премахване"
+ "Деинст. на актуализацията"
+ "Деинсталиране на приложението"
+ "Подробности за приложението"
+ "Избрано е 1 приложение"
+ "Избрано е 1 приспособление"
+ "Избрана е 1 папка"
+ "Избран е 1 пряк път"
+ "инсталиране на преки пътища"
+ "Разрешава на приложението да добавя преки пътища без намеса на потребителя."
+ "деинсталиране на преки пътища"
+ "Разрешава на приложението да премахва преки пътища без намеса на потребителя."
+ "четене на настройките и преките пътища в Начало"
+ "Разрешава на приложението да чете настройките и преките пътища в Начало."
+ "запис на настройките и преките пътища в Начало"
+ "Разрешава на приложението да променя настройките и преките пътища в Начало."
+ "Проблем при зареждане на приспособлението"
+ "Това е системно приложение и не може да се деинсталира."
+ "Ракетна площадка"
+ "Папка без име"
+ "Начален екран %1$d"
+ "Страница %1$d от %2$d"
+ "Начален екран %1$d от %2$d"
+ "Страница с приложения %1$d от %2$d"
+ "Страница с приспособления %1$d от %2$d"
+ "Добре дошли!"
+ "Персонализиране и приспособяване."
+
+
+ "Създаване на още екрани за приложения и папки"
+ "Организиране на мястото ви"
+ "Докоснете и задръжте фона, за да управлявате тапета, приспособленията и настройките."
+ "Изберете някои приложения"
+ "За да добавите приложение към началния си екран, го докоснете и задръжте."
+ "Ето една папка"
+ "За да създадете подобна, докоснете и задръжте приложение, след което го преместете върху друго."
+ "ОK"
+ "Папката е отворена – %1$d на %2$d"
+ "Докоснете, за да затворите папката"
+ "Докоснете, за да запазите новото име"
+ "Папката бе затворена"
+ "Папката е преименувана на „%1$s“"
+ "Папка: „%1$s“"
+
+
+
+
+ "Приспособления"
+ "Тапети"
+ "Настройки"
+
diff --git a/res/values-ca-land/strings.xml b/res/values-ca-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-ca-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000000..34004827e3
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Inici"
+ "Aplicacions principals d\'Android"
+
+ "Estableix el fons de pantalla"
+
+ "Seleccionats: %1$d"
+ "Seleccionats: %1$d"
+ "Seleccionats: %1$d"
+
+ "Fons de pantalla %1$d de %2$d"
+ "S\'ha seleccionat %1$s"
+ "Suprimeix"
+ "Selecciona una imatge"
+ "Fons de pantalla"
+ "Retalla el fons de pantalla"
+ "L\'aplicació no s\'ha instal·lat."
+ "Widgets"
+ "Widgets"
+ "Mostra la memòria"
+ "Mantén premut un widget per triar-lo."
+ "Compra"
+ "%1$d × %2$d"
+ "No s\'ha pogut deixar anar l\'element a Inici."
+ "Tria el widget que vulguis crear"
+ "Nom de la carpeta"
+ "Canvi de nom de la carpeta"
+ "D\'acord"
+ "Cancel·la"
+ "Afegir a la pantalla d\'inici"
+ "Aplicacions"
+ "Dreceres"
+ "Widgets"
+ "No queda espai a les pantalles d\'inici."
+ "Ja no queda espai en aquesta pantalla d\'inici."
+ "No queda espai al hotseat."
+ "Aquest widget és massa gran per al hotseat."
+ "S\'ha creat la drecera \"%s\"."
+ "S\'ha suprimit la drecera \"%s\"."
+ "La drecera \"%s\" ja existeix."
+ "Tria d\'una drecera"
+ "Tria d\'una aplicació"
+ "Aplicacions"
+ "Inici"
+ "Suprimeix"
+ "Desinstal·la"
+ "Suprimeix"
+ "Desinstal·la"
+ "Informació de l\'aplicació"
+ "Cerca"
+ "Cerca per veu"
+ "Aplicacions"
+ "Suprimeix"
+ "Desinstal·la l\'actualització"
+ "Desinstal·la l\'aplicació"
+ "Detalls de l\'aplicació"
+ "1 aplicació seleccionada"
+ "1 widget seleccionat"
+ "1 carpeta seleccionada"
+ "1 drecera seleccionada"
+ "instal·la dreceres"
+ "Permet que una aplicació afegeixi dreceres sense la intervenció de l\'usuari."
+ "desinstal·la dreceres"
+ "Permet que l\'aplicació suprimeixi dreceres sense la intervenció de l\'usuari."
+ "llegeix la configuració i les dreceres de la pantalla d\'inici"
+ "Permet que l\'aplicació llegeixi la configuració i les dreceres de la pantalla d\'inici."
+ "escriu la configuració i les dreceres de la pantalla d\'inici"
+ "Permet que l\'aplicació canviï la configuració i les dreceres de la pantalla d\'inici."
+ "S\'ha produït un problema en carregar el widget"
+ "Aquesta aplicació és una aplicació del sistema i no es pot desinstal·lar."
+ "Rocket Launcher"
+ "Carpeta sense nom"
+ "Pantalla d\'inici %1$d"
+ "Pàgina %1$d de %2$d"
+ "Pantalla d\'inici %1$d de %2$d"
+ "Pàgina d\'aplicacions %1$d de %2$d"
+ "Pàgina de widgets %1$d de %2$d"
+ "Hola!"
+ "Personalitza la pantalla d\'inici"
+
+
+ "Crea més pantalles per a aplicacions i carpetes"
+ "Organitza el teu espai"
+ "Toca i mantén premut el fons per gestionar el fons de pantalla, els widgets i la configuració."
+ "Tria unes quantes aplicacions"
+ "Per afegir una aplicació a la pantalla d\'inici, mantén-la premuda."
+ "Aquí hi ha una carpeta"
+ "Per crear-ne una com aquesta, mantén premuda una aplicació i, a continuació, mou-la sobre una altra."
+ "D\'acord"
+ "S\'ha obert la carpeta, %1$d per %2$d"
+ "Toca per tancar la carpeta"
+ "Toca per desar el canvi de nom"
+ "Carpeta tancada"
+ "S\'ha canviat el nom de la carpeta a %1$s"
+ "Carpeta: %1$s"
+
+
+
+
+ "Widgets"
+ "Fons de pantalla"
+ "Configuració"
+
diff --git a/res/values-cs-land/strings.xml b/res/values-cs-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-cs-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000000..1061e798c4
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Plocha"
+ "Android Core Apps"
+
+ "Nastavit jako tapetu"
+
+ "Vybráno: %1$d"
+ "Vybráno: %1$d"
+ "Vybráno: %1$d"
+
+ "Tapeta %1$d z %2$d"
+ "Vybrána položka %1$s"
+ "Smazat"
+ "Vybrat obrázek"
+ "Tapety"
+ "Oříznutí tapety"
+ "Aplikace není nainstalována."
+ "Widgety"
+ "Widgety"
+ "Zobrazit Mem"
+ "Widget vyberete dotykem a podržením."
+ "Obchod"
+ "%1$d × %2$d"
+ "Na tuto plochu položku nelze přesunout."
+ "Vyberte widget k vytvoření"
+ "Název složky"
+ "Přejmenovat složku"
+ "OK"
+ "Zrušit"
+ "Přidat na plochu"
+ "Aplikace"
+ "Klávesové zkratky"
+ "Widgety"
+ "Na plochách již není místo."
+ "Na této ploše již není místo."
+ "V sekci hotseat již není místo."
+ "Widget je pro hotseat příliš velký."
+ "Zástupce aplikace %s byl vytvořen."
+ "Zástupce aplikace %s byl odebrán."
+ "Zástupce aplikace %s již existuje."
+ "Výběr zástupce"
+ "Výběr aplikace"
+ "Aplikace"
+ "Plocha"
+ "Odstranit"
+ "Odinstalovat"
+ "Odstranit"
+ "Odinstalovat"
+ "Informace o aplikaci"
+ "Hledat"
+ "Hlasové vyhledávání"
+ "Aplikace"
+ "Odstranit"
+ "Odinstalovat aktualizaci"
+ "Odinstalovat aplikaci"
+ "Podrobnosti o aplikaci"
+ "Vybrána 1 aplikace"
+ "Vybrán 1 widget"
+ "Vybrána 1 složka"
+ "Vybrán 1 zástupce"
+ "instalace zástupce"
+ "Umožňuje aplikaci přidat zástupce bez zásahu uživatele."
+ "odinstalovat zástupce"
+ "Umožňuje aplikaci odstranit zástupce bez zásahu uživatele."
+ "čtení nastavení a odkazů plochy"
+ "Umožňuje aplikaci číst nastavení a odkazy na ploše."
+ "zápis nastavení a odkazů plochy"
+ "Umožňuje aplikaci změnit nastavení a odkazy na ploše."
+ "Problém s načtením widgetu"
+ "Toto je systémová aplikace a nelze ji odinstalovat."
+ "Rocket Launcher"
+ "Složka bez názvu"
+ "Plocha %1$d"
+ "Strana %1$d z %2$d"
+ "Plocha %1$d z %2$d"
+ "Stránka aplikací %1$d z %2$d"
+ "Stránka widgetů %1$d z %2$d"
+ "Vítejte!"
+ "Udělejte si pohodlí."
+
+
+ "Vytvořte několik obrazovek pro aplikace a složky"
+ "Organizace prostoru"
+ "Chcete-li spravovat tapetu, widgety a nastavení, dotkněte se pozadí a přidržte je."
+ "Vyberte nějaké aplikace"
+ "Chcete-li na plochu přidat aplikaci, dotkněte se jí a přidržte ji."
+ "Toto je složka"
+ "Chcete-li vytvořit složku, přetáhněte aplikaci na jinou aplikaci."
+ "OK"
+ "Složka otevřena, rozměry %1$d x %2$d"
+ "Dotykem složku zavřete"
+ "Dotykem uložíte změnu názvu"
+ "Složka je uzavřena"
+ "Složka přejmenována na %1$s"
+ "Složka: %1$s"
+
+
+
+
+ "Widgety"
+ "Tapety"
+ "Nastavení"
+
diff --git a/res/values-da-land/strings.xml b/res/values-da-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-da-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000000..5db246e52a
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Startskærm"
+ "Kerneapps i Android"
+
+ "Angiv baggrund"
+
+ "%1$d er valgt"
+ "%1$d er valgt"
+ "%1$d er valgt"
+
+ "Baggrund %1$d af %2$d"
+ "%1$s blev valgt"
+ "Slet"
+ "Vælg billede"
+ "Baggrunde"
+ "Beskær baggrund"
+ "Appen er ikke installeret."
+ "Widgets"
+ "Widgets"
+ "Vis Mem"
+ "Tryk på en widget, og hold den nede for at vælge."
+ "Køb i Google Play Butik"
+ "%1$d × %2$d"
+ "Elementet kunne ikke trækkes til startskærmen."
+ "Vælg den widget, du vil oprette"
+ "Mappenavn"
+ "Omdøb mappe"
+ "OK"
+ "Annuller"
+ "Føj til startskærm"
+ "Apps"
+ "Genveje"
+ "Widgets"
+ "Der er ikke mere plads på dine startskærme."
+ "Der er ikke mere plads på denne startskærm."
+ "Der er ikke mere plads i hotseat."
+ "Denne widget er for stor til hotseat."
+ "Genvejen \"%s\" blev oprettet."
+ "Genvejen \"%s\" blev fjernet."
+ "Genvejen \"%s\" findes allerede."
+ "Vælg genvej"
+ "Vælg app"
+ "Apps"
+ "Startskærm"
+ "Fjern"
+ "Afinstaller"
+ "Fjern"
+ "Afinstaller"
+ "Oplysninger om appen"
+ "Søg"
+ "Stemmesøgning"
+ "Apps"
+ "Fjern"
+ "Afinstaller opdatering"
+ "Afinstaller appen"
+ "Oplysninger om appen"
+ "1 app er valgt"
+ "1 widget er valgt"
+ "1 mappe er valgt"
+ "1 genvej er valgt"
+ "installer genveje"
+ "Tillader, at en app tilføjer genveje uden brugerens indgriben."
+ "afinstaller genveje"
+ "Tillader, at appen fjerner genveje uden brugerens indgriben."
+ "læs indstillinger og genveje for startskærmen"
+ "Tillader, at appen læser indstillingerne og genvejene på startskærmen."
+ "skriv indstillinger og genveje for startskærmen"
+ "Tillader, at appen ændrer indstillingerne og genvejene på startskærmen."
+ "Der er problemer med indlæsning af widgetten"
+ "Dette er en systemapp, som ikke kan afinstalleres."
+ "Rocket Launcher"
+ "Unavngiven mappe"
+ "Startskærm %1$d"
+ "Side %1$d ud af %2$d"
+ "Startskærm %1$d ud af %2$d"
+ "Apps-side %1$d ud af %2$d"
+ "Widgets-side %1$d ud af %2$d"
+ "Velkommen"
+ "Føl dig hjemme."
+
+
+ "Opret flere skærme til apps og mapper"
+ "Organiser din arbejdsplads"
+ "Tryk på en baggrund, og hold fingeren nede for at administrere baggrunde, widgets og indstillinger."
+ "Vælge nogle apps"
+ "Tryk på en app, og hold fingeren nede for at føje appen til startskærmen."
+ "Her kan du se en mappe"
+ "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."
+ "OK"
+ "Mappen er åben, %1$d gange %2$d"
+ "Tryk for at lukke mappen"
+ "Tryk for at gemme det nye navn"
+ "Mappen er lukket"
+ "Mappen er omdøbt til %1$s"
+ "Mappe: %1$s"
+
+
+
+
+ "Widgets"
+ "Baggrunde"
+ "Indstillinger"
+
diff --git a/res/values-de-land/strings.xml b/res/values-de-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-de-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000000..7a5e7e6b7b
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Startseite"
+ "Android Core Apps"
+
+ "Hintergrund auswählen"
+
+ "%1$d ausgewählt"
+ "%1$d ausgewählt"
+ "%1$d ausgewählt"
+
+ "Hintergrund %1$d von %2$d"
+ "%1$s ausgewählt"
+ "Entfernen"
+ "Bild auswählen"
+ "Hintergründe"
+ "Hintergrund zuschneiden"
+ "App ist nicht installiert."
+ "Widgets"
+ "Widgets"
+ "Speicher anzeigen"
+ "Zum Hinzufügen Widget berühren und halten"
+ "Einkaufen"
+ "%1$d × %2$d"
+ "Element wurde nicht auf diesem Startbildschirm abgelegt."
+ "Widget zum Erstellen auswählen"
+ "Ordnername"
+ "Ordner umbenennen"
+ "OK"
+ "Abbrechen"
+ "Zum Startbildschirm hinzufügen"
+ "Apps"
+ "Verknüpfungen"
+ "Widgets"
+ "Auf Ihren Startbildschirmen ist kein Platz mehr vorhanden."
+ "Auf diesem Startbildschirm ist kein Platz mehr vorhanden."
+ "Kein Platz mehr auf der App-Leiste"
+ "Dieses Widget ist zu groß für die App-Leiste."
+ "Verknüpfung \"%s\" wurde erstellt."
+ "Verknüpfung \"%s\" wurde entfernt."
+ "Verknüpfung \"%s\" ist bereits vorhanden."
+ "Verknüpfung auswählen"
+ "App auswählen"
+ "Apps"
+ "Startseite"
+ "Entfernen"
+ "Deinstallieren"
+ "Entfernen"
+ "Deinstallieren"
+ "App-Info"
+ "Suchen"
+ "Sprachsuche"
+ "Apps"
+ "Entfernen"
+ "Update deinstallieren"
+ "App deinstallieren"
+ "App-Details"
+ "1 App ausgewählt"
+ "1 Widget ausgewählt"
+ "1 Ordner ausgewählt"
+ "1 Verknüpfung ausgewählt"
+ "Verknüpfungen installieren"
+ "Ermöglicht einer App das Hinzufügen von Verknüpfungen ohne Eingreifen des Nutzers"
+ "Verknüpfungen deinstallieren"
+ "Ermöglicht einer App das Entfernen von Verknüpfungen ohne Eingreifen des Nutzers"
+ "Einstellungen und Verknüpfungen auf dem Startbildschirm lesen"
+ "Ermöglicht einer App, die Einstellungen und Verknüpfungen auf dem Startbildschirm zu lesen"
+ "Einstellungen und Verknüpfungen für den Startbildschirm schreiben"
+ "Ermöglicht einer App, die Einstellungen und Verknüpfungen auf dem Startbildschirm zu ändern"
+ "Problem beim Laden des Widgets"
+ "Dies ist eine Systemanwendung, die nicht deinstalliert werden kann."
+ "Rocket Launcher"
+ "Unbenannter Ordner"
+ "Startbildschirm %1$d"
+ "Seite %1$d von %2$d"
+ "Startbildschirm %1$d von %2$d"
+ "Apps-Seite %1$d von %2$d"
+ "Widgets-Seite %1$d von %2$d"
+ "Willkommen!"
+ "Gerät personalisieren"
+
+
+ "Weitere Bildschirme für Apps und Ordner erstellen"
+ "Arbeitsbereich organisieren"
+ "Hintergrund berühren und halten, um Hintergrund, Widgets und Einstellungen zu verwalten"
+ "Apps auswählen"
+ "Berühren und halten Sie eine App, um sie zum Startbildschirm hinzuzufügen."
+ "Hier ist ein Ordner"
+ "Um einen Ordner zu erstellen, berühren und halten Sie eine App und verschieben Sie sie auf eine andere."
+ "OK"
+ "Ordner geöffnet, %1$d x %2$d"
+ "Ordner durch Berühren schließen"
+ "Umbenennung durch Berühren speichern"
+ "Ordner geschlossen"
+ "Ordner umbenannt in %1$s"
+ "Ordner: %1$s"
+
+
+
+
+ "Widgets"
+ "Hintergründe"
+ "Einstellungen"
+
diff --git a/res/values-el-land/strings.xml b/res/values-el-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-el-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000000..74322e0e64
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Αρχική σελίδα"
+ "Βασικές εφαρμογές Android"
+
+ "Ορισμός ταπετσαρίας"
+
+ "%1$d επιλεγμένα"
+ "%1$d επιλεγμένα"
+ "%1$d επιλεγμένα"
+
+ "Ταπετσαρία %1$d από %2$d"
+ "Επιλέχθηκε το %1$s"
+ "Διαγραφή"
+ "Επιλογή εικόνας"
+ "Ταπετσαρίες"
+ "Περικοπή ταπετσαρίας"
+ "Η εφαρμογή δεν έχει εγκατασταθεί."
+ "Γραφικά στοιχεία"
+ "Γραφικά στοιχεία"
+ "Εμφάνιση Mem"
+ "Αγγίξτε παρατεταμένα για να πάρετε ένα γραφ.στοιχ."
+ "Αγορά"
+ "%1$d × %2$d"
+ "Αδυναμία τοποθέτησης στοιχείου στην Αρχική οθόνη."
+ "Επιλ. γραφ. στοιχείο για δημιουργία"
+ "Όνομα φακέλου"
+ "Μετονομασία φακέλου"
+ "OK"
+ "Ακύρωση"
+ "Προσθήκη στην αρχική οθόνη"
+ "Εφαρμογές"
+ "Συντομεύσεις"
+ "Γραφικά στοιχεία"
+ "Δεν υπάρχει άλλος χώρος στις Αρχικές οθόνες σας."
+ "Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."
+ "Δεν υπάρχει άλλος χώρος στη γραμμή γρήγορης πρόσβασης."
+ "Αυτό το γραφικό στοιχείο είναι πολύ μεγάλο για τη γραμμή γρήγορης πρόσβασης."
+ "Δημιουργήθηκε η συντόμευση \"%s\"."
+ "Η συντόμευση \"%s\" καταργήθηκε."
+ "Η συντόμευση \"%s\" υπάρχει ήδη."
+ "Επιλογή συντόμευσης"
+ "Επιλογή εφαρμογής"
+ "Εφαρμογές"
+ "Αρχική σελίδα"
+ "Κατάργηση"
+ "Κατάργηση εγκατάστασης"
+ "Κατάργηση"
+ "Κατάργηση εγκατάστασης"
+ "Πληροφορίες εφαρμογής"
+ "Αναζήτηση"
+ "Φωνητική αναζήτηση"
+ "Εφαρμογές"
+ "Κατάργηση"
+ "Κατάργηση εγκατάστασης ενημέρωσης"
+ "Κατάργηση εγκατάστασης εφαρμογής"
+ "Λεπτομέρειες εφαρμογής"
+ "Επιλέχτηκε 1 εφαρμογή"
+ "Επιλέχτηκε 1 γραφικό στοιχείο"
+ "Επιλέχτηκε 1 φάκελος"
+ "Επιλέχτηκε 1 συντόμευση"
+ "εγκατάσταση συντομεύσεων"
+ "Επιτρέπει σε μια εφαρμογή την προσθήκη συντομεύσεων χωρίς την παρέμβαση του χρήστη."
+ "κατάργηση εγκατάστασης συντομεύσεων"
+ "Επιτρέπει στην εφαρμογή την κατάργηση συντομεύσεων χωρίς την παρέμβαση του χρήστη."
+ "ανάγνωση ρυθμίσεων και συντομεύσεων αρχικής οθόνης"
+ "Επιτρέπει στην εφαρμογή την ανάγνωση των ρυθμίσεων και των συντομεύσεων στην Αρχική οθόνη."
+ "εγγραφή ρυθμίσεων και συντομεύσεων αρχικής οθόνης"
+ "Επιτρέπει στην εφαρμογή την αλλαγή των ρυθμίσεων και των συντομεύσεων στην Αρχική οθόνη."
+ "Παρουσιάστηκε πρόβλημα στη φόρτωση του γραφικού στοιχείου"
+ "Αυτή είναι μια εφαρμογή συστήματος και δεν είναι δυνατή η κατάργηση της εγκατάστασής της."
+ "Rocket Launcher"
+ "Φάκελος χωρίς όνομα"
+ "Αρχική οθόνη %1$d"
+ "Σελίδα %1$d από %2$d"
+ "Αρχική οθόνη %1$d από %2$d"
+ "Σελίδα εφαρμογών %1$d από %2$d"
+ "Σελίδα γραφικών στοιχείων %1$d από %2$d"
+ "Καλώς ορίσατε!"
+ "Νιώστε σαν στο σπίτι σας."
+
+
+ "Δημιουργία περισσότερων οθονών για εφαρμογές και φακέλους"
+ "Οργανώστε το χώρο σας"
+ "Αγγίξτε παρατεταμένα το φόντο για να διαχειριστείτε την ταπετσαρία, τα γραφικά στοιχεία και τις ρυθμίσεις."
+ "Επιλέξτε ορισμένες εφαρμογές"
+ "Για να προσθέσετε μια εφαρμογή στην αρχική σας οθόνη, αγγίξτε την παρατεταμένα."
+ "Ορίστε ένας φάκελος"
+ "Για να δημιουργήσετε έναν φάκελο σαν κι αυτόν, πατήστε παρατεταμένα μια εφαρμογή και στη συνέχεια, μετακινήστε τη πάνω σε μια άλλη."
+ "OK"
+ "Άνοιγμα φακέλου, %1$d επί %2$d"
+ "Αγγίξτε για να κλείσετε τον φάκελο"
+ "Αγγίξτε για να αποθηκεύσετε το νέο όνομα"
+ "Ο φάκελος έκλεισε"
+ "Ο φάκελος μετονομάστηκε σε %1$s"
+ "Φάκελος: %1$s"
+
+
+
+
+ "Γραφικά στοιχεία"
+ "Ταπετσαρίες"
+ "Ρυθμίσεις"
+
diff --git a/res/values-en-rGB-land/strings.xml b/res/values-en-rGB-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-en-rGB-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000000..316585e0ea
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Home"
+ "Android Core Apps"
+
+ "Set wallpaper"
+
+ "%1$d selected"
+ "%1$d selected"
+ "%1$d selected"
+
+ "Wallpaper %1$d of %2$d"
+ "Selected %1$s"
+ "Delete"
+ "Pick image"
+ "Wallpapers"
+ "Crop wallpaper"
+ "App isn\'t installed."
+ "Widgets"
+ "Widgets"
+ "Show Mem"
+ "Touch & hold to pick up a widget."
+ "Shop"
+ "%1$d × %2$d"
+ "Couldn\'t drop item on this Home screen."
+ "Choose widget to create"
+ "Folder name"
+ "Rename folder"
+ "OK"
+ "Cancel"
+ "Add to Home screen"
+ "Apps"
+ "Shortcuts"
+ "Widgets"
+ "No more room on your Home screens."
+ "No more room on this Home screen."
+ "No more room on the hot seat."
+ "This widget is too large for the hot seat."
+ "Shortcut \"%s\" created."
+ "Shortcut \"%s\" was removed."
+ "Shortcut \"%s\" already exists."
+ "Choose shortcut"
+ "Choose app"
+ "Apps"
+ "Home"
+ "Remove"
+ "Uninstall"
+ "Remove"
+ "Uninstall"
+ "App info"
+ "Search"
+ "Voice Search"
+ "Apps"
+ "Remove"
+ "Uninstall update"
+ "Uninstall app"
+ "App details"
+ "1 app selected"
+ "1 widget selected"
+ "1 folder selected"
+ "1 shortcut selected"
+ "install shortcuts"
+ "Allows an app to add shortcuts without user intervention."
+ "uninstall shortcuts"
+ "Allows the app to remove shortcuts without user intervention."
+ "read Home settings and shortcuts"
+ "Allows the app to read the settings and shortcuts in Home."
+ "write Home settings and shortcuts"
+ "Allows the app to change the settings and shortcuts in Home."
+ "Problem loading widget"
+ "This is a system app and can\'t be uninstalled."
+ "Rocket Launcher"
+ "Unnamed Folder"
+ "Home screen %1$d"
+ "Page %1$d of %2$d"
+ "Home screen %1$d of %2$d"
+ "Apps page %1$d of %2$d"
+ "Widgets page %1$d of %2$d"
+ "Welcome!"
+ "Make yourself at home."
+
+
+ "Create more screens for apps and folders"
+ "Organise your space"
+ "Touch & hold background to manage wallpaper, widgets and settings."
+ "Choose some apps"
+ "To add an app to your Home screen, touch & hold it."
+ "Here\'s a folder"
+ "To create one like this, touch & hold an app, then move it over another."
+ "OK"
+ "Folder opened, %1$d by %2$d"
+ "Touch to close folder"
+ "Touch to save rename"
+ "Folder closed"
+ "Folder renamed to %1$s"
+ "Folder: %1$s"
+
+
+
+
+ "Widgets"
+ "Wallpapers"
+ "Settings"
+
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000000..316585e0ea
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Home"
+ "Android Core Apps"
+
+ "Set wallpaper"
+
+ "%1$d selected"
+ "%1$d selected"
+ "%1$d selected"
+
+ "Wallpaper %1$d of %2$d"
+ "Selected %1$s"
+ "Delete"
+ "Pick image"
+ "Wallpapers"
+ "Crop wallpaper"
+ "App isn\'t installed."
+ "Widgets"
+ "Widgets"
+ "Show Mem"
+ "Touch & hold to pick up a widget."
+ "Shop"
+ "%1$d × %2$d"
+ "Couldn\'t drop item on this Home screen."
+ "Choose widget to create"
+ "Folder name"
+ "Rename folder"
+ "OK"
+ "Cancel"
+ "Add to Home screen"
+ "Apps"
+ "Shortcuts"
+ "Widgets"
+ "No more room on your Home screens."
+ "No more room on this Home screen."
+ "No more room on the hot seat."
+ "This widget is too large for the hot seat."
+ "Shortcut \"%s\" created."
+ "Shortcut \"%s\" was removed."
+ "Shortcut \"%s\" already exists."
+ "Choose shortcut"
+ "Choose app"
+ "Apps"
+ "Home"
+ "Remove"
+ "Uninstall"
+ "Remove"
+ "Uninstall"
+ "App info"
+ "Search"
+ "Voice Search"
+ "Apps"
+ "Remove"
+ "Uninstall update"
+ "Uninstall app"
+ "App details"
+ "1 app selected"
+ "1 widget selected"
+ "1 folder selected"
+ "1 shortcut selected"
+ "install shortcuts"
+ "Allows an app to add shortcuts without user intervention."
+ "uninstall shortcuts"
+ "Allows the app to remove shortcuts without user intervention."
+ "read Home settings and shortcuts"
+ "Allows the app to read the settings and shortcuts in Home."
+ "write Home settings and shortcuts"
+ "Allows the app to change the settings and shortcuts in Home."
+ "Problem loading widget"
+ "This is a system app and can\'t be uninstalled."
+ "Rocket Launcher"
+ "Unnamed Folder"
+ "Home screen %1$d"
+ "Page %1$d of %2$d"
+ "Home screen %1$d of %2$d"
+ "Apps page %1$d of %2$d"
+ "Widgets page %1$d of %2$d"
+ "Welcome!"
+ "Make yourself at home."
+
+
+ "Create more screens for apps and folders"
+ "Organise your space"
+ "Touch & hold background to manage wallpaper, widgets and settings."
+ "Choose some apps"
+ "To add an app to your Home screen, touch & hold it."
+ "Here\'s a folder"
+ "To create one like this, touch & hold an app, then move it over another."
+ "OK"
+ "Folder opened, %1$d by %2$d"
+ "Touch to close folder"
+ "Touch to save rename"
+ "Folder closed"
+ "Folder renamed to %1$s"
+ "Folder: %1$s"
+
+
+
+
+ "Widgets"
+ "Wallpapers"
+ "Settings"
+
diff --git a/res/values-es-land/strings.xml b/res/values-es-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-es-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-es-rUS-land/strings.xml b/res/values-es-rUS-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-es-rUS-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000000..c9a44a98c9
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Pantalla principal"
+ "Aplicaciones básicas de Android"
+
+ "Establecer como fondo de pantalla"
+
+ "%1$d seleccionados"
+ "%1$d seleccionado"
+ "%1$d seleccionados"
+
+ "Fondo de pantalla %1$d de %2$d"
+ "%1$s seleccionado"
+ "Eliminar"
+ "Elegir imagen"
+ "Fondos de pantalla"
+ "Recortar fondo de pantalla"
+ "No se instaló la aplicación."
+ "Widgets"
+ "Widgets"
+ "Mostrar memoria"
+ "Mantén presionado el widget que desees elegir."
+ "Comprar"
+ "%1$d × %2$d"
+ "Error al soltar elemento en la pantalla principal"
+ "Elegir los widgets para crear"
+ "Nombre de carpeta"
+ "Cambiar nombre de carpeta"
+ "Aceptar"
+ "Cancelar"
+ "Agregar a la pantalla principal"
+ "Aplicaciones"
+ "Accesos directos"
+ "Widgets"
+ "No hay más espacio en tus pantallas principales."
+ "No hay más espacio en esta pantalla principal."
+ "No hay más espacio en la barra de accesos directos."
+ "Este widget es demasiado grande para la barra de accesos directos."
+ "Se creó el acceso directo \"%s\"."
+ "Se eliminó el acceso directo \"%s\"."
+ "El acceso directo \"%s\" ya existe."
+ "Elegir acceso directo"
+ "Elegir aplicación"
+ "Aplicaciones"
+ "Pantalla principal"
+ "Eliminar"
+ "Desinstalar"
+ "Eliminar"
+ "Desinstalar"
+ "Información de la aplicación"
+ "Buscar"
+ "Búsqueda por voz"
+ "Aplicaciones"
+ "Eliminar"
+ "Desinstalar actualización"
+ "Desinstalar aplicación"
+ "Detalles de la aplicación"
+ "Se seleccionó 1 aplicación."
+ "Se seleccionó 1 widget."
+ "Se seleccionó 1 carpeta."
+ "Se seleccionó 1 acceso directo."
+ "instalar accesos directos"
+ "Permite que una aplicación agregue accesos directos sin que el usuario intervenga."
+ "desinstalar accesos directos"
+ "Permite que la aplicación elimine accesos directos sin que el usuario intervenga."
+ "leer configuración y accesos directos de la pantalla principal"
+ "Permite que la aplicación lea la configuración y los accesos directos de la pantalla principal."
+ "escribir configuración y accesos directos de la pantalla principal"
+ "Permite que la aplicación cambie la configuración y los accesos directos de la pantalla principal."
+ "Problema al cargar el widget"
+ "Esta es una aplicación del sistema y no se puede desinstalar."
+ "Lanzacohetes"
+ "Carpeta sin nombre"
+ "Pantalla principal %1$d"
+ "Página %1$d de %2$d"
+ "Pantalla principal %1$d de %2$d"
+ "Página de aplicaciones %1$d de %2$d"
+ "Página de widgets %1$d de %2$d"
+ "¡Bienvenido/a!"
+ "Siéntete como en casa."
+
+
+ "Crea más pantallas para aplicaciones y carpetas."
+ "Organiza tu espacio"
+ "Mantén presionado el fondo para administrar el fondo de pantalla, los widgets y la configuración."
+ "Elige algunas aplicaciones"
+ "Para agregar una aplicación a tu pantalla principal, mantenla presionada."
+ "Aquí tienes una carpeta"
+ "Para crear una carpeta como esta, mantén presionada una aplicación y luego muévela sobre otra."
+ "Aceptar"
+ "Carpeta abierta, %1$d por %2$d"
+ "Toca para cerrar la carpeta."
+ "Toca para guardar el nuevo nombre."
+ "Carpeta cerrada"
+ "El nombre de la carpeta se cambió a %1$s."
+ "Carpeta: %1$s"
+
+
+
+
+ "Widgets"
+ "Fondos de pantalla"
+ "Configuración"
+
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000000..193e096361
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Home"
+ "Aplicaciones básicas de Android"
+
+ "Establecer fondo"
+
+ "Seleccionados: %1$d"
+ "Seleccionados: %1$d"
+ "Seleccionados: %1$d"
+
+ "Fondo de pantalla %1$d de %2$d"
+ "%1$s seleccionado"
+ "Eliminar"
+ "Seleccionar imagen"
+ "Fondos de pantalla"
+ "Recortar fondo de pantalla"
+ "La aplicación no está instalada."
+ "Widgets"
+ "Widgets"
+ "Mostrar memoria"
+ "Mantén pulsado el widget que quieras seleccionar."
+ "Tienda"
+ "%1$d × %2$d"
+ "Error al arrastrar elemento a pantalla de inicio."
+ "Selecciona widget a añadir"
+ "Nombre de carpeta"
+ "Cambiar nombre de carpeta"
+ "Aceptar"
+ "Cancelar"
+ "Añadir a la pantalla de inicio"
+ "Aplicaciones"
+ "Accesos directos"
+ "Widgets"
+ "No queda espacio en las pantallas de inicio."
+ "No queda espacio en la pantalla de inicio."
+ "No queda espacio en la barra de accesos directos."
+ "Este widget es demasiado grande para la barra de accesos directos."
+ "Se ha creado el acceso directo \"%s\"."
+ "Se ha eliminado el acceso directo \"%s\"."
+ "El acceso directo \"%s\" ya existe."
+ "Selecciona un acceso directo"
+ "Selecciona una aplicación"
+ "Aplicaciones"
+ "Inicio"
+ "Eliminar"
+ "Desinstalar"
+ "Eliminar"
+ "Desinstalar"
+ "Información de la aplicación"
+ "Buscar"
+ "Búsqueda por voz"
+ "Aplicaciones"
+ "Eliminar"
+ "Desinstalar actualización"
+ "Desinstalar aplicación"
+ "Información de la aplicación"
+ "1 aplicación seleccionada"
+ "1 widget seleccionado"
+ "1 carpeta seleccionada"
+ "1 acceso directo seleccionado"
+ "instalar accesos directos"
+ "Permite que una aplicación añada accesos directos sin intervención del usuario."
+ "desinstalar accesos directos"
+ "Permite que la aplicación elimine accesos directos sin intervención del usuario."
+ "leer información de accesos directos y de ajustes de la pantalla de inicio"
+ "Permite que la aplicación consulte los ajustes y los accesos directos de la pantalla de inicio."
+ "escribir información de accesos directos y de ajustes de la pantalla de inicio"
+ "Permite que las aplicaciones cambien los ajustes y los accesos directos de la pantalla de inicio."
+ "Problema al cargar el widget"
+ "Esta aplicación es del sistema y no se puede desinstalar."
+ "Rocket Launcher"
+ "Carpeta sin nombre"
+ "Pantalla de inicio %1$d"
+ "Página %1$d de %2$d"
+ "Pantalla de inicio %1$d de %2$d"
+ "Página de aplicaciones %1$d de %2$d"
+ "Página de widgets %1$d de %2$d"
+ "Te damos la bienvenida"
+ "Personaliza tu pantalla de inicio."
+
+
+ "Crea más pantallas para aplicaciones y carpetas"
+ "Organiza tu espacio"
+ "Mantén pulsado el fondo para gestionar el fondo de pantalla, los widgets y los ajustes."
+ "Selecciona algunas aplicaciones"
+ "Para añadir una aplicación a tu pantalla de inicio, solo tienes que mantenerla pulsada."
+ "Esto es una carpeta"
+ "Para crear una carpeta como esta, mantén pulsada una aplicación y muévela sobre otra."
+ "Aceptar"
+ "Carpeta abierta, %1$d por %2$d"
+ "Toca para cerrar la carpeta"
+ "Toca para cambiar el nuevo nombre"
+ "Carpeta cerrada"
+ "Se ha cambiado el nombre de la carpeta a %1$s"
+ "Carpeta: %1$s"
+
+
+
+
+ "Widgets"
+ "Fondos de pantalla"
+ "Ajustes"
+
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000000..f026b08617
--- /dev/null
+++ b/res/values-et-rEE/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Avaekraan"
+ "Androidi tuumrakendused"
+
+ "Määra taustapilt"
+
+ "Valitud on %1$d"
+ "Valitud on %1$d"
+ "Valitud on %1$d"
+
+ "%1$d/%2$d taustapildist"
+ "Valitud on %1$s"
+ "Kustuta"
+ "Vali kujutis"
+ "Taustapildid"
+ "Taustapildi kärpimine"
+ "Rakendus pole installitud."
+ "Vidinad"
+ "Vidinad"
+ "Mälu kuvamine"
+ "Vidina valimiseks vajutage ja hoidke seda all."
+ "Pood"
+ "%1$d × %2$d"
+ "Üksust ei saanud sellele avaekraanile tuua."
+ "Valige loomiseks vidin"
+ "Kausta nimi"
+ "Kausta ümbernimetamine"
+ "OK"
+ "Tühista"
+ "Avaekraanile lisamine"
+ "Rakendused"
+ "Otseteed"
+ "Vidinad"
+ "Teie avaekraanidel ei ole enam ruumi."
+ "Sellel avaekraanil pole enam ruumi."
+ "Kohandataval dokialal pole rohkem ruumi."
+ "See vidin on kohandatava dokiala jaoks liiga suur."
+ "Otsetee „%s” on loodud."
+ "Otsetee „%s” on eemaldatud."
+ "Otsetee „%s” on juba olemas."
+ "Otsetee valimine"
+ "Rakenduse valimine"
+ "Rakendused"
+ "Avaekraan"
+ "Eemalda"
+ "Desinstalli"
+ "Eemalda"
+ "Desinstalli"
+ "Rakenduse teave"
+ "Otsing"
+ "Häälotsing"
+ "Rakendused"
+ "Eemalda"
+ "Desinstalli värskendus"
+ "Rakenduse desinstallimine"
+ "Rakenduse üksikasjad"
+ "Valitud on 1 rakendus"
+ "Valitud on 1 vidin"
+ "Valitud on 1 kaust"
+ "Valitud on 1 otsetee"
+ "installi otseteed"
+ "Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta."
+ "desinstalli otseteed"
+ "Võimaldab rakendusel eemaldada otseteid kasutaja sekkumiseta."
+ "loe avaekraani seadeid ja otseteid"
+ "Võimaldab rakendusel lugeda avaekraanil seadeid ja otseteid."
+ "kirjuta avaekraani seaded ja otseteed"
+ "Võimaldab rakendusel muuta avaekraanil seadeid ja otseteid."
+ "Probleem vidina laadimisel"
+ "See on süsteemirakendus ja seda ei saa desinstallida."
+ "Rocket Launcher"
+ "Nimetu kaust"
+ "Avaekraan %1$d"
+ "Leht %1$d/%2$d"
+ "Avaekraan %1$d/%2$d"
+ "Rakenduste leht %1$d/%2$d"
+ "Vidinate leht %1$d/%2$d"
+ "Tere tulemast!"
+ "Tundke end nagu kodus."
+
+
+ "Looge rakenduste ja kaustade jaoks rohkem ekraanikuvasid"
+ "Korraldage oma ruumi"
+ "Taustapildi, vidinate ja seadete haldamiseks puudutage tausta ning hoidke seda all."
+ "Valige mõned rakendused"
+ "Rakenduse lisamiseks avaekraanile vajutage ja hoidke seda all."
+ "Siin on kaust"
+ "Sarnase loomiseks vajutage ja hoidke rakendust all, seejärel viige see teise peale."
+ "OK"
+ "Kaust on avatud, %1$d x %2$d"
+ "Puudutage kausta sulgemiseks"
+ "Puudutage uue nime salvestamiseks"
+ "Kaust on suletud"
+ "Kausta uus nimi: %1$s"
+ "Kaust: %1$s"
+
+
+
+
+ "Vidinad"
+ "Taustapildid"
+ "Seaded"
+
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
new file mode 100644
index 0000000000..acd0727254
--- /dev/null
+++ b/res/values-et/strings.xml
@@ -0,0 +1,115 @@
+
+
+
+
+ "Käivitaja"
+ "Kodu"
+ "Androidi tuumrakendused"
+
+ "Taustapildi valimiskoht:"
+ "Määra taustapilt"
+ "Taustapildid"
+ "Rakendus pole installitud."
+ "Vidinad"
+ "Vidina valimiseks puudutage seda pikalt."
+ "Pood"
+ "%1$d × %2$d"
+ "Üksust ei saa sellele avaekraanile tuua."
+ "Valige loomiseks vidin"
+ "Kausta nimi"
+ "Nimeta kaust ümber"
+ "OK"
+ "Tühista"
+ "Lisa avamenüüsse"
+ "Rakendused"
+ "Otseteed"
+ "Vidinad"
+ "Taustapildid"
+ "Teie avakuvadel ei ole enam ruumi."
+ "Sellel avalehel pole enam ruumi."
+ "Kohandataval dokialal pole rohkem ruumi."
+ "See vidin on tööpunkti jaoks liiga suur."
+ "Otsetee „%s” loodud."
+ "Otsetee „%s” eemaldatud."
+ "Otsetee „%s” on juba olemas."
+ "Otsetee valimine"
+ "Rakenduse valimine"
+ "Rakendused"
+ "Kodu"
+ "Eemalda"
+ "Desinstalli"
+ "Eemalda"
+ "Desinstalli"
+ "Rakenduse teave"
+ "Otsing"
+ "Häälotsing"
+ "Rakendused"
+ "Eemalda"
+ "Desinstalli värskendus"
+ "Lisa"
+ "Rakenduste haldamine"
+ "Taustapilt"
+ "Otsing"
+ "Teadistused"
+ "Süsteemiseaded"
+ "Abi"
+ "Rakenduse desinstallimine"
+ "Rakenduse üksikasjad"
+ "Valitud on 1 rakendus"
+ "1 vidin on valitud"
+ "1 kaust on valitud"
+ "1 otsetee on valitud"
+ "otseteede installimine"
+ "Võimaldab rakendusel lisada otseteid kasutaja sekkumiseta."
+ "otseteede desinstallimine"
+ "Võimaldab rakendusel eemaldada otseteid kasutaja sekkumiseta."
+ "avalehe seadete ja otseteede lugemine"
+ "Võimaldab rakendusel lugeda avalehe seadeid ja otseteid."
+ "avalehe seadete ja otseteede kirjutamine"
+ "Võimaldab rakendusel muuta avalehel seadeid ja otseteid."
+ "Probleem vidina laadimisel"
+ "See on süsteemirakendus ja seda ei saa desinstallida."
+ "Rocket Launcher"
+ "Nimeta kaust"
+ "Avakuva %1$d"
+ "Leht %1$d/%2$d"
+ "Avakuva %1$d/%2$d"
+ "Rakenduste leht %1$d/%2$d"
+ "Vidinate leht %1$d/%2$d"
+ "Tunne end nagu kodus"
+ "Võite panna oma lemmikrakendused siia."
+ "Kõikide oma rakenduste nägemiseks puudutage ringi."
+ "Valige mõned rakendused"
+ "Rakenduse lisamiseks avakuvale puudutage seda pikalt."
+ "Korraldage oma rakendused kaustadesse"
+ "Rakenduse liigutamiseks pange sõrm rakendusele ja hoidke seda."
+ "Avakuval uue kausta tegemiseks virnastage üks rakendus teisele."
+ "OK"
+ "Kaust on avatud, %1$d x %2$d"
+ "Puudutage kausta sulgemiseks"
+ "Puudutage uue nime salvestamiseks"
+ "Kaust suletud"
+ "Kausta uus nimi: %1$s"
+ "%1$s"
+
+
+
+
+
diff --git a/res/values-fa-land/strings.xml b/res/values-fa-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-fa-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000000..3a0d0b8753
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "صفحه اصلی"
+ "برنامههای Android Core"
+
+ "تنظیم کاغذدیواری"
+
+ "%1$d انتخاب شد"
+ "%1$d انتخاب شد"
+ "%1$d انتخاب شد"
+
+ "کاغذ دیواری %1$d از %2$d"
+ "%1$s انتخاب شده"
+ "حذف"
+ "انتخاب تصویر"
+ "کاغذدیواریها"
+ "برش کاغذ دیواری"
+ "برنامه نصب نشده است."
+ "ابزارکها"
+ "ابزارکها"
+ "نمایش Mem"
+ "برای انتخاب ابزارک لمس کنید و نگه دارید."
+ "فروشگاه"
+ "%1$d × %2$d"
+ "این مورد را نمیتوان در این صفحه اصلی رها کرد."
+ "انتخاب ابزارکی که باید ایجاد شود"
+ "نام پوشه"
+ "تغییر نام پوشه"
+ "تأیید"
+ "لغو"
+ "افزودن به صفحه اصلی"
+ "برنامهها"
+ "میانبرها"
+ "ابزارکها"
+ "فضای بیشتری در صفحات نمایش اصلی شما موجود نیست."
+ "فضای بیشتری در این صفحه اصلی موجود نیست."
+ "فضای بیشتری در جایگاه اتصال نیست."
+ "این ابزارک بیش از حد برای جایگاه اتصال بزرگ است."
+ "میانبر «%s» ایجاد شد."
+ "میانبر «%s» حذف شد."
+ "میانبر «%s» در حال حاضر وجود دارد."
+ "انتخاب میانبر"
+ "انتخاب برنامه"
+ "برنامهها"
+ "صفحه اصلی"
+ "حذف"
+ "حذف نصب"
+ "حذف"
+ "حذف نصب"
+ "اطلاعات برنامه"
+ "جستجو"
+ "جستجوی شفاهی"
+ "برنامهها"
+ "حذف"
+ "حذف نصب بهروزرسانی"
+ "حذف نصب برنامه"
+ "جزئیات برنامه"
+ "۱ برنامه انتخاب شد"
+ "۱ ابزارک انتخاب شد"
+ "۱ پوشه انتخاب شد"
+ "۱ میانبر انتخاب شد"
+ "نصب میانبرها"
+ "به برنامه اجازه میدهد میانبرها را بدون دخالت کاربر اضافه کند."
+ "حذف نصب میانبرها"
+ "به برنامه اجازه میدهد میانبرها را بدون دخالت کاربر حذف کند."
+ "خواندن تنظیمات و میانبرهای صفحه اصلی"
+ "به برنامه اجازه میدهد تنظیمات و میانبرها را در صفحه اصلی بخواند."
+ "نوشتن تنظیمات و میانبرهای صفحه اصلی"
+ "به برنامه اجازه میدهد تنظیمات و میانبرها را در صفحه اصلی تغییر دهد."
+ "مشکل در بارگیری ابزارک"
+ "این برنامه سیستمی است و حذف نصب نمیشود."
+ "Rocket Launcher"
+ "پوشه بینام"
+ "صفحه اصلی %1$d"
+ "صفحه %1$d از %2$d"
+ "صفحه اصلی %1$d از %2$d"
+ "صفحه برنامهها %1$d از %2$d"
+ "صفحه ابزارکها %1$d از %2$d"
+ "خوش آمدید!"
+ "راحت باشید."
+
+
+ "صفحات بیشتری را برای برنامهها و پوشهها ایجاد کنید"
+ "فضای خود را سازماندهی کنید"
+ "برای مدیریت کاغذدیواری، ابزارکها و تنظیمات، پسزمینه را لمس کرده و نگهدارید."
+ "چند برنامه انتخاب کنید"
+ "اگر میخواهید برنامهای را به صفحه اصلی خود اضافه کنید، آن را لمس کرده، نگهدارید."
+ "اینجا یک پوشه است"
+ "برای ایجاد پوشهای مثل این، یک برنامه را لمس کرده و نگهدارید، سپس آن را روی برنامه دیگر بیاندازید."
+ "تأیید"
+ "پوشه باز شده، %1$d در %2$d"
+ "برای بستن پوشه لمس کنید"
+ "برای ذخیره تغییر نام لمس کنید"
+ "پوشه بسته شد"
+ "نام پوشه به %1$s تغییر کرد"
+ "پوشه: %1$s"
+
+
+
+
+ "ابزارکها"
+ "کاغذدیواریها"
+ "تنظیمات"
+
diff --git a/res/values-fi-land/strings.xml b/res/values-fi-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-fi-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000000..d327250319
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Aloitusruutu"
+ "Androidin ydinsovellukset"
+
+ "Aseta taustakuva"
+
+ "%1$d valittu"
+ "%1$d valittu"
+ "%1$d valittu"
+
+ "Taustakuva %1$d/%2$d"
+ "Valittu: %1$s"
+ "Poista"
+ "Valitse kuva"
+ "Taustakuvat"
+ "Rajaa taustakuva"
+ "Sovellusta ei ole asennettu."
+ "Widgetit"
+ "Widgetit"
+ "Näytä muisti"
+ "Valitse widget painamalla sitä pitkään."
+ "Kauppa"
+ "%1$d × %2$d"
+ "Kohteen lisääminen tähän aloitusruutuun epäonnistui."
+ "Valitse luotava widget"
+ "Kansion nimi"
+ "Nimeä kansio uudelleen"
+ "OK"
+ "Peruuta"
+ "Lisää aloitusruutuun"
+ "Sovellukset"
+ "Pikakuvakkeet"
+ "Widgetit"
+ "Aloitusruuduilla ei ole enää tilaa."
+ "Tässä aloitusruudussa ei ole enää tilaa."
+ "Hotseatissa ei ole enää tilaa."
+ "Tämä widget on liian suuri tähän hotseat-paikkaan."
+ "Pikakuvake %s luotiin."
+ "Pikakuvake %s poistettiin."
+ "Pikakuvake %s on jo olemassa."
+ "Valitse pikakuvake"
+ "Valitse sovellus"
+ "Sovellukset"
+ "Aloitusruutu"
+ "Poista"
+ "Poista"
+ "Poista"
+ "Poista"
+ "Sovelluksen tiedot"
+ "Haku"
+ "Puhehaku"
+ "Sovellukset"
+ "Poista"
+ "Poista päivitys"
+ "Poista sovellus"
+ "Sovelluksen tiedot"
+ "1 sovellus valittu"
+ "1 widget valittu"
+ "1 kansio valittu"
+ "1 pikakuvake valittu"
+ "asenna pikakuvakkeita"
+ "Antaa sovelluksen lisätä pikakuvakkeita itsenäisesti ilman käyttäjän valintaa."
+ "poista pikakuvakkeita"
+ "Antaa sovelluksen poistaa pikakuvakkeita ilman käyttäjän valintaa."
+ "lue aloitusruudun asetuksia ja pikakuvakkeita"
+ "Antaa sovelluksen lukea aloitusruudun asetukset ja pikakuvakkeet."
+ "kirjoita aloitusruudun asetuksia ja pikakuvakkeita"
+ "Antaa sovelluksen muuttaa aloitusruudun asetuksia ja pikakuvakkeita."
+ "Ongelma ladattaessa widgetiä"
+ "Tämä on järjestelmäsovellus, eikä sitä voi poistaa."
+ "Sinko"
+ "Nimetön kansio"
+ "Aloitusruutu %1$d"
+ "Sivu %1$d / %2$d"
+ "Aloitusruutu %1$d/%2$d"
+ "Apps-sivu %1$d / %2$d"
+ "Widgetit-sivu %1$d / %2$d"
+ "Tervetuloa!"
+ "Ole kuin kotonasi."
+
+
+ "Luo lisää ruutuja sovelluksille ja kansioille"
+ "Järjestä tilasi"
+ "Hallitse taustakuvaa, widgetejä ja asetuksia koskettamalla taustaa pitkään."
+ "Valitse joitakin sovelluksia"
+ "Voit lisätä sovelluksen aloitusruutuun koskettamalla sitä pitkään."
+ "Tässä on kansio"
+ "Luo se seuraavasti: kosketa sovellusta pitkään ja siirrä se sitten toisen päälle."
+ "OK"
+ "Kansio avattu, koko %1$d x %2$d"
+ "Sulje kansio koskettamalla"
+ "Tallenna uudella nimellä koskettamalla"
+ "Kansio on suljettu"
+ "Kansion nimeksi vaihdettiin %1$s"
+ "Kansio: %1$s"
+
+
+
+
+ "Widgetit"
+ "Taustakuvat"
+ "Asetukset"
+
diff --git a/res/values-fr-land/strings.xml b/res/values-fr-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-fr-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000000..9d8dbeb90b
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Lanceur3"
+ "Accueil"
+ "Applications de base Android"
+
+ "Définir le fond d\'écran"
+
+ "%1$d sélectionné"
+ "%1$d sélectionné"
+ "%1$d sélectionné(s)"
+
+ "Fond d\'écran %1$d sur %2$d"
+ "Sélection : %1$s"
+ "Supprimer"
+ "Sélectionner une image"
+ "Fonds d\'écran"
+ "Rogner le fond d\'écran"
+ "L\'application n\'est pas installée."
+ "Widgets"
+ "Widgets"
+ "Afficher la mémoire"
+ "Maintenez un doigt sur le widget pour l\'ajouter."
+ "Magasiner"
+ "%1$d × %2$d"
+ "Imposs. de déposer l\'élément sur l\'écran d\'accueil"
+ "Sélectionnez le widget à créer"
+ "Nom du dossier"
+ "Renommer le dossier"
+ "OK"
+ "Annuler"
+ "Ajouter à l\'écran d\'accueil"
+ "Applications"
+ "Raccourcis"
+ "Widgets"
+ "Vous n\'avez plus d\'espace libre sur vos écrans d\'accueil."
+ "Pas d\'espace libre sur l\'écran d\'accueil."
+ "Vous n\'avez plus de place sur la barre d\'accès rapide."
+ "Ce widget est trop volumineux pour la barre d\'accès rapide."
+ "Le raccourci « %s » a été créé."
+ "Le raccourci « %s » a été supprimé."
+ "Le raccourci « %s » existe déjà."
+ "Sélectionner un raccourci"
+ "Sélectionner une application"
+ "Applications"
+ "Accueil"
+ "Supprimer"
+ "Désinstaller"
+ "Supprimer"
+ "Désinstaller"
+ "Détails de l\'application"
+ "Rechercher"
+ "Recherche vocale"
+ "Applications"
+ "Supprimer"
+ "Désinstaller la mise à jour"
+ "Désinstaller l\'application"
+ "Détails de l\'application"
+ "1 application sélectionnée"
+ "1 widget sélectionné"
+ "1 dossier sélectionné"
+ "1 raccourci sélectionné"
+ "installer des raccourcis"
+ "Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."
+ "désinstaller des raccourcis"
+ "Permet à l\'application de supprimer des raccourcis sans l\'intervention de l\'utilisateur."
+ "lire les paramètres et les raccourcis de la page d\'accueil"
+ "Permet à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil."
+ "enregistrer les paramètres de la page d\'accueil et des raccourcis"
+ "Permet à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil."
+ "Problème lors du chargement du widget"
+ "Impossible de désinstaller cette application, car il s\'agit d\'une application système."
+ "Lance-missile"
+ "Dossier sans nom"
+ "Écran d\'accueil %1$d"
+ "Page %1$d sur %2$d"
+ "Écran d\'accueil %1$d sur %2$d"
+ "Page des applications : %1$d sur %2$d"
+ "Page des widgets : %1$d sur %2$d"
+ "Bienvenue!"
+ "Faites comme chez vous."
+
+
+ "Créer plus d\'écrans pour les applications et les dossiers"
+ "Organiser son espace personnel"
+ "Maintenez votre doigt sur l\'arrière-plan pour gérer les fonds d\'écran, les widgets et les paramètres."
+ "Sélectionner des applications"
+ "Pour ajouter une application à votre écran d\'accueil, maintenez votre doigt dessus."
+ "Voici un dossier"
+ "Pour créer un dossier comme ça, maintenez votre doigt sur une application, puis déplacez-la sur une autre."
+ "OK"
+ "Dossier ouvert, %1$d par %2$d"
+ "Toucher pour fermer le dossier"
+ "Toucher pour enregistrer le nouveau nom"
+ "Dossier fermé"
+ "Nouveau nom du dossier : %1$s"
+ "Dossier : %1$s"
+
+
+
+
+ "Widgets"
+ "Fonds d\'écran"
+ "Paramètres"
+
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000000..b1b0a79259
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Accueil"
+ "Applications de base Android"
+
+ "Définir comme fond d\'écran"
+
+ "%1$d fond d\'écran sélectionné"
+ "%1$d fond d\'écran sélectionné"
+ "%1$d fonds d\'écran sélectionnés"
+
+ "Fond d\'écran %1$d sur %2$d"
+ "%1$s est sélectionné."
+ "Supprimer"
+ "Sélectionner une image"
+ "Fonds d\'écran"
+ "Rogner le fond d\'écran"
+ "L\'application n\'est pas installée."
+ "Widgets"
+ "Widgets"
+ "Afficher la mémoire"
+ "App. de manière prolongée pour sélectionner widget."
+ "Boutique"
+ "%1$d x %2$d"
+ "Impossible de déposer élément sur écran d\'accueil."
+ "Sélectionner le widget à créer"
+ "Nom du dossier"
+ "Renommer le dossier"
+ "OK"
+ "Annuler"
+ "Ajouter à l\'écran d\'accueil"
+ "Applications"
+ "Raccourcis"
+ "Widgets"
+ "Vous n\'avez plus d\'espace libre sur vos écrans d\'accueil."
+ "Pas d\'espace libre sur cet écran d\'accueil."
+ "Vous n\'avez plus de place sur la barre d\'accès rapide."
+ "Ce widget est trop volumineux pour la barre d\'accès rapide."
+ "Le raccourci \"%s\" a été créé."
+ "Le raccourci \"%s\" a été supprimé."
+ "Le raccourci \"%s\" existe déjà."
+ "Sélectionner un raccourci"
+ "Sélectionner une application"
+ "Applications"
+ "Accueil"
+ "Supprimer"
+ "Désinstaller"
+ "Supprimer"
+ "Désinstaller"
+ "Informations sur l\'application"
+ "Rechercher"
+ "Recherche vocale"
+ "Applications"
+ "Supprimer"
+ "Désinstaller la mise à jour"
+ "Désinstaller l\'application"
+ "Informations sur l\'application"
+ "1 application sélectionnée"
+ "1 widget sélectionné"
+ "1 dossier sélectionné"
+ "1 raccourci sélectionné"
+ "installer des raccourcis"
+ "Permettre à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur"
+ "désinstaller des raccourcis"
+ "Permettre à l\'application de supprimer des raccourcis sans l\'intervention de l\'utilisateur"
+ "lire les paramètres et les raccourcis de l\'écran d\'accueil"
+ "Permettre à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil"
+ "modifier les paramètres et les raccourcis de l\'écran d\'accueil"
+ "Permettre à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil"
+ "Problème lors du chargement du widget."
+ "Impossible de désinstaller cette application, car il s\'agit d\'une application système."
+ "Rocket Launcher"
+ "Dossier sans nom"
+ "Écran d\'accueil %1$d"
+ "Page %1$d sur %2$d"
+ "Écran d\'accueil %1$d sur %2$d"
+ "Page des applications %1$d sur %2$d"
+ "Page des widgets %1$d sur %2$d"
+ "Bienvenue !"
+ "Familiarisez-vous avec l\'écran d\'accueil."
+
+
+ "Créez des écrans personnalisés pour vos applis et dossiers"
+ "Organisez votre espace"
+ "Appuyez de manière prolongée sur l\'arrière-plan pour gérer les fonds d\'écran, les widgets et les paramètres."
+ "Sélectionner des applications"
+ "Pour ajouter une application à votre écran d\'accueil, appuyez dessus de manière prolongée."
+ "Voici un dossier"
+ "Pour en créer un, appuyez de manière prolongée sur une application, puis déplacez-la vers une autre."
+ "OK"
+ "Dossier ouvert, %1$d par %2$d"
+ "Appuyez pour fermer le dossier."
+ "Appuyez pour enregistrer le nouveau nom."
+ "Dossier fermé"
+ "Nouveau nom du dossier : %1$s"
+ "Dossier \"%1$s\""
+
+
+
+
+ "Widgets"
+ "Fonds d\'écran"
+ "Paramètres"
+
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000000..eddd75ce44
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "होम"
+ "Android के मुख्य एप्लिकेशन"
+
+ "वॉलपेपर सेट करें"
+
+ "%1$d चयनित"
+ "%1$d चयनित"
+ "%1$d चयनित"
+
+ "वॉलपेपर %2$d में से %1$d"
+ "चयनित %1$s"
+ "हटाएं"
+ "चित्र चुनें"
+ "वॉलपेपर"
+ "वॉलपेपर काटें"
+ "एप्लिकेशन इंस्टॉल नहीं है."
+ "विजेट"
+ "विजेट"
+ "स्मृति दिखाएं"
+ "विजेट को चुनने के लिए स्पर्श करके रखें."
+ "खरीदारी करें"
+ "%1$d × %2$d"
+ "आइटम को इस होम स्क्रीन पर नहीं छोड़ा जा सका."
+ "बनाने के लिए विजेट चुनें"
+ "फ़ोल्डर का नाम"
+ "फ़ोल्डर का नाम बदलें"
+ "ठीक"
+ "रद्द करें"
+ "होम स्क्रीन में जोड़ें"
+ "एप्लिकेशन"
+ "शॉर्टकट"
+ "विजेट"
+ "आपकी होम स्क्रीन पर स्थान शेष नहीं है."
+ "इस होम स्क्रीन पर स्थान शेष नहीं है."
+ "हॉटसीट पर स्थान शेष नहीं है."
+ "हॉटसीट के लिए यह विजेट बहुत बड़ा है."
+ "शॉर्टकट \"%s\" बनाया गया."
+ "शॉर्टकट \"%s\" निकाल दिया गया था."
+ "शॉर्टकट \"%s\" पहले से मौजूद है."
+ "शॉर्टकट चुनें"
+ "एप्लिकेशन चुनें"
+ "एप्लिकेशन"
+ "होम"
+ "निकालें"
+ "अनइंस्टॉल करें"
+ "निकालें"
+ "अनइंस्टॉल करें"
+ "एप्लिकेशन की जानकारी"
+ "खोजें"
+ "बोलकर खोजें"
+ "एप्लिकेशन"
+ "निकालें"
+ "अपडेट अनइंस्टॉल करें"
+ "एप्लिकेशन अनइंस्टॉल करें"
+ "एप्लिकेशन का विवरण"
+ "1 एप्लिकेशन चयनित"
+ "1 विजेट चयनित"
+ "1 फ़ोल्डर चयनित"
+ "1 शॉर्टकट चयनित"
+ "शॉर्टकट इंस्टॉल करें"
+ "एप्लिकेशन को उपयोगकर्ता के हस्तक्षेप के बिना शॉर्टकट जोड़ने देती है."
+ "शॉर्टकट अनइंस्टॉल करें"
+ "एप्लिकेशन को उपयोगकर्ता के हस्तक्षेप के बिना शॉर्टकट निकालने देती है."
+ "होम सेटिंग और शॉर्टकट पढ़ें"
+ "एप्लिकेशन को होम में सेटिंग और शॉर्टकट पढ़ने देती है."
+ "होम सेटिंग और शॉर्टकट लिखें"
+ "एप्लिकेशन को होम में सेटिंग और शॉर्टकट बदलने देती है."
+ "विजेट लोड करने में समस्या"
+ "यह एक सिस्टम एप्लिकेशन है और इसे अनइंस्टॉल नहीं किया जा सकता."
+ "रॉकेट लॉन्चर"
+ "अनामित फ़ोल्डर"
+ "होम स्क्रीन %1$d"
+ "पृष्ठ %2$d में से %1$d"
+ "होम स्क्रीन %2$d में से %1$d"
+ "एप्लिकेशन पृष्ठ %2$d में से %1$d"
+ "विजेट पृष्ठ %2$d में से %1$d"
+ "स्वागत है!"
+ "जैसा चाहें वैसा उपयोग करें."
+
+
+ "एप्लिकेशन और फ़ोल्डर के लिए और अधिक स्क्रीन बनाएं"
+ "अपने स्थान को व्यवस्थित करें"
+ "वॉलपेपर, विजेट और सेटिंग प्रबंधित करने के लिए पृष्ठभूमि को स्पर्श करके रखें."
+ "कुछ एप्लिकेशन चुनें"
+ "किसी एप्लिकेशन को अपनी होम स्क्रीन से जोड़ने के लिए, उसे स्पर्श करके रखें."
+ "यहां एक फ़ोल्डर है"
+ "इसके जैसा कोई एक बनाने के लिए, किसी एप्लिकेशन को स्पर्श करके रखें, फिर इसे किसी दूसरे पर ले जाएं."
+ "ठीक"
+ "फ़ोल्डर खोला गया, %1$d गुणा %2$d"
+ "फ़ोल्डर बंद करने के लिए स्पर्श करें"
+ "बदला गया नाम सहेजने के लिए स्पर्श करें"
+ "फ़ोल्डर बंद किया गया"
+ "फ़ोल्डर का नाम बदलकर %1$s किया गया"
+ "फ़ोल्डर: %1$s"
+
+
+
+
+ "विजेट"
+ "वॉलपेपर"
+ "सेटिंग"
+
diff --git a/res/values-hr-land/strings.xml b/res/values-hr-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-hr-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000000..783946d153
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Pokretač3"
+ "Početna"
+ "Matične aplikacije za Android"
+
+ "Postavi pozadinsku sliku"
+
+ "Odabrano: %1$d"
+ "Odabrano: %1$d"
+ "Odabrano: %1$d"
+
+ "%1$d. pozadinska slika od %2$d"
+ "Odabrana je stavka %1$s"
+ "Izbriši"
+ "Odaberi sliku"
+ "Pozadinske slike"
+ "Obreži pozadinsku sliku"
+ "Aplikacija nije instalirana."
+ "Widgeti"
+ "Widgeti"
+ "Prikaži mem"
+ "Dodirnite i držite kako biste podigli widget."
+ "Kupi"
+ "%1$d × %2$d"
+ "Stavka nije ispuštena na ovaj početni zaslon."
+ "Odabir widgeta za stvaranje"
+ "Naziv mape"
+ "Preimenovanje mape"
+ "U redu"
+ "Odustani"
+ "Dodavanje na početni zaslon"
+ "Aplikacije"
+ "Prečaci"
+ "Widgeti"
+ "Na vašim početnim zaslonima više nema mjesta."
+ "Na ovom početnom zaslonu više nema mjesta."
+ "Na hotseatu više nema mjesta."
+ "Ovaj je widget prevelik za hotseat."
+ "Izrađen je prečac za \"%s\"."
+ "Uklonjen je prečac za \"%s\"."
+ "Prečac za \"%s\" već postoji."
+ "Odabir prečaca"
+ "Odabir aplikacije"
+ "Aplikacije"
+ "Početna"
+ "Ukloni"
+ "Deinstaliraj"
+ "Ukloni"
+ "Deinstaliraj"
+ "Informacije o aplikaciji"
+ "Pretraži"
+ "Glasovno pretraživanje"
+ "Aplikacije"
+ "Ukloni"
+ "Deinstalacija ažuriranja"
+ "Deinstaliranje aplikacije"
+ "Pojedinosti o aplikaciji"
+ "Odabrana je 1 aplikacija"
+ "Odabran je 1 widget"
+ "Odabrana je 1 mapa"
+ "Odabran je 1 prečac"
+ "instaliranje prečaca"
+ "Aplikaciji omogućuje dodavanje prečaca bez intervencije korisnika."
+ "deinstaliranje prečaca"
+ "Aplikaciji omogućuje uklanjanje prečaca bez intervencije korisnika."
+ "čitanje postavki početnog zaslona i prečaca"
+ "Aplikaciji omogućuje čitanje postavki i prečaca na početnom zaslonu."
+ "pisanje postavki početnog zaslona i prečaca"
+ "Aplikaciji omogućuje promjenu postavki i prečaca na početnom zaslonu."
+ "Problem pri učitavanju widgeta"
+ "Ovo je aplikacija sustava i ne može se ukloniti."
+ "Lansirna rampa"
+ "Neimenovana mapa"
+ "Početni zaslon %1$d"
+ "Stranica %1$d od %2$d"
+ "Početni zaslon %1$d od %2$d"
+ "Stranica aplikacija %1$d od %2$d"
+ "Stranica widgeta %1$d od %2$d"
+ "Dobro došli!"
+ "Osjećajte se kao kod kuće."
+
+
+ "Izradite više zaslona za aplikacije i mape"
+ "Organizirajte svoj prostor"
+ "Dodirnite i držite pozadinu da biste upravljali pozadinskom slikom, widgetima i postavkama."
+ "Odaberite neke aplikacije"
+ "Da biste dodali aplikaciju na početni zaslon, dodirnite je i zadržite."
+ "Evo mape"
+ "Da biste izradili ovakvu mapu, dodirnite i držite aplikaciju pa je pomaknite preko druge aplikacije."
+ "U redu"
+ "Mapa je otvorena, %1$d x %2$d"
+ "Dodirnite da biste zatvorili mapu"
+ "Dodirnite da biste spremili preimenovanje"
+ "Mapa je zatvorena"
+ "Mapa je preimenovana u %1$s"
+ "Mapa: %1$s"
+
+
+
+
+ "Widgeti"
+ "Pozadinske slike"
+ "Postavke"
+
diff --git a/res/values-hu-land/strings.xml b/res/values-hu-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-hu-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000000..8876652bec
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Főoldal"
+ "Alap Android-alkalmazások"
+
+ "Háttérkép beállítása"
+
+ "%1$d kiválasztva"
+ "%1$d kiválasztva"
+ "%1$d kiválasztva"
+
+ "%2$d/%1$d. háttérkép"
+ "%1$s kiválasztva"
+ "Törlés"
+ "Kép választása"
+ "Háttérképek"
+ "Háttérkép körbevágása"
+ "Az alkalmazás nincs telepítve."
+ "Modulok"
+ "Modulok"
+ "Mem. megjelenítése"
+ "Modul felvételéhez érintse meg, és tartsa lenyomva"
+ "Vásárlás"
+ "%1$d × %2$d"
+ "Nem lehet elemeket dobni erre a kezdőképernyőre."
+ "A létrehozáshoz válasszon modult"
+ "Mappa neve"
+ "Mappa átnevezése"
+ "OK"
+ "Mégse"
+ "Hozzáadás a kezdőképernyőhöz"
+ "Alkalmazások"
+ "Parancsikonok"
+ "Modulok"
+ "Nincs több hely a kezdőképernyőkön."
+ "Nincs több hely ezen a kezdőképernyőn."
+ "Nincs több hely az egyéni mezőben."
+ "Ez a modul túl nagy az egyéni mező számára."
+ "A(z) „%s” parancsikon létrehozva."
+ "A(z) „%s” parancsikon eltávolítva."
+ "A(z) „%s” parancsikon már létezik."
+ "Parancsikon választása"
+ "Válasszon alkalmazást"
+ "Alkalmazások"
+ "Főoldal"
+ "Eltávolítás"
+ "Eltávolítás"
+ "Eltávolítás"
+ "Eltávolítás"
+ "Alkalmazásinformáció"
+ "Keresés"
+ "Hangalapú keresés"
+ "Alkalmazások"
+ "Eltávolítás"
+ "Eltávolítja a frissítést"
+ "Alkalmazás eltávolítása"
+ "Az alkalmazás adatai"
+ "1 alkalmazás kiválasztva"
+ "1 modul kiválasztva"
+ "1 mappa kiválasztva"
+ "1 parancsikon kiválasztva"
+ "parancsikonok telepítése"
+ "Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül adjon hozzá parancsikonokat."
+ "parancsikonok eltávolítása"
+ "Lehetővé teszi egy alkalmazás számára, hogy felhasználói beavatkozás nélkül távolítson el parancsikonokat."
+ "Főoldal beállításainak és parancsikonjainak beolvasása"
+ "Lehetővé teszi az alkalmazás számára, hogy beolvassa a kezdőképernyő beállításait és parancsikonjait."
+ "Főoldal beállításainak és parancsikonjainak írása"
+ "Lehetővé teszi az alkalmazás számára, hogy módosítsa a kezdőképernyő beállításait és parancsikonjait."
+ "Probléma történt a modul betöltésekor"
+ "Ez egy rendszeralkalmazás, és nem lehet eltávolítani."
+ "Aknavető"
+ "Névtelen mappa"
+ "%1$d. kezdőképernyő"
+ "%2$d/%1$d. oldal"
+ "%2$d/%1$d. kezdőképernyő"
+ "%2$d/%1$d. alkalmazásoldal"
+ "%2$d/%1$d. moduloldal"
+ "Üdvözöljük!"
+ "Varázsolja egyedivé készülékét."
+
+
+ "Hozzon létre további képernyőket az alkalmazásoknak és mappáknak"
+ "Munkaterület rendezése"
+ "Érintse meg és tartsa lenyomva a hátteret a háttérkép, modulok és beállítások kezeléséhez."
+ "Válasszon ki néhány alkalmazást"
+ "Ha egy alkalmazást szeretne elhelyezni a kezdőképernyőn, érintse meg, és tartsa lenyomva."
+ "Itt egy mappa"
+ "Mappa létrehozásához érintse meg és tartsa lenyomva az alkalmazást, majd húzza egy másik fölé."
+ "OK"
+ "Mappa megnyitva – szélesség: %1$d; magasság: %2$d"
+ "Érintse meg a mappa bezárásához"
+ "Érintse meg az átnevezés mentéséhez"
+ "Mappa lezárva"
+ "A mappa új neve: %1$s"
+ "Mappa: %1$s"
+
+
+
+
+ "Modulok"
+ "Háttérképek"
+ "Beállítások"
+
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000000..ab7388ae92
--- /dev/null
+++ b/res/values-hy-rAM/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Հիմնական"
+ "Android Core Apps"
+
+ "Սահմանել պաստառը"
+
+ "%1$d ընտրված"
+ "%1$d ընտրված"
+ "%1$d ընտրված"
+
+ "%1$d պաստառ՝ %2$d-ից"
+ "Ընտրված է %1$s"
+ "Ջնջել"
+ "Ընտրել պատկեր"
+ "Պաստառներ"
+ "Եզրատել պաստառը"
+ "Ծրագիրը տեղադրված չէ:"
+ "Վիջեթներ"
+ "Վիջեթներ"
+ "Ցուցադրել մեմը"
+ "Հպեք և պահեք՝ վիջեթն ընտրելու համար:"
+ "Խանութ"
+ "%1$d × %2$d"
+ "Հնարավոր չէ տեղադրել տարրն այս հիմնական էկրանին:"
+ "Ստեղծելու համար ընտրեք վիջեթը"
+ "Թղթապանակի անունը"
+ "Վերանվանել թղթապանակը"
+ "Լավ"
+ "Չեղարկել"
+ "Ավելացնել հիմնական էկրանին"
+ "Ծրագրեր"
+ "Դյուրանցումներ"
+ "Վիջեթներ"
+ "Այլևս տեղ չկա ձեր հիմնական էկրաններին:"
+ "Այլևս տեղ չկա այս հիմնական էկրանին:"
+ "Թեժ նստատեղերում այլևս տեղ չկա:"
+ "Այս վիջեթը չափազանց մեծ է թեժ նստատեղերի համար:"
+ "«%s» դյուրանցումը ստեղծված է:"
+ "«%s» դյուրանցումը հեռացվեց:"
+ "«%s» դյուրանցումն արդեն գոյություն ունի:"
+ "Ընտրել դյուրանցումը"
+ "Ընտրել ծրագիրը"
+ "Ծրագրեր"
+ "Հիմնական"
+ "Հեռացնել"
+ "Ապատեղադրել"
+ "Հեռացնել"
+ "Ապատեղադրել"
+ "Ծրագրի տեղեկություններ"
+ "Որոնել"
+ "Ձայնային որոնում"
+ "Ծրագրեր"
+ "Հեռացնել"
+ "Ապատեղադրել թարմացումը"
+ "Ապատեղադրել ծրագիրը"
+ "Ծրագրի մանրամասներ"
+ "1 ընտրված ծրագիր"
+ "1 ընտրված վիջեթ"
+ "1 ընտրված թղթապանակ"
+ "1 ընտրված դյուրանցում"
+ "տեղադրել դյուրանցումներ"
+ "Ծրագրին թույլ է տալիս ավելացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"
+ "ապատեղադրել դյուրանցումները"
+ "Ծրագրին թույլ է տալիս հեռացնել դյուրանցումներ՝ առանց օգտագործողի միջամտության:"
+ "կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները"
+ "Ծրագրին թույլ է տալիս կարդալ հիմնաէջի կարգավորումներն ու դյուրանցումները:"
+ "ստեղծել հիմնաէջի կարգավորումներ ու դյուրանցումներ"
+ "Ծրագրին թույլ է տալիս փոփոխել հիմնաէջի կարգավորումներն ու դյուրանցումները:"
+ "Վիջեթի բեռնման խնդիր կա"
+ "Սա համակարգային ծրագիր է և չի կարող ապատեղադրվել:"
+ "Հրթիռային թողարկիչ"
+ "Անանուն թղթապանակ"
+ "Հիմնական էկրան %1$d"
+ "Էջ %1$d՝ %2$d-ից"
+ "Հիմնական էկրան %1$d` %2$d-ից"
+ "Ծրագրերի էջերը՝ %1$d %2$d-ից"
+ "Վիջեթների էջերը՝ %1$d %2$d-ից"
+ "Բարի գալուստ:"
+ "Զգացեք ձեզ ինչպես տանը:"
+
+
+ "Ստեղծեք նոր էկրաններ ծրագրերի և թղթապանակների համար"
+ "Կառավարեք ձեր տարածությունը"
+ "Հպեք և պահեք հետնաշերտի վրա՝ պաստառները, վիջեթներն ու կարգավորումները կառավարելու համար:"
+ "Ընտրեք ինչ-որ ծրագիր"
+ "Ձեր հիմնական էկրանին ծրագիր ավելացնելու համար հպեք և պահեք այն:"
+ "Ահա մի թղթապանակ"
+ "Նման թղթապանակ ստեղծելու համար հպեք և պահեք որևէ ծրագրի վրա, ապա տեղաշարժեք այն մեկ ուրիշ ծրագրի վրա:"
+ "Լավ"
+ "Թղթապանակը բաց է, %1$d-ից %2$d"
+ "Հպեք՝ թղթապանակը փակելու համար"
+ "Հպեք՝ վերանվանումը պահելու համար"
+ "Թղթապանակը փակ է"
+ "Թղթապանակը վերանվանվեց %1$s"
+ "Թղթապանակ՝ %1$s"
+
+
+
+
+ "Վիջեթներ"
+ "Պաստառներ"
+ "Կարգավորումներ"
+
diff --git a/res/values-in-land/strings.xml b/res/values-in-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-in-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000000..e1e93f7f0c
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Layar Utama"
+ "Aplikasi Inti Android"
+
+ "Setel wallpaper"
+
+ "%1$d dipilih"
+ "%1$d dipilih"
+ "%1$d dipilih"
+
+ "Wallpaper %1$d dari %2$d"
+ "%1$s terpilih"
+ "Hapus"
+ "Pilih gambar"
+ "Wallpaper"
+ "Pangkas wallpaper"
+ "Aplikasi tidak dipasang."
+ "Widget"
+ "Widget"
+ "Tampilkan Memori"
+ "Sentuh lama untuk memilih widget."
+ "Belanja"
+ "%1$d × %2$d"
+ "Tidak dapat melepas item ke layar Utama ini."
+ "Pilih widget untuk membuat"
+ "Nama folder"
+ "Ganti nama folder"
+ "Oke"
+ "Batal"
+ "Tambahkan ke layar Utama"
+ "Aplikasi"
+ "Pintasan"
+ "Widget"
+ "Tidak ada ruang lagi di layar Utama Anda."
+ "Tidak ada ruang lagi pada layar Utama ini."
+ "Tidak ada ruang lagi di hotseat."
+ "Widget ini terlalu besar untuk hotseat tersebut."
+ "Pintasan \"%s\" sudah dibuat."
+ "Pintasan \"%s\" telah dihapus."
+ "Pintasan \"%s\" sudah ada."
+ "Pilih pintasan"
+ "Pilih aplikasi"
+ "Aplikasi"
+ "Layar Utama"
+ "Hapus"
+ "Copot pemasangan"
+ "Hapus"
+ "Copot pemasangan"
+ "Info aplikasi"
+ "Telusuri"
+ "Penelusuran Suara"
+ "Aplikasi"
+ "Hapus"
+ "Copot pemasangan pembaruan"
+ "Copot aplikasi"
+ "Detail aplikasi"
+ "1 aplikasi dipilih"
+ "1 widget dipilih"
+ "1 folder dipilih"
+ "1 pintasan dipilih"
+ "memasang pintasan"
+ "Mengizinkan aplikasi menambahkan pintasan tanpa campur tangan pengguna."
+ "mencopot pemasangan pintasan"
+ "Mengizinkan aplikasi menghapus pintasan tanpa campur tangan pengguna."
+ "membaca setelan dan pintasan layar Utama"
+ "Mengizinkan aplikasi membaca setelan dan pintasan di layar Utama."
+ "menulis setelan dan pintasan layar Utama"
+ "Mengizinkan aplikasi mengubah setelan dan pintasan di layar Utama."
+ "Masalah memuat widget"
+ "Ini adalah aplikasi sistem dan tidak dapat dicopot pemasangannya."
+ "Rocket Launcher"
+ "Folder Tanpa Nama"
+ "Layar utama %1$d"
+ "Laman %1$d dari %2$d"
+ "Layar utama %1$d dari %2$d"
+ "Laman aplikasi %1$d dari %2$d"
+ "Laman widget %1$d dari %2$d"
+ "Selamat datang!"
+ "Serasa di rumah sendiri."
+
+
+ "Buat lebih banyak layar untuk aplikasi dan folder"
+ "Kelola ruang Anda"
+ "Sentuh lama latar belakang untuk mengelola wallpaper, widget, dan setelan."
+ "Pilih beberapa aplikasi"
+ "Untuk menambah aplikasi ke layar Utama Anda, sentuh lama aplikasi tersebut."
+ "Ini adalah folder"
+ "Untuk membuat seperti yang ini, sentuh lama aplikasi, lalu pindahkan ke atas aplikasi lain."
+ "Oke"
+ "Folder dibuka, %1$d x %2$d"
+ "Sentuh untuk menutup folder"
+ "Sentuh untuk menyimpan ganti nama"
+ "Folder ditutup"
+ "Folder diganti namanya menjadi %1$s"
+ "Folder: %1$s"
+
+
+
+
+ "Widget"
+ "Wallpaper"
+ "Setelan"
+
diff --git a/res/values-it-land/strings.xml b/res/values-it-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-it-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000000..d5ada8f150
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Home page"
+ "Applicazioni di base Android"
+
+ "Imposta sfondo"
+
+ "%1$d selezionati"
+ "%1$d selezionato"
+ "%1$d selezionati"
+
+ "Sfondo %1$d di %2$d"
+ "Elemento selezionato: %1$s"
+ "Elimina"
+ "Scegli immagine"
+ "Sfondi"
+ "Ritaglia sfondo"
+ "App non installata."
+ "Widget"
+ "Widget"
+ "Mostra Mem"
+ "Tocca e tieni premuto per scegliere un widget."
+ "Acquista"
+ "%1$d × %2$d"
+ "Rilascio elemento in schermata Home non riuscito."
+ "Scegli il widget da creare"
+ "Nome cartella"
+ "Rinomina cartella"
+ "OK"
+ "Annulla"
+ "Aggiungi a schermata Home"
+ "App"
+ "Scorciatoie"
+ "Widget"
+ "Spazio nelle schermate Home esaurito."
+ "Spazio nella schermata Home esaurito."
+ "Spazio nell\'area hotseat esaurito."
+ "Questo widget è troppo grande per l\'area hotseat."
+ "Scorciatoia \"%s\" creata."
+ "La scorciatoia \"%s\" è stata rimossa."
+ "Scorciatoia \"%s\" già presente."
+ "Scegli scorciatoia"
+ "Scegli app"
+ "App"
+ "Home page"
+ "Rimuovi"
+ "Disinstalla"
+ "Rimuovi"
+ "Disinstalla"
+ "Informazioni app"
+ "Cerca"
+ "Ricerca vocale"
+ "App"
+ "Rimuovi"
+ "Disinstalla aggiornamento"
+ "Disinstalla app"
+ "Dettagli sull\'app"
+ "1 app selezionata"
+ "1 widget selezionato"
+ "1 cartella selezionata"
+ "1 scorciatoia selezionata"
+ "aggiunta di scorciatoie"
+ "Consente a un\'app di aggiungere scorciatoie automaticamente."
+ "eliminazione di scorciatoie"
+ "Consente all\'app di rimuovere scorciatoie automaticamente."
+ "lettura di impostazioni e scorciatoie in Home"
+ "Consente all\'app di leggere le impostazioni e le scorciatoie in Home."
+ "creazione di impostazioni e scorciatoie in Home"
+ "Consente all\'app di modificare le impostazioni e le scorciatoie in Home."
+ "Errore durante il caricamento del widget"
+ "Questa è un\'app di sistema e non può essere disinstallata."
+ "Lanciamissili"
+ "Cartella senza nome"
+ "Schermata Home %1$d"
+ "Pagina %1$d di %2$d"
+ "Schermata Home %1$d di %2$d"
+ "Pagina di applicazioni %1$d di %2$d"
+ "Pagina di widget %1$d di %2$d"
+ "Benvenuto!"
+ "Personalizza la schermata Home."
+
+
+ "Creare più schermate per app e cartelle"
+ "Organizza il tuo spazio"
+ "Tocca e tieni premuto lo sfondo per gestire sfondi, widget e impostazioni."
+ "Scegli alcune applicazioni"
+ "Per aggiungere un\'app alla schermata Home, tocca e tieni premuto."
+ "Ecco una cartella"
+ "Per crearne una simile, tocca un\'app e tieni premuto, dopodiché spostala sopra un\'altra."
+ "OK"
+ "Cartella aperta, %1$d per %2$d"
+ "Tocca per chiudere la cartella"
+ "Tocca per salvare nuovo nome"
+ "Cartella chiusa"
+ "Nome della cartella sostituito con %1$s"
+ "Cartella: %1$s"
+
+
+
+
+ "Widget"
+ "Sfondi"
+ "Impostazioni"
+
diff --git a/res/values-iw-land/strings.xml b/res/values-iw-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-iw-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000000..603dbb5006
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "דף הבית"
+ "אפליקציות הליבה של Android"
+
+ "הגדר טפט"
+
+ "%1$d נבחרו"
+ "%1$d נבחרו"
+ "%1$d נבחרו"
+
+ "טפט %1$d מתוך %2$d"
+ "בחרת %1$s"
+ "מחק"
+ "בחר תמונה"
+ "טפטים"
+ "חתוך את הטפט"
+ "האפליקציה לא מותקנת."
+ "רכיבי ווידג\'ט"
+ "רכיבי ווידג\'ט"
+ "הצג זכרון"
+ "גע נגיעה רציפה בווידג\'ט כדי לבחור בו."
+ "קנה"
+ "%1$d × %2$d"
+ "לא ניתן היה לשחרר את הפריט במסך דף הבית הזה."
+ "בחר ווידג\'ט ליצירה"
+ "שם תיקיה"
+ "שנה את שם התיקיה"
+ "אישור"
+ "בטל"
+ "הוסף למסך דף הבית"
+ "אפליקציות"
+ "קיצורי דרך"
+ "רכיבי ווידג\'ט"
+ "אין יותר מקום במסכי דף הבית."
+ "אין עוד מקום במסך דף הבית הזה."
+ "אין יותר מקום בפס האפליקציות."
+ "הווידג\'ט הזה גדול מדי עבור פס האפליקציות."
+ "קיצור הדרך \'%s\' נוצר."
+ "קיצור הדרך \'%s\' הוסר."
+ "קיצור הדרך \'%s\' כבר קיים."
+ "בחר קיצור דרך"
+ "בחר אפליקציה"
+ "אפליקציות"
+ "דף הבית"
+ "הסר"
+ "הסר התקנה"
+ "הסר"
+ "הסר התקנה"
+ "פרטי אפליקציה"
+ "חפש"
+ "חיפוש קולי"
+ "אפליקציות"
+ "הסר"
+ "הסר את התקנת העדכון"
+ "הסר את התקנת האפליקציה"
+ "פרטי האפליקציה"
+ "נבחרה אפליקציה אחת"
+ "נבחר ווידג\'ט אחד"
+ "נבחרה תיקיה אחת"
+ "נבחר קיצור דרך אחד"
+ "התקן קיצורי דרך"
+ "מאפשר לאפליקציה להוסיף קיצורי דרך ללא התערבות המשתמש."
+ "הסר התקנה של קיצורי דרך"
+ "מאפשר לאפליקציה להסיר קיצורי דרך ללא התערבות המשתמש."
+ "קרא הגדרות וקיצורי דרך של דף הבית"
+ "מאפשר לאפליקציה לקרוא את ההגדרות וקיצורי הדרך בדף הבית."
+ "כתוב הגדרות וקיצורי דרך של דף הבית"
+ "מאפשר לאפליקציה לשנות את ההגדרות וקיצורי הדרך בדף הבית."
+ "בעיה בטעינת ווידג\'ט"
+ "זוהי אפליקציית מערכת ולא ניתן להסיר את התקנתה."
+ "Rocket Launcher"
+ "תיקיה ללא שם"
+ "מסך דף הבית %1$d"
+ "דף %1$d מתוך %2$d"
+ "מסך דף הבית %1$d מתוך %2$d"
+ "דף אפליקציות %1$d מתוך %2$d"
+ "דף רכיבי ווידג\'ט %1$d מתוך %2$d"
+ "ברוך הבא!"
+ "להרגיש בבית."
+
+
+ "צור מסכים נוספים עבור אפליקציות ותיקיות"
+ "ארגן את אזור העבודה שלך"
+ "גע נגיעה רציפה ברקע כדי לנהל את הטפט, רכיבי הווידג\'ט וההגדרות."
+ "בחר כמה אפליקציות"
+ "כדי להוסיף אפליקציה למסך דף הבית, גע בה נגיעה רציפה."
+ "הנה תיקייה"
+ "כדי ליצור תיקייה כזו, גע נגיעה רציפה באפליקציה, ולאחר מכן גרור ושחרר אותו על-גבי אפליקציה אחרת."
+ "אישור"
+ "תיקיה פתוחה, %1$d על %2$d"
+ "גע כדי לסגור את התיקיה"
+ "גע כדי לשמור את שינוי השם"
+ "התיקיה נסגרה"
+ "שם התיקיה שונה ל-%1$s"
+ "תיקיה: %1$s"
+
+
+
+
+ "רכיבי ווידג\'ט"
+ "טפטים"
+ "הגדרות"
+
diff --git a/res/values-ja-land/strings.xml b/res/values-ja-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-ja-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000000..6e39c540f6
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "ホーム"
+ "Android Core Apps"
+
+ "壁紙を設定"
+
+ "%1$d件選択済み"
+ "%1$d件選択済み"
+ "%1$d件選択済み"
+
+ "壁紙: %1$d/%2$d"
+ "選択: %1$s"
+ "削除"
+ "画像を選択"
+ "壁紙"
+ "壁紙をトリミング"
+ "このアプリはインストールされていません。"
+ "ウィジェット"
+ "ウィジェット"
+ "メモリーを表示"
+ "ウィジェットを追加するには押し続けます。"
+ "ショップ"
+ "%1$dx%2$d"
+ "このホーム画面にアイテムをドロップできませんでした"
+ "作成するウィジェットの選択"
+ "フォルダ名"
+ "フォルダ名を変更"
+ "OK"
+ "キャンセル"
+ "ホーム画面に追加"
+ "アプリ"
+ "ショートカット"
+ "ウィジェット"
+ "ホーム画面に空きスペースがありません。"
+ "このホーム画面に空きスペースがありません。"
+ "ホットシートに空きスペースがありません。"
+ "このウィジェットはホットシートには大きすぎます。"
+ "ショートカット「%s」を作成しました。"
+ "ショートカット「%s」を削除しました。"
+ "ショートカット「%s」は既に存在します。"
+ "ショートカットを選択"
+ "アプリを選択"
+ "アプリ"
+ "ホーム"
+ "削除"
+ "アンインストール"
+ "削除"
+ "アンインストール"
+ "アプリ情報"
+ "検索"
+ "音声検索"
+ "アプリ"
+ "削除"
+ "更新をアンインストール"
+ "アプリをアンインストール"
+ "アプリの詳細"
+ "1つのアプリが選択されています"
+ "1つのウィジェットが選択されています"
+ "1つのフォルダが選択されています"
+ "1つのショートカットが選択されています"
+ "ショートカットのインストール"
+ "ユーザー操作なしでショートカットを追加することをアプリに許可します。"
+ "ショートカットのアンインストール"
+ "ユーザー操作なしでショートカットを削除することをアプリに許可します。"
+ "ホームの設定とショートカットの読み取り"
+ "ホームの設定とショートカットの読み取りをアプリに許可します。"
+ "ホームの設定とショートカットの書き込み"
+ "ホームの設定とショートカットの変更をアプリに許可します。"
+ "ウィジェットを表示できません"
+ "このシステムアプリはアンインストールできません。"
+ "Rocket Launcher"
+ "名前のないフォルダ"
+ "ホーム画面: %1$d"
+ "%1$d/%2$dページ"
+ "ホーム画面: %1$d/%2$d"
+ "アプリの%1$d/%2$dページ"
+ "ウィジェットの%1$d/%2$dページ"
+ "ようこそ!"
+ "ホームをカスタマイズします。"
+
+
+ "アプリとフォルダの画面をもっと作成します"
+ "スペースを整理"
+ "壁紙、ウィジェット、設定を管理するには、背景を押し続けます。"
+ "アプリの選択"
+ "アプリをホーム画面に追加するにはアプリを押し続けます。"
+ "これがフォルダです"
+ "これと同じフォルダを作成するには、アプリを押し続けてから別のアプリの上に移動します。"
+ "OK"
+ "フォルダが開いています。%1$dx%2$dの大きさです"
+ "タップしてフォルダを閉じます"
+ "タップして名前の変更を保存します"
+ "フォルダは閉じています"
+ "フォルダの名前を「%1$s」に変更しました"
+ "フォルダ: %1$s"
+
+
+
+
+ "ウィジェット"
+ "壁紙"
+ "設定"
+
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000000..a2e3374e4c
--- /dev/null
+++ b/res/values-ka-rGE/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "მთავარი"
+ "Android-ის ბირთვის აპები"
+
+ "ფონის დაყენება"
+
+ "არჩეულია %1$d"
+ "არჩეულია %1$d"
+ "არჩეულია %1$d"
+
+ "ფონი %1$d %2$d-დან"
+ "არჩეულია %1$s"
+ "წაშლა"
+ "სურათის ამორჩევა"
+ "ფონები"
+ "ფონის ჩამოჭრა"
+ "აპი არ არის დაყენებული."
+ "ვიჯეტები"
+ "ვიჯეტები"
+ "Mem-ის ჩვენება"
+ "შეეხეთ და დააყოვნეთ ვიჯეტის ასარჩევად."
+ "მაღაზია"
+ "%1$d × %2$d"
+ "ერთეულის მთავარ ეკრანზე ჩაგდება ვერ მოხერხდა."
+ "აირჩიეთ ვიჯეტი შესაქმნელად"
+ "საქაღალდის სახელი"
+ "საქაღალდის გადარქმევა"
+ "კარგი"
+ "გაუქმება"
+ "მთავარ ეკრანზე დამატება"
+ "აპები"
+ "მალსახმობები"
+ "ვიჯეტები"
+ "მთავარ ეკრანებზე ადგილი აღარ არის."
+ "ამ მთავარ ეკრანზე ადგილი აღარ არის."
+ "hotseat-ში მეტი ადგილი არ არის."
+ "ეს ვიჯეტი ძალიან დიდია hotseat-ისთვის."
+ "შეიქმნა მალსახმობი „%s“."
+ "მასლახმობი „%s“ წაშლილია."
+ "მალსახმობი „%s“ უკვე არსებობს."
+ "აირჩიეთ მალსახმობი"
+ "აირჩიეთ აპი"
+ "აპები"
+ "მთავარი"
+ "წაშლა"
+ "დეინსტალაცია"
+ "წაშლა"
+ "დეინსტალაცია"
+ "აპის შესახებ"
+ "ძიება"
+ "ხმოვანი ძიება"
+ "აპები"
+ "წაშლა"
+ "განახლების დეინსტალაცია"
+ "აპის დეინსტალაცია"
+ "აპის შესახებ"
+ "არჩეულია 1 აპი"
+ "არჩეულია 1 ვიჯეტი"
+ "არჩეულია 1 საქაღალდე"
+ "არჩეულია 1 მალსახმობი"
+ "მალსახმობების დაყენება"
+ "აპისთვის მალსახმობების დამოუკიდებლად დამატების უფლების მიცემა."
+ "მალსახმობების წაშლა"
+ "აპისთვის მალსახმობების დამოუკიდებლად წაშლის უფლების მიცემა."
+ "მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვა"
+ "აპისთვის მთავარი ეკრანის პარამეტრებისა და მალსახმობების წაკითხვის უფლების მიცემა."
+ "მთავარი ეკრანის პარამეტრებისა და მალსახმობების ჩაწერა"
+ "აპისთვის მთავარი ეკრანის პარამეტრებისა და მალსახმობების შეცვლის უფლების მიცემა."
+ "პრობლემა ვიჯეტის ჩატვირთვისას"
+ "ეს სისტემური აპია და მისი წაშლა შეუძლებელია."
+ "ფეიერვერკი"
+ "უსახელო საქაღალდე"
+ "მთავარი ეკრანი %1$d"
+ "გვერდი %1$d %2$d-დან"
+ "მთავარი ეკრანი %1$d, %2$d-დან"
+ "აპების გვერდი %1$d, %2$d-დან"
+ "ვიჯეტების გვერდი %1$d, %2$d-დან"
+ "კეთილი იყოს თქვენი მობრძანება!"
+ "იგრძენით თავი საკუთარ სახლში"
+
+
+ "აპებისა და საქაღალდეებისთვის კიდევ ერთი ეკრანის შექმნა"
+ "თქვენი სივრცის ორგანიზება"
+ "თუ გსურთ ფონების, ვიჯეტების და პარამეტრების მართვა, შეეხეთ და არ აუშვათ ფონს."
+ "აირჩიეთ რამდენიმე აპი"
+ "აპის მთავარ ეკრანზე დასამატებლად შეეხეთ მის ხატულას და არ აუშვათ."
+ "აი, საქაღალდე"
+ "ასეთის შესაქმნელად, შეეხეთ და დააყოვნეთ აპზე, ხოლო შემდეგ გადააჩოჩეთ შემდეგზე."
+ "კარგი"
+ "საქაღალდე გახსნილია, %1$d x %2$d"
+ "შეეხეთ საქაღალდის დასახურად"
+ "შეეხეთ ახალი სახელის შესანახად"
+ "საქაღალდე დაიხურა"
+ "საქაღალდეს შეეცვალა სახელი „%1$s“-ად"
+ "საქაღალდე: %1$s"
+
+
+
+
+ "ვიჯეტები"
+ "ფონები"
+ "პარამეტრები"
+
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000000..977b2ddb60
--- /dev/null
+++ b/res/values-km-rKH/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "ដើម"
+ "កម្មវិធីសំខាន់ៗរបស់ Android"
+
+ "កំណត់ផ្ទាំងរូបភាព"
+
+ "បានជ្រើស %1$d"
+ "បានជ្រើស %1$d"
+ "បានជ្រើស %1$d"
+
+ "ផ្ទាំងរូបភាព %1$d នៃ %2$d"
+ "បានជ្រើស %1$s"
+ "លុប"
+ "ជ្រើសយករូបភាព"
+ "ផ្ទាំងរូបភាព"
+ "ច្រឹបផ្ទាំងរូបភាព"
+ "មិនបានដំឡើងកម្មវិធី។"
+ "ធាតុក្រាហ្វិក"
+ "ធាតុក្រាហ្វិក"
+ "បង្ហាញ Mem"
+ "ប៉ះ & សង្កត់ ដើម្បីជ្រើសធាតុក្រាហ្វិក។"
+ "ហាងទំនិញ"
+ "%1$d × %2$d"
+ "មិនអាចទម្លាក់ធាតុលើអេក្រង់ដើមនេះទេ"
+ "ជ្រើសធាតុក្រាហ្វិក ដើម្បីបង្កើត"
+ "ឈ្មោះថត"
+ "ប្ដូរឈ្មោះថត"
+ "យល់ព្រម"
+ "បោះបង់"
+ "បន្ថែមទៅអេក្រង់ដើម"
+ "កម្មវិធី"
+ "ផ្លូវកាត់"
+ "ធាតុក្រាហ្វិក"
+ "គ្មានបន្ទប់នៅលើអេក្រង់ដើមរស់អ្នកទៀតទេ។"
+ "គ្មានបន្ទប់នៅលើអេក្រង់ដើមនេះទៀតទេ។"
+ "គ្មានបន្ទប់នៅក្នុងមជ្ឈមណ្ឌលទៀតទេ។"
+ "ធាតុក្រាហ្វិកនេះធំពេកសម្រាប់មជ្ឈមណ្ឌល។"
+ "បានបង្កើតផ្លូវកាត់ \"%s\" ។"
+ "បានលុបផ្លូវកាត់ \"%s\" ។"
+ "មានផ្លូវកាត់ \"%s\" រួចហើយ។"
+ "ជ្រើសផ្លូវកាត់"
+ "ជ្រើសកម្មវិធី"
+ "កម្មវិធី"
+ "ដើម"
+ "លុបចេញ"
+ "លុប"
+ "លុបចេញ"
+ "លុប"
+ "ព័ត៌មានកម្មវិធី"
+ "ស្វែងរក"
+ "ស្វែងរកតាមសំឡេង"
+ "កម្មវិធី"
+ "លុបចេញ"
+ "លុបបច្ចុប្បន្នភាព"
+ "លុបកម្មវិធី"
+ "ព័ត៌មានលម្អិតកម្មវិធី"
+ "បានជ្រើសកម្មវិធី ១"
+ "បានជ្រើសធាតុ ១"
+ "បានជ្រើសថត ១"
+ "បានជ្រើសផ្លូវកាត់ ១"
+ "ដំឡើងផ្លូវកាត់"
+ "អនុញ្ញាតឲ្យកម្មវិធីបន្ថែមផ្លូវកាត់ ដោយមិនចាំបាច់អំពើពីអ្នកប្រើ។"
+ "លុបផ្លូវកាត់"
+ "អនុញ្ញាតឲ្យកម្មវិធីលុបផ្លូវកាត់ដោយមិនចាំបាច់អំពើពីអ្នកប្រើ។"
+ "អានការកំណត់ និងផ្លូវកាត់អេក្រង់ដើម"
+ "អនុញ្ញាតឲ្យកម្មវិធីអានការកំណត់ និងផ្លូវកាត់ក្នុងអេក្រង់ដើម។"
+ "សរសេរការកំណត់ និងផ្លូវកាត់លើអេក្រង់ដើម"
+ "អនុញ្ញាតឲ្យកម្មវិធីប្ដូរការកំណត់ និងផ្លូវកាត់ក្នុងអេក្រង់ដើម។"
+ "បញ្ហាក្នុងការផ្ទុកធាតុក្រាហ្វិក"
+ "នេះជាកម្មវិធីប្រព័ន្ធ មិនអាចលុបបានទេ។"
+ "កម្មវិធីចាប់ផ្ដើមរ៉ូកែត"
+ "ថតគ្មានឈ្មោះ"
+ "អេក្រង់ដើម %1$d"
+ "ទំព័រ %1$d នៃ %2$d"
+ "អេក្រង់ដើម %1$d នៃ %2$d"
+ "ទំព័រកម្មវិធី %1$d នៃ %2$d"
+ "ទំព័រធាតុក្រាហ្វិក %1$d នៃ %2$d"
+ "សូមស្វាគមន៍!"
+ "ធ្វើដោយខ្លួនឯងនៅលើអេក្រង់ដើម។"
+
+
+ "បង្កើតអេក្រង់ច្រើនសម្រាប់កម្មវិធី និងថតឯកសារ"
+ "រៀបចំចន្លោះរបស់អ្នក"
+ "ប៉ះ & សង្កត់លើផ្ទៃខាងក្រោម ដើម្បីគ្រប់គ្រងផ្ទាំងរូបភាព, ធាតុក្រាហ្វិក និងការកំណត់។"
+ "ជ្រើសកម្មវិធីមួយចំនួន"
+ "ប៉ះ & សង្កត់វា ដើម្បីបន្ថែមកម្មវិធីទៅអេក្រង់ដើមរបស់អ្នក"
+ "នេះជាថត"
+ "ដើម្បីបង្កើតមួយដូចនេះ ប៉ះ & សង្កត់លើកម្មវិធី បន្ទាប់មកផ្លាស់ទីវាទៅលើធាតុមួយផ្សេងទៀត។"
+ "យល់ព្រម"
+ "បានបើកថត %1$d ដោយ %2$d"
+ "ប៉ះ ដើម្បីបិទថត"
+ "ប៉ះ ដើម្បីរក្សាទុកការប្ដូរឈ្មោះ"
+ "បានបិទថត"
+ "បានប្ដូរឈ្មោះថតជា %1$s"
+ "ថត៖ %1$s"
+
+
+
+
+ "ធាតុក្រាហ្វិក"
+ "ផ្ទាំងរូបភាព"
+ "ការកំណត់"
+
diff --git a/res/values-ko-land/strings.xml b/res/values-ko-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-ko-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000000..31c2d69310
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "홈"
+ "Android 핵심 앱"
+
+ "배경화면 설정"
+
+ "%1$d개 선택됨"
+ "%1$d개 선택됨"
+ "%1$d개 선택됨"
+
+ "배경화면 %1$d/%2$d"
+ "%1$s 선택함"
+ "삭제"
+ "이미지 선택"
+ "배경화면"
+ "배경화면 잘라내기"
+ "앱이 설치되지 않았습니다."
+ "위젯"
+ "위젯"
+ "메모리 표시"
+ "위젯을 선택하려면 길게 터치하세요."
+ "쇼핑하기"
+ "%1$d×%2$d"
+ "홈 화면에 항목을 놓을 수 없습니다."
+ "만들 위젯 선택"
+ "폴더 이름"
+ "폴더 이름 바꾸기"
+ "확인"
+ "취소"
+ "홈 화면에 추가"
+ "앱"
+ "바로가기"
+ "위젯"
+ "홈 화면에 더 이상 공간이 없습니다."
+ "홈 화면에 더 이상 공간이 없습니다."
+ "즐겨찾는 앱 모음에 더 이상 빈 공간이 없습니다."
+ "이 위젯은 너무 커서 즐겨찾는 앱 모음에 들어갈 수 없습니다."
+ "바로가기(\'%s\')가 생성되었습니다."
+ "바로가기(\'%s\')가 삭제되었습니다."
+ "바로가기(\'%s\')가 이미 있습니다."
+ "바로가기 선택"
+ "앱 선택"
+ "앱"
+ "홈"
+ "삭제"
+ "제거"
+ "삭제"
+ "제거"
+ "앱 정보"
+ "검색"
+ "음성 검색"
+ "앱"
+ "삭제"
+ "업데이트 제거"
+ "앱 제거"
+ "앱 세부정보"
+ "앱 1개 선택됨"
+ "위젯 1개 선택됨"
+ "폴더 1개 선택됨"
+ "바로가기 1개 선택됨"
+ "바로가기 설치"
+ "앱이 사용자의 작업 없이 바로가기를 추가할 수 있도록 합니다."
+ "바로가기 제거"
+ "앱이 사용자의 작업 없이 바로가기를 삭제할 수 있도록 합니다."
+ "홈 설정 및 바로가기 읽기"
+ "앱이 홈에 있는 설정 및 바로가기를 읽을 수 있도록 합니다."
+ "홈 설정 및 바로가기 쓰기"
+ "앱이 홈에 있는 설정 및 바로가기를 변경할 수 있도록 합니다."
+ "위젯을 로드하는 중 문제가 발생했습니다."
+ "시스템 앱은 제거할 수 없습니다."
+ "로켓 실행기"
+ "이름이 없는 폴더"
+ "홈 화면 %1$d"
+ "페이지 %1$d/%2$d"
+ "홈 화면 %1$d/%2$d"
+ "앱 페이지 %1$d/%2$d"
+ "위젯 페이지 %1$d/%2$d"
+ "환영합니다!"
+ "편리한 사용 환경을 만드세요."
+
+
+ "앱 및 폴더를 표시할 화면 더 만들기"
+ "공간 관리하기"
+ "배경화면, 위젯, 설정을 관리하려면 백그라운드를 길게 터치합니다."
+ "앱 선택하기"
+ "홈 화면에 앱을 추가하려면 길게 터치합니다."
+ "폴더"
+ "폴더를 만들려면 앱을 길게 터치한 다음 다른 앱 위에 올려 놓으세요."
+ "확인"
+ "폴더 열림(%1$dX%2$d)"
+ "터치하여 폴더를 닫음"
+ "터치하여 바꾼 이름을 저장"
+ "폴더 닫음"
+ "폴더 이름 변경: %1$s"
+ "폴더: %1$s"
+
+
+
+
+ "위젯"
+ "배경화면"
+ "설정"
+
diff --git a/res/values-land/config.xml b/res/values-land/config.xml
new file mode 100644
index 0000000000..121bb0c146
--- /dev/null
+++ b/res/values-land/config.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ false
+
+ false
+
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
new file mode 100644
index 0000000000..5961b19363
--- /dev/null
+++ b/res/values-land/dimens.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ 8dip
+ 0dip
+
+
+
+ -1dp
+
+
+ 42dp
+ 3
+ 2
+ 2
+ 1
+
diff --git a/res/values-land/strings.xml b/res/values-land/strings.xml
new file mode 100644
index 0000000000..ec4c7e706f
--- /dev/null
+++ b/res/values-land/strings.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
new file mode 100644
index 0000000000..ccb5fcb069
--- /dev/null
+++ b/res/values-land/styles.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000000..e3a95dd655
--- /dev/null
+++ b/res/values-lo-rLA/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "ໜ້າຫຼັກ"
+ "ແອັບພລິເຄຊັນຫຼັກຂອງ Android"
+
+ "ຕັ້ງເປັນພາບພື້ນຫຼັງ"
+
+ "ເລືອກ %1$d ລາຍການແລ້ວ"
+ "ເລືອກ %1$d ລາຍການແລ້ວ"
+ "ເລືອກ %1$d ລາຍການແລ້ວ"
+
+ "ຮູບພືື້ນຫຼັງ %1$d ໃນ %2$d"
+ "ເລືອກແລ້ວ %1$s ອັນ"
+ "ລຶບ"
+ "ເລືອກຮູບ"
+ "ພາບພື້ນຫຼັງ"
+ "ຕັດຮູບພື້ນຫຼັງ"
+ "ແອັບຯບໍ່ໄດ້ຖືກຕິດຕັ້ງ."
+ "ວິດເຈັດ"
+ "ວິດເຈັດ"
+ "ສະແດງຄວາມຈຳ"
+ "ສຳພັດຄ້າງໄວ້ ເພື່ອຈັບວິດເຈັດ."
+ "ຮ້ານຄ້າ"
+ "%1$d × %2$d"
+ "ບໍ່ສາມາດວາງລາຍການໃສ່ໜ້າຈໍຫຼັກນີ້ໄດ້"
+ "ເລືອກວິດເຈັດເພື່ອສ້າງມັນ"
+ "ຊື່ໂຟນເດີ"
+ "ປ່ຽນຊື່ໂຟນເດີ"
+ "ຕົກລົງ"
+ "ຍົກເລີກ"
+ "ເພີ່ມໃສ່ໜ້າຈໍຫຼັກ"
+ "ແອັບຯ"
+ "ທາງລັດ"
+ "ວິດເຈັດ"
+ "ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກຂອງທ່ານ."
+ "ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."
+ "ບໍ່ມີຫ້ອງຫວ່າງໃນ hotseat ແລ້ວ."
+ "ວິດເຈັດ ມີຂະໜາດໃຫຍ່ເກີນໄປສຳລັບ hotseat."
+ "ທາງລັດ \"%s\" ຖືກສ້າງແລ້ວ."
+ "ທາງລັດ \"%s\" ຖືກລຶບແລ້ວ."
+ "ທາງລັດ \"%s\" ມີຢູ່ແລ້ວ."
+ "ເລືອກທາງລັດ"
+ "ເລືອກແອັບຯ"
+ "ແອັບຯ"
+ "ໜ້າຫຼັກ"
+ "ລຶບ"
+ "ຖອນການຕິດຕັ້ງ"
+ "ລຶບ"
+ "ຖອນການຕິດຕັ້ງ"
+ "ຂໍ້ມູນແອັບຯ"
+ "ຊອກຫາ"
+ "ຊອກຫາດ້ວຍສຽງ"
+ "ແອັບຯ"
+ "ລຶບ"
+ "ຖອນອັບເດດ"
+ "ຖອນແອັບຯ"
+ "ລາຍລະອຽດແອັບຯ"
+ "1 ແອັບຯຖືກເລືອກ"
+ "1 ວິດເຈັດຖືກເລືອກ"
+ "1 ໂຟນເດີຖືກເລືອກ"
+ "1 ທາງລັດຖືກເລືອກ"
+ "ຕິດຕັ້ງທາງລັດ"
+ "ອະນຸຍາດໃຫ້ແອັບຯ ເພີ່ມທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."
+ "ຖອນທາງລັດ"
+ "ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວ ລຶບທາງລັດໂດຍບໍ່ຕ້ອງຮັບການຢືນຢັນຈາກຜູ່ໃຊ້."
+ "ອ່ານການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ"
+ "ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວອ່ານການຕັ້ງຄ່າ ແລະທາງລັດໃນໜ້າຫຼັກ."
+ "ຂຽນການຕັ້ງຄ່າໜ້າຫຼັກ ແລະທາງລັດ"
+ "ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວ ປ່ຽນການຕັ້ງຄ່າ ແລະທາງລັດໃນໜ້າຫຼັກ."
+ "ມີບັນຫາໃນການໂຫລດວິດເຈັດ"
+ "ນີ້ແມ່ນແອັບຯຂອງລະບົບ ແລະບໍ່ສາມາດຖອນການຕິດຕັ້ງອອກໄດ້."
+ "Rocket Launcher"
+ "ໂຟນເດີຍັງບໍ່ຖືກຕັ້ງຊື່"
+ "ໜ້າຈໍຫຼັກ %1$d"
+ "ໜ້າ %1$d ຈາກ %2$d"
+ "ໜ້າຈໍຫຼັກ %1$d ໃນ %2$d"
+ "ແອັບຯໜ້າ %1$d ໃນ %2$d"
+ "ວິດເຈັດໜ້າ %1$d ໃນ %2$d"
+ "ຍິນດີຕ້ອນຮັບ!"
+ "ເຮັດໂຕໃຫ້ຄືຢູ່ໃນບ້ານ"
+
+
+ "ສ້າງຈໍເພີ່ມເຕີມສຳລັບແອັບຯ ແລະໂຟນເດີ"
+ "ຈັດການພື້ນທີ່ຂອງທ່ານ"
+ "ແຕະຄ້າງໄວ້ທີ່ພາບພື້ນຫຼັງເພື່ອຈັດການພາບພື້ນຫຼັງ, ວິດເຈັດແລະການຕັ້ງຄ່າ."
+ "ເລືອກແອັບຯ"
+ "ເພື່ອເພີ່ມແອັບຯໃສ່ໜ້າຈໍຫຼັກຂອງທ່ານ, ໃຫ້ແຕະຄ້າງໄວ້."
+ "ນີ້ແມ່ນໂຟນເດີ່"
+ "ເພື່ອສ້າງອັນໃໝ່ແບບນີ້, ແຕະຄ້າງໄວ້ທີ່ແອັບຯ ແລ້ວຍ້າຍມັນໄປຫາໂຕອື່ນ."
+ "ຕົກລົງ"
+ "ເປີດໂຟນເດີແລ້ວ, %1$d ຄູນ %2$d"
+ "ສຳພັດເພື່ອປິດໂຟນເດີ"
+ "ສຳພັດເພື່ອບັນທຶກການປ່ຽນຊື່"
+ "ປິດໂຟນເດີແລ້ວ"
+ "ປ່ຽນຊື່ໂຟນເດີເປັນ %1$s ແລ້ວ"
+ "ໂຟນເດີ: %1$s"
+
+
+
+
+ "ວິດເຈັດ"
+ "ພາບພື້ນຫຼັງ"
+ "ການຕັ້ງຄ່າ"
+
diff --git a/res/values-lt-land/strings.xml b/res/values-lt-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-lt-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000000..80d4412288
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Pagrindinis"
+ "Pagrindinės „Android“ programos"
+
+ "Nustatyti ekrano foną"
+
+ "Pasirinkta: %1$d"
+ "Pasirinkta: %1$d"
+ "Pasirinkta: %1$d"
+
+ "%1$d iš %2$d ekrano fonų"
+ "Pasirinkta: %1$s"
+ "Ištrinti"
+ "Pasirinkti vaizdą"
+ "Ekrano fonai"
+ "Ekrano fono apkirpimas"
+ "Programa neįdiegta."
+ "Valdikliai"
+ "Valdikliai"
+ "Rodyti atmintinę"
+ "Palieskite ir laikykite, kad pasirinkt. valdiklį."
+ "Apsipirkti"
+ "%1$d × %2$d"
+ "Nepavyko nuvilkti elemento į šį pagrindinį ekraną."
+ "Pasirinkite norimą kurti valdiklį"
+ "Aplanko pavadinimas"
+ "Pervardyti aplanką"
+ "Gerai"
+ "Atšaukti"
+ "Pridėti prie pagrindinio ekrano"
+ "Programos"
+ "Spartieji klavišai"
+ "Valdikliai"
+ "Pagrindiniuose ekranuose vietos nebėra."
+ "Šiame pagrindiniame ekrane vietos nebėra."
+ "Įtvirtintojoje srityje nebėra vietos."
+ "Šis valdiklis įtvirtintajai sričiai per didelis."
+ "Spartusis klavišas „%s“ sukurtas."
+ "Spartusis klavišas „%s“ pašalintas."
+ "Spartusis klavišas „%s“ jau yra."
+ "Pasirinkite spartųjį klavišą"
+ "Pasirinkite programą"
+ "Programos"
+ "Pagrindinis"
+ "Pašalinti"
+ "Pašalinti"
+ "Pašalinti"
+ "Pašalinti"
+ "Programos informacija"
+ "Ieškoti"
+ "Paieška balsu"
+ "Programos"
+ "Pašalinti"
+ "Pašalinti naujinį"
+ "Pašalinti programą"
+ "Išsami programos informacija"
+ "Pasirinkta 1 programa"
+ "Pasirinktas 1 valdiklis"
+ "Pasirinktas 1 aplankas"
+ "Pasirinktas 1 spartusis klavišas"
+ "įdiegti sparčiuosius klavišus"
+ "Programai leidžiama pridėti sparčiuosius klavišus be naudotojo įsikišimo."
+ "pašalinti sparčiuosius klavišus"
+ "Programai leidžiama pašalinti sparčiuosius klavišus be naudotojo įsikišimo."
+ "skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus"
+ "Programai leidžiama skaityti pagrindinio puslapio nustatymus ir sparčiuosius klavišus."
+ "rašyti pagrindinio puslapio nustatymus ir sparčiuosius klavišus"
+ "Programai leidžiama keisti pagrindinio puslapio nustatymus ir sparčiuosius klavišus."
+ "Problema įkeliant valdiklį"
+ "Tai sistemos programa ir jos negalima pašalinti."
+ "Rocket Launcher"
+ "Aplankas be pavadinimo"
+ "%1$d pagrindinis ekranas"
+ "%1$d psl. iš %2$d"
+ "%1$d pagrindinis ekranas iš %2$d"
+ "%1$d programų psl. iš %2$d"
+ "%1$d valdiklių psl. iš %2$d"
+ "Sveiki!"
+ "Jauskitės kaip namie."
+
+
+ "Sukurkite daugiau programų ir aplankų ekrano kopijų"
+ "Tvarkykite savo vietą"
+ "Palieskite ir laikykite foną, jei norite tvarkyti ekrano foną, valdiklius ir nustatymus."
+ "Pasirinkite kelias programas"
+ "Jei norite prie pagrindinio ekrano pridėti programą, palieskite ją ir laikykite."
+ "Štai aplankas"
+ "Kad sukurtumėte tokį patį, palieskite ir laikykite programą, tada perkelkite ją virš kitos programos."
+ "Gerai"
+ "Atidarytas aplankas, %1$d ir %2$d"
+ "Palieskite, kad uždarytumėte aplanką"
+ "Palieskite, kad išsaugotumėte naują pavadinimą"
+ "Aplankas uždarytas"
+ "Aplankas pervardytas kaip „%1$s“"
+ "Aplankas: „%1$s“"
+
+
+
+
+ "Valdikliai"
+ "Ekrano fonai"
+ "Nustatymai"
+
diff --git a/res/values-lv-land/strings.xml b/res/values-lv-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-lv-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000000..b70f5dc15e
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Sākums"
+ "Android pamatlietotnes"
+
+ "Iestatīt fona tapeti"
+
+ "Atlasīti: %1$d"
+ "Atlasīti: %1$d"
+ "Atlasīti: %1$d"
+
+ "%1$d. fona tapete no %2$d"
+ "Atlasīts: %1$s"
+ "Dzēst"
+ "Izvēlēties attēlu"
+ "Fona tapetes"
+ "Apgriezt fona tapeti"
+ "Lietotne nav instalēta."
+ "Logrīki"
+ "Logrīki"
+ "Rādīt atmiņu"
+ "Lai izvēlētos logrīku, pieskarieties un turiet to."
+ "Iepirkties"
+ "%1$d × %2$d"
+ "Nevarēja nomest vienumu šajā sākuma ekrānā."
+ "Izveidojamā logrīka izvēle"
+ "Mapes nosaukums"
+ "Mapes pārdēvēšana"
+ "Labi"
+ "Atcelt"
+ "Pievienošana sākuma ekrānam"
+ "Lietotnes"
+ "Saīsnes"
+ "Logrīki"
+ "Sākuma ekrānos vairs nav vietas."
+ "Šajā sākuma ekrānā vairs nav vietas."
+ "Režīmā “hotseat” vairs nav vietas."
+ "Šis logrīks ir pārāk liels režīmam “hotseat”."
+ "Tika izveidota saīsne “%s”."
+ "Tika noņemta saīsne “%s”."
+ "Saīsne “%s” jau pastāv."
+ "Saīsnes izvēle"
+ "Lietotnes izvēle"
+ "Lietotnes"
+ "Sākums"
+ "Noņemt"
+ "Atinstalēt"
+ "Noņemt"
+ "Atinstalēt"
+ "Lietotnes informācija"
+ "Meklēt"
+ "Meklēšana ar balsi"
+ "Lietotnes"
+ "Noņemt"
+ "Atinstalēt atjauninājumu"
+ "Atinstalēt lietotni"
+ "Lietotnes informācija"
+ "Atlasīta 1 lietotne"
+ "Atlasīts 1 logrīks"
+ "Atlasīta 1 mape"
+ "Atlasīta 1 saīsne"
+ "instalēt saīsnes"
+ "Ļauj lietotnei pievienot saīsnes, nejautājot lietotājam."
+ "atinstalēt saīsnes"
+ "Ļauj lietotnei noņemt saīsnes, nejautājot lietotājam."
+ "lasīt sākuma ekrāna iestatījumus un saīsnes"
+ "Ļauj lietotnei lasīt iestatījumus un saīsnes sākuma ekrānā."
+ "rakstīt sākuma ekrāna iestatījumus un saīsnes"
+ "Ļauj lietotnei mainīt iestatījumus un saīsnes sākuma ekrānā."
+ "Ielādējot logrīku, radās problēma."
+ "Šī ir sistēmas lietotne, un to nevar atinstalēt."
+ "Rocket Launcher"
+ "Mape bez nosaukuma"
+ "Sākuma ekrāns: %1$d"
+ "%1$d. lapa no %2$d"
+ "Sākuma ekrāns: %1$d no %2$d"
+ "%1$d. lietotņu lapa no %2$d"
+ "%1$d. logrīku lapa no %2$d"
+ "Laipni lūdzam!"
+ "Informācija par pamatfunkcijām"
+
+
+ "Izveidojiet vairāk ekrānu lietotnēm un mapēm."
+ "Kārtojiet savu darbvietu"
+ "Pieskarieties fonam un turiet to, lai pārvaldītu fona tapeti, logrīkus un iestatījumus."
+ "Izvēlieties dažas lietotnes"
+ "Lai sākuma ekrānam pievienotu lietotni, pieskarieties tai un turiet to."
+ "Lūk, mape!"
+ "Lai izveidotu tādu pašu, pieskarieties lietotnei un turiet to, pēc tam pārvietojiet to virs citas lietotnes."
+ "Labi"
+ "Atvērta mape: %1$d x %2$d"
+ "Pieskarieties, lai aizvērtu mapi."
+ "Pieskarieties, lai saglabātu pārdēvēto nosaukumu."
+ "Mape aizvērta"
+ "Mape pārdēvēta par: %1$s"
+ "Mape: %1$s"
+
+
+
+
+ "Logrīki"
+ "Fona tapetes"
+ "Iestatījumi"
+
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000000..7cf7a9ce19
--- /dev/null
+++ b/res/values-mn-rMN/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Нүүр"
+ "Андройд үндсэн апп"
+
+ "Ханын зургийг тохируулах"
+
+ "%1$d сонгогдсон"
+ "%1$d сонгогдсон"
+ "%1$d сонгогдсон"
+
+ "%2$d ханын цаасны %1$d нь"
+ "%1$s сонгогдсон"
+ "Устгах"
+ "Зураг сонгох"
+ "Ханын зураг"
+ "Ханын зургийг тайрах"
+ "Апп суугаагүй байна."
+ "Виджет"
+ "Виджет"
+ "Мем харуулах"
+ "Виджетийг авах бол хүрээд барина уу."
+ "Дэлгүүр"
+ "%1$d × %2$d"
+ "Энэ Нүүр дэлгэцэнд буулгах боломжгүй."
+ "Үүсгэх виджетээ сонгоно уу"
+ "Фолдерын нэр"
+ "Фолдерын нэр өөрчлөх"
+ "Тийм"
+ "Цуцлах"
+ "Нүүр дэлгэцэнд нэмэх"
+ "Апп"
+ "Товчлол"
+ "Виджет"
+ "Таны Нүүр дэлгэц зайгүй."
+ "Энэ Нүүр дэлгэц зайгүй."
+ "Суурь зайгүй."
+ "Энэ виджет сууринд хэт томдож байна."
+ "\"%s\" товчлол үүсэв."
+ "\"%s\" товчлол устгагдав."
+ "\"%s\" товчлол өмнө үүссэн байна."
+ "Товчлол сонгох"
+ "Апп сонгох"
+ "Апп"
+ "Нүүр"
+ "Устгах"
+ "Устгах"
+ "Устгах"
+ "Устгах"
+ "Апп мэдээлэл"
+ "Хайх"
+ "Дуун хайлт"
+ "Апп"
+ "Устгах"
+ "Шинэчлэлийг устгах"
+ "Апп устгах"
+ "Апп дэлгэрэнгүй"
+ "1 апп сонгогдсон"
+ "1 виджет сонгогдсон"
+ "1 фолдер сонгогдсон"
+ "1 товчлол сонгогдсон"
+ "товчлол суулгах"
+ "Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг нэмэж чадна"
+ "товчлолыг устгах"
+ "Апп нь хэрэглэгчийн оролцоогүйгээр товчлолыг устгаж чадна"
+ "Нүүрний тохиргоо болон товчлолыг унших"
+ "Апп нь Нүүрэндэх товчлол болон тохиргоог уншиж чадна."
+ "Нүүрний тохиргоо болон товчлолыг бичих"
+ "Апп нь Нүүрэндэх товчлол болон тохиргоог өөрчилж чадна."
+ "Виджет ачаалахад асуудал гарав"
+ "Энэ апп нь системийн апп ба устгах боломжгүй."
+ "Пуужин хөөргөгч"
+ "Нэргүй фолдер"
+ "Нүүр дэлгэц %1$d"
+ "%2$d-н %1$d хуудас"
+ "%2$d-н Нүүр дэлгэц %1$d"
+ "%2$d-н %1$d апп хуудас"
+ "%2$d-н %1$d виджет хуудас"
+ "Тавтай морилно уу!"
+ "Гэртээ байгаа мэт тухлаарай."
+
+
+ "Апп болон фолдеруудад зориулан өөр дэлгэцүүд үүсгээрэй"
+ "Өөрийнхөө зайг тохируулаарай"
+ "Арын дэвсгэр дээр хүрээд & дарснаар ханын зураг, виджет болон тохиргоог өөрчилж болно."
+ "Апп сонгоно уу"
+ "Нүүр дэлгэцэнд апп нэмэх бол хүрээд барина уу."
+ "Фолдер энд байна"
+ "Үүнтэй адилханыг үүсгэхийн тулд апп дээр хүрч & бариад нөгөөхийн дээр зөөнө үү."
+ "Тийм"
+ "%1$d%2$d фолдер нээгдэв"
+ "Фолдер хаах бол хүрнэ үү"
+ "Шинэ нэрийг хадгалах бол хүрнэ үү"
+ "Фолдер хаагдав"
+ "Фолдерын нэр %1$s болов"
+ "Фолдер: %1$s"
+
+
+
+
+ "Виджет"
+ "Ханын зураг"
+ "Тохиргоо"
+
diff --git a/res/values-ms-land/strings.xml b/res/values-ms-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-ms-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000000..4ee3545465
--- /dev/null
+++ b/res/values-ms-rMY/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Laman Utama"
+ "Apl Teras Android"
+
+ "Tetapkan kertas dinding"
+
+ "%1$d dipilih"
+ "%1$d dipilih"
+ "%1$d dipilih"
+
+ "Kertas dinding %1$d daripada %2$d"
+ "Memilih %1$s"
+ "Padam"
+ "Pilih imej"
+ "Kertas dinding"
+ "Pangkas kertas dinding"
+ "Apl tidak dipasang."
+ "Widget"
+ "Widget"
+ "Papar Mem"
+ "Sentuh & tahan untuk mengambil widget."
+ "Beli-belah"
+ "%1$d × %2$d"
+ "Tidak dapat melepaskan item pada Skrin Utama."
+ "Pilih widget yang hendak dibuat"
+ "Nama folder"
+ "Namakan semula folder"
+ "OK"
+ "Batal"
+ "Tambah ke skrin Laman Utama"
+ "Apl"
+ "Pintasan"
+ "Widget"
+ "Tiada lagi ruang pada skrin Laman Utama anda."
+ "Tiada lagi ruang pada skrin Laman Utama ini."
+ "Tiada lagi ruang pada kerusi panas."
+ "Widget ini terlalu besar untuk kerusi panas."
+ "Pintasan \"%s\" telah dibuat."
+ "Pintasan \"%s\" telah dialih keluar."
+ "Pintasan \"%s\" sudah wujud."
+ "Pilih jalan pintas"
+ "Pilih apl"
+ "Apl"
+ "Laman Utama"
+ "Alih keluar"
+ "Nyahpasang"
+ "Alih keluar"
+ "Nyahpasang"
+ "Maklumat apl"
+ "Cari"
+ "Carian Suara"
+ "Apl"
+ "Alih keluar"
+ "Nyahpasang kemas kini"
+ "Nyahpasang apl"
+ "Butiran apl"
+ "1 apl dipilih"
+ "1 widget dipilih"
+ "1 folder dipilih"
+ "1 pintasan dipilih"
+ "pasang pintasan"
+ "Membenarkan apl menambah pintasan tanpa campur tangan pengguna."
+ "nyahpasang pintasan"
+ "Membenarkan apl mengalih keluar pintasan tanpa campur tangan pengguna."
+ "baca tetapan dan pintasan Laman Utama"
+ "Membenarkan apl membaca tetapan dan pintasan di Laman Utama."
+ "tulis tetapan dan pintasan Laman Utama"
+ "Membenarkan apl menukar tetapan dan pintasan di Laman Utama."
+ "Masalah memuatkan widget"
+ "Ini ialah apl sistem dan tidak boleh dinyahpasang."
+ "Pelancar Roket"
+ "Folder Tanpa Nama"
+ "Skrin Laman Utama %1$d"
+ "Halaman %1$d daripada %2$d"
+ "Skrin Laman Utama %1$d daripada %2$d"
+ "Halaman apl %1$d daripada %2$d"
+ "Halaman widget %1$d daripada %2$d"
+ "Selamat datang!"
+ "Buat seperti berada di rumah sendiri."
+
+
+ "Buat lebih banyak skrin untuk apl dan folder"
+ "Susun ruang anda"
+ "Sentuh & tahan latar belakang untuk mengurus kertas dinding, widget dan tetapan."
+ "Pilih beberapa apl"
+ "Untuk menambahkan apl pada skrin Laman Utama anda, sentuh & tahan apl."
+ "Ini ada folder"
+ "Untuk membuat satu folder seperti ini, sentuh & tahan apl, kemudian alihkan ke atas folder lain."
+ "OK"
+ "Folder dibuka, %1$d kali %2$d"
+ "Sentuh untuk menutup folder"
+ "Sentuh untuk menyimpan penamaan semula"
+ "Folder ditutup"
+ "Folder dinamakan semula kepada %1$s"
+ "Folder: %1$s"
+
+
+
+
+ "Widget"
+ "Kertas dinding"
+ "Tetapan"
+
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
new file mode 100644
index 0000000000..d2f6bb1422
--- /dev/null
+++ b/res/values-ms/strings.xml
@@ -0,0 +1,115 @@
+
+
+
+
+ "Pelancar"
+ "Laman Utama"
+ "Apl Teras Android"
+
+ "Pilih kertas dinding dari"
+ "Tetapkan kertas dinding"
+ "Kertas dinding"
+ "Aplikasi tidak dipasang."
+ "Widget"
+ "Sentuh & tahan untuk mengambil widget."
+ "Kedai"
+ "%1$d × %2$d"
+ "Tidak dapat melepaskan item pada skrin Utama ini."
+ "Pilih widget untuk dibuat"
+ "Nama folder"
+ "Namakan semula folder"
+ "OK"
+ "Batal"
+ "Tambah ke Skrin utama"
+ "Aplikasi"
+ "Pintasan"
+ "Widget"
+ "Kertas dinding"
+ "Tiada lagi ruang pada skrin Utama anda."
+ "Tiada lagi ruang pada skrin Utama ini"
+ "Tiada lagi ruang pada kerusi panas."
+ "Widget ini terlalu besar untuk kerusi panas."
+ "Pintasan \"%s\" diwujudkan."
+ "Pintasan \"%s\" telah dialih keluar."
+ "Pintasan \"%s\" sudah pun wujud."
+ "Pilih pintasan"
+ "Pilih aplikasi"
+ "Apl"
+ "Laman Utama"
+ "Alih keluar"
+ "Nyahpasang"
+ "Alih keluar"
+ "Nyahpasang"
+ "Maklumat apl"
+ "Carian"
+ "Carian Suara"
+ "Aplikasi"
+ "Alih keluar"
+ "Nyahpasang kemas kini"
+ "Tambah"
+ "Mengurus apl"
+ "Kertas dinding"
+ "Cari"
+ "Pemberitahuan"
+ "Tetapan sistem"
+ "Bantuan"
+ "Nyahpasang aplikasi"
+ "Butiran aplikasi"
+ "1 aplikasi dipilih"
+ "1 widget dipilih"
+ "1 folder dipilih"
+ "1 pintasan dipilih"
+ "pasang pintasan"
+ "Membenarkan aplikasi menambah pintasan tanpa campur tangan pengguna."
+ "nyahpasang pintasan"
+ "Membenarkan apl mengalih keluar pintasan tanpa campur tangan pengguna."
+ "membaca tetapan dan pintasan Laman Utama"
+ "Membenarkan apl membaca tetapan dan pintasan di Laman Utama."
+ "menulis tetapan dan pintasan Laman Utama"
+ "Membenarkan apl menukar tetapan dan pintasan di Laman Utama."
+ "Masalah memuatkan widget"
+ "Ini adalah aplikasi sistem dan tidak boleh dinyahpasang."
+ "Pelancar Roket"
+ "Folder Tanpa Nama"
+ "Skrin utama %1$d"
+ "Halaman %1$d dari %2$d"
+ "Skrin utama %1$d dari %2$d"
+ "Halaman apl %1$d dari %2$d"
+ "Halaman widget %1$d dari %2$d"
+ "Buat diri anda seperti di rumah"
+ "Anda boleh meletakkan aplikasi kegemaran anda di sini."
+ "Untuk melihat semua aplikasi anda, sentuh bulatan."
+ "Pilih beberapa aplikasi"
+ "Untuk menambahkan aplikasi pada skrin Utama anda, sentuh & tahankannya."
+ "Susun aplikasi anda dengan folder"
+ "Untuk memindahkan aplikasi, sentuh & tahankannya."
+ "Untuk membuat folder baharu pada skrin Utama anda, tindihkan satu aplikasi di atas yang lain."
+ "OK"
+ "Folder dibuka, %1$d kali %2$d"
+ "Sentuh untuk menutup folder"
+ "Sentuh untuk menyimpan penamaan semula"
+ "Folder ditutup"
+ "Folder dinamakan semula kepada %1$s"
+ "Folder: %1$s"
+
+
+
+
+
diff --git a/res/values-nb-land/strings.xml b/res/values-nb-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-nb-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000000..ce50754bf7
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Startside"
+ "Kjerneapper for Android"
+
+ "Angi bakgrunn"
+
+ "%1$d valgt"
+ "%1$d valgt"
+ "%1$d valgt"
+
+ "Bakgrunn %1$d of %2$d"
+ "Valgt %1$s"
+ "Slett"
+ "Velg bilde"
+ "Bakgrunner"
+ "Beskjær bakgrunnen"
+ "Appen er ikke installert."
+ "Moduler"
+ "Moduler"
+ "Vis minne"
+ "Trykk og hold inne for å plukke opp en modul."
+ "Butikk"
+ "%1$d × %2$d"
+ "Kunne ikke slippe elementet på denne startsiden."
+ "Velg modul for oppretting"
+ "Mappenavn"
+ "Gi mappen nytt navn"
+ "OK"
+ "Avbryt"
+ "Legg til på startsiden"
+ "Apper"
+ "Snarveier"
+ "Moduler"
+ "Ikke mer plass på startsidene dine."
+ "Denne startsiden er full."
+ "Dokksonen er full."
+ "Denne modulen er for stor for dokksonen."
+ "Snarveien «%s» er opprettet."
+ "Snarveien «%s» er fjernet."
+ "Snarveien «%s» fins allerede."
+ "Valg av snarvei"
+ "Velg app"
+ "Apper"
+ "Startside"
+ "Fjern"
+ "Avinstaller"
+ "Fjern"
+ "Avinstaller"
+ "App-info"
+ "Søk"
+ "Talesøk"
+ "Apper"
+ "Fjern"
+ "Avinstaller oppdateringen"
+ "Avinstaller appen"
+ "Informasjon om appen"
+ "Én app er valgt"
+ "Én modul er valgt"
+ "Én mappe er valgt"
+ "Én snarvei er valgt"
+ "installere snarveier"
+ "Gir apper tillatelse til å legge til snarveier uten innblanding fra brukeren."
+ "avinstallere snarveier"
+ "Lar appen fjerne snarveier uten innblanding fra brukeren."
+ "lese startsideinnstillinger og -snarveier"
+ "Lar appen lese innstillingene og snarveiene på startsiden."
+ "angi startsideinnstillinger og -snarveier"
+ "Lar appen endre innstillingene og snarveiene på startsiden."
+ "Problem ved innlasting av modul"
+ "Dette er en systemapp som ikke kan avinstalleres."
+ "Rocket Launcher"
+ "Mappe uten navn"
+ "Startside %1$d"
+ "Side %1$d av %2$d"
+ "Startside %1$d av %2$d"
+ "Appside %1$d av %2$d"
+ "Modulside %1$d av %2$d"
+ "Velkommen!"
+ "Føl deg som hjemme."
+
+
+ "Opprett flere sider for apper og mapper"
+ "Organiser plassen din"
+ "Trykk og hold på bakgrunnen for å administrere bakgrunnen, moduler og innstillinger."
+ "Velg noen apper"
+ "Trykk og hold på en app for å legge den til på startsiden."
+ "Dette er en mappe"
+ "For å opprette en som denne, trykker og holder du på en app og flytter den over en annen."
+ "OK"
+ "Mappen er åpnet – %1$d ganger %2$d"
+ "Trykk for å lukke mappen"
+ "Trykk for å lagre det nye navnet"
+ "Mappen ble lukket"
+ "Mappen heter nå %1$s"
+ "Mappe: %1$s"
+
+
+
+
+ "Moduler"
+ "Bakgrunner"
+ "Innstillinger"
+
diff --git a/res/values-nl-land/strings.xml b/res/values-nl-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-nl-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000000..e2a2a0719d
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Startpagina"
+ "Android-kernapps"
+
+ "Achtergrond instellen"
+
+ "%1$d geselecteerd"
+ "%1$d geselecteerd"
+ "%1$d geselecteerd"
+
+ "Achtergrond %1$d van %2$d"
+ "%1$s is geselecteerd"
+ "Verwijderen"
+ "Afbeelding kiezen"
+ "Achtergronden"
+ "Achtergrond bijsnijden"
+ "App is niet geïnstalleerd."
+ "Widgets"
+ "Widgets"
+ "Geheugen weergeven"
+ "Blijf aanraken om een widget toe te voegen."
+ "Winkelen"
+ "%1$d × %2$d"
+ "Kan item niet neerzetten in dit startscherm."
+ "Widget selecteren om te maken"
+ "Mapnaam"
+ "Naam van map wijzigen"
+ "OK"
+ "Annuleren"
+ "Toevoegen aan startscherm"
+ "Apps"
+ "Snelkoppelingen"
+ "Widgets"
+ "Er is geen ruimte meer op uw startschermen."
+ "Er is geen ruimte meer op dit startscherm."
+ "Er is geen ruimte meer op de hotseat."
+ "Deze widget is te groot voor de hotseat."
+ "Snelkoppeling \'%s\' is gemaakt."
+ "Snelkoppeling \'%s\' is verwijderd."
+ "Snelkoppeling \'%s\' bestaat al."
+ "Snelkoppeling selecteren"
+ "App selecteren"
+ "Apps"
+ "Startpagina"
+ "Verwijderen"
+ "Verwijderen"
+ "Verwijderen"
+ "Verwijderen"
+ "App-info"
+ "Zoeken"
+ "Gesproken zoekopdracht"
+ "Apps"
+ "Verwijderen"
+ "Update verwijderen"
+ "App verwijderen"
+ "App-details"
+ "1 app geselecteerd"
+ "1 widget geselecteerd"
+ "1 map geselecteerd"
+ "1 snelkoppeling geselecteerd"
+ "snelkoppelingen installeren"
+ "Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."
+ "snelkoppelingen verwijderen"
+ "De app toestaan snelkoppelingen te verwijderen zonder tussenkomst van de gebruiker."
+ "instellingen en snelkoppelingen op de startpagina lezen"
+ "De app toestaan de instellingen en snelkoppelingen op de startpagina te lezen."
+ "instellingen en snelkoppelingen op de startpagina schrijven"
+ "De app toestaan de instellingen en snelkoppelingen op de startpagina te wijzigen."
+ "Probleem bij het laden van widget"
+ "Dit is een systeemapp die niet kan worden verwijderd."
+ "Rocket Launcher"
+ "Naamloze map"
+ "Startscherm %1$d"
+ "Pagina %1$d van %2$d"
+ "Startscherm %1$d van %2$d"
+ "App-pagina %1$d van %2$d"
+ "Widgetpagina %1$d van %2$d"
+ "Welkom!"
+ "Personaliseer uw startscherm."
+
+
+ "Meer schermen maken voor apps en mappen"
+ "Uw ruimte indelen"
+ "Blijf de achtergrond aanraken om de achtergrond, widgets en instellingen te beheren."
+ "Selecteer een aantal apps"
+ "Als u een app wilt toevoegen aan het startscherm, blijft u de app aanraken."
+ "Dit is een map"
+ "Als u een map zoals deze wilt maken, blijft u een app aanraken en schuift u deze boven op een andere app."
+ "OK"
+ "Map geopend, %1$d bij %2$d"
+ "Raak dit aan om de map te sluiten"
+ "Raak dit aan om de gewijzigde naam op te slaan"
+ "Map gesloten"
+ "De naam van de map is gewijzigd in %1$s"
+ "Map: %1$s"
+
+
+
+
+ "Widgets"
+ "Achtergronden"
+ "Instellingen"
+
diff --git a/res/values-nodpi/wallpapers.xml b/res/values-nodpi/wallpapers.xml
new file mode 100644
index 0000000000..1e340e4b28
--- /dev/null
+++ b/res/values-nodpi/wallpapers.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/res/values-pl-land/strings.xml b/res/values-pl-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-pl-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000000..b629f3d362
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Ekran główny"
+ "Główne aplikacje Androida"
+
+ "Ustaw tapetę"
+
+ "Wybrane: %1$d"
+ "Wybrane: %1$d"
+ "Wybrane: %1$d"
+
+ "Tapeta %1$d z %2$d"
+ "Wybrano %1$s"
+ "Usuń"
+ "Wybierz obraz"
+ "Tapety"
+ "Przytnij tapetę"
+ "Aplikacja nie jest zainstalowana."
+ "Widżety"
+ "Widżety"
+ "Pokaż pamięć"
+ "Aby dodać widżet, kliknij go i przytrzymaj."
+ "Sklep"
+ "%1$d × %2$d"
+ "Nie można upuścić elementu na tym ekranie głównym."
+ "Wybierz widżet, który chcesz dodać"
+ "Nazwa folderu"
+ "Zmień nazwę folderu"
+ "OK"
+ "Anuluj"
+ "Dodaj do ekranu głównego"
+ "Aplikacje"
+ "Skróty"
+ "Widżety"
+ "Brak miejsca na ekranach głównych."
+ "Brak miejsca na tym ekranie głównym."
+ "Brak miejsca w kieszonce."
+ "Ten widżet jest za duży, by umieścić go w kieszonce."
+ "Skrót „%s” został utworzony."
+ "Skrót „%s” został usunięty."
+ "Skrót „%s” już istnieje."
+ "Wybierz skrót"
+ "Wybierz aplikację"
+ "Aplikacje"
+ "Ekran główny"
+ "Usuń"
+ "Odinstaluj"
+ "Usuń"
+ "Odinstaluj"
+ "Informacje o aplikacji"
+ "Szukaj"
+ "Wyszukiwanie głosowe"
+ "Aplikacje"
+ "Usuń"
+ "Odinstaluj aktualizację"
+ "Odinstaluj aplikację"
+ "Szczegóły aplikacji"
+ "Wybrano 1 aplikację"
+ "Wybrano 1 widżet"
+ "Wybrano 1 folder"
+ "Wybrano 1 skrót"
+ "instalowanie skrótów"
+ "Pozwala aplikacji dodawać skróty bez interwencji użytkownika."
+ "odinstalowywanie skrótów"
+ "Pozwala aplikacji usuwać skróty bez interwencji użytkownika."
+ "odczytywanie ustawień i skrótów na ekranie głównym"
+ "Pozwala aplikacji na odczytywanie ustawień i skrótów na ekranie głównym."
+ "zapisywanie ustawień i skrótów na ekranie głównym"
+ "Umożliwia aplikacji zmianę ustawień i skrótów na ekranie głównym."
+ "Problem podczas ładowania widżetu"
+ "To aplikacja systemowa i nie można jej odinstalować."
+ "Wyrzutnia rakiet"
+ "Folder bez nazwy"
+ "Ekran główny %1$d"
+ "Strona %1$d z %2$d"
+ "Ekran główny %1$d z %2$d"
+ "Strona aplikacji: %1$d z %2$d"
+ "Strona widżetów: %1$d z %2$d"
+ "Witamy"
+ "Poczuj się jak u siebie."
+
+
+ "Dodaj więcej ekranów na aplikacje i foldery"
+ "Uporządkuj obszar roboczy"
+ "Kliknij i przytrzymaj tło, by zmienić tapetę, widżety lub ustawienia."
+ "Wybierz kilka aplikacji"
+ "Aby dodać aplikację na ekran główny, dotknij i przytrzymaj jej ikonę."
+ "Tu jest folder"
+ "Aby utworzyć taki sam, kliknij i przytrzymaj aplikację, a następnie przenieś ją na następną."
+ "OK"
+ "Folder otwarty, %1$d na %2$d"
+ "Kliknij, by zamknąć folder"
+ "Kliknij, by zapisać zmianę nazwy"
+ "Folder zamknięty"
+ "Nazwa folderu zmieniona na %1$s"
+ "Folder: %1$s"
+
+
+
+
+ "Widżety"
+ "Tapety"
+ "Ustawienia"
+
diff --git a/res/values-port/dimens.xml b/res/values-port/dimens.xml
new file mode 100644
index 0000000000..7194a2a77a
--- /dev/null
+++ b/res/values-port/dimens.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ -1dp
+
+
+ 1
+ 1
+
+ 2
+ 3
+
diff --git a/res/values-port/styles.xml b/res/values-port/styles.xml
new file mode 100644
index 0000000000..ab6a1eb925
--- /dev/null
+++ b/res/values-port/styles.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/res/values-pt-land/strings.xml b/res/values-pt-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-pt-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-pt-rPT-land/strings.xml b/res/values-pt-rPT-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-pt-rPT-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000000..1ef398226f
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Iniciador3"
+ "Ecrã principal"
+ "Aplicações principais do Android"
+
+ "Definir imagem fundo"
+
+ "%1$d selecionado(s)"
+ "%1$d selecionado(s)"
+ "%1$d selecionado(s)"
+
+ "Imagem de fundo %1$d de %2$d"
+ "%1$s selecionado"
+ "Eliminar"
+ "Escolher imagem"
+ "Imagens de fundo"
+ "Recortar imagem de fundo"
+ "A aplicação não está instalada."
+ "Widgets"
+ "Widgets"
+ "Mostrar mem"
+ "Prima sem soltar para escolher um widget."
+ "Comprar"
+ "%1$d × %2$d"
+ "Não foi possível largar o item neste Ecrã Principal."
+ "Escolher um widget para criar"
+ "Nome da pasta"
+ "Mudar o nome da pasta"
+ "OK"
+ "Cancelar"
+ "Adicionar ao Ecrã principal"
+ "Aplicações"
+ "Atalhos"
+ "Widgets"
+ "Sem espaço suficiente nos Ecrãs principais."
+ "Sem espaço suficiente neste Ecrã principal."
+ "Sem espaço suficiente na barra personalizável."
+ "Este widget é demasiado grande para a barra personalizável."
+ "Atalho “%s” criado."
+ "O atalho “%s” foi removido."
+ "O atalho “%s” já existe."
+ "Escolher atalho"
+ "Escolher aplicação"
+ "Aplicações"
+ "Ecrã principal"
+ "Remover"
+ "Desinstalar"
+ "Remover"
+ "Desinstalar"
+ "Informações da aplicação"
+ "Pesquisar"
+ "Pesquisa por Voz"
+ "Aplicações"
+ "Remover"
+ "Desinstalar atualização"
+ "Desinstalar a aplicação"
+ "Detalhes da aplicação"
+ "1 aplicação selecionada"
+ "1 widget selecionado"
+ "1 pasta selecionada"
+ "1 atalho selecionado"
+ "instalar atalhos"
+ "Permite a uma aplicação adicionar atalhos sem a intervenção do utilizador."
+ "desinstalar atalhos"
+ "Permite à aplicação remover atalhos sem intervenção do utilizador."
+ "ler definições e atalhos do Ecrã Principal"
+ "Permite à aplicação ler as definições e os atalhos no Ecrã Principal."
+ "escrever definições e atalhos do Ecrã principal"
+ "Permite à aplicação alterar as definições e os atalhos no Ecrã Principal."
+ "Problema ao carregar o widget"
+ "É uma aplicação de sistema e não pode ser desinstalada."
+ "Lança-mísseis"
+ "Pasta sem nome"
+ "Ecrã principal %1$d"
+ "Página %1$d de %2$d"
+ "Ecrã principal %1$d de %2$d"
+ "Página de aplicações %1$d de %2$d"
+ "Página de widgets %1$d de %2$d"
+ "Bem-vindo(a)!"
+ "Sinta-se em casa."
+
+
+ "Crie mais ecrãs para aplicações e pastas"
+ "Organizar o seu espaço"
+ "Toque sem soltar no fundo para gerir a imagem de fundo, os widgets e as definições."
+ "Escolher algumas aplicações"
+ "Para adicionar uma aplicação ao Ecrã principal, toque na mesma sem soltar."
+ "Eis uma pasta"
+ "Para criar uma pasta, toque sem soltar numa aplicação e arraste-a para cima de outra aplicação."
+ "OK"
+ "Pasta aberta, %1$d por %2$d"
+ "Toque para fechar a pasta"
+ "Toque para guardar o nome novo"
+ "Pasta fechada"
+ "Nome de pasta alterado para %1$s"
+ "Pasta: %1$s"
+
+
+
+
+ "Widgets"
+ "Imagens de fundo"
+ "Definições"
+
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000000..4d40388f5c
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Início"
+ "Principais aplicativos do Android"
+
+ "Definir plano de fundo"
+
+ "%1$d selecionados"
+ "%1$d selecionados"
+ "%1$d selecionados"
+
+ "Plano de fundo %1$d de %2$d"
+ "%1$s selecionado"
+ "Excluir"
+ "Escolher imagem"
+ "Planos de fundo"
+ "Cortar plano de fundo"
+ "O aplicativo não está instalado."
+ "Widgets"
+ "Widgets"
+ "Mostrar memória"
+ "Toque e pressione para selecionar um widget."
+ "Comprar"
+ "%1$d × %2$d"
+ "Não foi possível soltar o item nesta tela inicial."
+ "Selecione um widget para criar"
+ "Nome da pasta"
+ "Renomear pasta"
+ "Ok"
+ "Cancelar"
+ "Adicionar à tela inicial"
+ "Aplicativos"
+ "Atalhos"
+ "Widgets"
+ "Não há mais espaço nas telas iniciais."
+ "Não há mais espaço na tela inicial."
+ "Não há mais espaço no hotseat."
+ "Este widget é muito grande para o hotseat."
+ "Atalho \"%s\" criado."
+ "O atalho \"%s\" foi removido."
+ "O atalho \"%s\" já existe."
+ "Selecione um atalho"
+ "Selecione um aplicativo"
+ "Aplicativos"
+ "Início"
+ "Remover"
+ "Desinstalar"
+ "Remover"
+ "Desinstalar"
+ "Informações do aplicativo"
+ "Pesquisar"
+ "Pesquisa por voz"
+ "Aplicativos"
+ "Remover"
+ "Desinstalar atualização"
+ "Desinstalar aplicativo"
+ "Detalhes do aplicativo"
+ "Um aplicativo selecionado"
+ "Um widget selecionado"
+ "Uma pasta selecionada"
+ "Um atalho selecionado"
+ "instalar atalhos"
+ "Permite que um aplicativo adicione atalhos sem intervenção do usuário."
+ "desinstalar atalhos"
+ "Permite que o aplicativo remova atalhos sem a intervenção do usuário."
+ "ler configurações e atalhos da tela inicial"
+ "Permite que o aplicativo leia as configurações e os atalhos na tela inicial."
+ "gravar configurações e atalhos da tela inicial"
+ "Permite que o aplicativo altere as configurações e os atalhos na tela inicial."
+ "Problema ao carregar o widget"
+ "Este é um aplicativo do sistema e não pode ser desinstalado."
+ "Rocket Launcher"
+ "Pasta sem nome"
+ "Tela inicial %1$d"
+ "Página %1$d de %2$d"
+ "Tela inicial %1$d de %2$d"
+ "Página de aplicativos, %1$d de %2$d"
+ "Página de widgets, %1$d de %2$d"
+ "Bem-vindo!"
+ "Fique à vontade."
+
+
+ "Crie mais telas para aplicativos e pastas"
+ "Organize seu espaço"
+ "Toque e mantenha pressionada a tela de fundo para gerenciar o plano de fundo, os widgets e as configurações."
+ "Escolha alguns aplicativos"
+ "Para adicionar um aplicativo a sua tela inicial, toque e mantenha-o pressionado."
+ "Aqui está uma pasta"
+ "Para criar uma pasta como esta, mantenha pressionado um aplicativo e mova-o para cima de outro."
+ "Ok"
+ "Pasta aberta, %1$d por %2$d"
+ "Toque para fechar a pasta"
+ "Toque para salvar o novo nome"
+ "Pasta fechada"
+ "Pasta renomeada para %1$s"
+ "Pasta: %1$s"
+
+
+
+
+ "Widgets"
+ "Planos de fundo"
+ "Configurações"
+
diff --git a/res/values-rm-land/strings.xml b/res/values-rm-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-rm-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
new file mode 100644
index 0000000000..ddcf40442e
--- /dev/null
+++ b/res/values-rm/strings.xml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values-ro-land/strings.xml b/res/values-ro-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-ro-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000000..78d9da41fe
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Ecran de pornire"
+ "Android Core Apps"
+
+ "Setați imaginea de fundal"
+
+ "%1$d selectate"
+ "%1$d selectat"
+ "%1$d selectate"
+
+ "Imaginea de fundal %1$d din %2$d"
+ "S-a selectat %1$s"
+ "Ștergeți"
+ "Alegeți imaginea"
+ "Imagini de fundal"
+ "Decupați imaginea de fundal"
+ "Aplicația nu este instalată."
+ "Widgeturi"
+ "Widgeturi"
+ "Afișați memoria"
+ "Atingeți lung un widget pentru a-l alege."
+ "Cumpărați"
+ "%1$d × %2$d"
+ "Nu se poate plasa articolul pe ecranul de pornire."
+ "Alegeți widgetul de creat"
+ "Numele dosarului"
+ "Redenumiți dosarul"
+ "OK"
+ "Anulați"
+ "Adăugați la Ecranul de pornire"
+ "Aplicații"
+ "Comenzi rapide"
+ "Widgeturi"
+ "Nu mai este loc pe ecranele de pornire."
+ "Nu mai este loc pe acest Ecran de pornire."
+ "Nu mai este loc în bara de lansare rapidă."
+ "Acest widget este prea mare pentru bara de lansare rapidă."
+ "Comanda rapidă „%s\" a fost creată."
+ "Comanda rapidă „%s” a fost eliminată."
+ "Comanda rapidă „%s” există deja."
+ "Alegeți comanda rapidă"
+ "Alegeți aplicația"
+ "Aplicații"
+ "Ecran de pornire"
+ "Eliminați"
+ "Dezinstalați"
+ "Eliminați"
+ "Dezinstalați"
+ "Informații despre aplicație"
+ "Căutați"
+ "Căutare vocală"
+ "Aplicații"
+ "Eliminați"
+ "Dezinstalați actualizarea"
+ "Dezinstalați aplicația"
+ "Detalii despre aplicație"
+ "1 aplicație selectată"
+ "1 widget selectat"
+ "1 dosar selectat"
+ "1 comandă rapidă selectată"
+ "instalează comenzi rapide"
+ "Permite unei aplicații să adauge comenzi rapide fără intervenția utilizatorului."
+ "dezinstalează comenzi rapide"
+ "Permite aplicației să elimine comenzi rapide fără intervenția utilizatorului."
+ "citește setări și comenzi rapide pentru ecranul de pornire"
+ "Permite aplicației să citească setările și comenzile rapide din ecranul de pornire."
+ "scrie setări și comenzi rapide pentru ecranul de pornire"
+ "Permite aplicației să modifice setările și comenzile rapide din ecranul de pornire."
+ "Problemă la încărcarea widgetului"
+ "Aceasta este o aplicație de sistem și nu poate fi dezinstalată."
+ "Rocket Launcher"
+ "Dosar fără nume"
+ "Ecran de pornire %1$d"
+ "Pagina %1$d din %2$d"
+ "Ecranul de pornire %1$d din %2$d"
+ "Pagina de aplicații %1$d din %2$d"
+ "Pagina de widgeturi %1$d din %2$d"
+ "Bun venit!"
+ "Simțiți-vă ca acasă."
+
+
+ "Creați mai multe ecrane pentru aplicații și dosare"
+ "Organizați-vă spațiul"
+ "Atingeți lung fundalul pentru a gestiona imaginea de fundal, widgeturile și setările."
+ "Alegeți unele aplicații"
+ "Atingeți lung o aplicație pentru a o adăuga pe ecranul de pornire."
+ "Iată un dosar"
+ "Pentru a crea un dosar similar, atingeți și țineți degetul pe o aplicație, apoi mutați-o deasupra alteia."
+ "OK"
+ "Dosar deschis, %1$d pe %2$d"
+ "Atingeți pentru a închide dosarul"
+ "Atingeți pentru a salva redenumirea"
+ "Dosar închis"
+ "Dosar redenumit %1$s"
+ "Dosar: %1$s"
+
+
+
+
+ "Widgeturi"
+ "Imagini de fundal"
+ "Setări"
+
diff --git a/res/values-ru-land/strings.xml b/res/values-ru-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-ru-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000000..06fbd26e4a
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Главный экран"
+ "Основные приложения Android"
+
+ "Установить как обои"
+
+ "Выбрано: %1$d"
+ "Выбрано: %1$d"
+ "Выбрано: %1$d"
+
+ "Обои %1$d из %2$d"
+ "Выбран элемент \"%1$s\""
+ "Удалить"
+ "Выбрать изображение"
+ "Обои"
+ "Обрезать обои"
+ "Приложение удалено"
+ "Виджеты"
+ "Виджеты"
+ "Сведения о памяти"
+ "Чтобы выбрать виджет, нажмите на значок и удерживайте его."
+ "Google Play"
+ "%1$d x %2$d"
+ "Не удалось добавить элемент на главный экран"
+ "Выберите виджет"
+ "Название папки"
+ "Переименование папки"
+ "ОК"
+ "Отмена"
+ "Добавление на главный экран"
+ "Приложения"
+ "Ярлыки"
+ "Виджеты"
+ "На главных экранах все занято"
+ "На этом экране все занято"
+ "Нет свободного места в слоте"
+ "Виджет слишком велик для слота"
+ "Ярлык \"%s\" создан"
+ "Ярлык \"%s\" удален"
+ "Ярлык \"%s\" уже существует"
+ "Выбор ярлыка"
+ "Выбор приложения"
+ "Приложения"
+ "Главный экран"
+ "Удалить"
+ "Удалить"
+ "Удалить"
+ "Удалить"
+ "О приложении"
+ "Поиск"
+ "Голосовой поиск"
+ "Приложения"
+ "Удалить"
+ "Удалить обновление"
+ "Удалить приложение"
+ "О приложении"
+ "Выбрано 1 приложение"
+ "Выбран 1 виджет"
+ "Выбрана 1 папка"
+ "Выбран 1 ярлык"
+ "Создание ярлыков"
+ "Приложение сможет самостоятельно добавлять ярлыки."
+ "Удаление ярлыков"
+ "Приложение сможет самостоятельно удалять ярлыки."
+ "Доступ к настройкам и ярлыкам главного экрана"
+ "Приложение получит доступ к данным о настройках и ярлыках на главном экране."
+ "Изменение настроек и ярлыков главного экрана"
+ "Приложение сможет изменять настройки и ярлыки на главном экране."
+ "Не удалось загрузить виджет"
+ "Это системное приложение, его нельзя удалить."
+ "Rocket Launcher"
+ "Папка без названия"
+ "Главный экран %1$d"
+ "Стр. %1$d из %2$d"
+ "Главные экран %1$d из %2$d"
+ "Приложения: стр. %1$d из %2$d"
+ "Виджеты: стр. %1$d из %2$d"
+ "Добро пожаловать!"
+ "Будьте как дома"
+
+
+ "Создание дополнительных экранов для приложений и папок"
+ "Организация рабочего пространства"
+ "Чтобы перейти к управлению обоями, виджетами и настройками, нажмите на фоновое изображение и удерживайте его."
+ "Выберите приложения"
+ "Чтобы добавить приложение на главный экран, нажмите на значок и удерживайте его."
+ "Это папка"
+ "Чтобы создать папку, нажмите и удерживайте значок приложения, а затем перетащите его на другой значок."
+ "ОК"
+ "Папка открыта, %1$d x %2$d"
+ "Нажмите, чтобы закрыть папку"
+ "Нажмите, чтобы подтвердить переименование"
+ "Папка закрыта"
+ "Папка переименована в \"%1$s\""
+ "Папка: %1$s"
+
+
+
+
+ "Виджеты"
+ "Обои"
+ "Настройки"
+
diff --git a/res/values-sk-land/strings.xml b/res/values-sk-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-sk-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000000..29de526024
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Plocha"
+ "Android Core Apps"
+
+ "Nastaviť tapetu"
+
+ "Počet vybratých položiek: %1$d"
+ "Počet vybratých položiek: %1$d"
+ "Počet vybratých položiek: %1$d"
+
+ "Tapeta %1$d z %2$d"
+ "Vybratá položka %1$s"
+ "Odstrániť"
+ "Vybrať obrázok"
+ "Tapety"
+ "Orezanie tapety"
+ "Aplikácia nie je nainštalovaná."
+ "Miniaplikácie"
+ "Miniaplikácie"
+ "Zobraziť pamäť"
+ "Miniaplikáciu pridáte stlačením a podržaním."
+ "Obchod"
+ "%1$d × %2$d"
+ "Položku sa nepodarilo presunúť na túto plochu."
+ "Zvoľte miniaplikáciu na vytvorenie"
+ "Názov priečinka"
+ "Premenovať priečinok"
+ "OK"
+ "Zrušiť"
+ "Pridať na plochu"
+ "Aplikácie"
+ "Skratky"
+ "Miniaplikácie"
+ "Na plochách už nie je miesto."
+ "Na tejto ploche už nie je miesto"
+ "V časti hotseat už nie je miesto."
+ "Táto miniaplikácia je pre hotseat príliš veľká."
+ "Odkaz %s bol vytvorený."
+ "Odkaz %s bol odstránený."
+ "Odkaz %s už existuje."
+ "Vybrať odkaz"
+ "Vybrať aplikáciu"
+ "Aplikácie"
+ "Domovská stránka"
+ "Odstrániť"
+ "Odinštalovať"
+ "Odstrániť"
+ "Odinštalovať"
+ "Informácie o aplikácii"
+ "Vyhľadať"
+ "Hlasové vyhľadávanie"
+ "Aplikácie"
+ "Odstrániť"
+ "Odinštalovať aktualizáciu"
+ "Odinštalovať aplikáciu"
+ "Podrobnosti o aplikácii"
+ "Vybratá 1 aplikácia"
+ "Vybratá 1 miniaplikácia"
+ "Vybratý 1 priečinok"
+ "Vybratý 1 odkaz"
+ "inštalovať odkazy"
+ "Povoľuje aplikácii pridať odkazy bez zásahu používateľa."
+ "odinštalovať odkazy"
+ "Povoľuje aplikácii odstrániť odkazy bez zásahu používateľa."
+ "čítanie nastavení a odkazov plochy"
+ "Povoľuje aplikácii čítať nastavenia a odkazy na ploche."
+ "zápis nastavení a odkazov plochy"
+ "Povoľuje aplikácii zmeniť nastavenia a odkazy na ploche."
+ "Problém s načítaním miniaplikácií"
+ "Toto je systémová aplikácia a nedá sa odinštalovať."
+ "Raketomet"
+ "Nepomenovaný priečinok"
+ "Plocha %1$d"
+ "Stránka %1$d z %2$d"
+ "Plocha %1$d z %2$d"
+ "Stránka aplikácií %1$d z %2$d"
+ "Stránka miniaplikácií %1$d z %2$d"
+ "Vitajte!"
+ "Cíťte sa tu ako doma."
+
+
+ "Vytvorte viac obrazoviek pre aplikácie a priečinky"
+ "Usporiadajte svoj priestor"
+ "Ak chcete spravovať tapetu, miniaplikácie a nastavenia, dotknite sa pozadia a podržte."
+ "Vyberte niektoré aplikácie"
+ "Ak chcete pridať aplikáciu na plochu, dotknite sa jej a podržte."
+ "Tu je priečinok"
+ "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."
+ "OK"
+ "Otvorený priečinok, %1$d x %2$d"
+ "Dotykom zavriete priečinok"
+ "Dotykom premenovanie uložíte"
+ "Priečinok je uzavretý"
+ "Priečinok bol premenovaný na %1$s"
+ "Priečinok: %1$s"
+
+
+
+
+ "Miniaplikácie"
+ "Tapety"
+ "Nastavenia"
+
diff --git a/res/values-sl-land/strings.xml b/res/values-sl-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-sl-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000000..021eca846c
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Zaganjalnik3"
+ "Začetni zaslon"
+ "Osnovne aplikacije sistema Android"
+
+ "Nastavi ozadje"
+
+ "Št. izbranih: %1$d"
+ "Št. izbranih: %1$d"
+ "Št. izbranih: %1$d"
+
+ "Ozadje %1$d od %2$d"
+ "Izbrano: %1$s"
+ "Izbriši"
+ "Izberi sliko"
+ "Ozadja"
+ "Obrezovanje ozadja"
+ "Aplikacija ni nameščena."
+ "Pripomočki"
+ "Pripomočki"
+ "Pokaži pomnilnik"
+ "Za izbiro pripomočka se ga dotaknite in pridržite."
+ "Nakup"
+ "%1$d × %2$d"
+ "Elementa ni mogoče spustiti na začetni zaslon."
+ "Izberite pripomoček za ustvarjanje"
+ "Ime mape"
+ "Preimenovanje mape"
+ "V redu"
+ "Prekliči"
+ "Dodaj na začetni zaslon"
+ "Aplikacije"
+ "Bližnjice"
+ "Pripomočki"
+ "Na začetnih zaslonih ni več prostora."
+ "Na tem začetnem zaslonu ni več prostora."
+ "V vrstici z ikonami ni več prostora."
+ "Ta pripomoček je prevelik za vrstico z ikonami."
+ "Bližnjica »%s« je ustvarjena."
+ "Bližnjica »%s« je bila odstranjena."
+ "Bližnjica »%s« že obstaja."
+ "Izberite bližnjico"
+ "Izberite aplikacijo"
+ "Aplikacije"
+ "Začetni zaslon"
+ "Odstrani"
+ "Odstrani"
+ "Odstrani"
+ "Odstrani"
+ "Podatki o aplikaciji"
+ "Iskanje"
+ "Glasovno iskanje"
+ "Aplikacije"
+ "Odstrani"
+ "Odstrani posodobitev"
+ "Odstrani aplikacijo"
+ "Podrobnosti o aplikaciji"
+ "Izbrana je 1 aplikacija"
+ "Izbran je 1 pripomoček"
+ "Izbrana je 1 mapa"
+ "Izbrana je 1 bližnjica"
+ "namestitev bližnjic"
+ "Aplikaciji dovoli dodajanje bližnjic brez posredovanja uporabnika."
+ "odstranitev bližnjic"
+ "Aplikaciji dovoli odstranjevanje bližnjic brez posredovanja uporabnika."
+ "branje nastavitev in bližnjic na začetnem zaslonu"
+ "Aplikaciji dovoli branje nastavitev in bližnjic na začetnem zaslonu."
+ "zapis nastavitev in bližnjic na začetnem zaslonu"
+ "Aplikaciji dovoli spreminjanje nastavitev in bližnjic na začetnem zaslonu."
+ "Težava pri nalaganju pripomočka"
+ "To je sistemska aplikacija in je ni mogoče odstraniti."
+ "Raketno izstrelišče"
+ "Neimenovana mapa"
+ "Začetni zaslon %1$d"
+ "Stran %1$d od %2$d"
+ "Začetni zaslon %1$d od %2$d"
+ "Stran aplikacij %1$d od %2$d"
+ "Stran pripomočkov %1$d od %2$d"
+ "Pozdravljeni!"
+ "Počutite se kot doma."
+
+
+ "Ustvarite več zaslonov za aplikacije in mape"
+ "Organizirajte svoj prostor"
+ "Če želite upravljati ozadje, pripomočke in nastavitve, se dotaknite ozadja in ga pridržite."
+ "Izberite nekaj aplikacij"
+ "Če želite dodati aplikacijo na začetni zaslon, se je dotaknite in jo pridržite."
+ "To je mapa"
+ "Če želite ustvariti mapo, podobno tej, se dotaknite aplikacije in jo pridržite, nato pa jo premaknite nad drugo."
+ "V redu"
+ "Mapa je odprta, %1$d krat %2$d"
+ "Dotaknite se, da zaprete mapo"
+ "Dotaknite se, da shranite preimenovanje"
+ "Mapa je zaprta"
+ "Mapa je preimenovana v %1$s"
+ "Mapa: %1$s"
+
+
+
+
+ "Pripomočki"
+ "Ozadja"
+ "Nastavitve"
+
diff --git a/res/values-sr-land/strings.xml b/res/values-sr-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-sr-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000000..06d80efd1a
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Почетна"
+ "Основне Android апликације"
+
+ "Подеси позадину"
+
+ "Изабранo je %1$d"
+ "Изабрана je %1$d"
+ "Изабранo je %1$d"
+
+ "Позадина %1$d од %2$d"
+ "Изабрано је %1$s"
+ "Избриши"
+ "Изабери слику"
+ "Позадине"
+ "Опсецање позадине"
+ "Апликација није инсталирана."
+ "Виџети"
+ "Виџети"
+ "Прикажи меморију"
+ "Додирните и задржите да бисте изабрали виџет."
+ "Купујте"
+ "%1$d×%2$d"
+ "Није могуће отпустити ставку на почетни екран."
+ "Избор виџета за прављење"
+ "Назив директоријума"
+ "Преименовање директоријума"
+ "Потврди"
+ "Откажи"
+ "Додавање на почетни екран"
+ "Апликације"
+ "Пречице"
+ "Виџети"
+ "Нема више простора на почетним екранима."
+ "Нема више простора на овом почетном екрану."
+ "Нема више простора на траци актуелности."
+ "Овај виџет је превелики за траку актуелности."
+ "Пречица „%s“ је направљена."
+ "Пречица „%s“ је уклоњена."
+ "Пречица „%s“ већ постоји."
+ "Избор пречице"
+ "Избор апликације"
+ "Апликације"
+ "Почетна"
+ "Уклони"
+ "Деинсталирај"
+ "Уклони"
+ "Деинсталирај"
+ "Информације о апликацији"
+ "Претражи"
+ "Гласовна претрага"
+ "Апликације"
+ "Уклони"
+ "Деинсталирај ажурирање"
+ "Деинсталирање апликације"
+ "Детаљи о апликацији"
+ "Изабрана је 1 апликација"
+ "Изабран је 1 виџет"
+ "Изабран је 1 директоријум"
+ "Изабрана је 1 пречица"
+ "инсталирање пречица"
+ "Дозвољава апликацији да додаје пречице без интервенције корисника."
+ "деинсталирање пречица"
+ "Дозвољава апликацији да уклања пречице без интервенције корисника."
+ "читање подешавања и пречица на почетном екрану"
+ "Дозвољава апликацији да чита подешавања и пречице на почетном екрану."
+ "уписивање подешавања и пречица на почетном екрану"
+ "Дозвољава апликацији да мења подешавања и пречице на почетном екрану."
+ "Проблем при учитавању виџета"
+ "Ово је системска апликација и не може да се деинсталира."
+ "Лансер ракета"
+ "Неименовани директоријум"
+ "Почетни екран %1$d"
+ "%1$d. страница од %2$d"
+ "%1$d. почетни екран од %2$d"
+ "%1$d. страница апликација од %2$d"
+ "%1$d. страница виџета од %2$d"
+ "Добро дошли!"
+ "Осећајте се као код куће."
+
+
+ "Направите још екрана за апликације и директоријуме"
+ "Организујте простор"
+ "Додирните позадину и задржите да бисте управљали позадином, виџетима и подешавањима."
+ "Изаберите неколико апликација"
+ "Да бисте додали апликацију на почетни екран, додирните је и задржите."
+ "Ево једног директоријума"
+ "Да бисте направили директоријум попут овога, додирните и задржите апликацију, па је превуците преко друге."
+ "Потврди"
+ "Директоријум је отворен, %1$d пута %2$d"
+ "Додирните да бисте затворили директоријум"
+ "Додирните да бисте сачували промену имена"
+ "Директоријум је затворен"
+ "Директоријум је преименован у %1$s"
+ "Директоријум: %1$s"
+
+
+
+
+ "Виџети"
+ "Позадине"
+ "Подешавања"
+
diff --git a/res/values-sv-land/strings.xml b/res/values-sv-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-sv-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000000..bf7520006b
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Startskärm"
+ "Android Core Apps"
+
+ "Ange bakgrund"
+
+ "%1$d har valts"
+ "%1$d har valts"
+ "%1$d har valts"
+
+ "Bakgrund %1$d av %2$d"
+ "%1$s har valts"
+ "Ta bort"
+ "Välj bild"
+ "Bakgrunder"
+ "Beskär bakgrund"
+ "Appen är inte installerad."
+ "Widgetar"
+ "Widgetar"
+ "Visa Mem"
+ "Tryck länge om du vill flytta en widget."
+ "Butik"
+ "%1$d × %2$d"
+ "Objektet kunde inte släppas på startskärmen."
+ "Ange vilken widget du vill använda"
+ "Mappnamn"
+ "Byt namn på mapp"
+ "OK"
+ "Avbryt"
+ "Lägg till på startskärmen"
+ "Appar"
+ "Genvägar"
+ "Widgetar"
+ "Det finns inte plats för mer på dina startsidor."
+ "Det finns inte plats för mer på den här startskärmen."
+ "Utrymmet på Hotseat är fullt."
+ "Denna widget är för stor för Hotseat."
+ "Genvägen %s har skapats."
+ "Genvägen %s har tagits bort."
+ "Genvägen %s finns redan."
+ "Välj genväg"
+ "Välj app"
+ "Appar"
+ "Startskärm"
+ "Ta bort"
+ "Avinstallera"
+ "Ta bort"
+ "Avinstallera"
+ "Info om appen"
+ "Sök"
+ "Röstsökning"
+ "Appar"
+ "Ta bort"
+ "Avinstallera uppdatering"
+ "Avinstallera appen"
+ "Information om appen"
+ "En app har valts"
+ "En widget har valts"
+ "En mapp har valts"
+ "En genväg har valts"
+ "installera genvägar"
+ "Tillåter att en app lägger till genvägar utan åtgärd från användaren."
+ "avinstallera genvägar"
+ "Tillåter att appen tar bort genvägar utan åtgärd från användaren."
+ "läsa inställningar och genvägar för startsidan"
+ "Tillåter att appen läser inställningar och genvägar på startsidan."
+ "skriva inställningar och genvägar för startsidan"
+ "Tillåter att appen ändrar inställningar och genvägar på startsidan."
+ "Det gick inte att läsa in widgeten"
+ "Det här är en systemapp som inte kan avinstalleras."
+ "Rocket Launcher"
+ "Namnlös mapp"
+ "Startskärmen %1$d"
+ "Sidan %1$d av %2$d"
+ "Startskärmen %1$d av %2$d"
+ "Appsida %1$d av %2$d"
+ "Widget-sida %1$d av %2$d"
+ "Välkommen!"
+ "Känn dig som hemma."
+
+
+ "Skapa fler skärmar för appar och mappar"
+ "Organisera ditt utrymme"
+ "Tryck länge på bakgrunden om du vill hantera bakgrundsbilder, widgetar och inställningar."
+ "Välj några appar"
+ "Om du vill lägga till en app på startskärmen trycker du länge på den."
+ "Det här är en mapp"
+ "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."
+ "OK"
+ "Mappen är öppen, %1$d gånger %2$d"
+ "Tryck om du vill stänga mappen"
+ "Tryck om du vill spara det nya namnet"
+ "Mappen är stängd"
+ "Mappen har bytt namn till %1$s"
+ "Mapp: %1$s"
+
+
+
+
+ "Widgetar"
+ "Bakgrunder"
+ "Inställningar"
+
diff --git a/res/values-sw-land/strings.xml b/res/values-sw-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-sw-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000000..606a80d3bf
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,126 @@
+
+
+
+
+ "Kizindua3"
+ "Mwanzo"
+ "Programu Msingi za Android"
+
+ "Weka mandhari"
+
+ "%1$d zimechaguliwa"
+ "%1$d zimechaguliwa"
+ "%1$d zimechaguliwa"
+
+ "Mandhari %1$d ya %2$d"
+ "%1$s iliyochaguliwa"
+ "Futa"
+ "Chukua picha"
+ "Mandhari"
+ "Punguza mandhari"
+ "Programu haijasakinishwa."
+ "Wijeti"
+ "Wijeti"
+ "Onyesha Kumbukumbu"
+ "Gusa na ushikilie ili kuteua wijeti."
+ "Nunua"
+ "%1$d × %2$d"
+ "Haikuweza kudondosha kipengee kwenye skrini hii ya Kwanza."
+ "Chagua wijeti ili uunde"
+ "Jina la folda"
+ "lipe folda jina jipya"
+ "SAWA"
+ "Ghairi"
+ "Ongeza kwenye skrini ya Mwanzo"
+ "Programu"
+ "Njia za mkato"
+ "Wijeti"
+ "Hakuna nafasi zaidi kwenye skrini zako za Nyumbani."
+ "Hakuna nafasi katika skrini hii ya Mwanzo."
+ "Hakuna nafasi zaidi kwenye eneo kali."
+ "Wijeti hii ni kubwa zaidi kwa eneo kali."
+ "Njia ya mkato ya \"%s\" imeundwa."
+ "Njia ya mkato ya \"%s\" iliondolewa."
+ "\"%s\" la njia ya mkato tayari lipo."
+ "Chagua njia ya mkato"
+ "Chagua programu"
+ "Programu"
+ "Mwanzo"
+ "Ondoa"
+ "Ondoa"
+ "Ondoa"
+ "Ondoa"
+ "Maelezo ya programu"
+ "Tafuta"
+ "Kutafuta kwa Kutamka"
+ "Programu"
+ "Ondoa"
+ "Ondoa sasisho"
+ "Ondoa programu"
+ "Maelezo ya programu"
+ "Programu 1 imechaguliwa"
+ "Wijeti 1 imechaguliwa"
+ "Folda 1 limechaguliwa"
+ "Njia 1 ya mkato imechaguliwa"
+ "sakinisha njia za mkato"
+ "Huruhusu programu kuongeza njia za mkato bila mtumiaji kuingilia kati."
+ "ondoa njia za mikato"
+ "Huruhusu programu kuondoa njia za mikato bila mtumiaji kuingilia kati."
+ "soma mipangilio ya Mwanzo na njia za mkato"
+ "Huruhusu programu kusoma mipangilio na njia za mikato zilizo katika skirini ya Mwanzo."
+ "andika mipangilio ya skrini ya Mwanzo na njia za mkato"
+ "Huruhusu programu kubadilisha mipangilio na njia za mkato katika skrini ya Mwanzo."
+ "Tatizo la kupakia wijeti"
+ "Hii ni programu ya mfumo na haiwezi kuondolewa."
+ "Kizinduzi cha Roketi"
+ "Folda isiyo na jina"
+ "Skrini ya mwazo %1$d"
+ "Ukurasa%1$d wa %2$d"
+
+
+
+ "Ukurasa wa programu %1$d ya %2$d"
+ "Ukurasa wa wijeti %1$d ya %2$d"
+ "Karibu!"
+ "Jisikie huru."
+
+
+ "Unda skrini zaidi za programu na folda"
+ "Panga nafasi yako"
+ "Gusa na ushikile mandharinyuma ili udhibiti mandhari, wijeti, na mipangilio."
+ "Chagua programu kadhaa"
+ "Ili kuongeza programu kwenye Skrini yako Kuu, iguse na uishikilie."
+ "Folda hii hapa"
+ "Ili kuunda kama hii, gusa na ushikilie programu, kisha ipitishe juu ya nyingine."
+ "SAWA"
+ "Folda imefunguliwa, %1$d kwa %2$d"
+ "Gusa ili ufunge folda"
+ "Gusa ili uhifadhi jina jipya"
+ "Folda imefungwa"
+ "Folda imebadilishwa jina kuwa %1$s"
+ "Folda: %1$s"
+
+
+
+
+ "Wijeti"
+ "Mandhari"
+ "Mipangilio"
+
diff --git a/res/values-sw340dp-land/dimens.xml b/res/values-sw340dp-land/dimens.xml
new file mode 100644
index 0000000000..7901dc47dd
--- /dev/null
+++ b/res/values-sw340dp-land/dimens.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ 50dp
+
diff --git a/res/values-sw340dp-port/config.xml b/res/values-sw340dp-port/config.xml
new file mode 100644
index 0000000000..d31ee59690
--- /dev/null
+++ b/res/values-sw340dp-port/config.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ false
+
diff --git a/res/values-sw340dp-port/dimens.xml b/res/values-sw340dp-port/dimens.xml
new file mode 100644
index 0000000000..e360565703
--- /dev/null
+++ b/res/values-sw340dp-port/dimens.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ 70dp
+
diff --git a/res/values-sw340dp-port/styles.xml b/res/values-sw340dp-port/styles.xml
new file mode 100644
index 0000000000..24f4ba2b18
--- /dev/null
+++ b/res/values-sw340dp-port/styles.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values-sw340dp/dimens.xml b/res/values-sw340dp/dimens.xml
new file mode 100644
index 0000000000..69d6e58a3b
--- /dev/null
+++ b/res/values-sw340dp/dimens.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ 20dp
+
diff --git a/res/values-sw600dp-land/dimens.xml b/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000000..7f5594de3f
--- /dev/null
+++ b/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ 36dp
+ 8dp
+ 20dp
+ 14dp
+
+
+ 12dip
+ 20dip
+
diff --git a/res/values-sw600dp/config.xml b/res/values-sw600dp/config.xml
new file mode 100644
index 0000000000..2ec2f146c2
--- /dev/null
+++ b/res/values-sw600dp/config.xml
@@ -0,0 +1,14 @@
+
+ true
+ true
+
+
+ true
+
+
+ -1000
+
+
+ 14000
+
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000000..8d6c7f4e32
--- /dev/null
+++ b/res/values-sw600dp/dimens.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ 64dp
+
+
+ 60dp
+ 8dp
+ 8dp
+ @dimen/app_widget_preview_padding_left
+ @dimen/app_widget_preview_padding_right
+
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000000..375430417d
--- /dev/null
+++ b/res/values-sw600dp/styles.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000000..eb8f83c43d
--- /dev/null
+++ b/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 4
+ 2
+ 4
+ 2
+
+
+ 50dp
+
+
+ 100dip
+
+
+
+ 140dp
+ 80dp
+
diff --git a/res/values-sw720dp-port/dimens.xml b/res/values-sw720dp-port/dimens.xml
new file mode 100644
index 0000000000..62bdaaa97a
--- /dev/null
+++ b/res/values-sw720dp-port/dimens.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ 40dp
+ 24dp
+
+ 2
+ 2
+
+
+
+ 80dp
+ 160dp
+
diff --git a/res/values-sw720dp-port/styles.xml b/res/values-sw720dp-port/styles.xml
new file mode 100644
index 0000000000..57f07acaf7
--- /dev/null
+++ b/res/values-sw720dp-port/styles.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/res/values-sw720dp/config.xml b/res/values-sw720dp/config.xml
new file mode 100644
index 0000000000..4f537a9f40
--- /dev/null
+++ b/res/values-sw720dp/config.xml
@@ -0,0 +1,20 @@
+
+ true
+ true
+
+
+
+ 90
+
+
+
+ false
+
+ true
+
+
+ 18000
+
+
+ false
+
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000000..01227e1470
--- /dev/null
+++ b/res/values-sw720dp/dimens.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ 72dp
+
+
+ 8dip
+ 8dip
+
+
+ 196dp
+ 140dp
+
+
+ 0dp
+ 0dp
+
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
new file mode 100644
index 0000000000..7269e8db13
--- /dev/null
+++ b/res/values-sw720dp/styles.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values-th-land/strings.xml b/res/values-th-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-th-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000000..68118a79d1
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "หน้าแรก"
+ "แอปหลักของแอนดรอยด์"
+
+ "ตั้งค่าวอลเปเปอร์"
+
+ "เลือกไว้ %1$d"
+ "เลือกไว้ %1$d"
+ "เลือกไว้ %1$d"
+
+ "วอลเปเปอร์ %1$d จาก %2$d"
+ "เลือก %1$s แล้ว"
+ "ลบ"
+ "เลือกภาพ"
+ "วอลเปเปอร์"
+ "ครอบตัดวอลล์เปเปอร์"
+ "ไม่ได้ติดตั้งแอป"
+ "วิดเจ็ต"
+ "วิดเจ็ต"
+ "แสดง Mem"
+ "แตะค้างเพื่อรับวิดเจ็ต"
+ "เลือกซื้อ"
+ "%1$d × %2$d"
+ "ไม่สามารถวางรายการลงในหน้าจอหลักนี้"
+ "เลือกวิดเจ็ตที่จะสร้าง"
+ "ชื่อโฟลเดอร์"
+ "เปลี่ยนชื่อโฟลเดอร์"
+ "ตกลง"
+ "ยกเลิก"
+ "เพิ่มลงในหน้าแรก"
+ "แอป"
+ "ทางลัด"
+ "วิดเจ็ต"
+ "ไม่มีที่ว่างในหน้าจอหลักของคุณ"
+ "ไม่มีที่ว่างในหน้าจอหลักนี้"
+ "ไม่มีที่ว่างใน hotseat"
+ "วิดเจ็ตนี้มีขนาดใหญ่เกินไปสำหรับ hotseat"
+ "สร้างทางลัด \"%s\" แล้ว"
+ "นำทางลัด \"%s\" ออกแล้ว"
+ "มีทางลัด \"%s\" อยู่แล้ว"
+ "เลือกทางลัด"
+ "เลือกแอป"
+ "แอป"
+ "หน้าแรก"
+ "ลบ"
+ "ถอนการติดตั้ง"
+ "ลบ"
+ "ถอนการติดตั้ง"
+ "ข้อมูลแอป"
+ "ค้นหา"
+ "ค้นหาด้วยเสียง"
+ "แอป"
+ "ลบ"
+ "ถอนการติดตั้งการอัปเดต"
+ "ถอนการติดตั้งแอป"
+ "รายละเอียดแอป"
+ "เลือกไว้ 1 แอป"
+ "เลือกไว้ 1 วิดเจ็ต"
+ "เลือกไว้ 1 โฟลเดอร์"
+ "เลือกไว้ 1 ทางลัด"
+ "ติดตั้งทางลัด"
+ "อนุญาตให้แอปเพิ่มทางลัดโดยไม่ต้องให้ผู้ใช้จัดการ"
+ "ถอนการติดตั้งทางลัด"
+ "อนุญาตให้แอปนำทางลัดออกโดยไม่ต้องให้ผู้ใช้จัดการ"
+ "อ่านการตั้งค่าและทางลัดหน้าแรกแล้ว"
+ "อนุญาตให้แอปอ่านการตั้งค่าและทางลัดในหน้าแรก"
+ "เขียนการตั้งค่าและทางลัดหน้าแรกแล้ว"
+ "อนุญาตให้แอปเปลี่ยนการตั้งค่าและทางลัดในหน้าแรก"
+ "มีปัญหาขณะโหลดวิดเจ็ต"
+ "นี่เป็นแอประบบและไม่สามารถถอนการติดตั้งได้"
+ "Rocket Launcher"
+ "โฟลเดอร์ที่ไม่มีชื่อ"
+ "หน้าจอหลัก %1$d"
+ "หน้า %1$d จาก %2$d"
+ "หน้าจอหลัก %1$d จาก %2$d"
+ "แอปหน้า %1$d จาก %2$d"
+ "วิดเจ็ตหน้า %1$d จาก %2$d"
+ "ยินดีต้อนรับ!"
+ "ทำตัวตามสบาย"
+
+
+ "สร้างหน้าจอเพิ่มสำหรับแอปและโฟลเดอร์"
+ "จัดระเบียบพื้นที่ของคุณ"
+ "แตะพื้นหลังค้างไว้เพื่อจัดการวอลเปเปอร์ วิดเจ็ต และการตั้งค่า"
+ "เลือกบางแอป"
+ "หากต้องการเพิ่มแอปลงในหน้าจอหลัก ให้แตะแอปค้างไว้"
+ "นี่คือโฟลเดอร์"
+ "หากต้องการสร้างโฟลเดอร์ลักษณะนี้ แตะแอปค้างไว้ แล้วย้ายไปทับอีกแอปหนึ่ง"
+ "ตกลง"
+ "เปิดโฟลเดอร์ %1$d x %2$d"
+ "แตะเพื่อปิดโฟลเดอร์"
+ "แตะเพื่อบันทึกการเปลี่ยนชื่อ"
+ "โฟลเดอร์ปิดอยู่"
+ "เปลี่ยนชื่อโฟลเดอร์เป็น %1$s"
+ "โฟลเดอร์: %1$s"
+
+
+
+
+ "วิดเจ็ต"
+ "วอลเปเปอร์"
+ "การตั้งค่า"
+
diff --git a/res/values-tl-land/strings.xml b/res/values-tl-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-tl-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000000..56801b4551
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Home"
+ "Android Core Apps"
+
+ "Itakda ang wallpaper"
+
+ "%1$d ang napili"
+ "%1$d ang napili"
+ "%1$d ang napili"
+
+ "Wallpaper %1$d ng %2$d"
+ "Napili ang %1$s"
+ "Tanggalin"
+ "Pumili ng larawan"
+ "Mga Wallpaper"
+ "I-crop ang wallpaper"
+ "Hindi naka-install ang app."
+ "Mga Widget"
+ "Mga Widget"
+ "Ipakita ang Mem"
+ "Pindutin nang matagal upang kumuha ng widget."
+ "Mamili"
+ "%1$d × %2$d"
+ "Hindi ma-drop ang item sa Home screen na ito."
+ "Pumili ng widget na gagawin"
+ "Pangalan ng folder"
+ "Palitan ang pangalan ng folder"
+ "OK"
+ "Kanselahin"
+ "Idagdag sa Home screen"
+ "Apps"
+ "Mga Shortcut"
+ "Mga Widget"
+ "Wala nang lugar sa iyong mga Home screen."
+ "Wala nang lugar sa Home screen na ito."
+ "Wala nang lugar sa hotseat."
+ "Masyadong malaki ang widget na ito para sa hotseat."
+ "Nagawa ang shortcut na \"%s.\""
+ "Inalis ang shortcut na \"%s.\""
+ "Umiiral na ang shortcut na \"%s.\""
+ "Pumili ng shortcut"
+ "Pumili ng app"
+ "Apps"
+ "Home"
+ "Alisin"
+ "I-uninstall"
+ "Alisin"
+ "I-uninstall"
+ "Impormasyon ng app"
+ "Hanapin"
+ "Paghahanap Gamit ang Boses"
+ "Apps"
+ "Alisin"
+ "I-uninstall ang update"
+ "I-uninstall ang app"
+ "Mga detalye ng app"
+ "1 app ang napili"
+ "1 widget ang napili"
+ "1 folder ang napili"
+ "1 shortcut ang napili"
+ "i-install ang mga shortcut"
+ "Pinapayagan ang isang app na magdagdag ng mga shortcut nang walang panghihimasok ng user."
+ "i-uninstall ang mga shortcut"
+ "Pinapayagan ang app na mag-alis ng mga shortcut nang walang panghihimasok ng user."
+ "basahin ang mga setting at shortcut ng Home"
+ "Pinapayagan ang app na basahin ang mga setting at shortcut sa Home."
+ "magsulat ng mga setting at shortcut ng Home"
+ "Pinapayagan ang app na baguhin ang mga setting at shortcut sa Home."
+ "Problema sa pag-load ng widget"
+ "Isa itong app ng system at hindi maaaring i-uninstall."
+ "Rocket Launcher"
+ "Walang Pangalang Folder"
+ "Home screen %1$d"
+ "Pahina %1$d ng %2$d"
+ "Home screen %1$d ng %2$d"
+ "Pahina ng apps %1$d ng %2$d"
+ "Pahina ng widget %1$d ng %2$d"
+ "Maligayang pagdating!"
+ "Gawing kumportable ang iyong sarili."
+
+
+ "Gumawa ng higit pang mga screen para sa apps at mga folder"
+ "Ayusin ang iyong espasyo"
+ "Pindutin nang matagal ang background upang pamahalaan ang wallpaper, mga widget at setting"
+ "Pumili ng ilang apps"
+ "Upang magdagdag ng app sa iyong Home screen, pindutin ito nang matagal."
+ "Narito ang isang folder"
+ "Upang gumawa ng katulad nito, pindutin nang matagal ang isang app, pagkatapos ay ilipat ito sa isa pang folder."
+ "OK"
+ "Binuksan ang folder, %1$d by %2$d"
+ "Pindutin upang isara ang folder"
+ "Pindutin upang i-save ang pagpapalit ng pangalan"
+ "Nakasara ang folder"
+ "Pinalitan ang pangalan ng folder ng %1$s"
+ "Folder: %1$s"
+
+
+
+
+ "Mga Widget"
+ "Mga Wallpaper"
+ "Mga Setting"
+
diff --git a/res/values-tr-land/strings.xml b/res/values-tr-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-tr-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000000..dbbdee2d4c
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Ana ekran"
+ "Android Çekirdek Uygulamaları"
+
+ "Duvar kağıdını ayarla"
+
+ "%1$d tane seçildi"
+ "%1$d tane seçildi"
+ "%1$d tane seçildi"
+
+ "Duvar kağıdı %1$d / %2$d"
+ "%1$s seçildi"
+ "Sil"
+ "Resim seç"
+ "Duvar Kağıtları"
+ "Duvar kağıdını kırp"
+ "Uygulama yüklü değil."
+ "Widget\'lar"
+ "Widget\'lar"
+ "Belleği Göster"
+ "Widget seçmek için dokunun ve basılı tutun."
+ "Alışveriş yap"
+ "%1$d × %2$d"
+ "Öğe bu Ana ekrana bırakılamadı."
+ "Oluşturmak için widget seçin"
+ "Klasör adı"
+ "Klasörü yeniden adlandırın"
+ "Tamam"
+ "İptal"
+ "Ana ekrana ekleyin"
+ "Uygulamalar"
+ "Kısayollar"
+ "Widget\'lar"
+ "Ana ekranlarınızda yer kalmadı."
+ "Bu Ana ekranda yer kalmadı."
+ "Favori kısa yollarda yer yok"
+ "Bu widget, favori kısa yollar için çok büyük."
+ "\"%s\" kısayolu oluşturuldu."
+ "\"%s\" kısayolu kaldırıldı."
+ "\"%s\" kısayolu zaten var."
+ "Kısayolu seçin"
+ "Uygulama seçin"
+ "Uygulamalar"
+ "Ana ekran"
+ "Kaldır"
+ "Yüklemeyi kaldır"
+ "Kaldır"
+ "Yüklemeyi kaldır"
+ "Uygulama bilgileri"
+ "Ara"
+ "Sesli Arama"
+ "Uygulamalar"
+ "Kaldır"
+ "Güncellemeyi kaldır"
+ "Uygulamanın yüklemesini kaldır"
+ "Uygulama ayrıntıları"
+ "1 uygulama seçildi"
+ "1 widget seçildi"
+ "1 klasör seçildi"
+ "1 kısayol seçildi"
+ "kısayolları yükle"
+ "Uygulamaya, kullanıcı müdahalesi olmadan kısayol ekleme izni verir."
+ "kısayolların yüklemesini kaldır"
+ "Uygulamaya, kullanıcı müdahalesi olmadan kısayolları kaldırma izni verir."
+ "Ana ekran ayarlarını ve kısayollarını oku"
+ "Uygulamaya, Ana ekrandaki ayarları ve kısayolları okuma izni verir."
+ "Ana ekran ayarlarını ve kısayollarını yaz"
+ "Uygulamaya, Ana ekrandaki ayarları ve kısayolları değiştirme izni verir."
+ "Widget yüklenirken sorun oluştu"
+ "Bu bir sistem uygulamasıdır ve yüklemesi kaldırılamaz."
+ "Roket Fırlatıcı"
+ "Adsız Klasör"
+ "Ana ekran %1$d"
+ "Sayfa %1$d / %2$d"
+ "Ana ekran %1$d / %2$d"
+ "Uygulama sayfası %1$d / %2$d"
+ "Widget sayfası %1$d / %2$d"
+ "Hoş geldiniz!"
+ "Rahatınıza bakın."
+
+
+ "Uygulamalar ve klasörler için daha fazla ekran oluşturun"
+ "Alanınızı düzenleyin"
+ "Duvar kağıdını, widget\'ları ve ayarları yönetmek için arka plana uzun basın."
+ "İstediğiniz uygulamaları seçin"
+ "Bir uygulamayı Ana ekranınıza eklemek için, ilgili uygulamayı basılı tutun."
+ "İşte bir klasör"
+ "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."
+ "Tamam"
+ "Klasör açıldı, %1$d x %2$d"
+ "Klasörü kapatmak için dokunun"
+ "Yeni adı kaydetmek için dokunun"
+ "Klasör kapatıldı"
+ "Klasörün adı %1$s olarak değiştirildi"
+ "Klasör: %1$s"
+
+
+
+
+ "Widget\'lar"
+ "Duvar Kağıtları"
+ "Ayarlar"
+
diff --git a/res/values-uk-land/strings.xml b/res/values-uk-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-uk-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000000..e0f3211bdb
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Головний екран"
+ "Базові програми Android"
+
+ "Установити фон"
+
+ "Вибрано %1$d"
+ "Вибрано %1$d"
+ "Вибрано %1$d"
+
+ "Фоновий малюнок %1$d з %2$d"
+ "Вибрано %1$s"
+ "Видалити"
+ "Вибрати зображення"
+ "Фонові малюнки"
+ "Обрізати фоновий малюнок"
+ "Програму не встановлено."
+ "Віджети"
+ "Віджети"
+ "Показати пам’ять"
+ "Натисніть і утримуйте, щоб вибрати віджет."
+ "Магазин"
+ "%1$d × %2$d"
+ "Не вдалося додати елемент на цей головний екран."
+ "Вибрати віджет для створення"
+ "Назва папки"
+ "Перейменувати папку"
+ "OК"
+ "Скасувати"
+ "Додати на головний екран"
+ "Програми"
+ "Ярлики"
+ "Віджети"
+ "На головних екранах більше немає місця."
+ "На цьому головному екрані більше немає місця."
+ "Немає вільного місця."
+ "Цей віджет завеликий."
+ "Ярлик \"%s\" створено."
+ "Ярлик \"%s\" вилучено."
+ "Ярлик \"%s\" уже існує."
+ "Вибрати ярлик"
+ "Вибрати програму"
+ "Програми"
+ "Головний екран"
+ "Вилучити"
+ "Видалити"
+ "Вилучити"
+ "Видалити"
+ "Про програму"
+ "Пошук"
+ "Голосовий пошук"
+ "Програми"
+ "Вилучити"
+ "Видалити оновлення"
+ "Видалити програму"
+ "Відомості про програму"
+ "Вибрано 1 програму"
+ "Вибрано 1 віджет"
+ "Вибрано 1 папку"
+ "Вибрано 1 ярлик"
+ "установлювати ярлики"
+ "Дозволяє програмі самостійно додавати ярлики."
+ "видаляти ярлики"
+ "Дозволяє програмі самостійно вилучати ярлики."
+ "читати налаштування та ярлики головного екрана"
+ "Дозволяє програмі читати налаштування та ярлики на головному екрані."
+ "записувати налаштування та ярлики головного екрана"
+ "Дозволяє програмі змінювати налаштування та ярлики на головному екрані."
+ "Проблема із завантаженням віджета"
+ "Це системна програма, її неможливо видалити."
+ "Rocket Launcher"
+ "Папка без назви"
+ "Головний екран %1$d"
+ "Сторінка %1$d з %2$d"
+ "Головний екран %1$d з %2$d"
+ "Сторінка програм %1$d з %2$d"
+ "Сторінка віджетів %1$d з %2$d"
+ "Вітаємо!"
+ "Будьте як удома."
+
+
+ "Створюйте нові екрани для програм і папок"
+ "Організуйте робочий простір"
+ "Натисніть і утримуйте фон, щоб керувати фоновим малюнком, віджетами та налаштуваннями."
+ "Виберіть програми"
+ "Щоб додати програму на головний екран, торкніться й утримуйте її."
+ "Це папка"
+ "Щоб створити папку, натисніть і утримуйте програму, а потім перетягніть її на іншу."
+ "OК"
+ "Папку відкрито (%1$d х %2$d)"
+ "Торкніться, щоб закрити папку"
+ "Торкніться, щоб зберегти нову назву"
+ "Папку закрито"
+ "Папку перейменовано на %1$s"
+ "Папка %1$s"
+
+
+
+
+ "Віджети"
+ "Фонові малюнки"
+ "Налаштування"
+
diff --git a/res/values-vi-land/strings.xml b/res/values-vi-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-vi-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000000..3b081d0b1d
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "Màn hình chính"
+ "Ứng dụng lõi Android"
+
+ "Đặt hình nền"
+
+ "Đã chọn %1$d"
+ "Đã chọn %1$d"
+ "Đã chọn %1$d"
+
+ "Hình nền %1$d / %2$d"
+ "%1$s được chọn"
+ "Xóa"
+ "Chọn hình ảnh"
+ "Hình nền"
+ "Cắt hình nền"
+ "Ứng dụng chưa được cài đặt."
+ "Tiện ích con"
+ "Tiện ích con"
+ "Hiển thị bộ nhớ"
+ "Chạm và giữ để chọn tiện ích con."
+ "Mua"
+ "%1$d × %2$d"
+ "Không thể thả mục vào Màn hình chính này."
+ "Chọn tiện ích con để tạo"
+ "Tên thư mục"
+ "Đổi tên thư mục"
+ "OK"
+ "Hủy"
+ "Thêm vào Màn hình chính"
+ "Ứng dụng"
+ "Lối tắt"
+ "Tiện ích con"
+ "Không còn chỗ trên Màn hình chính của bạn."
+ "Không còn chỗ trên Màn hình chính này."
+ "Không còn chỗ trên vùng gắn."
+ "Tiện ích con này quá lớn cho vùng gắn."
+ "Lối tắt \"%s\" đã được tạo."
+ "Lối tắt \"%s\" đã bị xóa."
+ "Lối tắt \"%s\" đã tồn tại."
+ "Chọn lối tắt"
+ "Chọn ứng dụng"
+ "Ứng dụng"
+ "Màn hình chính"
+ "Xóa"
+ "Gỡ cài đặt"
+ "Xóa"
+ "Gỡ cài đặt"
+ "Thông tin ứng dụng"
+ "Tìm kiếm"
+ "Tìm kiếm bằng giọng nói"
+ "Ứng dụng"
+ "Xóa"
+ "Gỡ cài đặt cập nhật"
+ "Gỡ cài đặt ứng dụng"
+ "Thông tin chi tiết về ứng dụng"
+ "Đã chọn 1 ứng dụng"
+ "Đã chọn 1 tiện ích con"
+ "Đã chọn 1 thư mục"
+ "Đã chọn 1 lối tắt"
+ "cài đặt lối tắt"
+ "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."
+ "gỡ cài đặt lối tắt"
+ "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."
+ "đọc cài đặt và lối tắt trên Màn hình chính"
+ "Cho phép ứng dụng đọc cài đặt và lối tắt trên Màn hình chính."
+ "ghi cài đặt và lối tắt trên Màn hình chính"
+ "Cho phép ứng dụng thay đổi cài đặt và lối tắt trên Màn hình chính."
+ "Sự cố khi tải tiện ích con"
+ "Đây là ứng dụng hệ thống và không thể gỡ cài đặt."
+ "Rocket Launcher"
+ "Thư mục chưa đặt tên"
+ "Màn hình chính %1$d"
+ "Trang %1$d / %2$d"
+ "Màn hình chính %1$d / %2$d"
+ "Trang ứng dụng %1$d / %2$d"
+ "Trang tiện ích con %1$d / %2$d"
+ "Xin chào!"
+ "Tự nhiên như ở nhà."
+
+
+ "Tạo thêm màn hình cho ứng dụng và thư mục"
+ "Sắp xếp không gian của bạn"
+ "Chạm và giữ nền để quản lý hình nền, tiện ích con và cài đặt."
+ "Chọn một số ứng dụng"
+ "Để thêm ứng dụng vào Màn hình chính của bạn, chạm và giữ ứng dụng đó."
+ "Đây là một thư mục"
+ "Để 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."
+ "OK"
+ "Đã mở thư mục, %1$d x %2$d"
+ "Chạm để đóng thư mục"
+ "Chạm để lưu tên mới"
+ "Đã đóng thư mục"
+ "Đã đổi tên thư mục thành %1$s"
+ "Thư mục: %1$s"
+
+
+
+
+ "Tiện ích con"
+ "Hình nền"
+ "Cài đặt"
+
diff --git a/res/values-zh-rCN-land/strings.xml b/res/values-zh-rCN-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-zh-rCN-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000000..43a57c44f5
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "主屏"
+ "Android 核心应用"
+
+ "设置壁纸"
+
+ "已选择%1$d项"
+ "已选择%1$d项"
+ "已选择%1$d项"
+
+ "第%1$d张壁纸,共%2$d张"
+ "已选择%1$s"
+ "删除"
+ "选择图片"
+ "壁纸"
+ "剪裁壁纸"
+ "未安装该应用。"
+ "小部件"
+ "小部件"
+ "显示内存空间"
+ "触摸并按住小部件即可选择。"
+ "商店"
+ "%1$d × %2$d"
+ "无法将相关内容拖放到此主屏幕上。"
+ "选择要创建的小部件"
+ "文件夹名称"
+ "重命名文件夹"
+ "确定"
+ "取消"
+ "添加到主屏幕"
+ "应用"
+ "快捷方式"
+ "小部件"
+ "您的主屏幕上没有空间了。"
+ "此主屏幕上已没有空间。"
+ "底部区域已无空间。"
+ "该小部件太大,底部区域容纳不下。"
+ "已创建“%s”快捷方式。"
+ "已删除“%s”快捷方式。"
+ "“%s”快捷方式已存在。"
+ "选择快捷方式"
+ "选择应用"
+ "应用"
+ "主屏幕"
+ "删除"
+ "卸载"
+ "删除"
+ "卸载"
+ "应用信息"
+ "搜索"
+ "语音搜索"
+ "应用"
+ "删除"
+ "卸载更新内容"
+ "卸载应用"
+ "应用详情"
+ "已选择1个应用"
+ "已选择1个小部件"
+ "已选择1个文件夹"
+ "已选择1个快捷方式"
+ "安装快捷方式"
+ "允许应用自行添加快捷方式。"
+ "卸载快捷方式"
+ "允许应用自行删除快捷方式。"
+ "读取主屏幕设置和快捷方式"
+ "允许应用读取主屏幕中的设置和快捷方式。"
+ "写入主屏幕设置和快捷方式"
+ "允许应用更改主屏幕中的设置和快捷方式。"
+ "加载小部件时出现问题"
+ "这是系统应用,无法卸载。"
+ "火箭发射器"
+ "未命名文件夹"
+ "主屏幕%1$d"
+ "第%1$d页,共%2$d页"
+ "主屏幕:第%1$d屏,共%2$d屏"
+ "应用:第%1$d页,共%2$d页"
+ "小部件:第%1$d页,共%2$d页"
+ "欢迎!"
+ "您的主屏幕您做主。"
+
+
+ "添加更多屏幕来容纳应用和文件夹"
+ "整理您的空间"
+ "触摸并按住背景,即可管理壁纸、小部件和设置。"
+ "选择一些应用"
+ "要将应用添加到主屏幕,请触摸并按住该应用。"
+ "这是一个文件夹"
+ "要创建一个类似的文件夹,请触摸并按住某个应用,然后将其移至另一个应用上。"
+ "确定"
+ "文件夹已打开,大小为%1$d×%2$d"
+ "触摸可关闭文件夹"
+ "触摸可保存新名称"
+ "文件夹已关闭"
+ "已将文件夹重命名为“%1$s”"
+ "文件夹:%1$s"
+
+
+
+
+ "小部件"
+ "壁纸"
+ "设置"
+
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000000..eb3def434e
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "主畫面"
+ "Android 核心應用程式"
+
+ "設定桌布"
+
+ "已選取 %1$d 個"
+ "已選取 %1$d 個"
+ "已選取 %1$d 個"
+
+ "第 %1$d 張桌布,共 %2$d 張"
+ "已選取「%1$s」"
+ "刪除"
+ "選擇圖片"
+ "桌布"
+ "裁剪桌布"
+ "尚未安裝應用程式。"
+ "小工具"
+ "小工具"
+ "顯示記憶體"
+ "輕觸並按住小工具即可選取。"
+ "商店"
+ "%1$d × %2$d"
+ "無法將項目拖放至主畫面。"
+ "選擇要建立的小工具"
+ "資料夾名稱"
+ "重新命名資料夾"
+ "確定"
+ "取消"
+ "新增至主畫面"
+ "應用程式"
+ "捷徑"
+ "小工具"
+ "主畫面已無空間。"
+ "主畫面已無空間。"
+ "停駐區已無可用空間。"
+ "這個小工具過大,停駐區沒有足夠空間。"
+ "已建立「%s」捷徑。"
+ "已移除「%s」捷徑。"
+ "「%s」捷徑已存在。"
+ "選擇捷徑"
+ "選擇應用程式"
+ "應用程式"
+ "主畫面"
+ "移除"
+ "解除安裝"
+ "移除"
+ "解除安裝"
+ "應用程式資料"
+ "搜尋"
+ "語音搜尋"
+ "應用程式"
+ "移除"
+ "解除安裝更新"
+ "解除安裝應用程式"
+ "應用程式詳細資料"
+ "已選取 1 個應用程式"
+ "已選取 1 個小工具"
+ "已選取 1 個資料夾"
+ "已選取 1 個捷徑"
+ "安裝捷徑"
+ "允許應用程式無需用戶許可也可新增捷徑。"
+ "解除安裝捷徑"
+ "允許應用程式無需用戶許可也可移除捷徑。"
+ "讀取主畫面的設定和捷徑"
+ "允許應用程式讀取主畫面中的設定和捷徑。"
+ "寫入主畫面的設定和捷徑"
+ "允許應用程式更改主畫面中的設定和捷徑。"
+ "載入小工具時發生問題"
+ "這是系統應用程式,無法將其解除安裝。"
+ "Rocket Launcher"
+ "未命名的資料夾"
+ "主畫面 %1$d"
+ "第 %1$d 頁,共 %2$d 頁"
+ "主畫面 %1$d,共 %2$d 個"
+ "第 %1$d 個應用程式頁面,共 %2$d 頁"
+ "第 %1$d 個小工具頁面,共 %2$d 頁"
+ "歡迎!"
+ "自訂主畫面。"
+
+
+ "建立更多應用程式和資料夾的畫面"
+ "管理您的空間"
+ "輕觸並按住背景,即可管理桌布、小工具和設定。"
+ "選擇一些應用程式"
+ "輕觸並按住應用程式,即可加到主畫面。"
+ "資料夾顯示如下"
+ "如要建立類似的資料夾,請輕觸並按住某個應用程式,然後疊到另一個應用程式之上。"
+ "確定"
+ "資料夾已開啟 (%1$d x %2$d)"
+ "輕觸即可關閉資料夾"
+ "輕觸即可儲存新改的名稱"
+ "已關閉資料夾"
+ "資料夾已重新命名為「%1$s」"
+ "資料夾:%1$s"
+
+
+
+
+ "小工具"
+ "桌布"
+ "設定"
+
diff --git a/res/values-zh-rTW-land/strings.xml b/res/values-zh-rTW-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-zh-rTW-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000..a68f16319f
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Launcher3"
+ "主螢幕"
+ "Android 核心應用程式"
+
+ "設定桌布"
+
+ "已選取 %1$d 個項目"
+ "已選取 %1$d 個項目"
+ "已選取 %1$d 個項目"
+
+ "第 %1$d 張桌布,共 %2$d 張"
+ "已選取%1$s"
+ "刪除"
+ "選擇圖片"
+ "桌布"
+ "裁剪桌布"
+ "應用程式未安裝。"
+ "小工具"
+ "小工具"
+ "顯示記憶體"
+ "輕觸並按住小工具即可選取。"
+ "購物"
+ "%1$d × %2$d"
+ "無法將項目拖放至這個主螢幕上。"
+ "選擇要建立的小工具"
+ "資料夾名稱"
+ "重新命名資料夾"
+ "確定"
+ "取消"
+ "新增至主螢幕"
+ "應用程式"
+ "捷徑"
+ "小工具"
+ "主螢幕已無空間。"
+ "這個主螢幕已無空間。"
+ "停駐區已無空間。"
+ "這個小工具過大,停駐區無法容納。"
+ "已建立「%s」捷徑。"
+ "已移除「%s」捷徑。"
+ "「%s」捷徑已存在。"
+ "選擇捷徑"
+ "選擇應用程式"
+ "應用程式"
+ "主螢幕"
+ "移除"
+ "解除安裝"
+ "移除"
+ "解除安裝"
+ "應用程式資訊"
+ "搜尋"
+ "語音搜尋"
+ "應用程式"
+ "移除"
+ "解除安裝更新"
+ "解除安裝應用程式"
+ "應用程式詳細資料"
+ "已選取 1 個應用程式"
+ "已選取 1 個小工具"
+ "已選取 1 個資料夾"
+ "已選取 1 個捷徑"
+ "安裝捷徑"
+ "允許應用程式自動新增捷徑。"
+ "解除安裝捷徑"
+ "允許應用程式自動移除捷徑。"
+ "讀取主螢幕的設定和捷徑"
+ "允許應用程式讀取主螢幕中的設定和捷徑。"
+ "寫入主螢幕設定和捷徑"
+ "允許應用程式變更主螢幕中的設定和捷徑。"
+ "載入小工具時發生問題"
+ "這是系統應用程式,不可解除安裝。"
+ "Rocket Launcher"
+ "未命名的資料夾"
+ "主螢幕 %1$d"
+ "第 %1$d 頁,共 %2$d 頁"
+ "主螢幕:第 %1$d 頁,共 %2$d 頁"
+ "應用程式:第 %1$d 頁,共 %2$d 頁"
+ "小工具:第 %1$d 頁,共 %2$d 頁"
+ "歡迎使用!"
+ "主螢幕由您作主。"
+
+
+ "建立更多畫面容納應用程式和資料夾"
+ "管理您的空間"
+ "輕觸並按住背景,即可管理桌布、小工具和設定。"
+ "選擇一些應用程式"
+ "如要將應用程式新增至主螢幕,請輕觸並按住目標。"
+ "資料夾顯示如下"
+ "如要建立類似的資料夾,請輕觸並按住應用程式,然後將應用程式疊放在另一個應用程式上。"
+ "確定"
+ "資料夾已開啟 (%1$d x %2$d)"
+ "輕觸即可關閉資料夾"
+ "輕觸即可儲存新名稱"
+ "資料夾已關閉"
+ "已將資料夾重新命名為「%1$s」"
+ "資料夾:%1$s"
+
+
+
+
+ "小工具"
+ "桌布"
+ "設定"
+
diff --git a/res/values-zu-land/strings.xml b/res/values-zu-land/strings.xml
new file mode 100644
index 0000000000..b976926f00
--- /dev/null
+++ b/res/values-zu-land/strings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000000..da790e6828
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ "Isiqalisi3"
+ "Ikhaya"
+ "Izinhlelo zokusebenza ze-Android Core"
+
+ "Setha isithombe sangemuva"
+
+ "%1$d khethiwe"
+ "%1$d khethiwe"
+ "%1$d khethiwe"
+
+ "Isithombe sangemuva se-%1$d of %2$d"
+ "I-%1$s ekhethiwe"
+ "Susa"
+ "Thatha isithombe"
+ "Izithombe zangemuva"
+ "Nqampuna isithombe sangemuva"
+ "Uhlelo lokusebenza alufakiwe."
+ "Amawijethi"
+ "Amawijethi"
+ "Bonisa i-Mem"
+ "Thinta uphinde ubambe ukuze uphakamise iwijethi."
+ "Thenga"
+ "%1$d × %2$d"
+ "Ayikwazanga ukwehlisela into kulesi sikrini se-Ikhaya."
+ "Khetha iwijethi ongayidala"
+ "Igama lefolda"
+ "Qamba kabusha ifolda"
+ "KULUNGILE"
+ "Khansela"
+ "Faka kwisikrini saseKhaya"
+ "Izinhlelo zokusebenza"
+ "Izinqamuleli"
+ "Amawijethi"
+ "Akusenagumbi ezikrinini zakho Zekhaya."
+ "Asisekho isikhala kulesi sikrini Sasekhaya."
+ "Akusenagumbi ku-hotseat."
+ "Le wijethi inkulu kakhulu ukuba ku-hotseat."
+ "Isinqamuleli esithi \"%s\" sidaliwe."
+ "Isinqamuleli esithi \"%s\" sisusiwe."
+ "Isinqamuleli esithi \"%s\" sesivele sikhona."
+ "Khetha isinqamulelo"
+ "Khetha uhlelo lokusebenza"
+ "Izinhlelo zokusebenza"
+ "Ikhaya"
+ "Susa"
+ "Khipha"
+ "Susa"
+ "Khipha"
+ "Ulwazi lohlelo lokusebenza"
+ "Sesha"
+ "Ukusesha ngezwi"
+ "Izinhlelo zokusebenza"
+ "Susa"
+ "Khipha isibuyekezo"
+ "Khipha uhlelo lokusebenza"
+ "Imininingwane yohlelo lokusebenza"
+ "1 uhlelo lokusebenza olukhethiwe"
+ "1 iwijethi ekhethiwe"
+ "1 ifolda ekhethiwe"
+ "1 isinqamuleli esikhethiwe"
+ "faka izinqamuleli"
+ "Ivumela uhlelo lokusebenza ukufaka izinqamuleli ngaphandle kokungenelela komsebenzisi."
+ "khipha izinqamuleli"
+ "Ivumela uhlelo lokusebenza ukuthi lisuse izinqamuleli ngaphandle kokungenelela komsebenzisi."
+ "funda izilungiselelo zokuthi Ikhaya nezinqamuleli"
+ "Ivumela uhlelo lokusebenza ukuthi lifunde izilungiselelo nezinqamuleli ekhaya."
+ "bhala izilungiselelo zokuthi Ikhaya nezinqamuleli"
+ "Ivumela uhlelo lokusebenza ukuthi lushintshe izilungiselelo nezinqamuleli Ekhaya."
+ "Inkinga yokulayisha iwijethi"
+ "Lolu uhlelo lokusebenza lwesistimu futhi alikwazi ukukhishwa."
+ "Isiqalisi se-Rocket"
+ "Ifolda engenagama"
+ "Isikrini sasekhaya esingu-%1$d"
+ "Ikhasi elingu-%1$d kwangu-%2$d"
+ "Isikrini sasekhaya esingu-%1$d se-%2$d"
+ "Ikhasi lezinhlelo zokusebenza elingu-%1$d le-%2$d"
+ "Ikhasi lamawijethi elingu-%1$d le-%2$d"
+ "Siyakwamukela!"
+ "Zizwe usekhaya."
+
+
+ "Dala izikrini eziningi zezinhlelo zokusebenza namafolda"
+ "Hlela isikhala sakho"
+ "Thinta uphinde ubambe okungemuva ukuze uphathe isithombe sangemuva, amawijethi nezilungiselelo."
+ "Khetha izinhlelo zokusebenza ezithile"
+ "Ukuze ungeze uhlelo lokusebenza kusikrini sakho se-Ikhaya, thinta futhi uyibambe."
+ "Nayi ifolda"
+ "Ukuze udale eyodwa efana nale, thinta uphinde ubambe uhlelo lokusebenza, bese ulidlulisa ngaphezulu kwelinye."
+ "KULUNGILE"
+ "Ifolda ivuliwe, %1$d nge-%2$d"
+ "Thinta ukuze uvale ifolda"
+ "Thinta ukuze ulondoloze ukuqamba kabusha"
+ "Ifolda ivaliwe"
+ "Ifolda iqanjwe kabusha ngo-%1$s"
+ "Ifolda: %1$s"
+
+
+
+
+ "Amawijethi"
+ "Izithombe zangemuva"
+ "Izilungiselelo"
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000000..a2d3a83e06
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000000..dc35a3f682
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,40 @@
+
+
+
+
+ #DAFF0000
+ #DA0099CC
+
+ #20000000
+
+ #FCCC
+
+ #CCFFFFFF
+ #A0000000
+ #FFF
+
+ #FFF
+ #66000000
+ #FF333333
+ #FFFFFFFF
+
+ #64b1ea
+
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000000..497828125b
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,91 @@
+
+ false
+ false
+ false
+ false
+
+
+ 21
+
+
+ -1500
+
+
+
+ 65
+ 300
+ 250
+
+
+ 80
+
+ 58
+
+
+ 350
+ 600
+ 7
+ 250
+ 200
+ 300
+ 40
+ 100
+
+ 0
+
+
+ 250
+
+
+ 150
+
+
+
+ false
+
+ false
+
+
+ 0
+
+
+ 900
+
+
+ 128
+
+
+ 100
+ 500
+
+
+ 120
+
+
+ 800
+
+
+ 80
+ 250
+
+
+ 8000
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000000..b4b2367c77
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+ 6dp
+ 500dp
+ 48dp
+ 24dp
+
+
+ 106.5dp
+ 94.5dp
+ 32dp
+
+
+ 94dp
+ 20dp
+
+ 0dp
+ 0dp
+
+
+
+
+
+
+ 16dp
+ 0dp
+ 15dp
+ 50dp
+ 60dp
+ 130dp
+
+
+ 4dip
+ 12dip
+
+ 36dp
+ 36dp
+
+
+
+ 52dp
+ 0dp
+ 48dp
+
+
+ 12dp
+ 4dp
+ 16dp
+
+
+ 14dp
+
+
+
+ 20dp
+
+
+ 0dp
+ 0dp
+
+ 12dp
+
+
+ 16dp
+ 16dp
+ 32dp
+ 4dp
+ 2dp
+ 2dp
+
+
+ 0dp
+ 0dp
+ 0dp
+
+
+
+ 4dp
+ 10dp
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000000..cafa424425
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,297 @@
+
+
+
+
+
+
+
+ Launcher3
+
+ Home
+
+ Android Core Apps
+
+
+
+ Set wallpaper
+
+
+
+ %1$d selected
+ %1$d selected
+ %1$d selected
+
+
+ Wallpaper %1$d of %2$d
+
+ Selected %1$s
+
+
+ Delete
+
+ Pick image
+
+ Wallpapers
+
+ Crop wallpaper
+
+ App isn\'t installed.
+
+ Widgets
+
+ Widgets
+
+ Show Mem
+
+
+
+ Touch & hold to pick up a widget.
+
+ Shop
+
+
+ %1$d \u00d7 %2$d
+
+
+ %1$s (%2$d \u00d7 %3$d)
+
+ Couldn\'t drop item on this Home screen.
+
+ Choose widget to create
+
+
+
+
+ Folder name
+
+ Rename folder
+
+ OK
+
+ Cancel
+
+
+
+
+ Add to Home screen
+
+ Apps
+
+ Shortcuts
+
+ Widgets
+
+ No more room on your Home screens.
+
+ No more room on this Home screen.
+
+ No more room on the hotseat.
+
+ This widget is too large for the hotseat.
+
+ Shortcut \"%s\" created.
+
+ Shortcut \"%s\" was removed.
+
+ Shortcut \"%s\" already exists.
+
+
+ Choose shortcut
+
+ Choose app
+
+
+ Apps
+
+ Home
+
+ Remove
+
+ Uninstall
+
+
+ Remove
+
+ Uninstall
+
+ App info
+
+
+ Search
+
+ Voice Search
+
+ Apps
+
+ Remove
+
+
+ Uninstall update
+
+
+
+
+
+
+
+ Uninstall app
+
+ App details
+
+ 1 app selected
+
+ 1 widget selected
+
+ 1 folder selected
+
+ 1 shortcut selected
+
+
+
+
+ install shortcuts
+
+ Allows an app to add
+ shortcuts without user intervention.
+
+ uninstall shortcuts
+
+ Allows the app to remove
+ shortcuts without user intervention.
+
+ read Home settings and shortcuts
+
+ Allows the app to read the settings and
+ shortcuts in Home.
+
+ write Home settings and shortcuts
+
+ Allows the app to change the settings and
+ shortcuts in Home.
+
+
+
+
+
+ Problem loading widget
+
+
+ This is a system app and can\'t be uninstalled.
+
+
+ Rocket Launcher
+
+
+ Unnamed Folder
+
+
+
+
+
+ Home screen %1$d
+
+
+ Page %1$d of %2$d
+
+ Home screen %1$d of %2$d
+
+ Apps page %1$d of %2$d
+
+ Widgets page %1$d of %2$d
+
+
+
+ Welcome!
+
+ Make yourself at home.
+
+
+
+
+
+ Create more screens for apps and folders
+
+ Organize your space
+
+ Touch & hold background to manage wallpaper, widgets and settings.
+
+ Choose some apps
+
+ To add an app to your Home screen, touch & hold it.
+
+ Here\'s a folder
+
+ To create one like this, touch & hold an app, then move it over another.
+
+ OK
+
+ Error: custom workspace layout passed in but custom cling was not overwritten
+
+
+
+
+ Folder opened, %1$d by %2$d
+
+ Touch to close folder
+
+ Touch to save rename
+
+ Folder closed
+
+ Folder renamed to %1$s
+
+ Folder: %1$s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ * HPROF
+
+
+
+ Widgets
+
+ Wallpapers
+
+ Settings
+
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000000..a1d2c5ccf2
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml-sw600dp/default_workspace.xml b/res/xml-sw600dp/default_workspace.xml
new file mode 100644
index 0000000000..090c7a7afc
--- /dev/null
+++ b/res/xml-sw600dp/default_workspace.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml-sw720dp/default_workspace.xml b/res/xml-sw720dp/default_workspace.xml
new file mode 100644
index 0000000000..1c1d70e92c
--- /dev/null
+++ b/res/xml-sw720dp/default_workspace.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml
new file mode 100644
index 0000000000..26fc504f03
--- /dev/null
+++ b/res/xml/default_workspace.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/update_workspace.xml b/res/xml/update_workspace.xml
new file mode 100644
index 0000000000..38442b91cd
--- /dev/null
+++ b/res/xml/update_workspace.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/android/util/Pools.java b/src/android/util/Pools.java
new file mode 100644
index 0000000000..40bab1eaee
--- /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:
+ *
+ * public class MyPooledClass {
+ *
+ * private static final SynchronizedPool sPool =
+ * new SynchronizedPool(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);
+ * }
+ *
+ * . . .
+ * }
+ *
+ *
+ * @hide
+ */
+public final class Pools {
+
+ /**
+ * Interface for managing a pool of objects.
+ *
+ * @param The pooled type.
+ */
+ public static interface Pool {
+
+ /**
+ * @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 The pooled type.
+ */
+ public static class SimplePool implements Pool {
+ 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 The pooled type.
+ */
+ public static class SynchronizedPool extends SimplePool {
+ 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 0000000000..a671ed2b9d
--- /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 0000000000..614a081c8f
--- /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 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 info.toString() 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 0000000000..7fb9f22cc0
--- /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 0000000000..dfd4a1a103
--- /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 0000000000..8422382bbd
--- /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 mStripBytes = new ArrayList();
+ 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 getAllTags() {
+ ArrayList ret = new ArrayList();
+ 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 getAllTagsForIfd(int ifd) {
+ IfdData d = mIfdDatas[ifd];
+ if (d == null) {
+ return null;
+ }
+ ExifTag[] tags = d.getAllTags();
+ if (tags == null) {
+ return null;
+ }
+ ArrayList ret = new ArrayList(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 getAllTagsForTagId(short tag) {
+ ArrayList ret = new ArrayList();
+ 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 0000000000..a1cf0fc852
--- /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.
+ *
+ * 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 sOffsetTags = new HashSet();
+ 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 sBannedDefines = new HashSet(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:
+ *
+ *
TOP_LEFT is the normal orientation.
+ *
TOP_RIGHT is a left-right mirror.
+ *
BOTTOM_LEFT is a 180 degree rotation.
+ *
BOTTOM_RIGHT is a top-bottom mirror.
+ *
LEFT_TOP is mirrored about the top-left<->bottom-right axis.
+ *
RIGHT_TOP is a 90 degree clockwise rotation.
+ *
LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.
+ *
RIGHT_BOTTOM is a 270 degree clockwise rotation.
+ *
+ */
+ 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.
+ *
+ * e.g.
+ *
+ * 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 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 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 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 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 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 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 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}
+ *
+ * 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 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 l = new ArrayList();
+ 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 0000000000..bf923ec269
--- /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 0000000000..f00362b6b0
--- /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 mTagOffsets = new ArrayList();
+ 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 0000000000..7ca05f2e0b
--- /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.
+ *
+ * Below is an example of writing EXIF data into a file
+ *
+ *
+ * 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();
+ * }
+ * }
+ * }
+ * }
+ *
+ */
+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 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 stripNullValueTags(ExifData data) {
+ ArrayList nullTags = new ArrayList();
+ 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 0000000000..5467d423d7
--- /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.
+ *
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
+ * parser.
+ *
+ *
+ * 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.
+ * }
+ *
+ */
+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 mCorrespondingEvent = new TreeMap();
+
+ 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 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.
+ *
+ * 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.
+ *
+ * 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 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 0000000000..68e972fb7d
--- /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 0000000000..b8b387201f
--- /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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
+ *
The value overflows.
+ *
The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ */
+ 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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
+ *
The value overflows.
+ *
The component count in the definition of this tag is not 1.
+ *
+ */
+ 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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
+ *
The value overflows.
+ *
The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ */
+ 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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
+ *
The value overflows.
+ *
The component count in the definition for this tag is not 1.
+ *
+ */
+ 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:
+ *
+ *
The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.
+ *
The length of the string is not equal to (component count -1) or
+ * (component count) in the definition for this tag.
+ *
+ */
+ 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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.
+ *
The value overflows.
+ *
The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ *
+ * @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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.
+ *
The value overflows.
+ *
The component count in the definition for this tag is not 1.
+ *
+ *
+ * @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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .
+ *
The length does NOT match the component count in the definition for
+ * this tag.
+ *
+ */
+ 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:
+ *
+ *
The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .
+ *
The component count in the definition for this tag is not 1.
+ *
+ */
+ 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:
+ *
+ *
The corresponding setValue method for the class of the object passed
+ * in would fail.
+ *
There is no obvious way to cast the object passed in into an EXIF tag
+ * type.
+ *
+ */
+ 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 0000000000..093944aec9
--- /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 mExifTags = new HashMap();
+ 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 0000000000..7842edbd46
--- /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 0000000000..e3e787eff3
--- /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 0000000000..428e6b9fc4
--- /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 0000000000..591d63faf7
--- /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 0000000000..2e77b903f2
--- /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 sAllTextures
+ = new WeakHashMap();
+ 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 0000000000..100b0b3b9c
--- /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 0000000000..305e905210
--- /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 0000000000..4ead1315e0
--- /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 mTargetTextures = new ArrayList();
+
+ // 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 0000000000..6cd7149cbb
--- /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 0000000000..3cec558f63
--- /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 0000000000..16b220690e
--- /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 0000000000..93f0fdff95
--- /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 0000000000..3dcae4aece
--- /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 0000000000..f41a979b7d
--- /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 sBorderLines =
+ new HashMap();
+ 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 0000000000..2c4dc2c839
--- /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 0000000000..90a78656ed
--- /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 0000000000..ad15e75c65
--- /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 mItems = new ArrayList();
+
+ 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 0000000000..91f9bd0913
--- /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 0000000000..d955e4eaee
--- /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 data =
+ new ArrayList(DEFAULT_APPLICATIONS_NUMBER);
+ /** The list of apps that have been added since the last notify() call. */
+ public ArrayList added =
+ new ArrayList(DEFAULT_APPLICATIONS_NUMBER);
+ /** The list of apps that have been removed since the last notify() call. */
+ public ArrayList removed = new ArrayList();
+ /** The list of apps that have been modified since the last notify() call. */
+ public ArrayList modified = new ArrayList();
+
+ 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 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 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 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 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 apps = packageManager.queryIntentActivities(mainIntent, 0);
+ return apps != null ? apps : new ArrayList();
+ }
+
+ /**
+ * Returns whether apps contains component.
+ */
+ private static boolean findActivity(List 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 apps contains component.
+ */
+ private static boolean findActivity(ArrayList apps, ComponentName component) {
+ final int N = apps.size();
+ for (int i=0; i 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 0000000000..53f81bb1cb
--- /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