(mActivityRef.get());
+ }
+
+ private int generateNextRequestCode() {
+ int requestCode = REQUEST_CODE_PREFIX + mNextRequestCode;
+ mNextRequestCode = (mNextRequestCode + 1) % REQUEST_CODE_RANGE_SIZE;
+ return requestCode;
+ }
+
+ private void storeCallbackData(int requestCode, IntentCallback callback, int errorId) {
+ mOutstandingIntents.put(requestCode, callback);
+ mIntentErrors.put(requestCode, mApplicationContext.getString(errorId));
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/Clipboard.java b/ui/android/java/src/org/chromium/ui/base/Clipboard.java
new file mode 100644
index 0000000000000..67aaca273fe95
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/base/Clipboard.java
@@ -0,0 +1,172 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.base;
+
+import android.annotation.TargetApi;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Build;
+import android.widget.Toast;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.ui.R;
+
+/**
+ * Simple proxy that provides C++ code with an access pathway to the Android
+ * clipboard.
+ */
+@JNINamespace("ui")
+public class Clipboard {
+
+ private static final boolean IS_HTML_CLIPBOARD_SUPPORTED =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+
+ // Necessary for coercing clipboard contents to text if they require
+ // access to network resources, etceteras (e.g., URI in clipboard)
+ private final Context mContext;
+
+ private final ClipboardManager mClipboardManager;
+
+ /**
+ * Use the factory constructor instead.
+ *
+ * @param context for accessing the clipboard
+ */
+ public Clipboard(final Context context) {
+ mContext = context;
+ mClipboardManager = (ClipboardManager)
+ context.getSystemService(Context.CLIPBOARD_SERVICE);
+ }
+
+ /**
+ * Returns a new Clipboard object bound to the specified context.
+ *
+ * @param context for accessing the clipboard
+ * @return the new object
+ */
+ @CalledByNative
+ private static Clipboard create(final Context context) {
+ return new Clipboard(context);
+ }
+
+ /**
+ * Emulates the behavior of the now-deprecated
+ * {@link android.text.ClipboardManager#getText()} by invoking
+ * {@link android.content.ClipData.Item#coerceToText(Context)} on the first
+ * item in the clipboard (if any) and returning the result as a string.
+ *
+ * This is quite different than simply calling {@link Object#toString()} on
+ * the clip; consumers of this API should familiarize themselves with the
+ * process described in
+ * {@link android.content.ClipData.Item#coerceToText(Context)} before using
+ * this method.
+ *
+ * @return a string representation of the first item on the clipboard, if
+ * the clipboard currently has an item and coercion of the item into
+ * a string is possible; otherwise, null
+ */
+ @SuppressWarnings("javadoc")
+ @CalledByNative
+ private String getCoercedText() {
+ final ClipData clip = mClipboardManager.getPrimaryClip();
+ if (clip != null && clip.getItemCount() > 0) {
+ final CharSequence sequence = clip.getItemAt(0).coerceToText(mContext);
+ if (sequence != null) {
+ return sequence.toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the HTML text of top item on the primary clip on the Android clipboard.
+ *
+ * @return a Java string with the html text if any, or null if there is no html
+ * text or no entries on the primary clip.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @CalledByNative
+ private String getHTMLText() {
+ if (IS_HTML_CLIPBOARD_SUPPORTED) {
+ final ClipData clip = mClipboardManager.getPrimaryClip();
+ if (clip != null && clip.getItemCount() > 0) {
+ return clip.getItemAt(0).getHtmlText();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Emulates the behavior of the now-deprecated
+ * {@link android.text.ClipboardManager#setText(CharSequence)}, setting the
+ * clipboard's current primary clip to a plain-text clip that consists of
+ * the specified string.
+ *
+ * @param label will become the label of the clipboard's primary clip
+ * @param text will become the content of the clipboard's primary clip
+ */
+ public void setText(final String label, final String text) {
+ setPrimaryClipNoException(ClipData.newPlainText(label, text));
+ }
+
+ /**
+ * Emulates the behavior of the now-deprecated
+ * {@link android.text.ClipboardManager#setText(CharSequence)}, setting the
+ * clipboard's current primary clip to a plain-text clip that consists of
+ * the specified string.
+ *
+ * @param text will become the content of the clipboard's primary clip
+ */
+ @CalledByNative
+ public void setText(final String text) {
+ setText(null, text);
+ }
+
+ /**
+ * Writes HTML to the clipboard, together with a plain-text representation
+ * of that very data. This API is only available in Android JellyBean+ and
+ * will be a no-operation in older versions.
+ *
+ * @param html The HTML content to be pasted to the clipboard.
+ * @param label The Plain-text label for the HTML content.
+ * @param text Plain-text representation of the HTML content.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public void setHTMLText(final String html, final String label, final String text) {
+ if (IS_HTML_CLIPBOARD_SUPPORTED) {
+ setPrimaryClipNoException(ClipData.newHtmlText(label, text, html));
+ }
+ }
+
+ /**
+ * Writes HTML to the clipboard, together with a plain-text representation
+ * of that very data. This API is only available in Android JellyBean+ and
+ * will be a no-operation in older versions.
+ *
+ * @param html The HTML content to be pasted to the clipboard.
+ * @param text Plain-text representation of the HTML content.
+ */
+ @CalledByNative
+ public void setHTMLText(final String html, final String text) {
+ setHTMLText(html, null, text);
+ }
+
+ @CalledByNative
+ private static boolean isHTMLClipboardSupported() {
+ return IS_HTML_CLIPBOARD_SUPPORTED;
+ }
+
+ private void setPrimaryClipNoException(ClipData clip) {
+ try {
+ mClipboardManager.setPrimaryClip(clip);
+ } catch (Exception ex) {
+ // Ignore any exceptions here as certain devices have bugs and will fail.
+ String text = mContext.getString(R.string.copy_to_clipboard_failure_message);
+ Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/DeviceFormFactor.java b/ui/android/java/src/org/chromium/ui/base/DeviceFormFactor.java
new file mode 100644
index 0000000000000..78f86c9f9deb6
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/base/DeviceFormFactor.java
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.base;
+
+import android.content.Context;
+
+import org.chromium.base.CalledByNative;
+
+/**
+ * UI utilities for accessing form factor information.
+ */
+public class DeviceFormFactor {
+
+ /**
+ * The minimum width that would classify the device as a tablet.
+ */
+ private static final int MINIMUM_TABLET_WIDTH_DP = 600;
+
+ private static Boolean sIsTablet = null;
+
+ /**
+ * @param context Android's context
+ * @return Whether the app is should treat the device as a tablet for layout.
+ */
+ @CalledByNative
+ public static boolean isTablet(Context context) {
+ if (sIsTablet == null) {
+ int minimumScreenWidthDp = context.getResources().getConfiguration().
+ smallestScreenWidthDp;
+ sIsTablet = minimumScreenWidthDp >= MINIMUM_TABLET_WIDTH_DP;
+ }
+ return sIsTablet;
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
new file mode 100644
index 0000000000000..bc6efb78f9c1f
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
@@ -0,0 +1,371 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.base;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.ContentUriUtils;
+import org.chromium.base.JNINamespace;
+import org.chromium.ui.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A dialog that is triggered from a file input field that allows a user to select a file based on
+ * a set of accepted file types. The path of the selected file is passed to the native dialog.
+ */
+@JNINamespace("ui")
+class SelectFileDialog implements WindowAndroid.IntentCallback {
+ private static final String TAG = "SelectFileDialog";
+ private static final String IMAGE_TYPE = "image/";
+ private static final String VIDEO_TYPE = "video/";
+ private static final String AUDIO_TYPE = "audio/";
+ private static final String ALL_IMAGE_TYPES = IMAGE_TYPE + "*";
+ private static final String ALL_VIDEO_TYPES = VIDEO_TYPE + "*";
+ private static final String ALL_AUDIO_TYPES = AUDIO_TYPE + "*";
+ private static final String ANY_TYPES = "*/*";
+ private static final String CAPTURE_IMAGE_DIRECTORY = "browser-photos";
+ // Keep this variable in sync with the value defined in file_paths.xml.
+ private static final String IMAGE_FILE_PATH = "images";
+
+ private final long mNativeSelectFileDialog;
+ private List mFileTypes;
+ private boolean mCapture;
+ private Uri mCameraOutputUri;
+
+ private SelectFileDialog(long nativeSelectFileDialog) {
+ mNativeSelectFileDialog = nativeSelectFileDialog;
+ }
+
+ /**
+ * Creates and starts an intent based on the passed fileTypes and capture value.
+ * @param fileTypes MIME types requested (i.e. "image/*")
+ * @param capture The capture value as described in http://www.w3.org/TR/html-media-capture/
+ * @param multiple Whether it should be possible to select multiple files.
+ * @param window The WindowAndroid that can show intents
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+ @CalledByNative
+ private void selectFile(
+ String[] fileTypes, boolean capture, boolean multiple, WindowAndroid window) {
+ mFileTypes = new ArrayList(Arrays.asList(fileTypes));
+ mCapture = capture;
+
+ Intent chooser = new Intent(Intent.ACTION_CHOOSER);
+ Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ Context context = window.getApplicationContext();
+ camera.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ mCameraOutputUri = ContentUriUtils.getContentUriFromFile(
+ context, getFileForImageCapture(context));
+ } else {
+ mCameraOutputUri = Uri.fromFile(getFileForImageCapture(context));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot retrieve content uri from file", e);
+ }
+
+ if (mCameraOutputUri == null) {
+ onFileNotSelected();
+ return;
+ }
+
+ camera.putExtra(MediaStore.EXTRA_OUTPUT, mCameraOutputUri);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ camera.setClipData(
+ ClipData.newUri(context.getContentResolver(),
+ IMAGE_FILE_PATH, mCameraOutputUri));
+ }
+ Intent camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+ Intent soundRecorder = new Intent(
+ MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+
+ // Quick check - if the |capture| parameter is set and |fileTypes| has the appropriate MIME
+ // type, we should just launch the appropriate intent. Otherwise build up a chooser based on
+ // the accept type and then display that to the user.
+ if (captureCamera()) {
+ if (window.showIntent(camera, this, R.string.low_memory_error)) return;
+ } else if (captureCamcorder()) {
+ if (window.showIntent(camcorder, this, R.string.low_memory_error)) return;
+ } else if (captureMicrophone()) {
+ if (window.showIntent(soundRecorder, this, R.string.low_memory_error)) return;
+ }
+
+ Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ getContentIntent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && multiple)
+ getContentIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+
+ ArrayList extraIntents = new ArrayList();
+ if (!noSpecificType()) {
+ // Create a chooser based on the accept type that was specified in the webpage. Note
+ // that if the web page specified multiple accept types, we will have built a generic
+ // chooser above.
+ if (shouldShowImageTypes()) {
+ extraIntents.add(camera);
+ getContentIntent.setType(ALL_IMAGE_TYPES);
+ } else if (shouldShowVideoTypes()) {
+ extraIntents.add(camcorder);
+ getContentIntent.setType(ALL_VIDEO_TYPES);
+ } else if (shouldShowAudioTypes()) {
+ extraIntents.add(soundRecorder);
+ getContentIntent.setType(ALL_AUDIO_TYPES);
+ }
+ }
+
+ if (extraIntents.isEmpty()) {
+ // We couldn't resolve an accept type, so fallback to a generic chooser.
+ getContentIntent.setType(ANY_TYPES);
+ extraIntents.add(camera);
+ extraIntents.add(camcorder);
+ extraIntents.add(soundRecorder);
+ }
+
+ chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
+ extraIntents.toArray(new Intent[] { }));
+
+ chooser.putExtra(Intent.EXTRA_INTENT, getContentIntent);
+
+ if (!window.showIntent(chooser, this, R.string.low_memory_error)) {
+ onFileNotSelected();
+ }
+ }
+
+ /**
+ * Get a file for the image capture operation. For devices with JB MR2 or
+ * latter android versions, the file is put under IMAGE_FILE_PATH directory.
+ * For ICS devices, the file is put under CAPTURE_IMAGE_DIRECTORY.
+ *
+ * @param context The application context.
+ * @return file path for the captured image to be stored.
+ */
+ private File getFileForImageCapture(Context context) throws IOException {
+ File path;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ path = new File(context.getFilesDir(), IMAGE_FILE_PATH);
+ if (!path.exists() && !path.mkdir()) {
+ throw new IOException("Folder cannot be created.");
+ }
+ } else {
+ File externalDataDir = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DCIM);
+ path = new File(externalDataDir.getAbsolutePath() +
+ File.separator + CAPTURE_IMAGE_DIRECTORY);
+ if (!path.exists() && !path.mkdirs()) {
+ path = externalDataDir;
+ }
+ }
+ File photoFile = File.createTempFile(
+ String.valueOf(System.currentTimeMillis()), ".jpg", path);
+ return photoFile;
+ }
+
+ /**
+ * Callback method to handle the intent results and pass on the path to the native
+ * SelectFileDialog.
+ * @param window The window that has access to the application activity.
+ * @param resultCode The result code whether the intent returned successfully.
+ * @param contentResolver The content resolver used to extract the path of the selected file.
+ * @param results The results of the requested intent.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+ @Override
+ public void onIntentCompleted(WindowAndroid window, int resultCode,
+ ContentResolver contentResolver, Intent results) {
+ if (resultCode != Activity.RESULT_OK) {
+ onFileNotSelected();
+ return;
+ }
+
+ if (results == null) {
+ // If we have a successful return but no data, then assume this is the camera returning
+ // the photo that we requested.
+ // If the uri is a file, we need to convert it to the absolute path or otherwise
+ // android cannot handle it correctly on some earlier versions.
+ // http://crbug.com/423338.
+ String path = ContentResolver.SCHEME_FILE.equals(mCameraOutputUri.getScheme()) ?
+ mCameraOutputUri.getPath() : mCameraOutputUri.toString();
+ nativeOnFileSelected(mNativeSelectFileDialog, path,
+ mCameraOutputUri.getLastPathSegment());
+ // Broadcast to the media scanner that there's a new photo on the device so it will
+ // show up right away in the gallery (rather than waiting until the next time the media
+ // scanner runs).
+ window.sendBroadcast(new Intent(
+ Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mCameraOutputUri));
+ return;
+ }
+
+ // Path for when EXTRA_ALLOW_MULTIPLE Intent extra has been defined. Each of the selected
+ // files will be shared as an entry on the Intent's ClipData. This functionality is only
+ // available in Android JellyBean MR2 and higher.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 &&
+ results.getData() == null &&
+ results.getClipData() != null) {
+ ClipData clipData = results.getClipData();
+
+ int itemCount = clipData.getItemCount();
+ if (itemCount == 0) {
+ onFileNotSelected();
+ return;
+ }
+
+ Uri[] filePathArray = new Uri[itemCount];
+ for (int i = 0; i < itemCount; ++i) {
+ filePathArray[i] = clipData.getItemAt(i).getUri();
+ }
+ GetDisplayNameTask task = new GetDisplayNameTask(contentResolver, true);
+ task.execute(filePathArray);
+ return;
+ }
+
+ if (ContentResolver.SCHEME_FILE.equals(results.getData().getScheme())) {
+ nativeOnFileSelected(mNativeSelectFileDialog,
+ results.getData().getSchemeSpecificPart(), "");
+ return;
+ }
+
+ if (ContentResolver.SCHEME_CONTENT.equals(results.getScheme())) {
+ GetDisplayNameTask task = new GetDisplayNameTask(contentResolver, false);
+ task.execute(results.getData());
+ return;
+ }
+
+ onFileNotSelected();
+ window.showError(R.string.opening_file_error);
+ }
+
+ private void onFileNotSelected() {
+ nativeOnFileNotSelected(mNativeSelectFileDialog);
+ }
+
+ private boolean noSpecificType() {
+ // We use a single Intent to decide the type of the file chooser we display to the user,
+ // which means we can only give it a single type. If there are multiple accept types
+ // specified, we will fallback to a generic chooser (unless a capture parameter has been
+ // specified, in which case we'll try to satisfy that first.
+ return mFileTypes.size() != 1 || mFileTypes.contains(ANY_TYPES);
+ }
+
+ private boolean shouldShowTypes(String allTypes, String specificType) {
+ if (noSpecificType() || mFileTypes.contains(allTypes)) return true;
+ return acceptSpecificType(specificType);
+ }
+
+ private boolean shouldShowImageTypes() {
+ return shouldShowTypes(ALL_IMAGE_TYPES, IMAGE_TYPE);
+ }
+
+ private boolean shouldShowVideoTypes() {
+ return shouldShowTypes(ALL_VIDEO_TYPES, VIDEO_TYPE);
+ }
+
+ private boolean shouldShowAudioTypes() {
+ return shouldShowTypes(ALL_AUDIO_TYPES, AUDIO_TYPE);
+ }
+
+ private boolean acceptsSpecificType(String type) {
+ return mFileTypes.size() == 1 && TextUtils.equals(mFileTypes.get(0), type);
+ }
+
+ private boolean captureCamera() {
+ return mCapture && acceptsSpecificType(ALL_IMAGE_TYPES);
+ }
+
+ private boolean captureCamcorder() {
+ return mCapture && acceptsSpecificType(ALL_VIDEO_TYPES);
+ }
+
+ private boolean captureMicrophone() {
+ return mCapture && acceptsSpecificType(ALL_AUDIO_TYPES);
+ }
+
+ private boolean acceptSpecificType(String accept) {
+ for (String type : mFileTypes) {
+ if (type.startsWith(accept)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private class GetDisplayNameTask extends AsyncTask {
+ String[] mFilePaths;
+ final ContentResolver mContentResolver;
+ final boolean mIsMultiple;
+
+ public GetDisplayNameTask(ContentResolver contentResolver, boolean isMultiple) {
+ mContentResolver = contentResolver;
+ mIsMultiple = isMultiple;
+ }
+
+ @Override
+ protected String[] doInBackground(Uri...uris) {
+ mFilePaths = new String[uris.length];
+ String[] displayNames = new String[uris.length];
+ try {
+ for (int i = 0; i < uris.length; i++) {
+ mFilePaths[i] = uris[i].toString();
+ displayNames[i] = ContentUriUtils.getDisplayName(
+ uris[i], mContentResolver, MediaStore.MediaColumns.DISPLAY_NAME);
+ }
+ } catch (SecurityException e) {
+ // Some third party apps will present themselves as being able
+ // to handle the ACTION_GET_CONTENT intent but then declare themselves
+ // as exported=false (or more often omit the exported keyword in
+ // the manifest which defaults to false after JB).
+ // In those cases trying to access the contents raises a security exception
+ // which we should not crash on. See crbug.com/382367 for details.
+ Log.w(TAG, "Unable to extract results from the content provider");
+ return null;
+ }
+
+ return displayNames;
+ }
+
+ @Override
+ protected void onPostExecute(String[] result) {
+ if (result == null) {
+ onFileNotSelected();
+ return;
+ }
+ if (mIsMultiple) {
+ nativeOnMultipleFilesSelected(mNativeSelectFileDialog, mFilePaths, result);
+ } else {
+ nativeOnFileSelected(mNativeSelectFileDialog, mFilePaths[0], result[0]);
+ }
+ }
+ }
+
+ @CalledByNative
+ private static SelectFileDialog create(long nativeSelectFileDialog) {
+ return new SelectFileDialog(nativeSelectFileDialog);
+ }
+
+ private native void nativeOnFileSelected(long nativeSelectFileDialogImpl,
+ String filePath, String displayName);
+ private native void nativeOnMultipleFilesSelected(long nativeSelectFileDialogImpl,
+ String[] filePathArray, String[] displayNameArray);
+ private native void nativeOnFileNotSelected(long nativeSelectFileDialogImpl);
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/ViewAndroid.java b/ui/android/java/src/org/chromium/ui/base/ViewAndroid.java
new file mode 100644
index 0000000000000..f33971369a54c
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/base/ViewAndroid.java
@@ -0,0 +1,86 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.base;
+
+import android.view.View;
+
+import org.chromium.base.JNINamespace;
+
+/**
+ * From the Chromium architecture point of view, ViewAndroid and its native counterpart
+ * serve purpose of representing Android view where Chrome expects to have a cross platform
+ * handle to the system view type. As Views are Java object on Android, this ViewAndroid
+ * and its native counterpart provide the expected abstractions on the C++ side and allow
+ * it to be flexibly glued to an actual Android Java View at runtime.
+ *
+ * It should only be used where access to Android Views is needed from the C++ code.
+ */
+@JNINamespace("ui")
+public class ViewAndroid {
+ // Native pointer to the c++ ViewAndroid object.
+ private long mNativeViewAndroid = 0;
+ private final ViewAndroidDelegate mViewAndroidDelegate;
+ private final WindowAndroid mWindowAndroid;
+ private int mKeepScreenOnCount;
+ private View mKeepScreenOnView;
+
+ /**
+ * Constructs a View object.
+ */
+ public ViewAndroid(WindowAndroid nativeWindow, ViewAndroidDelegate viewAndroidDelegate) {
+ mWindowAndroid = nativeWindow;
+ mViewAndroidDelegate = viewAndroidDelegate;
+ mNativeViewAndroid = nativeInit(mWindowAndroid.getNativePointer());
+ }
+
+ public ViewAndroidDelegate getViewAndroidDelegate() {
+ return mViewAndroidDelegate;
+ }
+
+ /**
+ * Destroys the c++ ViewAndroid object if one has been created.
+ */
+ public void destroy() {
+ if (mNativeViewAndroid != 0) {
+ nativeDestroy(mNativeViewAndroid);
+ mNativeViewAndroid = 0;
+ }
+ }
+
+ /**
+ * Returns a pointer to the c++ AndroidWindow object.
+ * @return A pointer to the c++ AndroidWindow.
+ */
+ public long getNativePointer() {
+ return mNativeViewAndroid;
+ }
+
+ /**
+ * Set KeepScreenOn flag. If the flag already set, increase mKeepScreenOnCount.
+ */
+ public void incrementKeepScreenOnCount() {
+ mKeepScreenOnCount++;
+ if (mKeepScreenOnCount == 1) {
+ mKeepScreenOnView = mViewAndroidDelegate.acquireAnchorView();
+ mViewAndroidDelegate.setAnchorViewPosition(mKeepScreenOnView, 0, 0, 0, 0);
+ mKeepScreenOnView.setKeepScreenOn(true);
+ }
+ }
+
+ /**
+ * Decrease mKeepScreenOnCount, if it is decreased to 0, remove the flag.
+ */
+ public void decrementKeepScreenOnCount() {
+ assert mKeepScreenOnCount > 0;
+ mKeepScreenOnCount--;
+ if (mKeepScreenOnCount == 0) {
+ mViewAndroidDelegate.releaseAnchorView(mKeepScreenOnView);
+ mKeepScreenOnView = null;
+ }
+ }
+
+ private native long nativeInit(long windowPtr);
+ private native void nativeDestroy(long nativeViewAndroid);
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
new file mode 100644
index 0000000000000..6d3694cc9bfe1
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
@@ -0,0 +1,34 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.base;
+
+import android.view.View;
+
+/**
+ * Interface to acquire and release anchor views from the implementing View.
+ */
+public interface ViewAndroidDelegate {
+
+ /**
+ * @return An anchor view that can be used to anchor decoration views like Autofill popup.
+ */
+ View acquireAnchorView();
+
+ /**
+ * Set the anchor view to specified position and width (all units in dp).
+ * @param view The anchor view that needs to be positioned.
+ * @param x X coordinate of the top left corner of the anchor view.
+ * @param y Y coordinate of the top left corner of the anchor view.
+ * @param width The width of the anchor view.
+ * @param height The height of the anchor view.
+ */
+ void setAnchorViewPosition(View view, float x, float y, float width, float height);
+
+ /**
+ * Release given anchor view.
+ * @param anchorView The anchor view that needs to be released.
+ */
+ void releaseAnchorView(View anchorView);
+}
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
new file mode 100644
index 0000000000000..8f1c58f67ffcf
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -0,0 +1,298 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.base;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.widget.Toast;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.ui.VSyncMonitor;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+
+/**
+ * The window base class that has the minimum functionality.
+ */
+@JNINamespace("ui")
+public class WindowAndroid {
+ private static final String TAG = "WindowAndroid";
+
+ // Native pointer to the c++ WindowAndroid object.
+ private long mNativeWindowAndroid = 0;
+ private final VSyncMonitor mVSyncMonitor;
+
+ // A string used as a key to store intent errors in a bundle
+ static final String WINDOW_CALLBACK_ERRORS = "window_callback_errors";
+
+ // Error code returned when an Intent fails to start an Activity.
+ public static final int START_INTENT_FAILURE = -1;
+
+ protected Context mApplicationContext;
+ protected SparseArray mOutstandingIntents;
+
+ // Ideally, this would be a SparseArray, but there's no easy way to store a
+ // SparseArray in a bundle during saveInstanceState(). So we use a HashMap and suppress
+ // the Android lint warning "UseSparseArrays".
+ protected HashMap mIntentErrors;
+
+ private final VSyncMonitor.Listener mVSyncListener = new VSyncMonitor.Listener() {
+ @Override
+ public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
+ if (mNativeWindowAndroid != 0) {
+ nativeOnVSync(mNativeWindowAndroid,
+ vsyncTimeMicros,
+ mVSyncMonitor.getVSyncPeriodInMicroseconds());
+ }
+ }
+ };
+
+ /**
+ * @return true if onVSync handler is executing.
+ * @see org.chromium.ui.VSyncMonitor#isInsideVSync().
+ */
+ public boolean isInsideVSync() {
+ return mVSyncMonitor.isInsideVSync();
+ }
+
+ /**
+ * @param context The application context.
+ */
+ @SuppressLint("UseSparseArrays")
+ public WindowAndroid(Context context) {
+ assert context == context.getApplicationContext();
+ mApplicationContext = context;
+ mOutstandingIntents = new SparseArray();
+ mIntentErrors = new HashMap();
+ mVSyncMonitor = new VSyncMonitor(context, mVSyncListener);
+ }
+
+ /**
+ * Shows an intent and returns the results to the callback object.
+ * @param intent The PendingIntent that needs to be shown.
+ * @param callback The object that will receive the results for the intent.
+ * @param errorId The ID of error string to be show if activity is paused before intent
+ * results.
+ * @return Whether the intent was shown.
+ */
+ public boolean showIntent(PendingIntent intent, IntentCallback callback, int errorId) {
+ return showCancelableIntent(intent, callback, errorId) >= 0;
+ }
+
+ /**
+ * Shows an intent and returns the results to the callback object.
+ * @param intent The intent that needs to be shown.
+ * @param callback The object that will receive the results for the intent.
+ * @param errorId The ID of error string to be show if activity is paused before intent
+ * results.
+ * @return Whether the intent was shown.
+ */
+ public boolean showIntent(Intent intent, IntentCallback callback, int errorId) {
+ return showCancelableIntent(intent, callback, errorId) >= 0;
+ }
+
+ /**
+ * Shows an intent that could be canceled and returns the results to the callback object.
+ * @param intent The PendingIntent that needs to be shown.
+ * @param callback The object that will receive the results for the intent.
+ * @param errorId The ID of error string to be show if activity is paused before intent
+ * results.
+ * @return A non-negative request code that could be used for finishActivity, or
+ * START_INTENT_FAILURE if failed.
+ */
+ public int showCancelableIntent(PendingIntent intent, IntentCallback callback, int errorId) {
+ Log.d(TAG, "Can't show intent as context is not an Activity: " + intent);
+ return START_INTENT_FAILURE;
+ }
+
+ /**
+ * Shows an intent that could be canceled and returns the results to the callback object.
+ * @param intent The intent that needs to be showed.
+ * @param callback The object that will receive the results for the intent.
+ * @param errorId The ID of error string to be show if activity is paused before intent
+ * results.
+ * @return A non-negative request code that could be used for finishActivity, or
+ * START_INTENT_FAILURE if failed.
+ */
+ public int showCancelableIntent(Intent intent, IntentCallback callback, int errorId) {
+ Log.d(TAG, "Can't show intent as context is not an Activity: " + intent);
+ return START_INTENT_FAILURE;
+ }
+
+ /**
+ * Force finish another activity that you had previously started with showCancelableIntent.
+ * @param requestCode The request code returned from showCancelableIntent.
+ */
+ public void cancelIntent(int requestCode) {
+ Log.d(TAG, "Can't cancel intent as context is not an Activity: " + requestCode);
+ }
+
+ /**
+ * Removes a callback from the list of pending intents, so that nothing happens if/when the
+ * result for that intent is received.
+ * @param callback The object that should have received the results
+ * @return True if the callback was removed, false if it was not found.
+ */
+ public boolean removeIntentCallback(IntentCallback callback) {
+ int requestCode = mOutstandingIntents.indexOfValue(callback);
+ if (requestCode < 0) return false;
+ mOutstandingIntents.remove(requestCode);
+ mIntentErrors.remove(requestCode);
+ return true;
+ }
+
+ /**
+ * Displays an error message with a provided error message string.
+ * @param error The error message string to be displayed.
+ */
+ public void showError(String error) {
+ if (error != null) {
+ Toast.makeText(mApplicationContext, error, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * Displays an error message from the given resource id.
+ * @param resId The error message string's resource id.
+ */
+ public void showError(int resId) {
+ showError(mApplicationContext.getString(resId));
+ }
+
+ /**
+ * Displays an error message for a nonexistent callback.
+ * @param error The error message string to be displayed.
+ */
+ protected void showCallbackNonExistentError(String error) {
+ showError(error);
+ }
+
+ /**
+ * Broadcasts the given intent to all interested BroadcastReceivers.
+ */
+ public void sendBroadcast(Intent intent) {
+ mApplicationContext.sendBroadcast(intent);
+ }
+
+ /**
+ * @return A reference to owning Activity. The returned WeakReference will never be null, but
+ * the contained Activity can be null (either if it has been garbage collected or if
+ * this is in the context of a WebView that was not created using an Activity).
+ */
+ public WeakReference getActivity() {
+ return new WeakReference(null);
+ }
+
+ /**
+ * @return The application context for this activity.
+ */
+ public Context getApplicationContext() {
+ return mApplicationContext;
+ }
+
+ /**
+ * Saves the error messages that should be shown if any pending intents would return
+ * after the application has been put onPause.
+ * @param bundle The bundle to save the information in onPause
+ */
+ public void saveInstanceState(Bundle bundle) {
+ bundle.putSerializable(WINDOW_CALLBACK_ERRORS, mIntentErrors);
+ }
+
+ /**
+ * Restores the error messages that should be shown if any pending intents would return
+ * after the application has been put onPause.
+ * @param bundle The bundle to restore the information from onResume
+ */
+ public void restoreInstanceState(Bundle bundle) {
+ if (bundle == null) return;
+
+ Object errors = bundle.getSerializable(WINDOW_CALLBACK_ERRORS);
+ if (errors instanceof HashMap) {
+ @SuppressWarnings("unchecked")
+ HashMap intentErrors = (HashMap) errors;
+ mIntentErrors = intentErrors;
+ }
+ }
+
+ /**
+ * Responds to the intent result if the intent was created by the native window.
+ * @param requestCode Request code of the requested intent.
+ * @param resultCode Result code of the requested intent.
+ * @param data The data returned by the intent.
+ * @return Boolean value of whether the intent was started by the native window.
+ */
+ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+ return false;
+ }
+
+ @CalledByNative
+ private void requestVSyncUpdate() {
+ mVSyncMonitor.requestUpdate();
+ }
+
+ /**
+ * An interface that intent callback objects have to implement.
+ */
+ public interface IntentCallback {
+ /**
+ * Handles the data returned by the requested intent.
+ * @param window A window reference.
+ * @param resultCode Result code of the requested intent.
+ * @param contentResolver An instance of ContentResolver class for accessing returned data.
+ * @param data The data returned by the intent.
+ */
+ void onIntentCompleted(WindowAndroid window, int resultCode,
+ ContentResolver contentResolver, Intent data);
+ }
+
+ /**
+ * Tests that an activity is available to handle the passed in intent.
+ * @param intent The intent to check.
+ * @return True if an activity is available to process this intent when started, meaning that
+ * Context.startActivity will not throw ActivityNotFoundException.
+ */
+ public boolean canResolveActivity(Intent intent) {
+ return mApplicationContext.getPackageManager().resolveActivity(intent, 0) != null;
+ }
+
+ /**
+ * Destroys the c++ WindowAndroid object if one has been created.
+ */
+ public void destroy() {
+ if (mNativeWindowAndroid != 0) {
+ nativeDestroy(mNativeWindowAndroid);
+ mNativeWindowAndroid = 0;
+ }
+ }
+
+ /**
+ * Returns a pointer to the c++ AndroidWindow object and calls the initializer if
+ * the object has not been previously initialized.
+ * @return A pointer to the c++ AndroidWindow.
+ */
+ public long getNativePointer() {
+ if (mNativeWindowAndroid == 0) {
+ mNativeWindowAndroid = nativeInit();
+ }
+ return mNativeWindowAndroid;
+ }
+
+ private native long nativeInit();
+ private native void nativeOnVSync(long nativeWindowAndroid,
+ long vsyncTimeMicros,
+ long vsyncPeriodMicros);
+ private native void nativeDestroy(long nativeWindowAndroid);
+
+}
diff --git a/ui/android/java/src/org/chromium/ui/gfx/BitmapHelper.java b/ui/android/java/src/org/chromium/ui/gfx/BitmapHelper.java
new file mode 100644
index 0000000000000..8ffaae4b09371
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/gfx/BitmapHelper.java
@@ -0,0 +1,120 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.gfx;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Helper class to decode and sample down bitmap resources.
+ */
+@JNINamespace("gfx")
+public class BitmapHelper {
+ @CalledByNative
+ private static Bitmap createBitmap(int width,
+ int height,
+ int bitmapFormatValue) {
+ Bitmap.Config bitmapConfig = getBitmapConfigForFormat(bitmapFormatValue);
+ return Bitmap.createBitmap(width, height, bitmapConfig);
+ }
+
+ /**
+ * Decode and sample down a bitmap resource to the requested width and height.
+ *
+ * @param name The resource name of the bitmap to decode.
+ * @param reqWidth The requested width of the resulting bitmap.
+ * @param reqHeight The requested height of the resulting bitmap.
+ * @return A bitmap sampled down from the original with the same aspect ratio and dimensions.
+ * that are equal to or greater than the requested width and height.
+ */
+ @CalledByNative
+ private static Bitmap decodeDrawableResource(String name,
+ int reqWidth,
+ int reqHeight) {
+ Resources res = Resources.getSystem();
+ int resId = res.getIdentifier(name, null, null);
+ if (resId == 0) return null;
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, options);
+
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+ options.inJustDecodeBounds = false;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeResource(res, resId, options);
+ }
+
+ // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
+ private static int calculateInSampleSize(BitmapFactory.Options options,
+ int reqWidth,
+ int reqHeight) {
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+
+ // Calculate ratios of height and width to requested height and width
+ final int heightRatio = Math.round((float) height / (float) reqHeight);
+ final int widthRatio = Math.round((float) width / (float) reqWidth);
+
+ // Choose the smallest ratio as inSampleSize value, this will guarantee
+ // a final image with both dimensions larger than or equal to the
+ // requested height and width.
+ inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
+ }
+
+ return inSampleSize;
+ }
+
+ /**
+ * Provides a matching integer constant for the Bitmap.Config value passed.
+ *
+ * @param bitmapConfig The Bitmap Configuration value.
+ * @return Matching integer constant for the Bitmap.Config value passed.
+ */
+ @CalledByNative
+ private static int getBitmapFormatForConfig(Bitmap.Config bitmapConfig) {
+ switch (bitmapConfig) {
+ case ALPHA_8:
+ return BitmapFormat.ALPHA_8;
+ case ARGB_4444:
+ return BitmapFormat.ARGB_4444;
+ case ARGB_8888:
+ return BitmapFormat.ARGB_8888;
+ case RGB_565:
+ return BitmapFormat.RGB_565;
+ default:
+ return BitmapFormat.NO_CONFIG;
+ }
+ }
+
+ /**
+ * Provides a matching Bitmap.Config for the enum config value passed.
+ *
+ * @param bitmapFormatValue The Bitmap Configuration enum value.
+ * @return Matching Bitmap.Config for the enum value passed.
+ */
+ private static Bitmap.Config getBitmapConfigForFormat(int bitmapFormatValue) {
+ switch (bitmapFormatValue) {
+ case BitmapFormat.ALPHA_8:
+ return Bitmap.Config.ALPHA_8;
+ case BitmapFormat.ARGB_4444:
+ return Bitmap.Config.ARGB_4444;
+ case BitmapFormat.RGB_565:
+ return Bitmap.Config.RGB_565;
+ case BitmapFormat.ARGB_8888:
+ default:
+ return Bitmap.Config.ARGB_8888;
+ }
+ }
+
+}
diff --git a/ui/android/java/src/org/chromium/ui/gfx/DeviceDisplayInfo.java b/ui/android/java/src/org/chromium/ui/gfx/DeviceDisplayInfo.java
new file mode 100644
index 0000000000000..38569513d08dc
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/gfx/DeviceDisplayInfo.java
@@ -0,0 +1,214 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.gfx;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * This class facilitates access to android information typically only
+ * available using the Java SDK, including {@link Display} properties.
+ *
+ * Currently the information consists of very raw display information (height, width, DPI scale)
+ * regarding the main display.
+ */
+@JNINamespace("gfx")
+public class DeviceDisplayInfo {
+
+ private final Context mAppContext;
+ private final WindowManager mWinManager;
+ private Point mTempPoint = new Point();
+ private DisplayMetrics mTempMetrics = new DisplayMetrics();
+
+ private DeviceDisplayInfo(Context context) {
+ mAppContext = context.getApplicationContext();
+ mWinManager = (WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ /**
+ * @return Display height in physical pixels.
+ */
+ @CalledByNative
+ public int getDisplayHeight() {
+ getDisplay().getSize(mTempPoint);
+ return mTempPoint.y;
+ }
+
+ /**
+ * @return Display width in physical pixels.
+ */
+ @CalledByNative
+ public int getDisplayWidth() {
+ getDisplay().getSize(mTempPoint);
+ return mTempPoint.x;
+ }
+
+ /**
+ * @return Real physical display height in physical pixels.
+ */
+ @CalledByNative
+ public int getPhysicalDisplayHeight() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return 0;
+ }
+ getDisplay().getRealSize(mTempPoint);
+ return mTempPoint.y;
+ }
+
+ /**
+ * @return Real physical display width in physical pixels.
+ */
+ @CalledByNative
+ public int getPhysicalDisplayWidth() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return 0;
+ }
+ getDisplay().getRealSize(mTempPoint);
+ return mTempPoint.x;
+ }
+
+ @SuppressWarnings("deprecation")
+ private int getPixelFormat() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return getDisplay().getPixelFormat();
+ }
+ // JellyBean MR1 and later always uses RGBA_8888.
+ return PixelFormat.RGBA_8888;
+ }
+
+ /**
+ * @return Bits per pixel.
+ */
+ @CalledByNative
+ public int getBitsPerPixel() {
+ int format = getPixelFormat();
+ PixelFormat info = new PixelFormat();
+ PixelFormat.getPixelFormatInfo(format, info);
+ return info.bitsPerPixel;
+ }
+
+ /**
+ * @return Bits per component.
+ */
+ @SuppressWarnings("deprecation")
+ @CalledByNative
+ public int getBitsPerComponent() {
+ int format = getPixelFormat();
+ switch (format) {
+ case PixelFormat.RGBA_4444:
+ return 4;
+
+ case PixelFormat.RGBA_5551:
+ return 5;
+
+ case PixelFormat.RGBA_8888:
+ case PixelFormat.RGBX_8888:
+ case PixelFormat.RGB_888:
+ return 8;
+
+ case PixelFormat.RGB_332:
+ return 2;
+
+ case PixelFormat.RGB_565:
+ return 5;
+
+ // Non-RGB formats.
+ case PixelFormat.A_8:
+ case PixelFormat.LA_88:
+ case PixelFormat.L_8:
+ return 0;
+
+ // Unknown format. Use 8 as a sensible default.
+ default:
+ return 8;
+ }
+ }
+
+ /**
+ * @return A scaling factor for the Density Independent Pixel unit. 1.0 is
+ * 160dpi, 0.75 is 120dpi, 2.0 is 320dpi.
+ */
+ @CalledByNative
+ public double getDIPScale() {
+ getDisplay().getMetrics(mTempMetrics);
+ return mTempMetrics.density;
+ }
+
+ /**
+ * @return Smallest screen size in density-independent pixels that the
+ * application will see, regardless of orientation.
+ */
+ @CalledByNative
+ private int getSmallestDIPWidth() {
+ return mAppContext.getResources().getConfiguration().smallestScreenWidthDp;
+ }
+
+ /**
+ * @return the screen's rotation angle from its 'natural' orientation.
+ * Expected values are one of { 0, 90, 180, 270 }.
+ * See http://developer.android.com/reference/android/view/Display.html#getRotation()
+ * for more information about Display.getRotation() behavior.
+ */
+ @CalledByNative
+ public int getRotationDegrees() {
+ switch (getDisplay().getRotation()) {
+ case Surface.ROTATION_0:
+ return 0;
+ case Surface.ROTATION_90:
+ return 90;
+ case Surface.ROTATION_180:
+ return 180;
+ case Surface.ROTATION_270:
+ return 270;
+ }
+
+ // This should not happen.
+ assert false;
+ return 0;
+ }
+
+ /**
+ * Inform the native implementation to update its cached representation of
+ * the DeviceDisplayInfo values.
+ */
+ public void updateNativeSharedDisplayInfo() {
+ nativeUpdateSharedDeviceDisplayInfo(
+ getDisplayHeight(), getDisplayWidth(),
+ getPhysicalDisplayHeight(), getPhysicalDisplayWidth(),
+ getBitsPerPixel(), getBitsPerComponent(),
+ getDIPScale(), getSmallestDIPWidth(), getRotationDegrees());
+ }
+
+ private Display getDisplay() {
+ return mWinManager.getDefaultDisplay();
+ }
+
+ /**
+ * Creates DeviceDisplayInfo for a given Context.
+ *
+ * @param context A context to use.
+ * @return DeviceDisplayInfo associated with a given Context.
+ */
+ @CalledByNative
+ public static DeviceDisplayInfo create(Context context) {
+ return new DeviceDisplayInfo(context);
+ }
+
+ private native void nativeUpdateSharedDeviceDisplayInfo(
+ int displayHeight, int displayWidth,
+ int physicalDisplayHeight, int physicalDisplayWidth,
+ int bitsPerPixel, int bitsPerComponent, double dipScale,
+ int smallestDIPWidth, int rotationDegrees);
+
+}
diff --git a/ui/android/java/src/org/chromium/ui/gfx/OWNERS b/ui/android/java/src/org/chromium/ui/gfx/OWNERS
new file mode 100644
index 0000000000000..316565b6ed378
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/gfx/OWNERS
@@ -0,0 +1,3 @@
+aelias@chromium.org
+jdduke@chromium.org
+skyostil@chromium.org
diff --git a/ui/android/java/src/org/chromium/ui/gfx/ViewConfigurationHelper.java b/ui/android/java/src/org/chromium/ui/gfx/ViewConfigurationHelper.java
new file mode 100644
index 0000000000000..e8fc5b996d8de
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/gfx/ViewConfigurationHelper.java
@@ -0,0 +1,149 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.gfx;
+
+import android.content.ComponentCallbacks;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.ViewConfiguration;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.ui.R;
+
+/**
+ * This class facilitates access to ViewConfiguration-related properties, also
+ * providing native-code notifications when such properties have changed.
+ *
+ */
+@JNINamespace("gfx")
+public class ViewConfigurationHelper {
+
+ // Fallback constants when resource lookup fails, see
+ // ui/android/java/res/values/dimens.xml.
+ private static final float MIN_SCALING_SPAN_MM = 27.0f;
+ private static final float MIN_SCALING_TOUCH_MAJOR_DIP = 48.0f;
+
+ private final Context mAppContext;
+ private ViewConfiguration mViewConfiguration;
+
+ private ViewConfigurationHelper(Context context) {
+ mAppContext = context.getApplicationContext();
+ mViewConfiguration = ViewConfiguration.get(mAppContext);
+ }
+
+ private void registerListener() {
+ mAppContext.registerComponentCallbacks(
+ new ComponentCallbacks() {
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ updateNativeViewConfigurationIfNecessary();
+ }
+
+ @Override
+ public void onLowMemory() {
+ }
+ });
+ }
+
+ private void updateNativeViewConfigurationIfNecessary() {
+ // The ViewConfiguration will differ only if the density has changed.
+ ViewConfiguration configuration = ViewConfiguration.get(mAppContext);
+ if (mViewConfiguration == configuration) return;
+
+ mViewConfiguration = configuration;
+ nativeUpdateSharedViewConfiguration(
+ getScaledMaximumFlingVelocity(),
+ getScaledMinimumFlingVelocity(),
+ getScaledTouchSlop(),
+ getScaledDoubleTapSlop(),
+ getScaledMinScalingSpan(),
+ getScaledMinScalingTouchMajor());
+ }
+
+ @CalledByNative
+ private static int getDoubleTapTimeout() {
+ return ViewConfiguration.getDoubleTapTimeout();
+ }
+
+ @CalledByNative
+ private static int getLongPressTimeout() {
+ return ViewConfiguration.getLongPressTimeout();
+ }
+
+ @CalledByNative
+ private static int getTapTimeout() {
+ return ViewConfiguration.getTapTimeout();
+ }
+
+ @CalledByNative
+ private static float getScrollFriction() {
+ return ViewConfiguration.getScrollFriction();
+ }
+
+ @CalledByNative
+ private int getScaledMaximumFlingVelocity() {
+ return mViewConfiguration.getScaledMaximumFlingVelocity();
+ }
+
+ @CalledByNative
+ private int getScaledMinimumFlingVelocity() {
+ return mViewConfiguration.getScaledMinimumFlingVelocity();
+ }
+
+ @CalledByNative
+ private int getScaledTouchSlop() {
+ return mViewConfiguration.getScaledTouchSlop();
+ }
+
+ @CalledByNative
+ private int getScaledDoubleTapSlop() {
+ return mViewConfiguration.getScaledDoubleTapSlop();
+ }
+
+ @CalledByNative
+ private int getScaledMinScalingSpan() {
+ final Resources res = mAppContext.getResources();
+ int id = res.getIdentifier("config_minScalingSpan", "dimen", "android");
+ // Fall back to a sensible default if the internal identifier does not exist.
+ if (id == 0) id = R.dimen.config_min_scaling_span;
+ try {
+ return res.getDimensionPixelSize(id);
+ } catch (Resources.NotFoundException e) {
+ assert false : "MinScalingSpan resource lookup failed.";
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, MIN_SCALING_SPAN_MM,
+ res.getDisplayMetrics());
+ }
+ }
+
+ @CalledByNative
+ private int getScaledMinScalingTouchMajor() {
+ final Resources res = mAppContext.getResources();
+ int id = res.getIdentifier("config_minScalingTouchMajor", "dimen", "android");
+ // Fall back to a sensible default if the internal identifier does not exist.
+ if (id == 0) id = R.dimen.config_min_scaling_touch_major;
+ try {
+ return res.getDimensionPixelSize(id);
+ } catch (Resources.NotFoundException e) {
+ assert false : "MinScalingTouchMajor resource lookup failed.";
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ MIN_SCALING_TOUCH_MAJOR_DIP, res.getDisplayMetrics());
+ }
+ }
+
+ @CalledByNative
+ private static ViewConfigurationHelper createWithListener(Context context) {
+ ViewConfigurationHelper viewConfigurationHelper = new ViewConfigurationHelper(context);
+ viewConfigurationHelper.registerListener();
+ return viewConfigurationHelper;
+ }
+
+ private native void nativeUpdateSharedViewConfiguration(
+ int scaledMaximumFlingVelocity, int scaledMinimumFlingVelocity,
+ int scaledTouchSlop, int scaledDoubleTapSlop,
+ int scaledMinScalingSpan, int scaledMinScalingTouchMajor);
+}
diff --git a/ui/android/java/src/org/chromium/ui/gl/OWNERS b/ui/android/java/src/org/chromium/ui/gl/OWNERS
new file mode 100644
index 0000000000000..3a065315bc09a
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/gl/OWNERS
@@ -0,0 +1,2 @@
+epenner@chromium.org
+sievers@chromium.org
diff --git a/ui/android/java/src/org/chromium/ui/gl/SurfaceTextureListener.java b/ui/android/java/src/org/chromium/ui/gl/SurfaceTextureListener.java
new file mode 100644
index 0000000000000..bc57e1faca2e4
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/gl/SurfaceTextureListener.java
@@ -0,0 +1,40 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.gl;
+
+import android.graphics.SurfaceTexture;
+
+import org.chromium.base.JNINamespace;
+
+/**
+ * Listener to an android SurfaceTexture object for frame availability.
+ */
+@JNINamespace("gfx")
+class SurfaceTextureListener implements SurfaceTexture.OnFrameAvailableListener {
+ // Used to determine the class instance to dispatch the native call to.
+ private final long mNativeSurfaceTextureListener;
+
+ SurfaceTextureListener(long nativeSurfaceTextureListener) {
+ assert nativeSurfaceTextureListener != 0;
+ mNativeSurfaceTextureListener = nativeSurfaceTextureListener;
+ }
+
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ nativeFrameAvailable(mNativeSurfaceTextureListener);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nativeDestroy(mNativeSurfaceTextureListener);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private native void nativeFrameAvailable(long nativeSurfaceTextureListener);
+ private native void nativeDestroy(long nativeSurfaceTextureListener);
+}
diff --git a/ui/android/java/src/org/chromium/ui/gl/SurfaceTexturePlatformWrapper.java b/ui/android/java/src/org/chromium/ui/gl/SurfaceTexturePlatformWrapper.java
new file mode 100644
index 0000000000000..e785af6a108c3
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/gl/SurfaceTexturePlatformWrapper.java
@@ -0,0 +1,78 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.gl;
+
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.util.Log;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+
+/**
+ * Wrapper class for the underlying platform's SurfaceTexture in order to
+ * provide a stable JNI API.
+ */
+@JNINamespace("gfx")
+class SurfaceTexturePlatformWrapper {
+
+ private static final String TAG = "SurfaceTexturePlatformWrapper";
+
+ @CalledByNative
+ private static SurfaceTexture create(int textureId) {
+ return new SurfaceTexture(textureId);
+ }
+
+ @CalledByNative
+ private static SurfaceTexture createSingleBuffered(int textureId) {
+ assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ return new SurfaceTexture(textureId, true);
+ }
+
+ @CalledByNative
+ private static void destroy(SurfaceTexture surfaceTexture) {
+ surfaceTexture.setOnFrameAvailableListener(null);
+ surfaceTexture.release();
+ }
+
+ @CalledByNative
+ private static void setFrameAvailableCallback(SurfaceTexture surfaceTexture,
+ long nativeSurfaceTextureListener) {
+ surfaceTexture.setOnFrameAvailableListener(
+ new SurfaceTextureListener(nativeSurfaceTextureListener));
+ }
+
+ @CalledByNative
+ private static void updateTexImage(SurfaceTexture surfaceTexture) {
+ try {
+ surfaceTexture.updateTexImage();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error calling updateTexImage", e);
+ }
+ }
+
+ @CalledByNative
+ private static void releaseTexImage(SurfaceTexture surfaceTexture) {
+ assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ surfaceTexture.releaseTexImage();
+ }
+
+ @CalledByNative
+ private static void getTransformMatrix(SurfaceTexture surfaceTexture, float[] matrix) {
+ surfaceTexture.getTransformMatrix(matrix);
+ }
+
+ @CalledByNative
+ private static void attachToGLContext(SurfaceTexture surfaceTexture, int texName) {
+ assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+ surfaceTexture.attachToGLContext(texName);
+ }
+
+ @CalledByNative
+ private static void detachFromGLContext(SurfaceTexture surfaceTexture) {
+ assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+ surfaceTexture.detachFromGLContext();
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/interpolators/BakedBezierInterpolator.java b/ui/android/java/src/org/chromium/ui/interpolators/BakedBezierInterpolator.java
new file mode 100644
index 0000000000000..bffcb4890f3ad
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/interpolators/BakedBezierInterpolator.java
@@ -0,0 +1,163 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.interpolators;
+
+import android.view.animation.Interpolator;
+
+/**
+ * A pre-baked bezier-curved interpolator for quantum-paper transitions.
+ * TODO(dtrainor): Move to the API Compatability version iff that supports the curves we need and
+ * once we move to that SDK.
+ */
+public class BakedBezierInterpolator implements Interpolator {
+ /**
+ * Lookup table values.
+ * Generated using a Bezier curve from (0,0) to (1,1) with control points:
+ * P0 (0.0, 0.0)
+ * P1 (0.4, 0.0)
+ * P2 (0.2, 1.0)
+ * P3 (1.0, 1.0)
+ *
+ * Values sampled with x at regular intervals between 0 and 1.
+ */
+ private static final float[] TRANSFORM_VALUES = new float[] {
+ 0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f,
+ 0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f,
+ 0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f,
+ 0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f,
+ 0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f,
+ 0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f,
+ 0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f,
+ 0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f,
+ 0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f,
+ 0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f
+ };
+
+ /**
+ * Lookup table values.
+ * Generated using a Bezier curve from (0,0) to (1,1) with control points:
+ * P0 (0.0, 0.0)
+ * P1 (0.4, 0.0)
+ * P2 (1.0, 1.0)
+ * P3 (1.0, 1.0)
+ *
+ * Values sampled with x at regular intervals between 0 and 1.
+ */
+ private static final float[] FADE_OUT_VALUES = new float[] {
+ 0.0f, 0.0002f, 0.0008f, 0.0019f, 0.0032f, 0.0049f, 0.0069f, 0.0093f, 0.0119f, 0.0149f,
+ 0.0182f, 0.0218f, 0.0257f, 0.0299f, 0.0344f, 0.0392f, 0.0443f, 0.0496f, 0.0552f, 0.0603f,
+ 0.0656f, 0.0719f, 0.0785f, 0.0853f, 0.0923f, 0.0986f, 0.1051f, 0.1128f, 0.1206f, 0.1287f,
+ 0.1359f, 0.1433f, 0.1519f, 0.1607f, 0.1696f, 0.1776f, 0.1857f, 0.1952f, 0.2048f, 0.2145f,
+ 0.2232f, 0.2319f, 0.2421f, 0.2523f, 0.2627f, 0.2733f, 0.2826f, 0.2919f, 0.3027f, 0.3137f,
+ 0.3247f, 0.3358f, 0.3469f, 0.3582f, 0.3695f, 0.3809f, 0.3924f, 0.4039f, 0.4154f, 0.427f,
+ 0.4386f, 0.4503f, 0.4619f, 0.4751f, 0.4883f, 0.5f, 0.5117f, 0.5264f, 0.5381f, 0.5497f,
+ 0.5643f, 0.5759f, 0.5904f, 0.6033f, 0.6162f, 0.6305f, 0.6446f, 0.6587f, 0.6698f, 0.6836f,
+ 0.7f, 0.7134f, 0.7267f, 0.7425f, 0.7554f, 0.7706f, 0.7855f, 0.8f, 0.8143f, 0.8281f, 0.8438f,
+ 0.8588f, 0.8733f, 0.8892f, 0.9041f, 0.9215f, 0.9344f, 0.9518f, 0.9667f, 0.9826f, 0.9993f
+ };
+
+ /**
+ * Lookup table values.
+ * Generated using a Bezier curve from (0,0) to (1,1) with control points:
+ * P0 (0.0, 0.0)
+ * P1 (0.0, 0.0)
+ * P2 (0.2, 1.0)
+ * P3 (1.0, 1.0)
+ *
+ * Values sampled with x at regular intervals between 0 and 1.
+ */
+ private static final float[] FADE_IN_VALUES = new float[] {
+ 0.0029f, 0.043f, 0.0785f, 0.1147f, 0.1476f, 0.1742f, 0.2024f, 0.2319f, 0.2575f, 0.2786f,
+ 0.3055f, 0.3274f, 0.3498f, 0.3695f, 0.3895f, 0.4096f, 0.4299f, 0.4474f, 0.4649f, 0.4824f,
+ 0.5f, 0.5176f, 0.5322f, 0.5468f, 0.5643f, 0.5788f, 0.5918f, 0.6048f, 0.6191f, 0.6333f,
+ 0.6446f, 0.6573f, 0.6698f, 0.6808f, 0.6918f, 0.704f, 0.7148f, 0.7254f, 0.7346f, 0.7451f,
+ 0.7554f, 0.7655f, 0.7731f, 0.783f, 0.7916f, 0.8f, 0.8084f, 0.8166f, 0.8235f, 0.8315f,
+ 0.8393f, 0.8459f, 0.8535f, 0.8599f, 0.8672f, 0.8733f, 0.8794f, 0.8853f, 0.8911f, 0.8967f,
+ 0.9023f, 0.9077f, 0.9121f, 0.9173f, 0.9224f, 0.9265f, 0.9313f, 0.9352f, 0.9397f, 0.9434f,
+ 0.9476f, 0.9511f, 0.9544f, 0.9577f, 0.9614f, 0.9644f, 0.9673f, 0.9701f, 0.9727f, 0.9753f,
+ 0.9777f, 0.98f, 0.9818f, 0.9839f, 0.9859f, 0.9877f, 0.9891f, 0.9907f, 0.9922f, 0.9933f,
+ 0.9946f, 0.9957f, 0.9966f, 0.9974f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f
+ };
+
+ /**
+ * Lookup table values.
+ * Generated using a Bezier curve from (0,0) to (1,1) with control points:
+ * P0 (0.0, 0.0)
+ * P1 (0.0, 0.84)
+ * P2 (0.13, 0.99)
+ * P3 (1.0, 1.0)
+ */
+ private static final float[] TRANSFORM_FOLLOW_THROUGH_VALUES = new float[] {
+ 0.0767f, 0.315f, 0.4173f, 0.484f, 0.5396f, 0.5801f, 0.6129f, 0.644f, 0.6687f, 0.6876f,
+ 0.7102f, 0.7276f, 0.7443f, 0.7583f, 0.7718f, 0.7849f, 0.7975f, 0.8079f, 0.8179f, 0.8276f,
+ 0.8355f, 0.8446f, 0.8519f, 0.859f, 0.8659f, 0.8726f, 0.8791f, 0.8841f, 0.8902f, 0.8949f,
+ 0.9001f, 0.9051f, 0.9094f, 0.9136f, 0.9177f, 0.9217f, 0.925f, 0.9283f, 0.9319f, 0.9355f,
+ 0.938f, 0.9413f, 0.9437f, 0.9469f, 0.9491f, 0.9517f, 0.9539f, 0.9563f, 0.9583f, 0.9603f,
+ 0.9622f, 0.9643f, 0.9661f, 0.9679f, 0.9693f, 0.9709f, 0.9725f, 0.974f, 0.9753f, 0.9767f,
+ 0.9779f, 0.9792f, 0.9803f, 0.9816f, 0.9826f, 0.9835f, 0.9845f, 0.9854f, 0.9863f, 0.9872f,
+ 0.988f, 0.9888f, 0.9895f, 0.9903f, 0.991f, 0.9917f, 0.9922f, 0.9928f, 0.9934f, 0.9939f,
+ 0.9944f, 0.9948f, 0.9953f, 0.9957f, 0.9962f, 0.9965f, 0.9969f, 0.9972f, 0.9975f, 0.9978f,
+ 0.9981f, 0.9984f, 0.9986f, 0.9989f, 0.9991f, 0.9992f, 0.9994f, 0.9996f, 0.9997f, 0.9999f,
+ 1.0f
+ };
+
+ /**
+ * 0.4 to 0.2 bezier curve. Should be used for general movement.
+ */
+ public static final BakedBezierInterpolator TRANSFORM_CURVE =
+ new BakedBezierInterpolator(TRANSFORM_VALUES);
+
+ /**
+ * 0.4 to 1.0 bezier curve. Should be used for fading out.
+ */
+ public static final BakedBezierInterpolator FADE_OUT_CURVE =
+ new BakedBezierInterpolator(FADE_OUT_VALUES);
+
+ /**
+ * 0.0 to 0.2 bezier curve. Should be used for fading in.
+ */
+ public static final BakedBezierInterpolator FADE_IN_CURVE =
+ new BakedBezierInterpolator(FADE_IN_VALUES);
+
+ /**
+ * 0.0 to 0.13 by 0.84 to 0.99 bezier curve. Should be used for very quick transforms.
+ */
+ public static final BakedBezierInterpolator TRANSFORM_FOLLOW_THROUGH_CURVE =
+ new BakedBezierInterpolator(TRANSFORM_FOLLOW_THROUGH_VALUES);
+
+ private final float[] mValues;
+ private final float mStepSize;
+
+ /**
+ * Use the INSTANCE variable instead of instantiating.
+ */
+ private BakedBezierInterpolator(float[] values) {
+ super();
+ mValues = values;
+ mStepSize = 1.f / (mValues.length - 1);
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ if (input >= 1.0f) {
+ return 1.0f;
+ }
+
+ if (input <= 0f) {
+ return 0f;
+ }
+
+ int position = Math.min(
+ (int) (input * (mValues.length - 1)),
+ mValues.length - 2);
+
+ float quantized = position * mStepSize;
+ float difference = input - quantized;
+ float weight = difference / mStepSize;
+
+ return mValues[position] + weight * (mValues[position + 1] - mValues[position]);
+ }
+
+}
\ No newline at end of file
diff --git a/ui/android/java/src/org/chromium/ui/picker/ChromeDatePickerDialog.java b/ui/android/java/src/org/chromium/ui/picker/ChromeDatePickerDialog.java
new file mode 100644
index 0000000000000..f236ee161758c
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/ChromeDatePickerDialog.java
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.widget.DatePicker;
+
+/**
+ * The behavior of the DatePickerDialog changed after JellyBean so it now calls
+ * OndateSetListener.onDateSet() even when the dialog is dismissed (e.g. back button, tap
+ * outside). This class will call the listener instead of the DatePickerDialog only when the
+ * BUTTON_POSITIVE has been clicked.
+ */
+class ChromeDatePickerDialog extends android.app.DatePickerDialog {
+ private final OnDateSetListener mCallBack;
+
+ public ChromeDatePickerDialog(Context context,
+ OnDateSetListener callBack,
+ int year,
+ int monthOfYear,
+ int dayOfMonth) {
+ super(context, 0, callBack, year, monthOfYear, dayOfMonth);
+ mCallBack = callBack;
+ }
+
+ /**
+ * The superclass DatePickerDialog has null for OnDateSetListener so we need to call the
+ * listener manually.
+ */
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == BUTTON_POSITIVE && mCallBack != null) {
+ DatePicker datePicker = getDatePicker();
+ datePicker.clearFocus();
+ mCallBack.onDateSet(datePicker, datePicker.getYear(),
+ datePicker.getMonth(), datePicker.getDayOfMonth());
+ }
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/DateDialogNormalizer.java b/ui/android/java/src/org/chromium/ui/picker/DateDialogNormalizer.java
new file mode 100644
index 0000000000000..25a7495638717
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/DateDialogNormalizer.java
@@ -0,0 +1,77 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.widget.DatePicker;
+import android.widget.DatePicker.OnDateChangedListener;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Normalize a date dialog so that it respect min and max.
+ */
+public class DateDialogNormalizer {
+
+ private static void setLimits(DatePicker picker, long minMillis, long maxMillis) {
+ // DatePicker intervals are non inclusive, the DatePicker will throw an
+ // exception when setting the min/max attribute to the current date
+ // so make sure this never happens
+ if (maxMillis <= minMillis) {
+ return;
+ }
+ Calendar minCal = trimToDate(minMillis);
+ Calendar maxCal = trimToDate(maxMillis);
+ int currentYear = picker.getYear();
+ int currentMonth = picker.getMonth();
+ int currentDayOfMonth = picker.getDayOfMonth();
+ picker.updateDate(maxCal.get(Calendar.YEAR),
+ maxCal.get(Calendar.MONTH),
+ maxCal.get(Calendar.DAY_OF_MONTH));
+ picker.setMinDate(minCal.getTimeInMillis());
+ picker.updateDate(minCal.get(Calendar.YEAR),
+ minCal.get(Calendar.MONTH),
+ minCal.get(Calendar.DAY_OF_MONTH));
+ picker.setMaxDate(maxCal.getTimeInMillis());
+
+ // Restore the current date, this will keep the min/max settings
+ // previously set into account.
+ picker.updateDate(currentYear, currentMonth, currentDayOfMonth);
+ }
+
+ private static Calendar trimToDate(long time) {
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ cal.clear();
+ cal.setTimeInMillis(time);
+ Calendar result = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ result.clear();
+ result.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
+ 0, 0, 0);
+ return result;
+ }
+
+ /**
+ * Normalizes an existing DateDialogPicker changing the default date if
+ * needed to comply with the {@code min} and {@code max} attributes.
+ */
+ public static void normalize(DatePicker picker, OnDateChangedListener listener,
+ int year, int month, int day, int hour, int minute, long minMillis, long maxMillis) {
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ calendar.clear();
+ calendar.set(year, month, day, hour, minute, 0);
+ if (calendar.getTimeInMillis() < minMillis) {
+ calendar.clear();
+ calendar.setTimeInMillis(minMillis);
+ } else if (calendar.getTimeInMillis() > maxMillis) {
+ calendar.clear();
+ calendar.setTimeInMillis(maxMillis);
+ }
+ picker.init(
+ calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH), listener);
+
+ setLimits(picker, minMillis, maxMillis);
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/DateTimePickerDialog.java b/ui/android/java/src/org/chromium/ui/picker/DateTimePickerDialog.java
new file mode 100644
index 0000000000000..c9dde077ecf8c
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/DateTimePickerDialog.java
@@ -0,0 +1,147 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.text.format.Time;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.DatePicker;
+import android.widget.DatePicker.OnDateChangedListener;
+import android.widget.TimePicker;
+import android.widget.TimePicker.OnTimeChangedListener;
+
+import org.chromium.ui.R;
+
+public class DateTimePickerDialog extends AlertDialog implements OnClickListener,
+ OnDateChangedListener, OnTimeChangedListener {
+ private final DatePicker mDatePicker;
+ private final TimePicker mTimePicker;
+ private final OnDateTimeSetListener mCallBack;
+
+ private final long mMinTimeMillis;
+ private final long mMaxTimeMillis;
+
+ /**
+ * The callback used to indicate the user is done filling in the date.
+ */
+ public interface OnDateTimeSetListener {
+
+ /**
+ * @param dateView The DatePicker view associated with this listener.
+ * @param timeView The TimePicker view associated with this listener.
+ * @param year The year that was set.
+ * @param monthOfYear The month that was set (0-11) for compatibility
+ * with {@link java.util.Calendar}.
+ * @param dayOfMonth The day of the month that was set.
+ * @param hourOfDay The hour that was set.
+ * @param minute The minute that was set.
+ */
+ void onDateTimeSet(DatePicker dateView, TimePicker timeView, int year, int monthOfYear,
+ int dayOfMonth, int hourOfDay, int minute);
+ }
+
+ /**
+ * @param context The context the dialog is to run in.
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param monthOfYear The initial month of the dialog.
+ * @param dayOfMonth The initial day of the dialog.
+ */
+ public DateTimePickerDialog(Context context,
+ OnDateTimeSetListener callBack,
+ int year,
+ int monthOfYear,
+ int dayOfMonth,
+ int hourOfDay, int minute, boolean is24HourView,
+ double min, double max) {
+ super(context, 0);
+
+ mMinTimeMillis = (long) min;
+ mMaxTimeMillis = (long) max;
+
+ mCallBack = callBack;
+
+ setButton(BUTTON_POSITIVE, context.getText(
+ R.string.date_picker_dialog_set), this);
+ setButton(BUTTON_NEGATIVE, context.getText(android.R.string.cancel),
+ (OnClickListener) null);
+ setIcon(0);
+ setTitle(context.getText(R.string.date_time_picker_dialog_title));
+
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.date_time_picker_dialog, null);
+ setView(view);
+ mDatePicker = (DatePicker) view.findViewById(R.id.date_picker);
+ DateDialogNormalizer.normalize(mDatePicker, this,
+ year, monthOfYear, dayOfMonth, hourOfDay, minute, mMinTimeMillis, mMaxTimeMillis);
+
+ mTimePicker = (TimePicker) view.findViewById(R.id.time_picker);
+ mTimePicker.setIs24HourView(is24HourView);
+ mTimePicker.setCurrentHour(hourOfDay);
+ mTimePicker.setCurrentMinute(minute);
+ mTimePicker.setOnTimeChangedListener(this);
+ onTimeChanged(mTimePicker, mTimePicker.getCurrentHour(),
+ mTimePicker.getCurrentMinute());
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ tryNotifyDateTimeSet();
+ }
+
+ private void tryNotifyDateTimeSet() {
+ if (mCallBack != null) {
+ mDatePicker.clearFocus();
+ mCallBack.onDateTimeSet(mDatePicker, mTimePicker, mDatePicker.getYear(),
+ mDatePicker.getMonth(), mDatePicker.getDayOfMonth(),
+ mTimePicker.getCurrentHour(), mTimePicker.getCurrentMinute());
+ }
+ }
+
+ @Override
+ public void onDateChanged(DatePicker view, int year,
+ int month, int day) {
+ // Signal a time change so the max/min checks can be applied.
+ if (mTimePicker != null) {
+ onTimeChanged(mTimePicker, mTimePicker.getCurrentHour(),
+ mTimePicker.getCurrentMinute());
+ }
+ }
+
+ @Override
+ public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
+ Time time = new Time();
+ time.set(0, mTimePicker.getCurrentMinute(),
+ mTimePicker.getCurrentHour(), mDatePicker.getDayOfMonth(),
+ mDatePicker.getMonth(), mDatePicker.getYear());
+
+ if (time.toMillis(true) < mMinTimeMillis) {
+ time.set(mMinTimeMillis);
+ } else if (time.toMillis(true) > mMaxTimeMillis) {
+ time.set(mMaxTimeMillis);
+ }
+ mTimePicker.setCurrentHour(time.hour);
+ mTimePicker.setCurrentMinute(time.minute);
+ }
+
+ /**
+ * Sets the current date.
+ *
+ * @param year The date year.
+ * @param monthOfYear The date month.
+ * @param dayOfMonth The date day of month.
+ */
+ public void updateDateTime(int year, int monthOfYear, int dayOfMonth,
+ int hourOfDay, int minutOfHour) {
+ mDatePicker.updateDate(year, monthOfYear, dayOfMonth);
+ mTimePicker.setCurrentHour(hourOfDay);
+ mTimePicker.setCurrentMinute(minutOfHour);
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/DateTimeSuggestion.java b/ui/android/java/src/org/chromium/ui/picker/DateTimeSuggestion.java
new file mode 100644
index 0000000000000..4c814681ed373
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/DateTimeSuggestion.java
@@ -0,0 +1,59 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+/**
+ * Date/time suggestion container used to store information for each suggestion that will be shown
+ * in the suggestion list dialog. Keep in sync with date_time_suggestion.h.
+ */
+public class DateTimeSuggestion {
+ private final double mValue;
+ private final String mLocalizedValue;
+ private final String mLabel;
+
+ /**
+ * Constructs a color suggestion container.
+ * @param value The suggested date/time value.
+ * @param localizedValue The suggested value localized.
+ * @param label The label for the suggestion.
+ */
+ public DateTimeSuggestion(double value, String localizedValue, String label) {
+ mValue = value;
+ mLocalizedValue = localizedValue;
+ mLabel = label;
+ }
+
+ double value() {
+ return mValue;
+ }
+
+ String localizedValue() {
+ return mLocalizedValue;
+ }
+
+ String label() {
+ return mLabel;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof DateTimeSuggestion)) {
+ return false;
+ }
+ final DateTimeSuggestion other = (DateTimeSuggestion) object;
+ return mValue == other.mValue &&
+ mLocalizedValue == other.mLocalizedValue &&
+ mLabel == other.mLabel;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 31;
+ hash = 37 * hash + (int) mValue;
+ hash = 37 * hash + mLocalizedValue.hashCode();
+ hash = 37 * hash + mLabel.hashCode();
+ return hash;
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/DateTimeSuggestionListAdapter.java b/ui/android/java/src/org/chromium/ui/picker/DateTimeSuggestionListAdapter.java
new file mode 100644
index 0000000000000..c7a166c9d626b
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/DateTimeSuggestionListAdapter.java
@@ -0,0 +1,54 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import org.chromium.ui.R;
+
+import java.util.List;
+
+/**
+ * Date/time suggestion adapter for the suggestion dialog.
+ */
+class DateTimeSuggestionListAdapter extends ArrayAdapter {
+ private final Context mContext;
+
+ DateTimeSuggestionListAdapter(Context context, List objects) {
+ super(context, R.layout.date_time_suggestion, objects);
+ mContext = context;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View layout = convertView;
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ layout = inflater.inflate(R.layout.date_time_suggestion, parent, false);
+ }
+ TextView labelView = (TextView) layout.findViewById(R.id.date_time_suggestion_value);
+ TextView sublabelView = (TextView) layout.findViewById(R.id.date_time_suggestion_label);
+
+ if (position == getCount() - 1) {
+ labelView.setText(mContext.getText(R.string.date_picker_dialog_other_button_label));
+ sublabelView.setText("");
+ } else {
+ labelView.setText(getItem(position).localizedValue());
+ sublabelView.setText(getItem(position).label());
+ }
+
+ return layout;
+ }
+
+ @Override
+ public int getCount() {
+ return super.getCount() + 1;
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/InputDialogContainer.java b/ui/android/java/src/org/chromium/ui/picker/InputDialogContainer.java
new file mode 100644
index 0000000000000..48304d546fab0
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/InputDialogContainer.java
@@ -0,0 +1,383 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.app.AlertDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.text.format.DateFormat;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.DatePicker;
+import android.widget.ListView;
+import android.widget.TimePicker;
+
+import org.chromium.ui.R;
+import org.chromium.ui.picker.DateTimePickerDialog.OnDateTimeSetListener;
+import org.chromium.ui.picker.MultiFieldTimePickerDialog.OnMultiFieldTimeSetListener;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Opens the appropriate date/time picker dialog for the given dialog type.
+ */
+public class InputDialogContainer {
+
+ public interface InputActionDelegate {
+ void cancelDateTimeDialog();
+ void replaceDateTime(double value);
+ }
+
+ private static int sTextInputTypeDate;
+ private static int sTextInputTypeDateTime;
+ private static int sTextInputTypeDateTimeLocal;
+ private static int sTextInputTypeMonth;
+ private static int sTextInputTypeTime;
+ private static int sTextInputTypeWeek;
+
+ private final Context mContext;
+
+ // Prevents sending two notifications (from onClick and from onDismiss)
+ private boolean mDialogAlreadyDismissed;
+
+ private AlertDialog mDialog;
+ private final InputActionDelegate mInputActionDelegate;
+
+ public static void initializeInputTypes(int textInputTypeDate,
+ int textInputTypeDateTime, int textInputTypeDateTimeLocal,
+ int textInputTypeMonth, int textInputTypeTime,
+ int textInputTypeWeek) {
+ sTextInputTypeDate = textInputTypeDate;
+ sTextInputTypeDateTime = textInputTypeDateTime;
+ sTextInputTypeDateTimeLocal = textInputTypeDateTimeLocal;
+ sTextInputTypeMonth = textInputTypeMonth;
+ sTextInputTypeTime = textInputTypeTime;
+ sTextInputTypeWeek = textInputTypeWeek;
+ }
+
+ public static boolean isDialogInputType(int type) {
+ return type == sTextInputTypeDate || type == sTextInputTypeTime
+ || type == sTextInputTypeDateTime || type == sTextInputTypeDateTimeLocal
+ || type == sTextInputTypeMonth || type == sTextInputTypeWeek;
+ }
+
+ public InputDialogContainer(Context context, InputActionDelegate inputActionDelegate) {
+ mContext = context;
+ mInputActionDelegate = inputActionDelegate;
+ }
+
+ public void showPickerDialog(final int dialogType, double dialogValue,
+ double min, double max, double step) {
+ Calendar cal;
+ // |dialogValue|, |min|, |max| mean different things depending on the |dialogType|.
+ // For input type=month is the number of months since 1970.
+ // For input type=time it is milliseconds since midnight.
+ // For other types they are just milliseconds since 1970.
+ // If |dialogValue| is NaN it means an empty value. We will show the current time.
+ if (Double.isNaN(dialogValue)) {
+ cal = Calendar.getInstance();
+ cal.set(Calendar.MILLISECOND, 0);
+ } else {
+ if (dialogType == sTextInputTypeMonth) {
+ cal = MonthPicker.createDateFromValue(dialogValue);
+ } else if (dialogType == sTextInputTypeWeek) {
+ cal = WeekPicker.createDateFromValue(dialogValue);
+ } else {
+ GregorianCalendar gregorianCalendar =
+ new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ // According to the HTML spec we only use the Gregorian calendar
+ // so we ignore the Julian/Gregorian transition.
+ gregorianCalendar.setGregorianChange(new Date(Long.MIN_VALUE));
+ gregorianCalendar.setTimeInMillis((long) dialogValue);
+ cal = gregorianCalendar;
+ }
+ }
+ if (dialogType == sTextInputTypeDate) {
+ showPickerDialog(dialogType,
+ cal.get(Calendar.YEAR),
+ cal.get(Calendar.MONTH),
+ cal.get(Calendar.DAY_OF_MONTH),
+ 0, 0, 0, 0, 0, min, max, step);
+ } else if (dialogType == sTextInputTypeTime) {
+ showPickerDialog(dialogType, 0, 0, 0,
+ cal.get(Calendar.HOUR_OF_DAY),
+ cal.get(Calendar.MINUTE),
+ 0, 0, 0, min, max, step);
+ } else if (dialogType == sTextInputTypeDateTime ||
+ dialogType == sTextInputTypeDateTimeLocal) {
+ showPickerDialog(dialogType,
+ cal.get(Calendar.YEAR),
+ cal.get(Calendar.MONTH),
+ cal.get(Calendar.DAY_OF_MONTH),
+ cal.get(Calendar.HOUR_OF_DAY),
+ cal.get(Calendar.MINUTE),
+ cal.get(Calendar.SECOND),
+ cal.get(Calendar.MILLISECOND),
+ 0, min, max, step);
+ } else if (dialogType == sTextInputTypeMonth) {
+ showPickerDialog(dialogType, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), 0,
+ 0, 0, 0, 0, 0, min, max, step);
+ } else if (dialogType == sTextInputTypeWeek) {
+ int year = WeekPicker.getISOWeekYearForDate(cal);
+ int week = WeekPicker.getWeekForDate(cal);
+ showPickerDialog(dialogType, year, 0, 0, 0, 0, 0, 0, week, min, max, step);
+ }
+ }
+
+ void showSuggestionDialog(final int dialogType,
+ final double dialogValue,
+ final double min, final double max, final double step,
+ DateTimeSuggestion[] suggestions) {
+ ListView suggestionListView = new ListView(mContext);
+ final DateTimeSuggestionListAdapter adapter =
+ new DateTimeSuggestionListAdapter(mContext, Arrays.asList(suggestions));
+ suggestionListView.setAdapter(adapter);
+ suggestionListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ if (position == adapter.getCount() - 1) {
+ dismissDialog();
+ showPickerDialog(dialogType, dialogValue, min, max, step);
+ } else {
+ double suggestionValue = adapter.getItem(position).value();
+ mInputActionDelegate.replaceDateTime(suggestionValue);
+ dismissDialog();
+ mDialogAlreadyDismissed = true;
+ }
+ }
+ });
+
+ int dialogTitleId = R.string.date_picker_dialog_title;
+ if (dialogType == sTextInputTypeTime) {
+ dialogTitleId = R.string.time_picker_dialog_title;
+ } else if (dialogType == sTextInputTypeDateTime ||
+ dialogType == sTextInputTypeDateTimeLocal) {
+ dialogTitleId = R.string.date_time_picker_dialog_title;
+ } else if (dialogType == sTextInputTypeMonth) {
+ dialogTitleId = R.string.month_picker_dialog_title;
+ } else if (dialogType == sTextInputTypeWeek) {
+ dialogTitleId = R.string.week_picker_dialog_title;
+ }
+
+ mDialog = new AlertDialog.Builder(mContext)
+ .setTitle(dialogTitleId)
+ .setView(suggestionListView)
+ .setNegativeButton(mContext.getText(android.R.string.cancel),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismissDialog();
+ }
+ })
+ .create();
+
+ mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ if (mDialog == dialog && !mDialogAlreadyDismissed) {
+ mDialogAlreadyDismissed = true;
+ mInputActionDelegate.cancelDateTimeDialog();
+ }
+ }
+ });
+ mDialogAlreadyDismissed = false;
+ mDialog.show();
+ }
+
+ public void showDialog(final int type, final double value,
+ double min, double max, double step,
+ DateTimeSuggestion[] suggestions) {
+ // When the web page asks to show a dialog while there is one already open,
+ // dismiss the old one.
+ dismissDialog();
+ if (suggestions == null) {
+ showPickerDialog(type, value, min, max, step);
+ } else {
+ showSuggestionDialog(type, value, min, max, step, suggestions);
+ }
+ }
+
+ protected void showPickerDialog(final int dialogType,
+ int year, int month, int monthDay,
+ int hourOfDay, int minute, int second, int millis, int week,
+ double min, double max, double step) {
+ if (isDialogShowing()) mDialog.dismiss();
+
+ int stepTime = (int) step;
+
+ if (dialogType == sTextInputTypeDate) {
+ ChromeDatePickerDialog dialog = new ChromeDatePickerDialog(mContext,
+ new DateListener(dialogType),
+ year, month, monthDay);
+ DateDialogNormalizer.normalize(dialog.getDatePicker(), dialog,
+ year, month, monthDay,
+ 0, 0,
+ (long) min, (long) max);
+
+ dialog.setTitle(mContext.getText(R.string.date_picker_dialog_title));
+ mDialog = dialog;
+ } else if (dialogType == sTextInputTypeTime) {
+ mDialog = new MultiFieldTimePickerDialog(
+ mContext, 0 /* theme */ ,
+ hourOfDay, minute, second, millis,
+ (int) min, (int) max, stepTime,
+ DateFormat.is24HourFormat(mContext),
+ new FullTimeListener(dialogType));
+ } else if (dialogType == sTextInputTypeDateTime ||
+ dialogType == sTextInputTypeDateTimeLocal) {
+ mDialog = new DateTimePickerDialog(mContext,
+ new DateTimeListener(dialogType),
+ year, month, monthDay,
+ hourOfDay, minute,
+ DateFormat.is24HourFormat(mContext), min, max);
+ } else if (dialogType == sTextInputTypeMonth) {
+ mDialog = new MonthPickerDialog(mContext, new MonthOrWeekListener(dialogType),
+ year, month, min, max);
+ } else if (dialogType == sTextInputTypeWeek) {
+ mDialog = new WeekPickerDialog(mContext, new MonthOrWeekListener(dialogType),
+ year, week, min, max);
+ }
+
+ mDialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ mContext.getText(R.string.date_picker_dialog_set),
+ (DialogInterface.OnClickListener) mDialog);
+
+ mDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+ mContext.getText(android.R.string.cancel),
+ (DialogInterface.OnClickListener) null);
+
+ mDialog.setButton(DialogInterface.BUTTON_NEUTRAL,
+ mContext.getText(R.string.date_picker_dialog_clear),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mDialogAlreadyDismissed = true;
+ mInputActionDelegate.replaceDateTime(Double.NaN);
+ }
+ });
+
+ mDialog.setOnDismissListener(
+ new OnDismissListener() {
+ @Override
+ public void onDismiss(final DialogInterface dialog) {
+ if (!mDialogAlreadyDismissed) {
+ mDialogAlreadyDismissed = true;
+ mInputActionDelegate.cancelDateTimeDialog();
+ }
+ }
+ });
+
+ mDialogAlreadyDismissed = false;
+ mDialog.show();
+ }
+
+ boolean isDialogShowing() {
+ return mDialog != null && mDialog.isShowing();
+ }
+
+ void dismissDialog() {
+ if (isDialogShowing()) mDialog.dismiss();
+ }
+
+ private class DateListener implements OnDateSetListener {
+ private final int mDialogType;
+
+ DateListener(int dialogType) {
+ mDialogType = dialogType;
+ }
+
+ @Override
+ public void onDateSet(DatePicker view, int year, int month, int monthDay) {
+ setFieldDateTimeValue(mDialogType, year, month, monthDay, 0, 0, 0, 0, 0);
+ }
+ }
+
+ private class FullTimeListener implements OnMultiFieldTimeSetListener {
+ private final int mDialogType;
+ FullTimeListener(int dialogType) {
+ mDialogType = dialogType;
+ }
+
+ @Override
+ public void onTimeSet(int hourOfDay, int minute, int second, int milli) {
+ setFieldDateTimeValue(mDialogType, 0, 0, 0, hourOfDay, minute, second, milli, 0);
+ }
+ }
+
+ private class DateTimeListener implements OnDateTimeSetListener {
+ private final boolean mLocal;
+ private final int mDialogType;
+
+ public DateTimeListener(int dialogType) {
+ mLocal = dialogType == sTextInputTypeDateTimeLocal;
+ mDialogType = dialogType;
+ }
+
+ @Override
+ public void onDateTimeSet(DatePicker dateView, TimePicker timeView,
+ int year, int month, int monthDay,
+ int hourOfDay, int minute) {
+ setFieldDateTimeValue(mDialogType, year, month, monthDay, hourOfDay, minute, 0, 0, 0);
+ }
+ }
+
+ private class MonthOrWeekListener implements TwoFieldDatePickerDialog.OnValueSetListener {
+ private final int mDialogType;
+
+ MonthOrWeekListener(int dialogType) {
+ mDialogType = dialogType;
+ }
+
+ @Override
+ public void onValueSet(int year, int positionInYear) {
+ if (mDialogType == sTextInputTypeMonth) {
+ setFieldDateTimeValue(mDialogType, year, positionInYear, 0, 0, 0, 0, 0, 0);
+ } else {
+ setFieldDateTimeValue(mDialogType, year, 0, 0, 0, 0, 0, 0, positionInYear);
+ }
+ }
+ }
+
+ protected void setFieldDateTimeValue(int dialogType,
+ int year, int month, int monthDay,
+ int hourOfDay, int minute, int second, int millis,
+ int week) {
+ // Prevents more than one callback being sent to the native
+ // side when the dialog triggers multiple events.
+ if (mDialogAlreadyDismissed)
+ return;
+ mDialogAlreadyDismissed = true;
+
+ if (dialogType == sTextInputTypeMonth) {
+ mInputActionDelegate.replaceDateTime((year - 1970) * 12 + month);
+ } else if (dialogType == sTextInputTypeWeek) {
+ mInputActionDelegate.replaceDateTime(
+ WeekPicker.createDateFromWeek(year, week).getTimeInMillis());
+ } else if (dialogType == sTextInputTypeTime) {
+ mInputActionDelegate.replaceDateTime(TimeUnit.HOURS.toMillis(hourOfDay) +
+ TimeUnit.MINUTES.toMillis(minute) +
+ TimeUnit.SECONDS.toMillis(second) +
+ millis);
+ } else {
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ cal.clear();
+ cal.set(Calendar.YEAR, year);
+ cal.set(Calendar.MONTH, month);
+ cal.set(Calendar.DAY_OF_MONTH, monthDay);
+ cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
+ cal.set(Calendar.MINUTE, minute);
+ cal.set(Calendar.SECOND, second);
+ cal.set(Calendar.MILLISECOND, millis);
+ mInputActionDelegate.replaceDateTime(cal.getTimeInMillis());
+ }
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/MonthPicker.java b/ui/android/java/src/org/chromium/ui/picker/MonthPicker.java
new file mode 100644
index 0000000000000..416217c0dc287
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/MonthPicker.java
@@ -0,0 +1,117 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.content.Context;
+
+import org.chromium.ui.R;
+
+import java.text.DateFormatSymbols;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class MonthPicker extends TwoFieldDatePicker {
+ private static final int MONTHS_NUMBER = 12;
+
+ private final String[] mShortMonths;
+
+ public MonthPicker(Context context, double minValue, double maxValue) {
+ super(context, minValue, maxValue);
+
+ getPositionInYearSpinner().setContentDescription(
+ getResources().getString(R.string.accessibility_date_picker_month));
+
+ // initialization based on locale
+ mShortMonths =
+ DateFormatSymbols.getInstance(Locale.getDefault()).getShortMonths();
+
+ // initialize to current date
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), null);
+ }
+
+ /**
+ * Creates a date object from the |value| which is months since epoch.
+ */
+ public static Calendar createDateFromValue(double value) {
+ int year = (int) Math.min(value / 12 + 1970, Integer.MAX_VALUE);
+ int month = (int) (value % 12);
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ cal.clear();
+ cal.set(year, month, 1);
+ return cal;
+ }
+
+ @Override
+ protected Calendar getDateForValue(double value) {
+ return MonthPicker.createDateFromValue(value);
+ }
+
+ @Override
+ protected void setCurrentDate(int year, int month) {
+ Calendar date = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ date.set(year, month, 1);
+ if (date.before(getMinDate())) {
+ setCurrentDate(getMinDate());
+ } else if (date.after(getMaxDate())) {
+ setCurrentDate(getMaxDate());
+ } else {
+ setCurrentDate(date);
+ }
+ }
+
+ @Override
+ protected void updateSpinners() {
+ super.updateSpinners();
+
+ // make sure the month names are a zero based array
+ // with the months in the month spinner
+ String[] displayedValues = Arrays.copyOfRange(mShortMonths,
+ getPositionInYearSpinner().getMinValue(),
+ getPositionInYearSpinner().getMaxValue() + 1);
+ getPositionInYearSpinner().setDisplayedValues(displayedValues);
+ }
+
+ /**
+ * @return The selected month.
+ */
+ public int getMonth() {
+ return getCurrentDate().get(Calendar.MONTH);
+ }
+
+ @Override
+ public int getPositionInYear() {
+ return getMonth();
+ }
+
+ @Override
+ protected int getMaxYear() {
+ return getMaxDate().get(Calendar.YEAR);
+ }
+
+ @Override
+ protected int getMinYear() {
+ return getMinDate().get(Calendar.YEAR);
+ }
+
+
+ @Override
+ protected int getMaxPositionInYear(int year) {
+ if (year == getMaxDate().get(Calendar.YEAR)) {
+ return getMaxDate().get(Calendar.MONTH);
+ }
+ return MONTHS_NUMBER - 1;
+ }
+
+ @Override
+ protected int getMinPositionInYear(int year) {
+ if (year == getMinDate().get(Calendar.YEAR)) {
+ return getMinDate().get(Calendar.MONTH);
+ }
+ return 0;
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/MonthPickerDialog.java b/ui/android/java/src/org/chromium/ui/picker/MonthPickerDialog.java
new file mode 100644
index 0000000000000..58aafa67d1051
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/MonthPickerDialog.java
@@ -0,0 +1,38 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.content.Context;
+
+import org.chromium.ui.R;
+
+public class MonthPickerDialog extends TwoFieldDatePickerDialog {
+
+ /**
+ * @param context The context the dialog is to run in.
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param monthOfYear The initial month of the dialog.
+ */
+ public MonthPickerDialog(Context context, OnValueSetListener callBack,
+ int year, int monthOfYear, double minMonth, double maxMonth) {
+ super(context, callBack, year, monthOfYear, minMonth, maxMonth);
+ setTitle(R.string.month_picker_dialog_title);
+ }
+
+ @Override
+ protected TwoFieldDatePicker createPicker(Context context, double minValue, double maxValue) {
+ return new MonthPicker(context, minValue, maxValue);
+ }
+
+ /**
+ * Gets the {@link MonthPicker} contained in this dialog.
+ *
+ * @return The calendar view.
+ */
+ public MonthPicker getMonthPicker() {
+ return (MonthPicker) mPicker;
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/MultiFieldTimePickerDialog.java b/ui/android/java/src/org/chromium/ui/picker/MultiFieldTimePickerDialog.java
new file mode 100644
index 0000000000000..d03c16509f87a
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/MultiFieldTimePickerDialog.java
@@ -0,0 +1,273 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.NumberPicker;
+
+import org.chromium.ui.R;
+
+import java.util.ArrayList;
+
+/**
+ * A time picker dialog with upto 5 number pickers left to right:
+ * hour, minute, second, milli, AM/PM.
+ *
+ * If is24hourFormat is true then AM/PM picker is not displayed and
+ * hour range is 0..23. Otherwise hour range is 1..12.
+ * The milli picker is not displayed if step >= SECOND_IN_MILLIS
+ * The second picker is not displayed if step >= MINUTE_IN_MILLIS.
+ */
+public class MultiFieldTimePickerDialog
+ extends AlertDialog implements OnClickListener {
+
+ private final NumberPicker mHourSpinner;
+ private final NumberPicker mMinuteSpinner;
+ private final NumberPicker mSecSpinner;
+ private final NumberPicker mMilliSpinner;
+ private final NumberPicker mAmPmSpinner;
+ private final OnMultiFieldTimeSetListener mListener;
+ private final int mStep;
+ private final int mBaseMilli;
+ private final boolean mIs24hourFormat;
+
+ public interface OnMultiFieldTimeSetListener {
+ void onTimeSet(int hourOfDay, int minute, int second, int milli);
+ }
+
+ private static final int SECOND_IN_MILLIS = 1000;
+ private static final int MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+ private static final int HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+
+ public MultiFieldTimePickerDialog(
+ Context context,
+ int theme,
+ int hour, int minute, int second, int milli,
+ int min, int max, int step, boolean is24hourFormat,
+ OnMultiFieldTimeSetListener listener) {
+ super(context, theme);
+ mListener = listener;
+ mStep = step;
+ mIs24hourFormat = is24hourFormat;
+
+ if (min >= max) {
+ min = 0;
+ max = 24 * HOUR_IN_MILLIS - 1;
+ }
+ if (step < 0 || step >= 24 * HOUR_IN_MILLIS) {
+ step = MINUTE_IN_MILLIS;
+ }
+
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(R.layout.multi_field_time_picker_dialog, null);
+ setView(view);
+
+ mHourSpinner = (NumberPicker) view.findViewById(R.id.hour);
+ mMinuteSpinner = (NumberPicker) view.findViewById(R.id.minute);
+ mSecSpinner = (NumberPicker) view.findViewById(R.id.second);
+ mMilliSpinner = (NumberPicker) view.findViewById(R.id.milli);
+ mAmPmSpinner = (NumberPicker) view.findViewById(R.id.ampm);
+
+ int minHour = min / HOUR_IN_MILLIS;
+ int maxHour = max / HOUR_IN_MILLIS;
+ min -= minHour * HOUR_IN_MILLIS;
+ max -= maxHour * HOUR_IN_MILLIS;
+
+ if (minHour == maxHour) {
+ mHourSpinner.setEnabled(false);
+ hour = minHour;
+ }
+
+ if (is24hourFormat) {
+ mAmPmSpinner.setVisibility(View.GONE);
+ } else {
+ int minAmPm = minHour / 12;
+ int maxAmPm = maxHour / 12;
+ int amPm = hour / 12;
+ mAmPmSpinner.setMinValue(minAmPm);
+ mAmPmSpinner.setMaxValue(maxAmPm);
+ mAmPmSpinner.setDisplayedValues(new String[] {
+ context.getString(R.string.time_picker_dialog_am),
+ context.getString(R.string.time_picker_dialog_pm)
+ });
+
+ hour %= 12;
+ if (hour == 0) {
+ hour = 12;
+ }
+ if (minAmPm == maxAmPm) {
+ mAmPmSpinner.setEnabled(false);
+ amPm = minAmPm;
+
+ minHour %= 12;
+ maxHour %= 12;
+ if (minHour == 0 && maxHour == 0) {
+ minHour = 12;
+ maxHour = 12;
+ } else if (minHour == 0) {
+ minHour = maxHour;
+ maxHour = 12;
+ } else if (maxHour == 0) {
+ maxHour = 12;
+ }
+ } else {
+ minHour = 1;
+ maxHour = 12;
+ }
+ mAmPmSpinner.setValue(amPm);
+ }
+
+ if (minHour == maxHour) {
+ mHourSpinner.setEnabled(false);
+ }
+ mHourSpinner.setMinValue(minHour);
+ mHourSpinner.setMaxValue(maxHour);
+ mHourSpinner.setValue(hour);
+
+ NumberFormatter twoDigitPaddingFormatter = new NumberFormatter("%02d");
+
+ int minMinute = min / MINUTE_IN_MILLIS;
+ int maxMinute = max / MINUTE_IN_MILLIS;
+ min -= minMinute * MINUTE_IN_MILLIS;
+ max -= maxMinute * MINUTE_IN_MILLIS;
+
+ if (minHour == maxHour) {
+ mMinuteSpinner.setMinValue(minMinute);
+ mMinuteSpinner.setMaxValue(maxMinute);
+ if (minMinute == maxMinute) {
+ // Set this otherwise the box is empty until you stroke it.
+ mMinuteSpinner.setDisplayedValues(
+ new String[] { twoDigitPaddingFormatter.format(minMinute) });
+ mMinuteSpinner.setEnabled(false);
+ minute = minMinute;
+ }
+ } else {
+ mMinuteSpinner.setMinValue(0);
+ mMinuteSpinner.setMaxValue(59);
+ }
+
+ if (step >= HOUR_IN_MILLIS) {
+ mMinuteSpinner.setEnabled(false);
+ }
+
+ mMinuteSpinner.setValue(minute);
+ mMinuteSpinner.setFormatter(twoDigitPaddingFormatter);
+
+ if (step >= MINUTE_IN_MILLIS) {
+ // Remove the ':' in front of the second spinner as well.
+ view.findViewById(R.id.second_colon).setVisibility(View.GONE);
+ mSecSpinner.setVisibility(View.GONE);
+ }
+
+ int minSecond = min / SECOND_IN_MILLIS;
+ int maxSecond = max / SECOND_IN_MILLIS;
+ min -= minSecond * SECOND_IN_MILLIS;
+ max -= maxSecond * SECOND_IN_MILLIS;
+
+ if (minHour == maxHour && minMinute == maxMinute) {
+ mSecSpinner.setMinValue(minSecond);
+ mSecSpinner.setMaxValue(maxSecond);
+ if (minSecond == maxSecond) {
+ // Set this otherwise the box is empty until you stroke it.
+ mSecSpinner.setDisplayedValues(
+ new String[] { twoDigitPaddingFormatter.format(minSecond) });
+ mSecSpinner.setEnabled(false);
+ second = minSecond;
+ }
+ } else {
+ mSecSpinner.setMinValue(0);
+ mSecSpinner.setMaxValue(59);
+ }
+
+ mSecSpinner.setValue(second);
+ mSecSpinner.setFormatter(twoDigitPaddingFormatter);
+
+ if (step >= SECOND_IN_MILLIS) {
+ // Remove the '.' in front of the milli spinner as well.
+ view.findViewById(R.id.second_dot).setVisibility(View.GONE);
+ mMilliSpinner.setVisibility(View.GONE);
+ }
+
+ // Round to the nearest step.
+ milli = ((milli + step / 2) / step) * step;
+ if (step == 1 || step == 10 || step == 100) {
+ if (minHour == maxHour && minMinute == maxMinute && minSecond == maxSecond) {
+ mMilliSpinner.setMinValue(min / step);
+ mMilliSpinner.setMaxValue(max / step);
+
+ if (min == max) {
+ mMilliSpinner.setEnabled(false);
+ milli = min;
+ }
+ } else {
+ mMilliSpinner.setMinValue(0);
+ mMilliSpinner.setMaxValue(999 / step);
+ }
+
+ if (step == 1) {
+ mMilliSpinner.setFormatter(new NumberFormatter("%03d"));
+ } else if (step == 10) {
+ mMilliSpinner.setFormatter(new NumberFormatter("%02d"));
+ } else if (step == 100) {
+ mMilliSpinner.setFormatter(new NumberFormatter("%d"));
+ }
+ mMilliSpinner.setValue(milli / step);
+ mBaseMilli = 0;
+ } else if (step < SECOND_IN_MILLIS) {
+ // Non-decimal step value.
+ ArrayList strValue = new ArrayList();
+ for (int i = min; i < max; i += step) {
+ strValue.add(String.format("%03d", i));
+ }
+ mMilliSpinner.setMinValue(0);
+ mMilliSpinner.setMaxValue(strValue.size() - 1);
+ mMilliSpinner.setValue((milli - min) / step);
+ mMilliSpinner.setDisplayedValues(strValue.toArray(new String[strValue.size()]));
+ mBaseMilli = min;
+ } else {
+ mBaseMilli = 0;
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ notifyDateSet();
+ }
+
+ private void notifyDateSet() {
+ int hour = mHourSpinner.getValue();
+ int minute = mMinuteSpinner.getValue();
+ int sec = mSecSpinner.getValue();
+ int milli = mMilliSpinner.getValue() * mStep + mBaseMilli;
+ if (!mIs24hourFormat) {
+ int ampm = mAmPmSpinner.getValue();
+ if (hour == 12) {
+ hour = 0;
+ }
+ hour += ampm * 12;
+ }
+ mListener.onTimeSet(hour, minute, sec, milli);
+ }
+
+ private static class NumberFormatter implements NumberPicker.Formatter {
+ private final String mFormat;
+
+ NumberFormatter(String format) {
+ mFormat = format;
+ }
+
+ @Override
+ public String format(int value) {
+ return String.format(mFormat, value);
+ }
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/OWNERS b/ui/android/java/src/org/chromium/ui/picker/OWNERS
new file mode 100644
index 0000000000000..a4238acc2e4c4
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/OWNERS
@@ -0,0 +1,2 @@
+aurimas@chromium.org
+miguelg@chromium.org
diff --git a/ui/android/java/src/org/chromium/ui/picker/TwoFieldDatePicker.java b/ui/android/java/src/org/chromium/ui/picker/TwoFieldDatePicker.java
new file mode 100644
index 0000000000000..4e84683a32c24
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/TwoFieldDatePicker.java
@@ -0,0 +1,250 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnValueChangeListener;
+
+import org.chromium.ui.R;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * This class is heavily based on android.widget.DatePicker.
+ */
+public abstract class TwoFieldDatePicker extends FrameLayout {
+
+ private final NumberPicker mPositionInYearSpinner;
+
+ private final NumberPicker mYearSpinner;
+
+ private OnMonthOrWeekChangedListener mMonthOrWeekChangedListener;
+
+ // It'd be nice to use android.text.Time like in other Dialogs but
+ // it suffers from the 2038 effect so it would prevent us from
+ // having dates over 2038.
+ private Calendar mMinDate;
+
+ private Calendar mMaxDate;
+
+ private Calendar mCurrentDate;
+
+ /**
+ * The callback used to indicate the user changes\d the date.
+ */
+ public interface OnMonthOrWeekChangedListener {
+
+ /**
+ * Called upon a date change.
+ *
+ * @param view The view associated with this listener.
+ * @param year The year that was set.
+ * @param positionInYear The month or week in year.
+ */
+ void onMonthOrWeekChanged(TwoFieldDatePicker view, int year, int positionInYear);
+ }
+
+ public TwoFieldDatePicker(Context context, double minValue, double maxValue) {
+ super(context, null, android.R.attr.datePickerStyle);
+
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.two_field_date_picker, this, true);
+
+ OnValueChangeListener onChangeListener = new OnValueChangeListener() {
+ @Override
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ int year = getYear();
+ int positionInYear = getPositionInYear();
+ // take care of wrapping of days and months to update greater fields
+ if (picker == mPositionInYearSpinner) {
+ positionInYear = newVal;
+ if (oldVal == picker.getMaxValue() && newVal == picker.getMinValue()) {
+ year += 1;
+ positionInYear = getMinPositionInYear(year);
+ } else if (oldVal == picker.getMinValue() && newVal == picker.getMaxValue()) {
+ year -= 1;
+ positionInYear = getMaxPositionInYear(year);
+ }
+ } else if (picker == mYearSpinner) {
+ year = newVal;
+ } else {
+ throw new IllegalArgumentException();
+ }
+
+ // now set the date to the adjusted one
+ setCurrentDate(year, positionInYear);
+ updateSpinners();
+ notifyDateChanged();
+ }
+ };
+
+ mCurrentDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ if (minValue >= maxValue) {
+ mMinDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ mMinDate.set(0, 0, 1);
+ mMaxDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ mMaxDate.set(9999, 0, 1);
+ } else {
+ mMinDate = getDateForValue(minValue);
+ mMaxDate = getDateForValue(maxValue);
+ }
+
+ // month
+ mPositionInYearSpinner = (NumberPicker) findViewById(R.id.position_in_year);
+ mPositionInYearSpinner.setOnLongPressUpdateInterval(200);
+ mPositionInYearSpinner.setOnValueChangedListener(onChangeListener);
+
+ // year
+ mYearSpinner = (NumberPicker) findViewById(R.id.year);
+ mYearSpinner.setOnLongPressUpdateInterval(100);
+ mYearSpinner.setOnValueChangedListener(onChangeListener);
+ }
+
+ /**
+ * Initialize the state. If the provided values designate an inconsistent
+ * date the values are normalized before updating the spinners.
+ *
+ * @param year The initial year.
+ * @param positionInYear The initial month starting from zero or week in year.
+ * @param onMonthOrWeekChangedListener How user is notified date is changed by
+ * user, can be null.
+ */
+ public void init(int year, int positionInYear,
+ OnMonthOrWeekChangedListener onMonthOrWeekChangedListener) {
+ setCurrentDate(year, positionInYear);
+ updateSpinners();
+ mMonthOrWeekChangedListener = onMonthOrWeekChangedListener;
+ }
+
+ public boolean isNewDate(int year, int positionInYear) {
+ return (getYear() != year || getPositionInYear() != positionInYear);
+ }
+
+ /**
+ * Subclasses know the semantics of @value, and need to return
+ * a Calendar corresponding to it.
+ */
+ protected abstract Calendar getDateForValue(double value);
+
+ /**
+ * Updates the current date.
+ *
+ * @param year The year.
+ * @param positionInYear The month or week in year.
+ */
+ public void updateDate(int year, int positionInYear) {
+ if (!isNewDate(year, positionInYear)) {
+ return;
+ }
+ setCurrentDate(year, positionInYear);
+ updateSpinners();
+ notifyDateChanged();
+ }
+
+ /**
+ * Subclasses know the semantics of @positionInYear, and need to update @mCurrentDate to the
+ * appropriate date.
+ */
+ protected abstract void setCurrentDate(int year, int positionInYear);
+
+ protected void setCurrentDate(Calendar date) {
+ mCurrentDate = date;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return true;
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
+ String selectedDateUtterance = DateUtils.formatDateTime(getContext(),
+ mCurrentDate.getTimeInMillis(), flags);
+ event.getText().add(selectedDateUtterance);
+ }
+
+ /**
+ * @return The selected year.
+ */
+ public int getYear() {
+ return mCurrentDate.get(Calendar.YEAR);
+ }
+
+ /**
+ * @return The selected month or week.
+ */
+ public abstract int getPositionInYear();
+
+ protected abstract int getMaxYear();
+
+ protected abstract int getMinYear();
+
+ protected abstract int getMaxPositionInYear(int year);
+
+ protected abstract int getMinPositionInYear(int year);
+
+ protected Calendar getMaxDate() {
+ return mMaxDate;
+ }
+
+ protected Calendar getMinDate() {
+ return mMinDate;
+ }
+
+ protected Calendar getCurrentDate() {
+ return mCurrentDate;
+ }
+
+ protected NumberPicker getPositionInYearSpinner() {
+ return mPositionInYearSpinner;
+ }
+
+ protected NumberPicker getYearSpinner() {
+ return mYearSpinner;
+ }
+
+ /**
+ * This method should be subclassed to update the spinners based on mCurrentDate.
+ */
+ protected void updateSpinners() {
+ mPositionInYearSpinner.setDisplayedValues(null);
+
+ // set the spinner ranges respecting the min and max dates
+ mPositionInYearSpinner.setMinValue(getMinPositionInYear(getYear()));
+ mPositionInYearSpinner.setMaxValue(getMaxPositionInYear(getYear()));
+ mPositionInYearSpinner.setWrapSelectorWheel(
+ !mCurrentDate.equals(mMinDate) && !mCurrentDate.equals(mMaxDate));
+
+ // year spinner range does not change based on the current date
+ mYearSpinner.setMinValue(getMinYear());
+ mYearSpinner.setMaxValue(getMaxYear());
+ mYearSpinner.setWrapSelectorWheel(false);
+
+ // set the spinner values
+ mYearSpinner.setValue(getYear());
+ mPositionInYearSpinner.setValue(getPositionInYear());
+ }
+
+ /**
+ * Notifies the listener, if such, for a change in the selected date.
+ */
+ protected void notifyDateChanged() {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ if (mMonthOrWeekChangedListener != null) {
+ mMonthOrWeekChangedListener.onMonthOrWeekChanged(this, getYear(), getPositionInYear());
+ }
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/TwoFieldDatePickerDialog.java b/ui/android/java/src/org/chromium/ui/picker/TwoFieldDatePickerDialog.java
new file mode 100644
index 0000000000000..e3f01e71ee254
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/TwoFieldDatePickerDialog.java
@@ -0,0 +1,113 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+
+import org.chromium.ui.R;
+import org.chromium.ui.picker.TwoFieldDatePicker.OnMonthOrWeekChangedListener;
+
+public abstract class TwoFieldDatePickerDialog extends AlertDialog implements OnClickListener,
+ OnMonthOrWeekChangedListener {
+
+ private static final String YEAR = "year";
+ private static final String POSITION_IN_YEAR = "position_in_year";
+
+ protected final TwoFieldDatePicker mPicker;
+ protected final OnValueSetListener mCallBack;
+
+ /**
+ * The callback used to indicate the user is done filling in the date.
+ */
+ public interface OnValueSetListener {
+
+ /**
+ * @param year The year that was set.
+ * @param positionInYear The position in the year that was set.
+ */
+ void onValueSet(int year, int positionInYear);
+ }
+
+ /**
+ * @param context The context the dialog is to run in.
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param weekOfYear The initial week of the dialog.
+ */
+ public TwoFieldDatePickerDialog(Context context,
+ OnValueSetListener callBack,
+ int year,
+ int positionInYear,
+ double minValue,
+ double maxValue) {
+ this(context, 0, callBack, year, positionInYear, minValue, maxValue);
+ }
+
+ /**
+ * @param context The context the dialog is to run in.
+ * @param theme the theme to apply to this dialog
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param weekOfYear The initial week of the dialog.
+ */
+ public TwoFieldDatePickerDialog(Context context,
+ int theme,
+ OnValueSetListener callBack,
+ int year,
+ int positionInYear,
+ double minValue,
+ double maxValue) {
+ super(context, theme);
+
+ mCallBack = callBack;
+
+ setButton(BUTTON_POSITIVE, context.getText(
+ R.string.date_picker_dialog_set), this);
+ setButton(BUTTON_NEGATIVE, context.getText(android.R.string.cancel),
+ (OnClickListener) null);
+ setIcon(0);
+
+ mPicker = createPicker(context, minValue, maxValue);
+ setView(mPicker);
+ mPicker.init(year, positionInYear, this);
+ }
+
+ protected TwoFieldDatePicker createPicker(Context context, double minValue, double maxValue) {
+ return null;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ tryNotifyDateSet();
+ }
+
+ /**
+ * Notifies the listener, if such, that a date has been set.
+ */
+ protected void tryNotifyDateSet() {
+ if (mCallBack != null) {
+ mPicker.clearFocus();
+ mCallBack.onValueSet(mPicker.getYear(), mPicker.getPositionInYear());
+ }
+ }
+
+ @Override
+ public void onMonthOrWeekChanged(TwoFieldDatePicker view, int year, int positionInYear) {
+ mPicker.init(year, positionInYear, null);
+ }
+
+ /**
+ * Sets the current date.
+ *
+ * @param year The date week year.
+ * @param weekOfYear The date week.
+ */
+ public void updateDate(int year, int weekOfYear) {
+ mPicker.updateDate(year, weekOfYear);
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/WeekPicker.java b/ui/android/java/src/org/chromium/ui/picker/WeekPicker.java
new file mode 100644
index 0000000000000..bb9a25d08b461
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/WeekPicker.java
@@ -0,0 +1,141 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.content.Context;
+
+import org.chromium.ui.R;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+// This class is heavily based on android.widget.DatePicker.
+public class WeekPicker extends TwoFieldDatePicker {
+
+ public WeekPicker(Context context, double minValue, double maxValue) {
+ super(context, minValue, maxValue);
+
+ getPositionInYearSpinner().setContentDescription(
+ getResources().getString(R.string.accessibility_date_picker_week));
+
+ // initialize to current date
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ cal.setFirstDayOfWeek(Calendar.MONDAY);
+ cal.setMinimalDaysInFirstWeek(4);
+ cal.setTimeInMillis(System.currentTimeMillis());
+ init(getISOWeekYearForDate(cal), getWeekForDate(cal), null);
+ }
+
+ /**
+ * Creates a date object from the |year| and |week|.
+ */
+ public static Calendar createDateFromWeek(int year, int week) {
+ Calendar date = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ date.clear();
+ date.setFirstDayOfWeek(Calendar.MONDAY);
+ date.setMinimalDaysInFirstWeek(4);
+ date.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
+ date.set(Calendar.YEAR, year);
+ date.set(Calendar.WEEK_OF_YEAR, week);
+ return date;
+ }
+
+ /**
+ * Creates a date object from the |value| which is milliseconds since epoch.
+ */
+ public static Calendar createDateFromValue(double value) {
+ Calendar date = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ date.clear();
+ date.setFirstDayOfWeek(Calendar.MONDAY);
+ date.setMinimalDaysInFirstWeek(4);
+ date.setTimeInMillis((long) value);
+ return date;
+ }
+
+ @Override
+ protected Calendar getDateForValue(double value) {
+ return WeekPicker.createDateFromValue(value);
+ }
+
+ public static int getISOWeekYearForDate(Calendar date) {
+ int year = date.get(Calendar.YEAR);
+ int month = date.get(Calendar.MONTH);
+ int week = date.get(Calendar.WEEK_OF_YEAR);
+ if (month == 0 && week > 51) {
+ year--;
+ } else if (month == 11 && week == 1) {
+ year++;
+ }
+ return year;
+ }
+
+ public static int getWeekForDate(Calendar date) {
+ return date.get(Calendar.WEEK_OF_YEAR);
+ }
+
+ @Override
+ protected void setCurrentDate(int year, int week) {
+ Calendar date = createDateFromWeek(year, week);
+ if (date.before(getMinDate())) {
+ setCurrentDate(getMinDate());
+ } else if (date.after(getMaxDate())) {
+ setCurrentDate(getMaxDate());
+ } else {
+ setCurrentDate(date);
+ }
+ }
+
+ private int getNumberOfWeeks(int year) {
+ // Create a date in the middle of the year, where the week year matches the year.
+ Calendar date = createDateFromWeek(year, 20);
+ return date.getActualMaximum(Calendar.WEEK_OF_YEAR);
+ }
+
+ /**
+ * @return The selected year.
+ */
+ @Override
+ public int getYear() {
+ return getISOWeekYearForDate(getCurrentDate());
+ }
+
+ /**
+ * @return The selected week.
+ */
+ public int getWeek() {
+ return getWeekForDate(getCurrentDate());
+ }
+
+ @Override
+ public int getPositionInYear() {
+ return getWeek();
+ }
+
+ @Override
+ protected int getMaxYear() {
+ return getISOWeekYearForDate(getMaxDate());
+ }
+
+ @Override
+ protected int getMinYear() {
+ return getISOWeekYearForDate(getMinDate());
+ }
+
+ @Override
+ protected int getMaxPositionInYear(int year) {
+ if (year == getISOWeekYearForDate(getMaxDate())) {
+ return getWeekForDate(getMaxDate());
+ }
+ return getNumberOfWeeks(year);
+ }
+
+ @Override
+ protected int getMinPositionInYear(int year) {
+ if (year == getISOWeekYearForDate(getMinDate())) {
+ return getWeekForDate(getMinDate());
+ }
+ return 1;
+ }
+}
diff --git a/ui/android/java/src/org/chromium/ui/picker/WeekPickerDialog.java b/ui/android/java/src/org/chromium/ui/picker/WeekPickerDialog.java
new file mode 100644
index 0000000000000..e769c1a25c472
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/picker/WeekPickerDialog.java
@@ -0,0 +1,56 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.picker;
+
+import android.content.Context;
+
+import org.chromium.ui.R;
+
+public class WeekPickerDialog extends TwoFieldDatePickerDialog {
+
+ /**
+ * @param context The context the dialog is to run in.
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param weekOfYear The initial week of the dialog.
+ */
+ public WeekPickerDialog(Context context,
+ OnValueSetListener callBack,
+ int year, int weekOfYear,
+ double minValue, double maxValue) {
+ this(context, 0, callBack, year, weekOfYear, minValue, maxValue);
+ }
+
+ /**
+ * @param context The context the dialog is to run in.
+ * @param theme the theme to apply to this dialog
+ * @param callBack How the parent is notified that the date is set.
+ * @param year The initial year of the dialog.
+ * @param weekOfYear The initial week of the dialog.
+ */
+ public WeekPickerDialog(Context context,
+ int theme,
+ OnValueSetListener callBack,
+ int year,
+ int weekOfYear,
+ double minValue, double maxValue) {
+ super(context, theme, callBack, year, weekOfYear, minValue, maxValue);
+ setTitle(R.string.week_picker_dialog_title);
+ }
+
+ @Override
+ protected TwoFieldDatePicker createPicker(Context context, double minValue, double maxValue) {
+ return new WeekPicker(context, minValue, maxValue);
+ }
+
+ /**
+ * Gets the {@link WeekPicker} contained in this dialog.
+ *
+ * @return The calendar view.
+ */
+ public WeekPicker getWeekPicker() {
+ return (WeekPicker) mPicker;
+ }
+}
diff --git a/ui/android/java/strings/android_ui_strings.grd b/ui/android/java/strings/android_ui_strings.grd
new file mode 100644
index 0000000000000..c2a42a5eaedb7
--- /dev/null
+++ b/ui/android/java/strings/android_ui_strings.grd
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Unable to complete previous operation due to low memory
+
+
+ Failed to open selected file
+
+
+ More
+
+
+ Hue
+
+
+ Saturation
+
+
+ Value
+
+
+ Set
+
+
+ Cancel
+
+
+ Select color
+
+
+ Red
+
+
+ Cyan
+
+
+ Blue
+
+
+ Green
+
+
+ Magenta
+
+
+ Yellow
+
+
+ Black
+
+
+ White
+
+
+ Failed to copy to the clipboard
+
+
+ Month
+
+
+ Year
+
+
+ Set
+
+
+ Set month
+
+
+ Week
+
+
+ Set week
+
+
+ AM
+
+
+ PM
+
+
+ Set time
+
+
+ Hour
+
+
+ Minute
+
+
+ Second
+
+
+ Millisecond
+
+
+ AM/PM
+
+
+ :
+
+
+ :
+
+
+ .
+
+
+ Set date and time
+
+
+ Date
+
+
+ Time
+
+
+ Other
+
+
+ Set date
+
+
+ Clear
+
+
+
+
diff --git a/ui/android/java/strings/translations/android_ui_strings_am.xtb b/ui/android/java/strings/translations/android_ui_strings_am.xtb
new file mode 100644
index 0000000000000..f5ac9dbecfb99
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_am.xtb
@@ -0,0 +1,43 @@
+
+
+
+አረንጓዴ
+:
+ዓመት
+ቀን ያዘጋጁ
+ይቅር
+ቀለም ይምረጡ
+ሮዝ
+የተመረጠውን ፋይል መክፈት አልተሳካም
+ጥቁር
+ሰዓት
+ቢጫ
+ቀን
+ቀይ
+ሰማያዊ
+አዘጋጅ
+ደቂቃ
+ሰከንድ
+ሳምንት
+ሰዓት ያዘጋጁ
+ወር
+ሳምንት ያዘጋጁ
+በአነስተኛ ማህደረ ትውስታ ምክንያት ቀዳሚውን ክወና ማጠናቀቅ አልተቻለም
+ጥዋት
+ተጨማሪ
+እሴት
+ውሃ ሰማያዊ
+ጥዋት/ከሰዓት
+ለይ ቀለም
+ነጭ
+.
+የቀለም ሙሌት
+ሚሊሰከንድ
+ሰዓት
+ሌላ
+አጽዳ
+ከሰዓት
+ወር ያዘጋጁ
+ወደ ቅንጥብ ሰሌዳው መቅዳት አልተሳካም
+ቀን እና ሰዓት ያዘጋጁ
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_ar.xtb b/ui/android/java/strings/translations/android_ui_strings_ar.xtb
new file mode 100644
index 0000000000000..369d494bb57f9
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_ar.xtb
@@ -0,0 +1,43 @@
+
+
+
+أخضر
+:
+عام
+تعيين التاريخ
+إلغاء
+اختيار اللون
+أرجواني
+أخفق فتح الملف المحدد
+أسود
+الساعات
+أصفر
+التاريخ
+أحمر
+أزرق
+تعيين
+الدقائق
+الثواني
+الأسبوع
+تعيين الوقت
+شهر
+تعيين الأسبوع
+تعذر إكمال العملية السابقة نظرًا لانخفاض الذاكرة
+صباحًا
+المزيد
+القيمة
+سماوي
+صباحًا/مساءً
+تدرج اللون
+أبيض
+.
+تشبع اللون
+مللي ثانية
+الوقت
+أخرى
+محو
+مساءً
+تعيين الشهر
+أخفق النسخ إلى الحافظة
+تعيين التاريخ والوقت
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_bg.xtb b/ui/android/java/strings/translations/android_ui_strings_bg.xtb
new file mode 100644
index 0000000000000..d65a3ac76c851
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_bg.xtb
@@ -0,0 +1,43 @@
+
+
+
+зелено
+:
+Година
+Задаване на датата
+Отказ
+Избор на цвят
+пурпурно
+Файлът не можа да се отвори
+черно
+Час
+жълто
+Дата
+червено
+синьо
+Задаване
+Mинута
+Секунда
+Седмица
+Задаване на часа
+Месец
+Задаване на седмицата
+Предишната операция не можа да завърши поради недостиг на памет
+AM
+Още
+Стойност
+синьозелено
+AM/PM
+Цветови тон
+бяло
+.
+Насищане
+Милисекунда
+Време
+Друго
+Изчистване
+PM
+Задаване на месеца
+Копирането в буферната памет не бе успешно
+Задаване на датата и часа
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_ca.xtb b/ui/android/java/strings/translations/android_ui_strings_ca.xtb
new file mode 100644
index 0000000000000..668c7b9fc3845
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_ca.xtb
@@ -0,0 +1,43 @@
+
+
+
+Verd
+:
+Any
+Defineix la data
+Cancel·la
+Selecció de color
+Magenta
+No s'ha pogut obrir fitxer sel.
+Negre
+Hora
+Groc
+Data
+Vermell
+Blau
+Configura
+Minut
+Segon
+Setmana
+Defineix l'hora
+Mes
+Defineix la setmana
+No es pot completar l'operació anterior perquè hi ha poca memòria.
+a.m.
+Més
+Valor
+Cian
+a. m./p. m.
+To
+Blanc
+.
+Saturació
+Mil·lisegon
+Hora
+Altres
+Esborra
+p.m.
+Defineix el mes
+No s'ha pogut copiar el contingut al porta-retalls.
+Defineix la data i l'hora
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_cs.xtb b/ui/android/java/strings/translations/android_ui_strings_cs.xtb
new file mode 100644
index 0000000000000..c673bd20d1e7d
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_cs.xtb
@@ -0,0 +1,43 @@
+
+
+
+Zelená
+:
+Rok
+Nastavení data
+Zrušit
+Výběr barvy
+Purpurová
+Vybraný soubor nelze otevřít
+Černá
+Hodina
+Žlutá
+Datum
+Červená
+Modrá
+Nastavit
+Minuta
+Sekunda
+Týden
+Nastavení času
+Měsíc
+Nastavení týdne
+Předchozí operaci nelze dokončit z důvodu nedostatku paměti
+AM
+Více
+Hodnota
+Azurová
+AM/PM
+Odstín
+Bílá
+,
+Sytost
+Milisekunda
+Čas
+Ostatní
+Vymazat
+PM
+Nastavení měsíce
+Zkopírování obsahu do schránky se nezdařilo
+Nastavení data a času
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_da.xtb b/ui/android/java/strings/translations/android_ui_strings_da.xtb
new file mode 100644
index 0000000000000..ffad8c634f20a
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_da.xtb
@@ -0,0 +1,43 @@
+
+
+
+Grøn
+:
+År
+Angiv dato
+Annuller
+Vælg farve
+Magenta
+Den valgte fil kunne ikke åbnes
+Sort
+Time
+Gul
+Dato
+Rød
+Blå
+Angiv
+Minut
+Sekund
+Uge
+Angiv klokkeslæt
+Måned
+Angiv uge
+Den tidligerere handling kunne ikke fuldføres på grund af manglende hukommelse
+AM
+Mere
+Værdi
+Cyan
+AM/PM
+Farvetone
+Hvid
+.
+Mætning
+Millisekund
+Tid
+Andet
+Ryd
+PM
+Angiv måned
+Der kunne ikke kopieres til udklipsholder
+Angiv dato og klokkeslæt
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_de.xtb b/ui/android/java/strings/translations/android_ui_strings_de.xtb
new file mode 100644
index 0000000000000..144213320eb89
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_de.xtb
@@ -0,0 +1,43 @@
+
+
+
+Grün
+:
+Jahr
+Datum festlegen
+Abbrechen
+Farbe auswählen
+Magenta
+Fehler beim Öffnen der Datei
+Schwarz
+Stunde
+Gelb
+Datum
+Rot
+Blau
+Festlegen
+Minute
+Sekunde
+Woche
+Uhrzeit festlegen
+Monat
+Woche festlegen
+Zu wenig Speicher für vorherige Operation
+AM
+Mehr
+Wert
+Cyan
+AM/PM
+Farbton
+Weiß
+.
+Sättigung
+Millisekunde
+Zeit
+Sonstiges
+Löschen
+PM
+Monat festlegen
+Fehler beim Kopieren in die Zwischenablage
+Datum und Uhrzeit festlegen
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_el.xtb b/ui/android/java/strings/translations/android_ui_strings_el.xtb
new file mode 100644
index 0000000000000..1403909258b0e
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_el.xtb
@@ -0,0 +1,43 @@
+
+
+
+Πράσινο
+:
+Έτος
+Ορισμός ημερομηνίας
+Ακύρωση
+Επιλογή χρώματος
+Ματζέντα
+Αποτυχ. ανοίγμ. επιλεγμ. αρχείου
+Μαύρο
+Ώρα
+Κίτρινο
+Ημερομηνία
+Κόκκινο
+Μπλε
+Ορισμός
+Λεπτό
+Δευτερόλεπτο
+Εβδομάδα
+Ορισμός χρόνου
+Μήνας
+Ορισμός εβδομάδας
+Δεν ήταν δυνατή η ολοκλήρωση της προηγούμενης λειτουργίας λόγω χαμηλού επιπέδου μνήμης
+π.μ.
+Περισσότερα
+Τιμή
+Κυανό
+Π.Μ./Μ.Μ.
+Απόχρωση
+Λευκό
+.
+Κορεσμός
+Χιλιοστό του δευτερολέπτου
+Ώρα
+Άλλο
+Διαγραφή
+μ.μ.
+Ορισμός μήνα
+Αποτυχία αντιγραφής στο πρόχειρο
+Ορισμός ημερομηνίας και ώρας
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_en-GB.xtb b/ui/android/java/strings/translations/android_ui_strings_en-GB.xtb
new file mode 100644
index 0000000000000..e8d01090f9d8d
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_en-GB.xtb
@@ -0,0 +1,43 @@
+
+
+
+Green
+:
+Year
+Set date
+Cancel
+Select colour
+Magenta
+Failed to open selected file
+Black
+Hour
+Yellow
+Date
+Red
+Blue
+Set
+Minute
+Second
+Week
+Set time
+Month
+Set week
+Unable to complete previous operation due to low memory
+a.m.
+More
+Value
+Cyan
+AM/PM
+Hue
+White
+.
+Saturation
+Millisecond
+Time
+Other
+Clear
+p.m.
+Set month
+Failed to copy to the clipboard
+Set date and time
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_es-419.xtb b/ui/android/java/strings/translations/android_ui_strings_es-419.xtb
new file mode 100644
index 0000000000000..7cf6daf686a5f
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_es-419.xtb
@@ -0,0 +1,43 @@
+
+
+
+Verde
+:
+Año
+Establecer fecha
+Cancelar
+Seleccionar color
+Magenta
+Error al abrir el archivo
+Negro
+Hora
+Amarillo
+Fecha
+Rojo
+Azul
+Establecer
+Minuto
+Segundo
+Semana
+Establecer hora
+Mes
+Establecer semana
+Memoria insuficiente para completar la operación anterior
+a. m.
+Más
+Valor
+Cian
+AM/PM
+Tono
+Blanco
+.
+Saturación
+Milisegundo
+Hora
+Otros
+Borrar
+p. m.
+Establecer mes
+Error al copiar al portapapeles
+Establecer fecha y hora
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_es.xtb b/ui/android/java/strings/translations/android_ui_strings_es.xtb
new file mode 100644
index 0000000000000..4bc723ce14e21
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_es.xtb
@@ -0,0 +1,43 @@
+
+
+
+Verde
+:
+Año
+Establecer fecha
+Cancelar
+Seleccionar color
+Magenta
+Error abrir archivo seleccionado
+Negro
+Hora
+Amarillo
+Fecha
+Rojo
+Azul
+Establecer
+Minuto
+Segundo
+Semana
+Establecer hora
+Mes
+Establecer semana
+No se ha podido completar la operación anterior por falta de memoria
+AM
+Más
+Valor
+Cian
+A.M./P.M.
+Matiz
+Blanco
+.
+Saturación
+Milisegundo
+Hora
+Otro
+Eliminar
+PM
+Establecer mes
+Error al copiar en el portapapeles
+Establecer fecha y hora
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_fa.xtb b/ui/android/java/strings/translations/android_ui_strings_fa.xtb
new file mode 100644
index 0000000000000..4f23d1f5503bc
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_fa.xtb
@@ -0,0 +1,43 @@
+
+
+
+سبز
+:
+سال
+تنظیم تاریخ
+لغو
+انتخاب رنگ
+سرخابی
+باز کردن فایل انتخابی انجام نشد
+سیاه
+ساعت
+زرد
+تاریخ
+قرمز
+آبی
+تنظیم
+دقیقه
+ثانیه
+هفته
+تنظیم زمان
+ماه
+تنظیم هفته
+به دلیل کم بودن حافظه، تکمیل عملیات قبلی امکانپذیر نیست
+ق.ظ
+بیشتر
+مقدار
+فیروزهای
+ق.ظ/ب.ظ
+رنگمایه
+سفید
+.
+اشباع رنگ
+میلیثانیه
+زمان
+دیگر
+پاک کردن
+ب.ظ
+تنظیم ماه
+کپی در کلیپ بورد ناموفق بود
+تنظیم تاریخ و زمان
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_fi.xtb b/ui/android/java/strings/translations/android_ui_strings_fi.xtb
new file mode 100644
index 0000000000000..49b29a15d2e60
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_fi.xtb
@@ -0,0 +1,43 @@
+
+
+
+Vihreä
+.
+Vuosi
+Aseta päivämäärä
+Peruuta
+Valitse väri
+Purppura
+Valittua tiedostoa ei voi avata
+Musta
+Tunti
+Keltainen
+Päiväys
+Punainen
+Sininen
+Aseta
+Minuutti
+Sekunti
+Viikko
+Aseta aika
+Kuukausi
+Aseta viikko
+Edellistä toimintoa ei voi suorittaa. Muisti ei riitä.
+ap
+Lisää
+Arvo
+Turkoosi
+AP/IP
+Sävy
+Valkoinen
+,
+Värikylläisyys
+Millisekunti
+Aika
+Muu
+Tyhjennä
+ip
+Aseta kuukausi
+Kopiointi leikepöydälle epäonnistui
+Aseta päivämäärä ja aika
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_fil.xtb b/ui/android/java/strings/translations/android_ui_strings_fil.xtb
new file mode 100644
index 0000000000000..247e6afa5c832
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_fil.xtb
@@ -0,0 +1,43 @@
+
+
+
+Berde
+:
+Taon
+Magtakda ng petsa
+Ikansela
+Pumili ng kulay
+Magenta
+Hindi mabuksan ang napiling file
+Itim
+Oras
+Dilaw
+Petsa
+Pula
+Asul
+Itakda
+Minuto
+Segundo
+Linggo
+Itakda ang oras
+Buwan
+Itakda ang linggo
+Hindi makumpleto ang nakaraang operasyon dahil sa mababang memory
+AM
+Higit pa
+Value
+Cyan
+AM/PM
+Hue
+Puti
+.
+Saturation
+Millisecond
+Oras
+Iba pa
+I-clear
+PM
+Itakda ang buwan
+Nabigong kopyahin sa clipboard
+Itakda ang petsa at oras
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_fr.xtb b/ui/android/java/strings/translations/android_ui_strings_fr.xtb
new file mode 100644
index 0000000000000..314fc115eeec2
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_fr.xtb
@@ -0,0 +1,43 @@
+
+
+
+Vert
+:
+Année
+Définir la date
+Annuler
+Sélectionner couleur
+Magenta
+Échec de l'ouverture du fichier.
+Noir
+Heure
+Jaune
+Date
+Rouge
+Bleu
+Définir
+Minute
+Seconde
+Semaine
+Définir l'heure
+Mois
+Définir la semaine
+Impossible de terminer l'opération précédente. Mémoire insuffisante.
+a.m.
+Plus
+Valeur
+Cyan
+AM/PM
+Teinte
+Blanc
+.
+Saturation
+Milliseconde
+Heure
+Autre
+Effacer
+p.m.
+Définir le mois
+Échec de la copie du contenu dans le Presse-papiers.
+Définir la date et l'heure
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_hi.xtb b/ui/android/java/strings/translations/android_ui_strings_hi.xtb
new file mode 100644
index 0000000000000..57e74a7ce3bfd
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_hi.xtb
@@ -0,0 +1,43 @@
+
+
+
+हरा
+:
+वर्ष
+दिनांक सेट करें
+रहने दें
+रंग चुनें
+मैजेंटा
+चयनित फ़ाइल खोलने में विफल
+काला
+घंटा
+पीला
+दिनांक
+लाल
+नीला
+सेट करें
+मिनट
+सेकंड
+सप्ताह
+समय सेट करें
+माह
+सप्ताह सेट करें
+कम स्मृति के कारण पिछली कार्रवाई पूरी करने में असमर्थ
+पूर्वाह्न
+अधिक
+मान
+स्यान
+पूर्वाह्न/अपराह्न
+रंग
+सफ़ेद
+.
+संतृप्तता
+मिलीसेकंड
+समय
+अन्य
+साफ़ करें
+अपराह्न
+माह सेट करें
+क्लिपबोर्ड पर प्रतिलिपि बनाने में विफल रहा
+दिनांक और समय सेट करें
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_hr.xtb b/ui/android/java/strings/translations/android_ui_strings_hr.xtb
new file mode 100644
index 0000000000000..6837ecacd56bf
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_hr.xtb
@@ -0,0 +1,43 @@
+
+
+
+Zelena
+.
+Godina
+Postavite datum
+Odustani
+Odaberite boju
+Magenta
+Odabrana datoteka nije otvorena
+Crna
+sat
+Žuta
+Datum
+Crvena
+Plava
+Postavi
+minuta
+sekunda
+Tjedan
+Postavite vrijeme
+Mjesec
+Postavite tjedan
+Nije moguće dovršiti prethodnu operaciju jer nema dovoljno memorije
+AM
+Više
+Vrijednost
+Cijan
+prijepodne/poslijepodne
+Ton
+Bijela
+.
+Zasićenje
+milisekunda
+Vrijeme
+Ostalo
+Izbriši
+PM
+Postavite mjesec
+Nije kopirano u međuspremnik
+Postavite datum i vrijeme
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_hu.xtb b/ui/android/java/strings/translations/android_ui_strings_hu.xtb
new file mode 100644
index 0000000000000..0ab3e9dabc82c
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_hu.xtb
@@ -0,0 +1,43 @@
+
+
+
+Zöld
+:
+Év
+Dátum beállítása
+Mégse
+Szín kiválasztása
+Magenta
+A fájl megnyitása sikertelen
+Fekete
+Óra
+Sárga
+Dátum
+Piros
+Kék
+Beállítás
+Perc
+Másodperc
+Hét
+Idő beállítása
+hónap
+Hét beállítása
+Az előző műveletet memóriahiány miatt nem lehet elvégezni
+de.
+Hosszabban
+Érték
+Cián
+de./du.
+Színárnyalat
+Fehér
+.
+Telítettség
+Ezredmásodperc
+Idő
+Egyéb
+Törlés
+du.
+Hónap beállítása
+Nem sikerült a vágólapra másolni
+Dátum és idő beállítása
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_id.xtb b/ui/android/java/strings/translations/android_ui_strings_id.xtb
new file mode 100644
index 0000000000000..b1793ba2832a4
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_id.xtb
@@ -0,0 +1,43 @@
+
+
+
+Hijau
+.
+Tahun
+Setel tanggal
+Batal
+Pilih warna
+Magenta
+Gagal membuka file terpilih
+Hitam
+Jam
+Kuning
+Tanggal
+Merah
+Biru
+Setel
+Menit
+Detik
+Minggu
+Setel waktu
+Bulan
+Setel minggu
+Tidak dapat menyelesaikan operasi sebelumnya karena sisa memori sedikit
+AM
+Lainnya
+Nilai
+Sian
+AM/PM
+Rona
+Putih
+:
+Saturasi
+Milidetik
+Waktu
+Lainnya
+Hapus
+PM
+Setel bulan
+Gagal menyalin ke papan klip
+Setel tanggal dan waktu
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_it.xtb b/ui/android/java/strings/translations/android_ui_strings_it.xtb
new file mode 100644
index 0000000000000..7ef7536e9d70f
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_it.xtb
@@ -0,0 +1,43 @@
+
+
+
+Verde
+:
+Anno
+Imposta data
+Annulla
+Seleziona colore
+Magenta
+Impossibile aprire file selez.
+Nero
+Ora
+Giallo
+Data
+Rosso
+Blu
+Imposta
+Minuto
+Secondo
+Settimana
+Imposta ora
+Mese
+Imposta settimana
+Impossibile completare l'operazione precedente. Memoria insufficiente.
+AM
+Più
+Valore
+Ciano
+AM/PM
+Tonalità
+Bianco
+.
+Saturazione
+Millisecondo
+Ora
+Altro
+Cancella
+PM
+Imposta mese
+Impossibile copiare negli appunti
+Imposta data e ora
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_iw.xtb b/ui/android/java/strings/translations/android_ui_strings_iw.xtb
new file mode 100644
index 0000000000000..8116871141bc3
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_iw.xtb
@@ -0,0 +1,43 @@
+
+
+
+ירוק
+:
+שנה
+הגדרת תאריך
+ביטול
+בחירת צבע
+מגנטה
+פתיחת הקובץ הנבחר נכשלה
+שחור
+שעה
+צהוב
+תאריך
+אדום
+כחול
+הגדר
+דקה
+שנייה
+שבוע
+הגדרת שעה
+חודש
+הגדרת שבוע
+לא ניתן להשלים את הפעולה הקודמת עקב מחסור בזיכרון
+AM
+עוד
+ערך
+ציאן
+AM/PM
+גוון
+לבן
+.
+רווייה
+אלפית שנייה
+שעה
+אחר
+נקה
+PM
+הגדרת חודש
+ההעתקה אל הלוח נכשלה
+הגדרת תאריך ושעה
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_ja.xtb b/ui/android/java/strings/translations/android_ui_strings_ja.xtb
new file mode 100644
index 0000000000000..d87e73c17afcb
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_ja.xtb
@@ -0,0 +1,43 @@
+
+
+
+緑
+:
+年
+日付の設定
+キャンセル
+色の選択
+マゼンタ
+選択したファイルを開けません
+黒
+時
+黄
+日付
+赤
+青
+設定
+分
+秒
+週
+時間の設定
+月
+週の設定
+メモリ不足のため直前の操作を完了できません
+AM
+詳細表示
+値
+シアン
+AM/PM
+色調
+白
+.
+彩度
+ミリ秒
+時間
+その他
+クリア
+PM
+月の設定
+クリップボードにコピーできませんでした
+日時の設定
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_ko.xtb b/ui/android/java/strings/translations/android_ui_strings_ko.xtb
new file mode 100644
index 0000000000000..1f9856f545e06
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_ko.xtb
@@ -0,0 +1,43 @@
+
+
+
+녹색
+:
+연도
+날짜 설정
+취소
+색상 선택
+자홍색
+선택한 파일을 열지 못했습니다.
+검정색
+시
+노란색
+날짜
+빨간색
+파란색
+설정
+분
+초
+주
+시간 설정
+월
+주 설정
+메모리가 부족하여 이전 작업을 완료할 수 없습니다.
+오전
+더보기
+값
+청록색
+오전/오후
+색조
+흰색
+.
+채도
+밀리초
+시간
+기타
+삭제
+오후
+월 설정
+클립보드로 복사하지 못했습니다.
+날짜 및 시간 설정
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_lt.xtb b/ui/android/java/strings/translations/android_ui_strings_lt.xtb
new file mode 100644
index 0000000000000..5abc0d1513fd3
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_lt.xtb
@@ -0,0 +1,43 @@
+
+
+
+Žalia
+:
+Metai
+Nustatykite datą
+Atšaukti
+Pasirinkite spalvą
+Purpurinė
+Atid. pasir. failą įvyko klaida
+Juoda
+Valanda
+Geltona
+Data
+Raudona
+Mėlyna
+Nustatyti
+Minutė
+Sekundė
+Savaitė
+Nustatykite laiką
+Mėnuo
+Nustatykite savaitę
+Nepavyko baigti ankstesnio veiksmo dėl atminties trūkumo
+priešpiet
+Daugiau
+Reikšmė
+Žydra
+iki pietų / po pietų
+Spalva
+Balta
+.
+Spalvų sodrumas
+Milisekundė
+Laikas
+Kitas
+Išvalyti
+popiet
+Nustatykite mėnesį
+Nepavyko nukopijuoti į iškarpinę
+Nustatykite datą ir laiką
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_lv.xtb b/ui/android/java/strings/translations/android_ui_strings_lv.xtb
new file mode 100644
index 0000000000000..600c434c195ca
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_lv.xtb
@@ -0,0 +1,43 @@
+
+
+
+Zaļa
+:
+Gads
+Datuma iestatīšana
+Atcelt
+Krāsas izvēle
+Fuksīnsarkana
+Neizdevās atvērt atlasīto failu.
+Melna
+Stundas
+Dzeltena
+Datums
+Sarkana
+Zila
+Iestatīt
+Minūtes
+Sekundes
+Nedēļa
+Laika iestatīšana
+Mēnesis
+Nedēļas iestatīšana
+Iepriekšējo darbību nevar pabeigt mazā atmiņas apjoma dēļ.
+AM
+Vairāk
+Vērtība
+Ciānzila
+priekšpusdienā/pēcpusdienā
+Nokrāsa
+Balta
+:
+Piesātinājums
+Milisekundes
+Laiks
+Cits
+Notīrīt
+PM
+Mēneša iestatīšana
+Neizdevās kopēt starpliktuvē.
+Datuma un laika iestatīšana
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_nl.xtb b/ui/android/java/strings/translations/android_ui_strings_nl.xtb
new file mode 100644
index 0000000000000..4a9d795bee474
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_nl.xtb
@@ -0,0 +1,43 @@
+
+
+
+Groen
+:
+Jaar
+Datum instellen
+Annuleren
+Kleur selecteren
+Magenta
+Kan geselec. bestand niet openen
+Zwart
+Uur
+Geel
+Datum
+Rood
+Blauw
+Instellen
+Minuut
+Seconde
+Week
+Tijd instellen
+Maand
+Week instellen
+Kan vorige bewerking niet voltooien. Te weinig geheugen
+a.m.
+Meer
+Waarde
+Cyaan
+a.m./p.m.
+Kleurtoon
+Wit
+.
+Verzadiging
+Milliseconde
+Tijd
+Overige
+Wissen
+p.m.
+Maand instellen
+Kopiëren naar het klembord mislukt
+Datum en tijd instellen
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_no.xtb b/ui/android/java/strings/translations/android_ui_strings_no.xtb
new file mode 100644
index 0000000000000..3b40da4c217a1
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_no.xtb
@@ -0,0 +1,43 @@
+
+
+
+Grønn
+:
+År
+Angi dato
+Avbryt
+Velg farge
+Magenta
+Kunne ikke åpne den valgte filen
+Svart
+Time
+Gul
+Dato
+Rød
+Blå
+Angi
+Minutt
+Sekund
+Uke
+Angi tid
+Måned
+Angi uke
+Kan ikke fullføre forrige handling på grunn av lite minne
+AM
+Mer
+Verdi
+Cyan
+AM/PM
+Fargetone
+Hvit
+.
+Metning
+Millisekund
+Klokkeslett
+Annet
+Tøm
+PM
+Angi måned
+Kunne ikke kopiere til utklippstavlen
+Angi dato og klokkeslett
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_pl.xtb b/ui/android/java/strings/translations/android_ui_strings_pl.xtb
new file mode 100644
index 0000000000000..88ea63b36aa77
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_pl.xtb
@@ -0,0 +1,43 @@
+
+
+
+Zielony
+:
+Rok
+Ustaw datę
+Anuluj
+Wybierz kolor
+Magenta
+Nie udało się otworzyć pliku
+Czarny
+Godzina
+Żółty
+Data
+Czerwony
+Niebieski
+Ustaw
+Minuta
+Sekunda
+Tydzień
+Ustaw czas
+Miesiąc
+Ustaw tydzień
+Zbyt mało pamięci, by ukończyć poprzednią operację
+AM
+Więcej
+Wartość
+Cyjan
+rano/po południu
+Odcień
+Biały
+.
+Nasycenie
+Milisekunda
+Godzina
+Inne
+Wyczyść
+PM
+Ustaw miesiąc
+Nie udało się skopiować do schowka
+Ustaw datę i godzinę
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_pt-BR.xtb b/ui/android/java/strings/translations/android_ui_strings_pt-BR.xtb
new file mode 100644
index 0000000000000..bbf856f52b849
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_pt-BR.xtb
@@ -0,0 +1,43 @@
+
+
+
+Verde
+:
+Ano
+Definir data
+Cancelar
+Selecionar cor
+Magenta
+Erro ao abrir arq. selecionado
+Preto
+Hora
+Amarelo
+Data
+Vermelho
+Azul
+Definir
+Minuto
+Segundo
+Semana
+Definir hora
+Mês
+Definir semana
+Devido à insuficiência de memória, não foi possível concluir a operação anterior
+AM
+Mais
+Valor
+Ciano
+AM/PM
+Matiz
+Branco
+.
+Saturação
+Milissegundo
+Tempo
+Outro
+Limpar
+PM
+Definir mês
+Falha ao copiar para a área de transferência
+Definir data e hora
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_pt-PT.xtb b/ui/android/java/strings/translations/android_ui_strings_pt-PT.xtb
new file mode 100644
index 0000000000000..79e78c65d134b
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_pt-PT.xtb
@@ -0,0 +1,43 @@
+
+
+
+Verde
+:
+Ano
+Definir data
+Cancelar
+Selecionar cor
+Magenta
+Falha ao abrir o fich. selec.
+Preto
+Hora
+Amarelo
+Data
+Vermelho
+Azul
+Definir
+Minuto
+Segundo
+Semana
+Definir hora
+Mês
+Definir semana
+Não foi possível concluir a operação anterior devido à baixa memória disponível
+AM
+Mais
+Valor
+Turquesa
+AM/PM
+Tonalidade
+Branco
+.
+Saturação
+Milissegundo
+Tempo
+Outros
+Limpar
+PM
+Definir mês
+Falha ao copiar para a área de transferência
+Definir data e hora
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_ro.xtb b/ui/android/java/strings/translations/android_ui_strings_ro.xtb
new file mode 100644
index 0000000000000..8bb0643284445
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_ro.xtb
@@ -0,0 +1,43 @@
+
+
+
+Verde
+:
+An
+Setați data
+Anulați
+Selectați culoarea
+Magenta
+Fișier. select. nu s-a deschis
+Negru
+Oră
+Galben
+Data
+Roșu
+Albastru
+Setați
+Minut
+Secundă
+Săptămână
+Setați ora
+Lună
+Setați săptămâna
+Operația anterioară nu se poate finaliza, din cauza memoriei insuficiente
+a.m.
+Mai multe
+Valoare
+Cyan
+AM/PM
+Nuanță
+Alb
+.
+Saturație
+Milisecundă
+Oră
+Altele
+Ștergeți
+p.m.
+Setați luna
+Nu s-a copiat în clipboard
+Setați data și ora
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_ru.xtb b/ui/android/java/strings/translations/android_ui_strings_ru.xtb
new file mode 100644
index 0000000000000..fcdc91ce86121
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_ru.xtb
@@ -0,0 +1,43 @@
+
+
+
+Зеленый
+:
+Год
+Выберите дату
+Отмена
+Выберите цвет
+Пурпурный
+Не удалось открыть файл
+Черный
+Часы
+Желтый
+Дата
+Красный
+Синий
+Установить
+Минуты
+Секунды
+Неделя
+Установите время
+Месяц
+Выберите неделю
+Не удалось завершить операцию (недостаточно памяти)
+AM
+Подробнее...
+Значение
+Голубой
+AM/PM
+Тон
+Белый
+:
+Насыщенность
+Миллисекунды
+Время
+Другое
+Очистить
+PM
+Выберите месяц
+Не удалось копировать данные в буфер обмена
+Установите дату и время
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_sk.xtb b/ui/android/java/strings/translations/android_ui_strings_sk.xtb
new file mode 100644
index 0000000000000..c47e79c01c484
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_sk.xtb
@@ -0,0 +1,43 @@
+
+
+
+Zelená
+:
+Rok
+Nastavenie dátumu
+Zrušiť
+Výber farby
+Purpurová
+Vybr. súbor sa nepodar. otvoriť
+Čierna
+Hodina
+Žltá
+Dátum
+Červená
+Modrá
+Nastaviť
+Minúta
+Sekunda
+Týždeň
+Nastavenie času
+Mesiac
+Nastavenie týždňa
+Predchádzajúca operácia sa nedokončila z dôvodu nedostatku pamäte
+AM
+Viac
+Hodnota
+Azúrová
+dop. / odp.
+Odtieň
+Biela
+.
+Sýtosť
+Milisekunda
+Čas
+Iné
+Vymazať
+PM
+Nastavenie mesiaca
+Skopírovanie do schránky sa nepodarilo
+Nastavenie dátumu a času
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_sl.xtb b/ui/android/java/strings/translations/android_ui_strings_sl.xtb
new file mode 100644
index 0000000000000..badfb7ee33690
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_sl.xtb
@@ -0,0 +1,43 @@
+
+
+
+Zelena
+:
+Leto
+Nastavitev datuma
+Prekliči
+Izbira barve
+Škrlatna
+Izb. dat. ni bilo mogoče odpreti
+Črna
+Ura
+Rumena
+Datum
+Rdeča
+Modra
+Nastavi
+Minuta
+Sekunda
+Teden
+Nastavitev časa
+Mesec
+Nastavitev tedna
+Prejšnjega dejanja ni mogoče končati, ker primanjkuje pomnilnika
+dop.
+Več
+Vrednost
+Cianova
+Dopoldne/popoldne
+Odtenek
+Bela
+.
+Nasičenost
+Milisekunda
+Čas
+Drugo
+Počisti
+pop.
+Nastavitev meseca
+Kopiranje v odložišče ni uspelo
+Nastavitev datuma in časa
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_sr.xtb b/ui/android/java/strings/translations/android_ui_strings_sr.xtb
new file mode 100644
index 0000000000000..47825d7aa0e75
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_sr.xtb
@@ -0,0 +1,43 @@
+
+
+
+Зелена
+:
+Година
+Подесите датум
+Откажи
+Изаберите боју
+Циклама
+Неуспешно отварање изабр. датот.
+Црна
+Сат
+Жута
+Датум
+Црвена
+Плава
+Постави
+Минут
+Секунд
+Недеља
+Подесите време
+Месец
+Подесите недељу
+Није могуће довршити претходну радњу због недостатка меморије
+AM
+Више
+Вредност
+Плавозелена
+пре подне/по подне
+Нијанса
+Бела
+,
+Засићеност боја
+Милисекунд
+Време
+Друго
+Обриши
+PM
+Подесите месец
+Копирање у привремену меморију није успело
+Подесите датум и време
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_sv.xtb b/ui/android/java/strings/translations/android_ui_strings_sv.xtb
new file mode 100644
index 0000000000000..252968df1ef26
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_sv.xtb
@@ -0,0 +1,43 @@
+
+
+
+Grön
+.
+År
+Ange datum
+Avbryt
+Välj färg
+Magenta
+Det gick inte att öppna filen
+Svart
+Timme
+Gul
+Datum
+Röd
+Blå
+Ange
+Minut
+Sekund
+Vecka
+Ange tid
+Månad
+Ange vecka
+Föregående åtgärd kan inte slutföras. För lite minne.
+AM
+Mer
+Värde
+Cyanblå
+FM/EM
+Nyans
+Vit
+,
+Mättnad
+Millisekund
+Tid
+Övrigt
+Rensa
+PM
+Ange månad
+Det gick inte att kopiera till Urklipp
+Ange datum och tid
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_sw.xtb b/ui/android/java/strings/translations/android_ui_strings_sw.xtb
new file mode 100644
index 0000000000000..a1b2c441b1242
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_sw.xtb
@@ -0,0 +1,43 @@
+
+
+
+Kijani
+:
+Mwaka
+Weka tarehe
+Ghairi
+Chagua rangi
+Rangi ya damu ya mzee
+Imeshindwa kufungua faili iliyochaguliwa
+Nyeusi
+Saa
+Manjano
+Tarehe
+Nyekundu
+Samawati
+Weka
+Dakika
+Sekunde
+Juma
+Weka muda
+Mwezi
+Weka wiki
+Imeshindwa kukamilisha jukumu lililotangulia kwa sababu ya nafasi ndogo ya hifadhi
+AM
+Zaidi
+Thamani
+Samawati-Kijani
+AM / PM
+Rangi
+Nyeupe
+.
+Kukolea
+Milisekunde
+Muda
+Nyingine
+Futa
+PM
+Weka mwezi
+Ilishindwa kunakiliwa kwenda kwenye ubao klipu.
+Weka tarehe na saa
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_th.xtb b/ui/android/java/strings/translations/android_ui_strings_th.xtb
new file mode 100644
index 0000000000000..7039d1bdf0eb4
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_th.xtb
@@ -0,0 +1,43 @@
+
+
+
+สีเขียว
+.
+ปี
+ตั้งวันที่
+ยกเลิก
+เลือกสี
+สีม่วงแดง
+ไม่สามารถเปิดไฟล์ที่เลือก
+สีดำ
+ชั่วโมง
+สีเหลือง
+วันที่
+สีแดง
+สีน้ำเงิน
+ตั้งค่า
+นาที
+วินาที
+สัปดาห์
+ตั้งเวลา
+เดือน
+ตั้งสัปดาห์
+ไม่สามารถดำเนินการก่อนหน้าให้สิ้นสุดได้เพราะหน่วยความจำเหลือน้อย
+AM
+เพิ่มเติม
+ราคา
+สีฟ้า
+AM/PM
+โทนสี
+สีขาว
+.
+ความอิ่มตัวของสี
+มิลลิวินาที
+เวลา
+อื่นๆ
+ล้าง
+PM
+ตั้งเดือน
+ไม่สามารถคัดลอกไปยังคลิปบอร์ด
+ตั้งวันที่และเวลา
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_tr.xtb b/ui/android/java/strings/translations/android_ui_strings_tr.xtb
new file mode 100644
index 0000000000000..4c44caf5b94f3
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_tr.xtb
@@ -0,0 +1,43 @@
+
+
+
+Yeşil
+:
+Yıl
+Tarihi ayarlayın
+İptal
+Renk seçin
+Macenta
+Seçilen dosya açılamadı
+Siyah
+Saat
+Sarı
+Tarih
+Kırmızı
+Mavi
+Ayarla
+Dakika
+Saniye
+Hafta
+Saati ayarlayın
+Ay
+Haftayı ayarlayın
+Bellek yetersiz olduğundan önceki işlem tamamlanamadı
+ÖÖ
+Daha fazla
+Değer
+Camgöbeği
+AM/PM
+Ton
+Beyaz
+.
+Doygunluk
+Milisaniye
+Zaman
+Diğer
+Temizle
+ÖS
+Ayı ayarlayın
+Panoya kopyalanamadı
+Tarihi ve saati ayarlayın
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_uk.xtb b/ui/android/java/strings/translations/android_ui_strings_uk.xtb
new file mode 100644
index 0000000000000..976dc0844633d
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_uk.xtb
@@ -0,0 +1,43 @@
+
+
+
+Зелений
+:
+Рік
+Вибрати дату
+Скасувати
+Вибрати колір
+Пурпурний
+Не вдалося відкрити файл
+Чорний
+Години
+Жовтий
+Дата
+Червоний
+Синій
+Встановити
+Хвилини
+Секунди
+Тиждень
+Вибрати час
+Місяць
+Вибрати тиждень
+Не вдається закінчити попередню операцію через нестачу пам’яті
+дп
+Більше
+Яскравість
+Бірюзовий
+д.п./п.п.
+Тон
+Білий
+.
+Насиченість
+Мілісекунди
+Час
+Інше
+Очистити
+пп
+Вибрати місяць
+Не вдалося скопіювати в буфер обміну
+Вибрати дату й час
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_vi.xtb b/ui/android/java/strings/translations/android_ui_strings_vi.xtb
new file mode 100644
index 0000000000000..deaf2d2ab36ca
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_vi.xtb
@@ -0,0 +1,43 @@
+
+
+
+Xanh lục
+:
+Năm
+Đặt ngày
+Hủy
+Chọn màu
+Đỏ thẫm
+Không mở được tệp đã chọn
+Đen
+Giờ
+Vàng
+Ngày Tháng
+Đỏ
+Xanh lam
+Đặt
+Phút
+Giây
+Tuần
+Đặt thời gian
+Tháng
+Đặt tuần
+Không thể hoàn tất thao tác trước do bộ nhớ thấp
+SA
+Thêm
+Giá trị
+Lục lam
+SA/CH
+Màu sắc
+Trắng
+.
+Độ bão hòa
+Mili giây
+Thời gian
+Khác
+Xóa
+CH
+Đặt tháng
+Sao chép sang khay nhớ tạm không thành công
+Đặt ngày giờ
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_zh-CN.xtb b/ui/android/java/strings/translations/android_ui_strings_zh-CN.xtb
new file mode 100644
index 0000000000000..c5bc02e039fb2
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_zh-CN.xtb
@@ -0,0 +1,43 @@
+
+
+
+绿色
+:
+年
+设置日期
+取消
+选择颜色
+洋红色
+无法打开所选文件
+黑色
+小时
+黄色
+日期
+红色
+蓝色
+设置
+分钟
+秒
+周
+设置时间
+月
+设置星期
+内存不足,无法完成上一操作
+上午
+更多
+值
+青色
+上午/下午
+色调
+白色
+.
+饱和度
+毫秒
+时间
+其他
+清除
+下午
+设置月份
+未能复制到剪贴板
+设置日期和时间
+
\ No newline at end of file
diff --git a/ui/android/java/strings/translations/android_ui_strings_zh-TW.xtb b/ui/android/java/strings/translations/android_ui_strings_zh-TW.xtb
new file mode 100644
index 0000000000000..0eb11180bff0c
--- /dev/null
+++ b/ui/android/java/strings/translations/android_ui_strings_zh-TW.xtb
@@ -0,0 +1,43 @@
+
+
+
+綠色
+:
+年
+設定日期
+取消
+選取顏色
+洋紅色
+無法開啟選取的檔案
+黑色
+小時
+黃色
+日期
+紅色
+藍色
+設定
+分鐘
+秒
+週
+設定時間
+月
+設定週次
+記憶體不足,無法完成前一項操作
+AM
+詳細資訊
+值
+青色
+AM/PM
+色調
+白色
+.
+飽和度
+毫秒
+時間
+其他
+清除
+PM
+設定月份
+無法複製到剪貼簿
+設定日期和時間
+
\ No newline at end of file