findAccessibilityNodeInfosByText(String text,
+ int virtualViewId) {
+ return delegate.findAccessibilityNodeInfosByText(text, virtualViewId);
+ }
+
+ @Override
+ public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ return delegate.performAction(virtualViewId, action, arguments);
+ }
+ };
+ }
+
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+ return mAccessibilityNodeProvider;
+ }
+}
diff --git a/src/org/chromium/content/browser/input/AdapterInputConnection.java b/src/org/chromium/content/browser/input/AdapterInputConnection.java
index 75b7fa5..ab8a1fc 100644
--- a/src/org/chromium/content/browser/input/AdapterInputConnection.java
+++ b/src/org/chromium/content/browser/input/AdapterInputConnection.java
@@ -51,7 +51,8 @@ public class AdapterInputConnection extends BaseInputConnection {
mImeAdapter = imeAdapter;
mImeAdapter.setInputConnection(this);
mSingleLine = true;
- outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
+ outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN
+ | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
@@ -124,25 +125,24 @@ public void setEditableText(String text, int selectionStart, int selectionEnd,
// Non-breaking spaces can cause the IME to get confused. Replace with normal spaces.
text = text.replace('\u00A0', ' ');
- Editable editable = getEditable();
-
- int prevSelectionStart = Selection.getSelectionStart(editable);
- int prevSelectionEnd = Selection.getSelectionEnd(editable);
- int prevCompositionStart = getComposingSpanStart(editable);
- int prevCompositionEnd = getComposingSpanEnd(editable);
- String prevText = editable.toString();
-
selectionStart = Math.min(selectionStart, text.length());
selectionEnd = Math.min(selectionEnd, text.length());
compositionStart = Math.min(compositionStart, text.length());
compositionEnd = Math.min(compositionEnd, text.length());
+ Editable editable = getEditable();
+ String prevText = editable.toString();
boolean textUnchanged = prevText.equals(text);
if (!textUnchanged) {
editable.replace(0, editable.length(), text);
}
+ int prevSelectionStart = Selection.getSelectionStart(editable);
+ int prevSelectionEnd = Selection.getSelectionEnd(editable);
+ int prevCompositionStart = getComposingSpanStart(editable);
+ int prevCompositionEnd = getComposingSpanEnd(editable);
+
if (prevSelectionStart == selectionStart && prevSelectionEnd == selectionEnd
&& prevCompositionStart == compositionStart
&& prevCompositionEnd == compositionEnd) {
@@ -311,7 +311,6 @@ public boolean deleteSurroundingText(int leftLength, int rightLength) {
@Override
public boolean sendKeyEvent(KeyEvent event) {
if (DEBUG) Log.w(TAG, "sendKeyEvent [" + event.getAction() + "]");
- mImeAdapter.hideSelectionAndInsertionHandleControllers();
// If this is a key-up, and backspace/del or if the key has a character representation,
// need to update the underlying Editable (i.e. the local representation of the text
@@ -352,19 +351,9 @@ public boolean finishComposingText() {
return true;
}
- // TODO(aurimas): remove this workaround of changing composition before confirmComposition
- // Blink should support keeping the cursor (http://crbug.com/239923)
- int selectionStart = Selection.getSelectionStart(editable);
- int compositionStart = getComposingSpanStart(editable);
super.finishComposingText();
+ mImeAdapter.finishComposingText();
- beginBatchEdit();
- if (compositionStart != -1 && compositionStart < selectionStart
- && !mImeAdapter.setComposingRegion(compositionStart, selectionStart)) {
- return false;
- }
- if (!mImeAdapter.checkCompositionQueueAndCallNative("", 0, true)) return false;
- endBatchEdit();
return true;
}
diff --git a/src/org/chromium/content/browser/input/DateDialogNormalizer.java b/src/org/chromium/content/browser/input/DateDialogNormalizer.java
new file mode 100644
index 0000000..1dbc6c7
--- /dev/null
+++ b/src/org/chromium/content/browser/input/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.content.browser.input;
+
+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.
+ */
+class DateDialogNormalizer {
+
+ private static void setLimits(DatePicker picker, long min, long max) {
+ // 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 (max <= min) {
+ return;
+ }
+ Calendar minCal = trimToDate(min);
+ Calendar maxCal = trimToDate(max);
+ 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.
+ */
+ static void normalize(DatePicker picker, OnDateChangedListener listener,
+ int year, int month, int day, int hour, int minute, long min, long max) {
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ calendar.clear();
+ calendar.set(year, month, day, hour, minute, 0);
+ if (calendar.getTimeInMillis() < min) {
+ calendar.clear();
+ calendar.setTimeInMillis(min);
+ } else if (calendar.getTimeInMillis() > max) {
+ calendar.clear();
+ calendar.setTimeInMillis(max);
+ }
+ picker.init(
+ calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH), listener);
+
+ setLimits(picker, min, max);
+ }
+}
diff --git a/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java b/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java
index f8fca54..0c58217 100644
--- a/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java
+++ b/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java
@@ -28,10 +28,10 @@ private DateTimeChooserAndroid(Context context,
@Override
public void replaceDateTime(
int dialogType,
- int year, int month, int day, int hour, int minute, int second) {
+ int year, int month, int day, int hour, int minute, int second, int week) {
nativeReplaceDateTime(mNativeDateTimeChooserAndroid,
dialogType,
- year, month, day, hour, minute, second);
+ year, month, day, hour, minute, second, week);
}
@Override
@@ -42,9 +42,9 @@ public void cancelDateTimeDialog() {
}
private void showDialog(int dialogType, int year, int month, int monthDay,
- int hour, int minute, int second) {
+ int hour, int minute, int second, int week, double min, double max) {
mInputDialogContainer.showDialog(dialogType, year, month, monthDay,
- hour, minute, second);
+ hour, minute, second, week, min, max);
}
@CalledByNative
@@ -52,25 +52,26 @@ private static DateTimeChooserAndroid createDateTimeChooser(
ContentViewCore contentViewCore,
int nativeDateTimeChooserAndroid, int dialogType,
int year, int month, int day,
- int hour, int minute, int second) {
+ int hour, int minute, int second, int week, double min, double max) {
DateTimeChooserAndroid chooser =
new DateTimeChooserAndroid(
contentViewCore.getContext(), nativeDateTimeChooserAndroid);
- chooser.showDialog(dialogType, year, month, day, hour, minute, second);
+ chooser.showDialog(dialogType, year, month, day, hour, minute, second, week, min, max);
return chooser;
}
@CalledByNative
private static void initializeDateInputTypes(int textInputTypeDate, int textInputTypeDateTime,
int textInputTypeDateTimeLocal, int textInputTypeMonth,
- int textInputTypeTime) {
- InputDialogContainer.initializeInputTypes(textInputTypeDate, textInputTypeDateTime,
- textInputTypeDateTimeLocal, textInputTypeMonth, textInputTypeTime);
+ int textInputTypeTime, int textInputTypeWeek) {
+ InputDialogContainer.initializeInputTypes(textInputTypeDate,
+ textInputTypeDateTime, textInputTypeDateTimeLocal,
+ textInputTypeMonth, textInputTypeTime, textInputTypeWeek);
}
private native void nativeReplaceDateTime(int nativeDateTimeChooserAndroid,
int dialogType,
- int year, int month, int day, int hour, int minute, int second);
+ int year, int month, int day, int hour, int minute, int second, int week);
private native void nativeCancelDialog(int nativeDateTimeChooserAndroid);
}
diff --git a/src/org/chromium/content/browser/input/DateTimePickerDialog.java b/src/org/chromium/content/browser/input/DateTimePickerDialog.java
index 29bca66..4012b22 100644
--- a/src/org/chromium/content/browser/input/DateTimePickerDialog.java
+++ b/src/org/chromium/content/browser/input/DateTimePickerDialog.java
@@ -9,12 +9,12 @@
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Build;
-import android.os.Bundle;
+import android.text.format.Time;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.DatePicker;
-import android.widget.TimePicker;
import android.widget.DatePicker.OnDateChangedListener;
+import android.widget.TimePicker;
import android.widget.TimePicker.OnTimeChangedListener;
import org.chromium.content.R;
@@ -33,6 +33,9 @@ class DateTimePickerDialog extends AlertDialog implements OnClickListener,
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.
*/
@@ -64,27 +67,12 @@ public DateTimePickerDialog(Context context,
int year,
int monthOfYear,
int dayOfMonth,
- int hourOfDay, int minute, boolean is24HourView) {
- this(context, 0, callBack, year, monthOfYear, dayOfMonth,
- hourOfDay, minute, is24HourView);
- }
+ int hourOfDay, int minute, boolean is24HourView,
+ long min, long max) {
+ super(context, 0);
- /**
- * @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 monthOfYear The initial month of the dialog.
- * @param dayOfMonth The initial day of the dialog.
- */
- public DateTimePickerDialog(Context context,
- int theme,
- OnDateTimeSetListener callBack,
- int year,
- int monthOfYear,
- int dayOfMonth,
- int hourOfDay, int minute, boolean is24HourView) {
- super(context, theme);
+ mMinTimeMillis = min;
+ mMaxTimeMillis = max;
mCallBack = callBack;
@@ -100,13 +88,16 @@ public DateTimePickerDialog(Context context,
View view = inflater.inflate(R.layout.date_time_picker_dialog, null);
setView(view);
mDatePicker = (DatePicker) view.findViewById(R.id.date_picker);
- mDatePicker.init(year, monthOfYear, dayOfMonth, this);
+ DateDialogNormalizer.normalize(mDatePicker, this,
+ year, monthOfYear, dayOfMonth, hourOfDay, minute, min, max);
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
@@ -138,30 +129,27 @@ protected void onStop() {
@Override
public void onDateChanged(DatePicker view, int year,
int month, int day) {
- mDatePicker.init(year, month, day, null);
+ // 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) {
- /* do nothing */
- }
-
- /**
- * Gets the {@link DatePicker} contained in this dialog.
- *
- * @return The DatePicker view.
- */
- public DatePicker getDatePicker() {
- return mDatePicker;
- }
-
- /**
- * Gets the {@link TimePicker} contained in this dialog.
- *
- * @return The TimePicker view.
- */
- public TimePicker getTimePicker() {
- return mTimePicker;
+ 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);
}
/**
@@ -177,30 +165,4 @@ public void updateDateTime(int year, int monthOfYear, int dayOfMonth,
mTimePicker.setCurrentHour(hourOfDay);
mTimePicker.setCurrentMinute(minutOfHour);
}
-
- @Override
- public Bundle onSaveInstanceState() {
- Bundle state = super.onSaveInstanceState();
- state.putInt(YEAR, mDatePicker.getYear());
- state.putInt(MONTH, mDatePicker.getMonth());
- state.putInt(DAY, mDatePicker.getDayOfMonth());
- state.putInt(HOUR, mTimePicker.getCurrentHour());
- state.putInt(MINUTE, mTimePicker.getCurrentMinute());
- state.putBoolean(IS_24_HOUR, mTimePicker.is24HourView());
- return state;
- }
-
- @Override
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- super.onRestoreInstanceState(savedInstanceState);
- int year = savedInstanceState.getInt(YEAR);
- int month = savedInstanceState.getInt(MONTH);
- int day = savedInstanceState.getInt(DAY);
- mDatePicker.init(year, month, day, this);
- int hour = savedInstanceState.getInt(HOUR);
- int minute = savedInstanceState.getInt(MINUTE);
- mTimePicker.setIs24HourView(savedInstanceState.getBoolean(IS_24_HOUR));
- mTimePicker.setCurrentHour(hour);
- mTimePicker.setCurrentMinute(minute);
- }
}
diff --git a/src/org/chromium/content/browser/input/ImeAdapter.java b/src/org/chromium/content/browser/input/ImeAdapter.java
index 354b242..dd85bfc 100644
--- a/src/org/chromium/content/browser/input/ImeAdapter.java
+++ b/src/org/chromium/content/browser/input/ImeAdapter.java
@@ -4,7 +4,6 @@
package org.chromium.content.browser.input;
-import android.content.Context;
import android.os.Handler;
import android.os.ResultReceiver;
import android.view.KeyCharacterMap;
@@ -40,7 +39,7 @@
*/
@JNINamespace("content")
public class ImeAdapter {
- public interface ViewEmbedder {
+ public interface ImeAdapterDelegate {
/**
* @param isFinish whether the event is occurring because input is finished.
*/
@@ -98,14 +97,11 @@ public void run() {
static int sModifierNumLockOn;
private int mNativeImeAdapterAndroid;
- private final Context mContext;
private InputMethodManagerWrapper mInputMethodManagerWrapper;
private AdapterInputConnection mInputConnection;
- private final ViewEmbedder mViewEmbedder;
+ private final ImeAdapterDelegate mViewEmbedder;
private final Handler mHandler;
private DelayedDismissInput mDismissInput = null;
- private final SelectionHandleController mSelectionHandleController;
- private final InsertionHandleController mInsertionHandleController;
private int mTextInputType;
private int mInitialSelectionStart;
private int mInitialSelectionEnd;
@@ -114,17 +110,12 @@ public void run() {
boolean mIsShowWithoutHideOutstanding = false;
/**
- * @param context View context.
- * @param selectionHandleController The controller that handles selection.
- * @param insertionHandleController The controller that handles insertion.
+ * @param wrapper InputMethodManagerWrapper that should receive all the call directed to
+ * InputMethodManager.
* @param embedder The view that is used for callbacks from ImeAdapter.
*/
- public ImeAdapter(Context context, SelectionHandleController selectionHandleController,
- InsertionHandleController insertionHandleController, ViewEmbedder embedder) {
- mContext = context;
- mInputMethodManagerWrapper = new InputMethodManagerWrapper(context);
- mSelectionHandleController = selectionHandleController;
- mInsertionHandleController = insertionHandleController;
+ public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) {
+ mInputMethodManagerWrapper = wrapper;
mViewEmbedder = embedder;
mHandler = new Handler();
}
@@ -137,7 +128,7 @@ public AdapterInputConnection get(View view, ImeAdapter imeAdapter,
}
@VisibleForTesting
- protected void setInputMethodManagerWrapper(InputMethodManagerWrapper immw) {
+ public void setInputMethodManagerWrapper(InputMethodManagerWrapper immw) {
mInputMethodManagerWrapper = immw;
}
@@ -206,11 +197,6 @@ private static int getModifiers(int metaState) {
return modifiers;
}
- void hideSelectionAndInsertionHandleControllers() {
- mSelectionHandleController.hideAndDisallowAutomaticShowing();
- mInsertionHandleController.hideAndDisallowAutomaticShowing();
- }
-
public boolean isActive() {
return mInputConnection != null && mInputConnection.isActive();
}
@@ -260,7 +246,9 @@ public void attach(int nativeImeAdapter, int textInputType, int selectionStart,
mTextInputType = textInputType;
mInitialSelectionStart = selectionStart;
mInitialSelectionEnd = selectionEnd;
- nativeAttachImeAdapter(mNativeImeAdapterAndroid);
+ if (nativeImeAdapter != 0) {
+ nativeAttachImeAdapter(mNativeImeAdapterAndroid);
+ }
}
/**
@@ -351,10 +339,6 @@ boolean checkCompositionQueueAndCallNative(String text, int newCursorPosition,
// Committing an empty string finishes the current composition.
boolean isFinish = text.isEmpty();
- if (!isFinish) {
- mSelectionHandleController.hideAndDisallowAutomaticShowing();
- mInsertionHandleController.hideAndDisallowAutomaticShowing();
- }
mViewEmbedder.onImeEvent(isFinish);
int keyCode = shouldSendKeyEventWithKeyCode(text);
long timeStampMs = System.currentTimeMillis();
@@ -377,6 +361,10 @@ boolean checkCompositionQueueAndCallNative(String text, int newCursorPosition,
return true;
}
+ void finishComposingText() {
+ nativeFinishComposingText(mNativeImeAdapterAndroid);
+ }
+
boolean translateAndSendNativeEvents(KeyEvent event) {
if (mNativeImeAdapterAndroid == 0) return false;
@@ -558,6 +546,8 @@ private native void nativeSetComposingText(int nativeImeAdapterAndroid, String t
private native void nativeCommitText(int nativeImeAdapterAndroid, String text);
+ private native void nativeFinishComposingText(int nativeImeAdapterAndroid);
+
private native void nativeAttachImeAdapter(int nativeImeAdapterAndroid);
private native void nativeSetEditableSelectionOffsets(int nativeImeAdapterAndroid,
diff --git a/src/org/chromium/content/browser/input/InputDialogContainer.java b/src/org/chromium/content/browser/input/InputDialogContainer.java
index 11189c5..07dd7e0 100644
--- a/src/org/chromium/content/browser/input/InputDialogContainer.java
+++ b/src/org/chromium/content/browser/input/InputDialogContainer.java
@@ -19,7 +19,7 @@
import android.widget.TimePicker;
import org.chromium.content.browser.input.DateTimePickerDialog.OnDateTimeSetListener;
-import org.chromium.content.browser.input.MonthPickerDialog.OnMonthSetListener;
+import org.chromium.content.browser.input.TwoFieldDatePickerDialog;
import org.chromium.content.R;
import java.text.ParseException;
@@ -32,7 +32,7 @@ public class InputDialogContainer {
interface InputActionDelegate {
void cancelDateTimeDialog();
void replaceDateTime(int dialogType,
- int year, int month, int day, int hour, int minute, int second);
+ int year, int month, int day, int hour, int minute, int second, int week);
}
// Default values used in Time representations of selected date/time before formatting.
@@ -42,6 +42,7 @@ void replaceDateTime(int dialogType,
private static final int MONTHDAY_DEFAULT = 1;
private static final int HOUR_DEFAULT = 0;
private static final int MINUTE_DEFAULT = 0;
+ private static final int WEEK_DEFAULT = 0;
// Date formats as accepted by Time.format.
private static final String HTML_DATE_FORMAT = "%Y-%m-%d";
@@ -51,12 +52,14 @@ void replaceDateTime(int dialogType,
private static final String HTML_DATE_TIME_FORMAT = "%Y-%m-%dT%H:%MZ";
private static final String HTML_DATE_TIME_LOCAL_FORMAT = "%Y-%m-%dT%H:%M";
private static final String HTML_MONTH_FORMAT = "%Y-%m";
+ private static final String HTML_WEEK_FORMAT = "%Y-%w";
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 Context mContext;
@@ -66,19 +69,22 @@ void replaceDateTime(int dialogType,
private AlertDialog mDialog;
private InputActionDelegate mInputActionDelegate;
- static void initializeInputTypes(int textInputTypeDate, int textInputTypeDateTime,
- int textInputTypeDateTimeLocal, int textInputTypeMonth, int textInputTypeTime) {
+ 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;
}
static boolean isDialogInputType(int type) {
return type == sTextInputTypeDate || type == sTextInputTypeTime
|| type == sTextInputTypeDateTime || type == sTextInputTypeDateTimeLocal
- || type == sTextInputTypeMonth;
+ || type == sTextInputTypeMonth || type == sTextInputTypeWeek;
}
InputDialogContainer(Context context, InputActionDelegate inputActionDelegate) {
@@ -93,7 +99,7 @@ private Time normalizeTime(int year, int month, int monthDay,
minute == 0 && second == 0) {
Calendar cal = Calendar.getInstance();
result.set(cal.get(Calendar.SECOND), cal.get(Calendar.MINUTE),
- cal.get(Calendar.HOUR), cal.get(Calendar.DATE),
+ cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.DATE),
cal.get(Calendar.MONTH), cal.get(Calendar.YEAR));
} else {
result.set(second, minute, hour, monthDay, month, year);
@@ -102,26 +108,48 @@ private Time normalizeTime(int year, int month, int monthDay,
}
void showDialog(final int dialogType, int year, int month, int monthDay,
- int hour, int minute, int second) {
+ int hour, int minute, int second, int week, double min, double max) {
if (isDialogShowing()) mDialog.dismiss();
+ // Java Date dialogs like longs but Blink prefers doubles..
+ // Both parameters mean different things depending on the type
+ // For input type=month min and max come as number on months since 1970
+ // For other types (including type=time) they are just milliseconds since 1970
+ // In any case the cast here is safe given the above restrictions.
+ long minTime = (long) min;
+ long maxTime = (long) max;
+
Time time = normalizeTime(year, month, monthDay, hour, minute, second);
if (dialogType == sTextInputTypeDate) {
- mDialog = new DatePickerDialog(mContext, new DateListener(dialogType),
- time.year, time.month, time.monthDay);
- mDialog.setTitle(mContext.getText(R.string.date_picker_dialog_title));
+ DatePickerDialog dialog = new DatePickerDialog(mContext,
+ new DateListener(dialogType), time.year, time.month, time.monthDay);
+ DateDialogNormalizer.normalize(dialog.getDatePicker(), dialog,
+ time.year, time.month, time.monthDay, 0, 0, minTime, maxTime);
+
+ dialog.setTitle(mContext.getText(R.string.date_picker_dialog_title));
+ mDialog = dialog;
} else if (dialogType == sTextInputTypeTime) {
- mDialog = new TimePickerDialog(mContext, new TimeListener(dialogType),
- time.hour, time.minute, DateFormat.is24HourFormat(mContext));
+ mDialog = TimeDialog.create(mContext, new TimeListener(dialogType),
+ time.hour, time.minute, DateFormat.is24HourFormat(mContext),
+ minTime, maxTime);
} else if (dialogType == sTextInputTypeDateTime ||
dialogType == sTextInputTypeDateTimeLocal) {
mDialog = new DateTimePickerDialog(mContext,
new DateTimeListener(dialogType),
time.year, time.month, time.monthDay,
- time.hour, time.minute, DateFormat.is24HourFormat(mContext));
+ time.hour, time.minute, DateFormat.is24HourFormat(mContext),
+ minTime, maxTime);
} else if (dialogType == sTextInputTypeMonth) {
- mDialog = new MonthPickerDialog(mContext, new MonthListener(dialogType),
- time.year, time.month);
+ mDialog = new MonthPickerDialog(mContext, new MonthOrWeekListener(dialogType),
+ time.year, time.month, minTime, maxTime);
+ } else if (dialogType == sTextInputTypeWeek) {
+ if (week == 0) {
+ Calendar cal = Calendar.getInstance();
+ year = WeekPicker.getISOWeekYearForDate(cal);
+ week = WeekPicker.getWeekForDate(cal);
+ }
+ mDialog = new WeekPickerDialog(mContext, new MonthOrWeekListener(dialogType),
+ year, week, minTime, maxTime);
}
mDialog.setButton(DialogInterface.BUTTON_POSITIVE,
@@ -144,7 +172,7 @@ public void onClick(DialogInterface dialog, int which) {
@Override
public void onClick(DialogInterface dialog, int which) {
mDialogAlreadyDismissed = true;
- mInputActionDelegate.replaceDateTime(dialogType, 0, 0, 0, 0, 0, 0);
+ mInputActionDelegate.replaceDateTime(dialogType, 0, 0, 0, 0, 0, 0, 0);
}
});
@@ -171,7 +199,8 @@ private class DateListener implements OnDateSetListener {
public void onDateSet(DatePicker view, int year, int month, int monthDay) {
if (!mDialogAlreadyDismissed) {
setFieldDateTimeValue(mDialogType,
- year, month, monthDay, HOUR_DEFAULT, MINUTE_DEFAULT,
+ year, month, monthDay,
+ HOUR_DEFAULT, MINUTE_DEFAULT, WEEK_DEFAULT,
HTML_DATE_FORMAT);
}
}
@@ -189,7 +218,7 @@ public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
if (!mDialogAlreadyDismissed) {
setFieldDateTimeValue(mDialogType,
YEAR_DEFAULT, MONTH_DEFAULT, MONTHDAY_DEFAULT,
- hourOfDay, minute, HTML_TIME_FORMAT);
+ hourOfDay, minute, WEEK_DEFAULT, HTML_TIME_FORMAT);
}
}
}
@@ -208,36 +237,43 @@ public void onDateTimeSet(DatePicker dateView, TimePicker timeView,
int year, int month, int monthDay,
int hourOfDay, int minute) {
if (!mDialogAlreadyDismissed) {
- setFieldDateTimeValue(mDialogType, year, month, monthDay, hourOfDay, minute,
+ setFieldDateTimeValue(mDialogType, year, month, monthDay,
+ hourOfDay, minute, WEEK_DEFAULT,
mLocal ? HTML_DATE_TIME_LOCAL_FORMAT : HTML_DATE_TIME_FORMAT);
}
}
}
- private class MonthListener implements OnMonthSetListener {
+ private class MonthOrWeekListener implements TwoFieldDatePickerDialog.OnValueSetListener {
private final int mDialogType;
- MonthListener(int dialogType) {
+ MonthOrWeekListener(int dialogType) {
mDialogType = dialogType;
}
@Override
- public void onMonthSet(MonthPicker view, int year, int month) {
+ public void onValueSet(int year, int positionInYear) {
if (!mDialogAlreadyDismissed) {
- setFieldDateTimeValue(mDialogType, year, month, MONTHDAY_DEFAULT,
- HOUR_DEFAULT, MINUTE_DEFAULT, HTML_MONTH_FORMAT);
+ if (mDialogType == sTextInputTypeMonth) {
+ setFieldDateTimeValue(mDialogType, year, positionInYear, MONTHDAY_DEFAULT,
+ HOUR_DEFAULT, MINUTE_DEFAULT, WEEK_DEFAULT,
+ HTML_MONTH_FORMAT);
+ } else {
+ setFieldDateTimeValue(mDialogType, year, MONTH_DEFAULT, MONTHDAY_DEFAULT,
+ HOUR_DEFAULT, MINUTE_DEFAULT, positionInYear, HTML_WEEK_FORMAT);
+ }
}
}
}
private void setFieldDateTimeValue(int dialogType,
int year, int month, int monthDay, int hourOfDay,
- int minute, String dateFormat) {
+ int minute, int week, String dateFormat) {
// Prevents more than one callback being sent to the native
// side when the dialog triggers multiple events.
mDialogAlreadyDismissed = true;
mInputActionDelegate.replaceDateTime(dialogType,
- year, month, monthDay, hourOfDay, minute, 0 /* second */);
+ year, month, monthDay, hourOfDay, minute, 0 /* second */, week);
}
}
diff --git a/src/org/chromium/content/browser/input/InputMethodManagerWrapper.java b/src/org/chromium/content/browser/input/InputMethodManagerWrapper.java
index 34b8b4e..becc019 100644
--- a/src/org/chromium/content/browser/input/InputMethodManagerWrapper.java
+++ b/src/org/chromium/content/browser/input/InputMethodManagerWrapper.java
@@ -13,7 +13,7 @@
/**
* Wrapper around Android's InputMethodManager
*/
-class InputMethodManagerWrapper {
+public class InputMethodManagerWrapper {
private final Context mContext;
public InputMethodManagerWrapper(Context context) {
diff --git a/src/org/chromium/content/browser/input/MonthPicker.java b/src/org/chromium/content/browser/input/MonthPicker.java
index 01e636e..3e06d75 100644
--- a/src/org/chromium/content/browser/input/MonthPicker.java
+++ b/src/org/chromium/content/browser/input/MonthPicker.java
@@ -5,466 +5,105 @@
package org.chromium.content.browser.input;
import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.format.DateUtils;
-import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.DatePicker;
-import android.widget.FrameLayout;
-import android.widget.NumberPicker;
-import android.widget.NumberPicker.OnValueChangeListener;
import java.text.DateFormatSymbols;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
-import java.util.TimeZone;
import org.chromium.content.R;
-// This class is heavily based on android.widget.DatePicker.
-public class MonthPicker extends FrameLayout {
-
- private static final int DEFAULT_START_YEAR = 1900;
-
- private static final int DEFAULT_END_YEAR = 2100;
-
- private static final boolean DEFAULT_ENABLED_STATE = true;
-
- private final NumberPicker mMonthSpinner;
-
- private final NumberPicker mYearSpinner;
-
- private Locale mCurrentLocale;
-
- private OnMonthChangedListener mMonthChangedListener;
+public class MonthPicker extends TwoFieldDatePicker {
+ private static final int MONTHS_NUMBER = 12;
private String[] mShortMonths;
- private int mNumberOfMonths;
-
- private Calendar mMinDate;
+ public MonthPicker(Context context, long minValue, long maxValue) {
+ super(context, minValue, maxValue);
- private Calendar mMaxDate;
-
- private Calendar mCurrentDate;
-
- private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
-
- /**
- * The callback used to indicate the user changes\d the date.
- */
- public interface OnMonthChangedListener {
-
- /**
- * Called upon a date change.
- *
- * @param view The 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}.
- */
- void onMonthChanged(MonthPicker view, int year, int monthOfYear);
- }
-
- public MonthPicker(Context context) {
- this(context, null);
- }
-
- public MonthPicker(Context context, AttributeSet attrs) {
- this(context, attrs, android.R.attr.datePickerStyle);
- }
-
- public MonthPicker(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ getPositionInYearSpinner().setContentDescription(
+ getResources().getString(R.string.accessibility_date_picker_month));
// initialization based on locale
- setCurrentLocale(Locale.getDefault());
-
- int startYear = DEFAULT_START_YEAR;
- int endYear = DEFAULT_END_YEAR;
-
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.month_picker, this, true);
-
- OnValueChangeListener onChangeListener = new OnValueChangeListener() {
- @Override
- public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
- Calendar tempDate = getCalendarForLocale(null, mCurrentLocale);
- tempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
-
- // take care of wrapping of days and months to update greater fields
- if (picker == mMonthSpinner) {
- if (oldVal == 11 && newVal == 0) {
- tempDate.add(Calendar.MONTH, 1);
- } else if (oldVal == 0 && newVal == 11) {
- tempDate.add(Calendar.MONTH, -1);
- } else {
- tempDate.add(Calendar.MONTH, newVal - oldVal);
- }
- } else if (picker == mYearSpinner) {
- tempDate.set(Calendar.YEAR, newVal);
- } else {
- throw new IllegalArgumentException();
- }
-
- // now set the date to the adjusted one
- setDate(tempDate.get(Calendar.YEAR), tempDate.get(Calendar.MONTH));
- updateSpinners();
- notifyDateChanged();
- }
- };
-
- // month
- mMonthSpinner = (NumberPicker) findViewById(R.id.month);
- mMonthSpinner.setMinValue(0);
- mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
- mMonthSpinner.setDisplayedValues(mShortMonths);
- mMonthSpinner.setOnLongPressUpdateInterval(200);
- mMonthSpinner.setOnValueChangedListener(onChangeListener);
-
- // year
- mYearSpinner = (NumberPicker) findViewById(R.id.year);
- mYearSpinner.setOnLongPressUpdateInterval(100);
- mYearSpinner.setOnValueChangedListener(onChangeListener);
-
- Calendar tempDate = getCalendarForLocale(null, mCurrentLocale);
- tempDate.set(startYear, 0, 1);
-
- setMinDate(tempDate.getTimeInMillis());
- tempDate.set(endYear, 11, 31);
- setMaxDate(tempDate.getTimeInMillis());
+ mShortMonths =
+ DateFormatSymbols.getInstance(Locale.getDefault()).getShortMonths();
// initialize to current date
- mCurrentDate.setTimeInMillis(System.currentTimeMillis());
- init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), null);
- }
-
- /**
- * Gets the minimal date supported by this {@link DatePicker} in
- * milliseconds since January 1, 1970 00:00:00 in
- * {@link TimeZone#getDefault()} time zone.
- *
- * Note: The default minimal date is 01/01/1900.
- *
- *
- * @return The minimal supported date.
- */
- public long getMinDate() {
- return mMinDate.getTimeInMillis();
- }
-
- /**
- * Sets the minimal date supported by this {@link NumberPicker} in
- * milliseconds since January 1, 1970 00:00:00 in
- * {@link TimeZone#getDefault()} time zone.
- *
- * @param minDate The minimal supported date.
- */
- public void setMinDate(long minDate) {
- Calendar tempDate = getCalendarForLocale(null, mCurrentLocale);
- tempDate.setTimeInMillis(minDate);
- if (tempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
- && tempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
- return;
- }
- mMinDate.setTimeInMillis(minDate);
- if (mCurrentDate.before(mMinDate)) {
- mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
- }
- updateSpinners();
- }
-
- /**
- * Gets the maximal date supported by this {@link DatePicker} in
- * milliseconds since January 1, 1970 00:00:00 in
- * {@link TimeZone#getDefault()} time zone.
- *
- * Note: The default maximal date is 12/31/2100.
- *
- *
- * @return The maximal supported date.
- */
- public long getMaxDate() {
- return mMaxDate.getTimeInMillis();
- }
-
- /**
- * Sets the maximal date supported by this {@link DatePicker} in
- * milliseconds since January 1, 1970 00:00:00 in
- * {@link TimeZone#getDefault()} time zone.
- *
- * @param maxDate The maximal supported date.
- */
- public void setMaxDate(long maxDate) {
- Calendar tempDate = getCalendarForLocale(null, mCurrentLocale);
- tempDate.setTimeInMillis(maxDate);
- if (tempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
- && tempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
- return;
- }
- mMaxDate.setTimeInMillis(maxDate);
- if (mCurrentDate.after(mMaxDate)) {
- mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
- }
- updateSpinners();
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- if (mIsEnabled == enabled) {
- return;
- }
- super.setEnabled(enabled);
- mMonthSpinner.setEnabled(enabled);
- mYearSpinner.setEnabled(enabled);
- mIsEnabled = enabled;
+ Calendar cal = Calendar.getInstance();
+ init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), null);
}
@Override
- public boolean isEnabled() {
- return mIsEnabled;
+ protected Calendar createDateFromValue(long value) {
+ int year = (int)Math.min(value / 12 + 1970, Integer.MAX_VALUE);
+ int month = (int) (value % 12);
+ Calendar cal = Calendar.getInstance();
+ cal.clear();
+ cal.set(year, month, 1);
+ return cal;
}
@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);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- setCurrentLocale(newConfig.locale);
- }
-
- /**
- * Sets the current locale.
- *
- * @param locale The current locale.
- */
- private void setCurrentLocale(Locale locale) {
- if (locale.equals(mCurrentLocale)) {
- return;
- }
-
- mCurrentLocale = locale;
- mMinDate = getCalendarForLocale(mMinDate, locale);
- mMaxDate = getCalendarForLocale(mMaxDate, locale);
- mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
-
- mShortMonths =
- DateFormatSymbols.getInstance(mCurrentLocale).getShortMonths();
- mNumberOfMonths = mShortMonths.length;
- }
-
- /**
- * Gets a calendar for locale bootstrapped with the value of a given calendar.
- *
- * @param oldCalendar The old calendar.
- * @param locale The locale.
- */
- private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
- if (oldCalendar == null) {
- return Calendar.getInstance(locale);
+ protected void setCurrentDate(int year, int month) {
+ Calendar date = Calendar.getInstance();
+ date.set(year, month, 1);
+ if (date.before(getMinDate())) {
+ setCurrentDate(getMinDate());
+ } else if (date.after(getMaxDate())) {
+ setCurrentDate(getMaxDate());
} else {
- final long currentTimeMillis = oldCalendar.getTimeInMillis();
- Calendar newCalendar = Calendar.getInstance(locale);
- newCalendar.setTimeInMillis(currentTimeMillis);
- return newCalendar;
- }
- }
-
- /**
- * Updates the current date.
- *
- * @param year The year.
- * @param month The month which is starting from zero.
- */
- public void updateMonth(int year, int month) {
- if (!isNewDate(year, month)) {
- return;
+ setCurrentDate(date);
}
- setDate(year, month);
- updateSpinners();
- notifyDateChanged();
}
- // Override so we are in complete control of save / restore for this widget.
@Override
- protected void dispatchRestoreInstanceState(SparseArray container) {
- dispatchThawSelfOnly(container);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- return new SavedState(superState, getYear(), getMonth());
- }
-
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
- setDate(ss.mYear, ss.mMonth);
- updateSpinners();
- }
-
- /**
- * 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 monthOfYear The initial month starting from zero.
- * @param onMonthChangedListener How user is notified date is changed by
- * user, can be null.
- */
- public void init(int year, int monthOfYear, OnMonthChangedListener onMonthChangedListener) {
- setDate(year, monthOfYear);
- updateSpinners();
- mMonthChangedListener = onMonthChangedListener;
- }
-
- private boolean isNewDate(int year, int month) {
- return (mCurrentDate.get(Calendar.YEAR) != year
- || mCurrentDate.get(Calendar.MONTH) != month);
- }
-
- private void setDate(int year, int month) {
- mCurrentDate.set(year, month, 1);
- if (mCurrentDate.before(mMinDate)) {
- mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
- } else if (mCurrentDate.after(mMaxDate)) {
- mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
- }
- }
-
- private void updateSpinners() {
- // set the spinner ranges respecting the min and max dates
- if (mCurrentDate.equals(mMinDate)) {
- mMonthSpinner.setDisplayedValues(null);
- mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
- mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
- mMonthSpinner.setWrapSelectorWheel(false);
- } else if (mCurrentDate.equals(mMaxDate)) {
- mMonthSpinner.setDisplayedValues(null);
- mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
- mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
- mMonthSpinner.setWrapSelectorWheel(false);
- } else {
- mMonthSpinner.setDisplayedValues(null);
- mMonthSpinner.setMinValue(0);
- mMonthSpinner.setMaxValue(11);
- mMonthSpinner.setWrapSelectorWheel(true);
- }
+ 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,
- mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
- mMonthSpinner.setDisplayedValues(displayedValues);
-
- // year spinner range does not change based on the current date
- mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
- mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
- mYearSpinner.setWrapSelectorWheel(false);
-
- // set the spinner values
- mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
- mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
- }
-
- /**
- * @return The selected year.
- */
- public int getYear() {
- return mCurrentDate.get(Calendar.YEAR);
+ getPositionInYearSpinner().getMinValue(),
+ getPositionInYearSpinner().getMaxValue() + 1);
+ getPositionInYearSpinner().setDisplayedValues(displayedValues);
}
/**
* @return The selected month.
*/
public int getMonth() {
- return mCurrentDate.get(Calendar.MONTH);
+ return getCurrentDate().get(Calendar.MONTH);
}
- /**
- * @return The selected day of month.
- */
- public int getDayOfMonth() {
- return mCurrentDate.get(Calendar.DAY_OF_MONTH);
+ @Override
+ public int getPositionInYear() {
+ return getMonth();
}
- /**
- * Notifies the listener, if such, for a change in the selected date.
- */
- private void notifyDateChanged() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- if (mMonthChangedListener != null) {
- mMonthChangedListener.onMonthChanged(this, getYear(), getMonth());
- }
+ @Override
+ protected int getMaxYear() {
+ return getMaxDate().get(Calendar.YEAR);
}
- /**
- * Class for managing state storing/restoring.
- */
- private static class SavedState extends BaseSavedState {
-
- private final int mYear;
+ @Override
+ protected int getMinYear() {
+ return getMinDate().get(Calendar.YEAR);
+ }
- private final int mMonth;
- /**
- * Constructor called from {@link DatePicker#onSaveInstanceState()}
- */
- private SavedState(Parcelable superState, int year, int month) {
- super(superState);
- mYear = year;
- mMonth = month;
- }
-
- /**
- * Constructor called from {@link #CREATOR}
- */
- private SavedState(Parcel in) {
- super(in);
- mYear = in.readInt();
- mMonth = in.readInt();
+ @Override
+ protected int getMaxPositionInYear() {
+ if (getYear() == getMaxDate().get(Calendar.YEAR)) {
+ return getMaxDate().get(Calendar.MONTH);
}
+ return MONTHS_NUMBER - 1;
+ }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(mYear);
- dest.writeInt(mMonth);
+ @Override
+ protected int getMinPositionInYear() {
+ if (getYear() == getMinDate().get(Calendar.YEAR)) {
+ return getMinDate().get(Calendar.MONTH);
}
-
- @SuppressWarnings("all")
- // suppress unused and hiding
- public static final Parcelable.Creator CREATOR = new Creator() {
-
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
+ return 0;
}
}
diff --git a/src/org/chromium/content/browser/input/MonthPickerDialog.java b/src/org/chromium/content/browser/input/MonthPickerDialog.java
index 354810e..24a4e8b 100644
--- a/src/org/chromium/content/browser/input/MonthPickerDialog.java
+++ b/src/org/chromium/content/browser/input/MonthPickerDialog.java
@@ -4,39 +4,11 @@
package org.chromium.content.browser.input;
-import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.View;
-import org.chromium.content.browser.input.MonthPicker.OnMonthChangedListener;
import org.chromium.content.R;
-public class MonthPickerDialog extends AlertDialog implements OnClickListener,
- OnMonthChangedListener {
-
- private static final String YEAR = "year";
- private static final String MONTH = "month";
-
- private final MonthPicker mMonthPicker;
- private final OnMonthSetListener mCallBack;
-
- /**
- * The callback used to indicate the user is done filling in the date.
- */
- public interface OnMonthSetListener {
-
- /**
- * @param view The 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}.
- */
- void onMonthSet(MonthPicker view, int year, int monthOfYear);
- }
+public class MonthPickerDialog extends TwoFieldDatePickerDialog {
/**
* @param context The context the dialog is to run in.
@@ -44,69 +16,24 @@ public interface OnMonthSetListener {
* @param year The initial year of the dialog.
* @param monthOfYear The initial month of the dialog.
*/
- public MonthPickerDialog(Context context,
- OnMonthSetListener callBack,
- int year,
- int monthOfYear) {
- this(context, 0, callBack, year, monthOfYear);
- }
-
- /**
- * @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 monthOfYear The initial month of the dialog.
- */
- public MonthPickerDialog(Context context,
- int theme,
- OnMonthSetListener callBack,
- int year,
- int monthOfYear) {
- 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);
+ public MonthPickerDialog(Context context, OnValueSetListener callBack,
+ int year, int monthOfYear, long minMonth, long maxMonth) {
+ super(context, callBack, year, monthOfYear, minMonth, maxMonth);
setTitle(R.string.month_picker_dialog_title);
-
- mMonthPicker = new MonthPicker(context);
- setView(mMonthPicker);
- mMonthPicker.init(year, monthOfYear, this);
}
@Override
- public void onClick(DialogInterface dialog, int which) {
- tryNotifyMonthSet();
- }
-
- private void tryNotifyMonthSet() {
- if (mCallBack != null) {
- mMonthPicker.clearFocus();
- mCallBack.onMonthSet(mMonthPicker, mMonthPicker.getYear(),
- mMonthPicker.getMonth());
- }
+ protected TwoFieldDatePicker createPicker(Context context, long minValue, long maxValue) {
+ return new MonthPicker(context, minValue, maxValue);
}
@Override
- protected void onStop() {
- if (Build.VERSION.SDK_INT >= 16) {
- // The default behavior of dialogs changed in JellyBean and onwards.
- // Dismissing a dialog (by pressing back for example)
- // applies the chosen date. This code is added here so that the custom
- // pickers behave the same as the internal DatePickerDialog.
- tryNotifyMonthSet();
+ protected void tryNotifyDateSet() {
+ if (mCallBack != null) {
+ MonthPicker picker = getMonthPicker();
+ picker.clearFocus();
+ mCallBack.onValueSet(picker.getYear(), picker.getMonth());
}
- super.onStop();
- }
-
- @Override
- public void onMonthChanged(MonthPicker view, int year, int month) {
- mMonthPicker.init(year, month, null);
}
/**
@@ -115,32 +42,6 @@ public void onMonthChanged(MonthPicker view, int year, int month) {
* @return The calendar view.
*/
public MonthPicker getMonthPicker() {
- return mMonthPicker;
- }
-
- /**
- * Sets the current date.
- *
- * @param year The date year.
- * @param monthOfYear The date month.
- */
- public void updateDate(int year, int monthOfYear) {
- mMonthPicker.updateMonth(year, monthOfYear);
- }
-
- @Override
- public Bundle onSaveInstanceState() {
- Bundle state = super.onSaveInstanceState();
- state.putInt(YEAR, mMonthPicker.getYear());
- state.putInt(MONTH, mMonthPicker.getMonth());
- return state;
- }
-
- @Override
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- super.onRestoreInstanceState(savedInstanceState);
- int year = savedInstanceState.getInt(YEAR);
- int month = savedInstanceState.getInt(MONTH);
- mMonthPicker.init(year, month, this);
+ return (MonthPicker) mPicker;
}
}
diff --git a/src/org/chromium/content/browser/input/SelectionHandleController.java b/src/org/chromium/content/browser/input/SelectionHandleController.java
index f65c6a5..77b5c76 100644
--- a/src/org/chromium/content/browser/input/SelectionHandleController.java
+++ b/src/org/chromium/content/browser/input/SelectionHandleController.java
@@ -12,7 +12,7 @@
public abstract class SelectionHandleController implements CursorController {
// The following constants match the ones in
- // third_party/WebKit/Source/WebKit/chromium/public/WebTextDirection.h
+ // third_party/WebKit/public/web/WebTextDirection.h
private static final int TEXT_DIRECTION_DEFAULT = 0;
private static final int TEXT_DIRECTION_LTR = 1;
private static final int TEXT_DIRECTION_RTL = 2;
diff --git a/src/org/chromium/content/browser/input/TimeDialog.java b/src/org/chromium/content/browser/input/TimeDialog.java
new file mode 100644
index 0000000..60d196f
--- /dev/null
+++ b/src/org/chromium/content/browser/input/TimeDialog.java
@@ -0,0 +1,70 @@
+// 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.content.browser.input;
+
+import android.app.TimePickerDialog;
+import android.content.Context;
+import android.text.format.Time;
+import android.widget.TimePicker;
+
+/**
+ * Wrapper on {@code TimePickerDialog} to control min and max times.
+ */
+public class TimeDialog extends TimePickerDialog {
+
+ private Time mMinTime;
+ private Time mMaxTime;
+
+ public static TimeDialog create(Context context, OnTimeSetListener callBack,
+ int hour, int minute, boolean is24HourView, long min, long max) {
+ Time time = getBoundedTime(hour, minute, min, max);
+ return new TimeDialog(context, callBack, time.hour, time.minute,
+ is24HourView, min, max);
+ }
+
+ private TimeDialog(
+ Context context, OnTimeSetListener callBack,
+ int hourOfDay, int minute, boolean is24HourView, long min, long max) {
+ super(context, callBack, hourOfDay, minute, is24HourView);
+ if (min >= max) {
+ mMinTime = getTimeForHourAndMinute(0, 0);
+ mMaxTime = getTimeForHourAndMinute(23, 59);
+ } else {
+ mMinTime = getTimeForMillis(min);
+ mMaxTime = getTimeForMillis(max);
+ }
+ }
+
+ @Override
+ public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
+ Time time = getBoundedTime(hourOfDay, minute,
+ mMinTime.toMillis(true), mMaxTime.toMillis(true));
+ super.onTimeChanged(view, time.hour, time.minute);
+ updateTime(time.hour, time.minute);
+ }
+
+ private static Time getBoundedTime(int hour, int minute,
+ long min, long max) {
+ Time time = getTimeForHourAndMinute(hour, minute);
+ if (time.toMillis(true) < min) {
+ return getTimeForMillis(min);
+ } else if (time.toMillis(true) > max) {
+ return getTimeForMillis(max);
+ }
+ return time;
+ }
+
+ private static Time getTimeForMillis(long millis) {
+ Time time = new Time("GMT");
+ time.set(millis);
+ return time;
+ }
+
+ private static Time getTimeForHourAndMinute(int hour, int minute) {
+ Time time = new Time("GMT");
+ time.set(0, minute, hour, 1, 0, 1970);
+ return time;
+ }
+}
diff --git a/src/org/chromium/content/browser/input/TwoFieldDatePicker.java b/src/org/chromium/content/browser/input/TwoFieldDatePicker.java
new file mode 100644
index 0000000..4eaa0b7
--- /dev/null
+++ b/src/org/chromium/content/browser/input/TwoFieldDatePicker.java
@@ -0,0 +1,246 @@
+// 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.content.browser.input;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnValueChangeListener;
+import android.text.format.DateUtils;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
+import android.widget.NumberPicker;
+
+import java.util.Calendar;
+
+import org.chromium.content.R;
+
+// This class is heavily based on android.widget.DatePicker.
+public abstract class TwoFieldDatePicker extends FrameLayout {
+
+ private NumberPicker mPositionInYearSpinner;
+
+ private 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, long minValue, long 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;
+ } else if (oldVal == picker.getMinValue() && newVal == picker.getMaxValue()) {
+ year -=1;
+ }
+ } 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();
+ if (minValue >= maxValue) {
+ mMinDate = Calendar.getInstance();
+ mMinDate.set(0, 0, 1);
+ mMaxDate = Calendar.getInstance();
+ mMaxDate.set(9999, 0, 1);
+ } else {
+ mMinDate = createDateFromValue(minValue);
+ mMaxDate = createDateFromValue(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 onMonthChangedListener 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 createDateFromValue(long 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();
+
+ protected abstract int getMinPositionInYear();
+
+ 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());
+ mPositionInYearSpinner.setMaxValue(getMaxPositionInYear());
+ 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/src/org/chromium/content/browser/input/TwoFieldDatePickerDialog.java b/src/org/chromium/content/browser/input/TwoFieldDatePickerDialog.java
new file mode 100644
index 0000000..4dc3fa4
--- /dev/null
+++ b/src/org/chromium/content/browser/input/TwoFieldDatePickerDialog.java
@@ -0,0 +1,124 @@
+// 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.content.browser.input;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+
+import org.chromium.content.browser.input.TwoFieldDatePicker.OnMonthOrWeekChangedListener;
+import org.chromium.content.R;
+
+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 week in year.
+ * with {@link java.util.Calendar}.
+ */
+ 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,
+ long minValue,
+ long 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,
+ long minValue,
+ long 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, long minValue, long maxValue) {
+ return null;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ tryNotifyDateSet();
+ }
+
+ /**
+ * Notifies the listener, if such, that a date has been set.
+ */
+ protected abstract void tryNotifyDateSet();
+
+ @Override
+ protected void onStop() {
+ if (Build.VERSION.SDK_INT >= 16) {
+ // The default behavior of dialogs changed in JellyBean and onwards.
+ // Dismissing a dialog (by pressing back for example)
+ // applies the chosen date. This code is added here so that the custom
+ // pickers behave the same as the internal DatePickerDialog.
+ tryNotifyDateSet();
+ }
+ super.onStop();
+ }
+
+ @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/src/org/chromium/content/browser/input/WeekPicker.java b/src/org/chromium/content/browser/input/WeekPicker.java
new file mode 100644
index 0000000..117793b
--- /dev/null
+++ b/src/org/chromium/content/browser/input/WeekPicker.java
@@ -0,0 +1,130 @@
+// 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.content.browser.input;
+
+import android.content.Context;
+
+import java.util.Calendar;
+
+import org.chromium.content.R;
+
+// This class is heavily based on android.widget.DatePicker.
+public class WeekPicker extends TwoFieldDatePicker {
+
+ public WeekPicker(Context context, long minValue, long maxValue) {
+ super(context, minValue, maxValue);
+
+ getPositionInYearSpinner().setContentDescription(
+ getResources().getString(R.string.accessibility_date_picker_week));
+
+ // initialize to current date
+ Calendar cal = Calendar.getInstance();
+ cal.setFirstDayOfWeek(Calendar.MONDAY);
+ cal.setMinimalDaysInFirstWeek(4);
+ cal.setTimeInMillis(System.currentTimeMillis());
+ init(getISOWeekYearForDate(cal), getWeekForDate(cal), null);
+ }
+
+ private Calendar createDateFromWeek(int year, int week) {
+ Calendar date = Calendar.getInstance();
+ 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;
+ }
+
+ @Override
+ protected Calendar createDateFromValue(long value) {
+ Calendar date = Calendar.getInstance();
+ date.clear();
+ date.setFirstDayOfWeek(Calendar.MONDAY);
+ date.setMinimalDaysInFirstWeek(4);
+ date.setTimeInMillis(value);
+ return date;
+ }
+
+ 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() {
+ // Create a date in the middle of the year, where the week year matches the year.
+ Calendar date = createDateFromWeek(getYear(), 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() {
+ if (getYear() == getISOWeekYearForDate(getMaxDate())) {
+ return getWeekForDate(getMaxDate());
+ }
+ return getNumberOfWeeks();
+ }
+
+ @Override
+ protected int getMinPositionInYear() {
+ if (getYear() == getISOWeekYearForDate(getMinDate())) {
+ return getWeekForDate(getMinDate());
+ }
+ return 1;
+ }
+}
diff --git a/src/org/chromium/content/browser/input/WeekPickerDialog.java b/src/org/chromium/content/browser/input/WeekPickerDialog.java
new file mode 100644
index 0000000..83db991
--- /dev/null
+++ b/src/org/chromium/content/browser/input/WeekPickerDialog.java
@@ -0,0 +1,65 @@
+// 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.content.browser.input;
+
+import android.content.Context;
+
+import org.chromium.content.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,
+ long minValue, long 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,
+ long minValue, long maxValue) {
+ super(context, theme, callBack, year, weekOfYear, minValue, maxValue);
+ setTitle(R.string.week_picker_dialog_title);
+ }
+
+ @Override
+ protected TwoFieldDatePicker createPicker(Context context, long minValue, long maxValue) {
+ return new WeekPicker(context, minValue, maxValue);
+ }
+
+ @Override
+ protected void tryNotifyDateSet() {
+ if (mCallBack != null) {
+ WeekPicker picker = getWeekPicker();
+ picker.clearFocus();
+ mCallBack.onValueSet(picker.getYear(), picker.getWeek());
+ }
+ }
+
+ /**
+ * Gets the {@link WeekPicker} contained in this dialog.
+ *
+ * @return The calendar view.
+ */
+ public WeekPicker getWeekPicker() {
+ return (WeekPicker) mPicker;
+ }
+}
diff --git a/src/org/chromium/content/common/CommandLine.java b/src/org/chromium/content/common/CommandLine.java
index 83cb348..ce00449 100644
--- a/src/org/chromium/content/common/CommandLine.java
+++ b/src/org/chromium/content/common/CommandLine.java
@@ -70,6 +70,9 @@ public abstract class CommandLine {
// Native switch - chrome_switches::kEnableInstantExtendedAPI
public static final String ENABLE_INSTANT_EXTENDED_API = "enable-instant-extended-api";
+ // Native switch - content_switches::kEnableSpeechRecognition
+ public static final String ENABLE_SPEECH_RECOGNITION = "enable-speech-recognition";
+
// Public abstract interface, implemented in derived classes.
// All these methods reflect their native-side counterparts.
/**
@@ -162,16 +165,9 @@ public static void init(String[] args) {
* @param file The fully qualified command line file.
*/
public static void initFromFile(String file) {
- char[] buffer = new char[0];
- try {
- // Arbitrary clamp of 8k on the amount of file we read in.
- buffer = readUtf8FileFully(file, 8 * 1024);
- } catch (FileNotFoundException e) {
- // Ignore: having a command line file is optional.
- } catch (IOException e) {
- Log.w(TAG, "error reading command line file " + file + e);
- }
- init(tokenizeQuotedAruments(buffer));
+ // Arbitrary clamp of 8k on the amount of file we read in.
+ char[] buffer = readUtf8FileFully(file, 8 * 1024);
+ init(buffer == null ? null : tokenizeQuotedAruments(buffer));
}
/**
@@ -257,29 +253,43 @@ private static void setInstance(CommandLine commandLine) {
/**
* @param fileName the file to read in.
- * @param sizeLimit cap on the file size: will throw an exception if exceeded
- * @return Array of chars read from the file
- * @throws FileNotFoundException file does not exceed
- * @throws IOException error encountered accessing the file
+ * @param sizeLimit cap on the file size.
+ * @return Array of chars read from the file, or null if the file cannot be read
+ * or if its length exceeds |sizeLimit|.
*/
- private static char[] readUtf8FileFully(String fileName, int sizeLimit) throws
- FileNotFoundException, IOException {
+ private static char[] readUtf8FileFully(String fileName, int sizeLimit) {
Reader reader = null;
+ File f = new File(fileName);
+ long fileLength = f.length();
+
+ if (fileLength == 0) {
+ return null;
+ }
+
+ if (fileLength > sizeLimit) {
+ Log.w(TAG, "File " + fileName + " length " + fileLength + " exceeds limit "
+ + sizeLimit);
+ return null;
+ }
+
try {
- File f = new File(fileName);
- if (f.length() > sizeLimit) {
- throw new IOException("File " + fileName + " length " + f.length() +
- " exceeds limit " + sizeLimit);
- }
- char[] buffer = new char[(int) f.length()];
+ char[] buffer = new char[(int) fileLength];
reader = new InputStreamReader(new FileInputStream(f), "UTF-8");
int charsRead = reader.read(buffer);
// Debug check that we've exhausted the input stream (will fail e.g. if the
// file grew after we inspected its length).
assert !reader.ready();
return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer;
+ } catch (FileNotFoundException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
} finally {
- if (reader != null) reader.close();
+ try {
+ if (reader != null) reader.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to close file reader.", e);
+ }
}
}
diff --git a/src/org/chromium/content/common/ResultCodes.java b/src/org/chromium/content/common/ResultCodes.java
index 567b949..07641b7 100644
--- a/src/org/chromium/content/common/ResultCodes.java
+++ b/src/org/chromium/content/common/ResultCodes.java
@@ -1,4 +1,9 @@
+
+
+
+
package org.chromium.content.common;
+
public class ResultCodes {
public static final int RESULT_CODE_NORMAL_EXIT = 0;
public static final int RESULT_CODE_KILLED = 1;
diff --git a/src/org/chromium/content/common/TopControlsState.java b/src/org/chromium/content/common/TopControlsState.java
new file mode 100644
index 0000000..5638e5c
--- /dev/null
+++ b/src/org/chromium/content/common/TopControlsState.java
@@ -0,0 +1,17 @@
+
+
+
+
+package org.chromium.content.common;
+
+public class TopControlsState {
+
+
+
+
+
+
+public static final int SHOWN = 1;
+public static final int HIDDEN = 2;
+public static final int BOTH = 3;
+}
diff --git a/src/org/chromium/content/common/TopControlsState.template b/src/org/chromium/content/common/TopControlsState.template
new file mode 100644
index 0000000..97a3f17
--- /dev/null
+++ b/src/org/chromium/content/common/TopControlsState.template
@@ -0,0 +1,12 @@
+// Copyright (c) 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.content.common;
+
+public class TopControlsState {
+#define DEFINE_TOP_CONTROLS_STATE(name, value) public static final int name = value;
+#include "content/public/common/top_controls_state_list.h"
+#undef DEFINE_TOP_CONTROLS_STATE
+}
+
diff --git a/src/org/chromium/content/common/TraceEvent.java b/src/org/chromium/content/common/TraceEvent.java
index d606bb7..85954e3 100644
--- a/src/org/chromium/content/common/TraceEvent.java
+++ b/src/org/chromium/content/common/TraceEvent.java
@@ -6,10 +6,11 @@
import android.os.Build;
import android.os.Looper;
+import android.os.MessageQueue;
+import android.os.SystemClock;
import android.util.Log;
import android.util.Printer;
-import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -22,19 +23,139 @@
// be ignored. (Perhaps we could devise to buffer them up in future?).
public class TraceEvent {
- private static boolean sEnabled = false;
+ private static volatile boolean sEnabled = false;
+
+ /**
+ * A class that records, traces and logs statistics about the main Looper.
+ * The output of this class can be used in a number of interesting ways:
+ *
+ *
-
+ * When using chrometrace, there will be a near-continuous line of
+ * measurements showing both event dispatches as well as idles;
+ *
-
+ * Logging messages are output for events that run too long on the
+ * event dispatcher, making it easy to identify problematic areas;
+ *
-
+ * Statistics are output whenever there is an idle after a non-trivial
+ * amount of activity, allowing information to be gathered about task
+ * density and execution cadence on the Looper;
+ *
+ *
+ * The class attaches itself as an idle handler to the main Looper, and
+ * monitors the execution of events and idle notifications. Task counters
+ * accumulate between idle notifications and get reset when a new idle
+ * notification is received.
+ */
+ private final static class LooperMonitor implements Printer, MessageQueue.IdleHandler {
+ // Tags for dumping to logcat or TraceEvent
+ private final static String TAG = "TraceEvent.LooperMonitor";
+ private final static String IDLE_EVENT_NAME = "Looper.queueIdle";
+ private final static String DISPATCH_EVENT_NAME =
+ "Looper.dispatchMessage";
+
+ // Calculation constants
+ private final static long FRAME_DURATION_MILLIS = 1000L / 60L; // 60 FPS
+ // A reasonable threshold for defining a Looper event as "long running"
+ private final static long MIN_INTERESTING_DURATION_MILLIS =
+ FRAME_DURATION_MILLIS;
+ // A reasonable threshold for a "burst" of tasks on the Looper
+ private final static long MIN_INTERESTING_BURST_DURATION_MILLIS =
+ MIN_INTERESTING_DURATION_MILLIS * 3;
+
+ // Stats tracking
+ private long mLastIdleStartedAt = 0L;
+ private long mLastWorkStartedAt = 0L;
+ private int mNumTasksSeen = 0;
+ private int mNumIdlesSeen = 0;
+ private int mNumTasksSinceLastIdle = 0;
+
+ // State
+ private boolean mIdleMonitorAttached = false;
- private static class LooperTracePrinter implements Printer {
- private static final String NAME = "Looper.dispatchMessage";
@Override
- public void println(String line) {
- if (line.startsWith(">>>>>")) {
- TraceEvent.begin(NAME, line);
+ public void println(final String line) {
+ if (line.startsWith(">")) {
+ begin(line);
} else {
- assert line.startsWith("<<<<<");
- TraceEvent.end(NAME);
+ assert line.startsWith("<");
+ end(line);
+ }
+ }
+
+ // Called from within the begin/end methods only.
+ // This method can only execute on the looper thread, because that is
+ // the only thread that is permitted to call Looper.myqueue().
+ private final void syncIdleMonitoring() {
+ if (sEnabled && !mIdleMonitorAttached) {
+ // approximate start time for computational purposes
+ mLastIdleStartedAt = SystemClock.elapsedRealtime();
+ Looper.myQueue().addIdleHandler(
+ LooperMonitor.getInstance());
+ mIdleMonitorAttached = true;
+ Log.v(TAG, "attached idle handler");
+ } else if (mIdleMonitorAttached && !sEnabled) {
+ Looper.myQueue().removeIdleHandler(
+ LooperMonitor.getInstance());
+ mIdleMonitorAttached = false;
+ Log.v(TAG, "detached idle handler");
}
}
+
+ private final void begin(final String line) {
+ // Close-out any prior 'idle' period before starting new task.
+ if (mNumTasksSinceLastIdle == 0) {
+ TraceEvent.end(IDLE_EVENT_NAME);
+ }
+ TraceEvent.begin(DISPATCH_EVENT_NAME, line);
+ mLastWorkStartedAt = SystemClock.elapsedRealtime();
+ syncIdleMonitoring();
+ }
+
+ private final void end(final String line) {
+ final long elapsed = SystemClock.elapsedRealtime()
+ - mLastWorkStartedAt;
+ if (elapsed > MIN_INTERESTING_DURATION_MILLIS) {
+ traceAndLog(Log.WARN, "observed a task that took "
+ + elapsed + "ms: " + line);
+ }
+ TraceEvent.end(DISPATCH_EVENT_NAME);
+ syncIdleMonitoring();
+ mNumTasksSeen++;
+ mNumTasksSinceLastIdle++;
+ }
+
+ private static void traceAndLog(int level, String message) {
+ TraceEvent.instant("TraceEvent.LooperMonitor:IdleStats", message);
+ Log.println(level, TAG, message);
+ }
+
+ @Override
+ public final boolean queueIdle() {
+ final long now = SystemClock.elapsedRealtime();
+ if (mLastIdleStartedAt == 0) mLastIdleStartedAt = now;
+ final long elapsed = now - mLastIdleStartedAt;
+ mNumIdlesSeen++;
+ TraceEvent.begin(IDLE_EVENT_NAME, mNumTasksSinceLastIdle + " tasks since last idle.");
+ if (elapsed > MIN_INTERESTING_BURST_DURATION_MILLIS) {
+ // Dump stats
+ String statsString = mNumTasksSeen + " tasks and "
+ + mNumIdlesSeen + " idles processed so far, "
+ + mNumTasksSinceLastIdle + " tasks bursted and "
+ + elapsed + "ms elapsed since last idle";
+ traceAndLog(Log.DEBUG, statsString);
+ }
+ mLastIdleStartedAt = now;
+ mNumTasksSinceLastIdle = 0;
+ return true; // stay installed
+ }
+
+ // Holder for monitor avoids unnecessary construction on non-debug runs
+ private final static class Holder {
+ private final static LooperMonitor sInstance = new LooperMonitor();
+ }
+ public final static LooperMonitor getInstance() {
+ return Holder.sInstance;
+ }
}
private static long sTraceTagView;
@@ -112,7 +233,8 @@ public static void setEnabledToMatchNative() {
public static synchronized void setEnabled(boolean enabled) {
if (sEnabled == enabled) return;
sEnabled = enabled;
- Looper.getMainLooper().setMessageLogging(enabled ? new LooperTracePrinter() : null);
+ Looper.getMainLooper().setMessageLogging(
+ enabled ? LooperMonitor.getInstance() : null);
}
/**
diff --git a/src/org/chromium/media/AudioManagerAndroid.java b/src/org/chromium/media/AudioManagerAndroid.java
index 2ed09bd..a7afdae 100644
--- a/src/org/chromium/media/AudioManagerAndroid.java
+++ b/src/org/chromium/media/AudioManagerAndroid.java
@@ -8,7 +8,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
import android.os.Build;
import android.util.Log;
@@ -17,10 +21,15 @@
@JNINamespace("media")
class AudioManagerAndroid {
- private static final String TAG = AudioManagerAndroid.class.getSimpleName();
+ private static final String TAG = "AudioManagerAndroid";
+
// Most of Google lead devices use 44.1K as the default sampling rate, 44.1K
// is also widely used on other android devices.
private static final int DEFAULT_SAMPLING_RATE = 44100;
+ // Randomly picked up frame size which is close to return value on N4.
+ // Return this default value when
+ // getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) fails.
+ private static final int DEFAULT_FRAME_PER_BUFFER = 256;
private final AudioManager mAudioManager;
private final Context mContext;
@@ -32,6 +41,9 @@ class AudioManagerAndroid {
public void setMode(int mode) {
try {
mAudioManager.setMode(mode);
+ if (mode == AudioManager.MODE_IN_COMMUNICATION) {
+ mAudioManager.setSpeakerphoneOn(true);
+ }
} catch (SecurityException e) {
Log.e(TAG, "setMode exception: " + e.getMessage());
logDeviceInfo();
@@ -81,8 +93,14 @@ public void unregisterHeadsetReceiver() {
mAudioManager.setSpeakerphoneOn(mOriginalSpeakerStatus);
}
+ private void logDeviceInfo() {
+ Log.i(TAG, "Manufacturer:" + Build.MANUFACTURER +
+ " Board: " + Build.BOARD + " Device: " + Build.DEVICE +
+ " Model: " + Build.MODEL + " PRODUCT: " + Build.PRODUCT);
+ }
+
@CalledByNative
- public int getNativeOutputSampleRate() {
+ private int getNativeOutputSampleRate() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
String sampleRateString = mAudioManager.getProperty(
AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
@@ -93,9 +111,58 @@ public int getNativeOutputSampleRate() {
}
}
- private void logDeviceInfo() {
- Log.i(TAG, "Manufacturer:" + Build.MANUFACTURER +
- " Board: " + Build.BOARD + " Device: " + Build.DEVICE +
- " Model: " + Build.MODEL + " PRODUCT: " + Build.PRODUCT);
+ /**
+ * Returns the minimum frame size required for audio input.
+ *
+ * @param sampleRate sampling rate
+ * @param channels number of channels
+ */
+ @CalledByNative
+ private static int getMinInputFrameSize(int sampleRate, int channels) {
+ int channelConfig;
+ if (channels == 1) {
+ channelConfig = AudioFormat.CHANNEL_IN_MONO;
+ } else if (channels == 2) {
+ channelConfig = AudioFormat.CHANNEL_IN_STEREO;
+ } else {
+ return -1;
+ }
+ return AudioRecord.getMinBufferSize(
+ sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels;
}
+
+ /**
+ * Returns the minimum frame size required for audio output.
+ *
+ * @param sampleRate sampling rate
+ * @param channels number of channels
+ */
+ @CalledByNative
+ private static int getMinOutputFrameSize(int sampleRate, int channels) {
+ int channelConfig;
+ if (channels == 1) {
+ channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+ } else if (channels == 2) {
+ channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+ } else {
+ return -1;
+ }
+ return AudioTrack.getMinBufferSize(
+ sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels;
+ }
+
+ @CalledByNative
+ private boolean isAudioLowLatencySupported() {
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+ }
+
+ @CalledByNative
+ private int getAudioLowLatencyOutputFrameSize() {
+ String framesPerBuffer =
+ mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+ return (framesPerBuffer == null ?
+ DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer));
+ }
+
}
diff --git a/src/org/chromium/media/MediaCodecBridge.java b/src/org/chromium/media/MediaCodecBridge.java
index 9e7894c..ed5d947 100644
--- a/src/org/chromium/media/MediaCodecBridge.java
+++ b/src/org/chromium/media/MediaCodecBridge.java
@@ -27,12 +27,25 @@ class MediaCodecBridge {
private static final String TAG = "MediaCodecBridge";
+ // Error code for MediaCodecBridge. Keep this value in sync with
+ // INFO_MEDIA_CODEC_ERROR in media_codec_bridge.h.
+ private static final int MEDIA_CODEC_ERROR = -1000;
+
+ // After a flush(), dequeueOutputBuffer() can often produce empty presentation timestamps
+ // for several frames. As a result, the player may find that the time does not increase
+ // after decoding a frame. To detect this, we check whether the presentation timestamp from
+ // dequeueOutputBuffer() is larger than input_timestamp - MAX_PRESENTATION_TIMESTAMP_SHIFT_US
+ // after a flush. And we set the presentation timestamp from dequeueOutputBuffer() to be
+ // non-decreasing for the remaining frames.
+ private static final long MAX_PRESENTATION_TIMESTAMP_SHIFT_US = 100000;
+
private ByteBuffer[] mInputBuffers;
private ByteBuffer[] mOutputBuffers;
private MediaCodec mMediaCodec;
-
private AudioTrack mAudioTrack;
+ private boolean mFlushed;
+ private long mLastPresentationTimeUs;
private static class DequeueOutputResult {
private final int mIndex;
@@ -41,8 +54,8 @@ private static class DequeueOutputResult {
private final long mPresentationTimeMicroseconds;
private final int mNumBytes;
- private DequeueOutputResult(
- int index, int flags, int offset, long presentationTimeMicroseconds, int numBytes) {
+ private DequeueOutputResult(int index, int flags, int offset,
+ long presentationTimeMicroseconds, int numBytes) {
mIndex = index;
mFlags = flags;
mOffset = offset;
@@ -68,6 +81,8 @@ private DequeueOutputResult(
private MediaCodecBridge(String mime) {
mMediaCodec = MediaCodec.createDecoderByType(mime);
+ mLastPresentationTimeUs = 0;
+ mFlushed = true;
}
@CalledByNative
@@ -91,12 +106,18 @@ private void start() {
@CalledByNative
private int dequeueInputBuffer(long timeoutUs) {
- return mMediaCodec.dequeueInputBuffer(timeoutUs);
+ try {
+ return mMediaCodec.dequeueInputBuffer(timeoutUs);
+ } catch(Exception e) {
+ Log.e(TAG, "Cannot dequeue Input buffer " + e.toString());
+ }
+ return MEDIA_CODEC_ERROR;
}
@CalledByNative
private void flush() {
mMediaCodec.flush();
+ mFlushed = true;
if (mAudioTrack != null) {
mAudioTrack.flush();
}
@@ -111,8 +132,13 @@ private void stop() {
}
@CalledByNative
- private MediaFormat getOutputFormat() {
- return mMediaCodec.getOutputFormat();
+ private int getOutputHeight() {
+ return mMediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_HEIGHT);
+ }
+
+ @CalledByNative
+ private int getOutputWidth() {
+ return mMediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_WIDTH);
}
@CalledByNative
@@ -128,7 +154,27 @@ private ByteBuffer getOutputBuffer(int index) {
@CalledByNative
private void queueInputBuffer(
int index, int offset, int size, long presentationTimeUs, int flags) {
- mMediaCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
+ resetLastPresentationTimeIfNeeded(presentationTimeUs);
+ try {
+ mMediaCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to queue input buffer " + e.toString());
+ }
+ }
+
+ @CalledByNative
+ private void queueSecureInputBuffer(
+ int index, int offset, byte[] iv, byte[] keyId, int[] numBytesOfClearData,
+ int[] numBytesOfEncryptedData, int numSubSamples, long presentationTimeUs) {
+ resetLastPresentationTimeIfNeeded(presentationTimeUs);
+ try {
+ MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
+ cryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData,
+ keyId, iv, MediaCodec.CRYPTO_MODE_AES_CTR);
+ mMediaCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, 0);
+ } catch(IllegalStateException e) {
+ Log.e(TAG, "Failed to queue secure input buffer " + e.toString());
+ }
}
@CalledByNative
@@ -144,31 +190,85 @@ private void getOutputBuffers() {
@CalledByNative
private DequeueOutputResult dequeueOutputBuffer(long timeoutUs) {
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
- int index = mMediaCodec.dequeueOutputBuffer(info, timeoutUs);
+ int index = MEDIA_CODEC_ERROR;
+ try {
+ index = mMediaCodec.dequeueOutputBuffer(info, timeoutUs);
+ if (info.presentationTimeUs < mLastPresentationTimeUs) {
+ // TODO(qinmin): return a special code through DequeueOutputResult
+ // to notify the native code the the frame has a wrong presentation
+ // timestamp and should be skipped.
+ info.presentationTimeUs = mLastPresentationTimeUs;
+ }
+ mLastPresentationTimeUs = info.presentationTimeUs;
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Cannot dequeue output buffer " + e.toString());
+ }
return new DequeueOutputResult(
index, info.flags, info.offset, info.presentationTimeUs, info.size);
}
@CalledByNative
- private void configureVideo(MediaFormat format, Surface surface, MediaCrypto crypto,
+ private boolean configureVideo(MediaFormat format, Surface surface, MediaCrypto crypto,
int flags) {
- mMediaCodec.configure(format, surface, crypto, flags);
+ try {
+ mMediaCodec.configure(format, surface, crypto, flags);
+ return true;
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Cannot configure the video codec " + e.toString());
+ }
+ return false;
}
@CalledByNative
- private void configureAudio(MediaFormat format, MediaCrypto crypto, int flags,
+ private static MediaFormat createAudioFormat(String mime, int SampleRate, int ChannelCount) {
+ return MediaFormat.createAudioFormat(mime, SampleRate, ChannelCount);
+ }
+
+ @CalledByNative
+ private static MediaFormat createVideoFormat(String mime, int width, int height) {
+ return MediaFormat.createVideoFormat(mime, width, height);
+ }
+
+ @CalledByNative
+ private static void setCodecSpecificData(MediaFormat format, int index, ByteBuffer bytes) {
+ String name = null;
+ if (index == 0) {
+ name = "csd-0";
+ } else if (index == 1) {
+ name = "csd-1";
+ }
+ if (name != null) {
+ format.setByteBuffer(name, bytes);
+ }
+ }
+
+ @CalledByNative
+ private static void setFrameHasADTSHeader(MediaFormat format) {
+ format.setInteger(MediaFormat.KEY_IS_ADTS, 1);
+ }
+
+ @CalledByNative
+ private boolean configureAudio(MediaFormat format, MediaCrypto crypto, int flags,
boolean playAudio) {
- mMediaCodec.configure(format, null, crypto, flags);
- if (playAudio) {
- int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
- int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
- int channelConfig = (channelCount == 1) ? AudioFormat.CHANNEL_OUT_MONO :
- AudioFormat.CHANNEL_OUT_STEREO;
- int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig,
- AudioFormat.ENCODING_PCM_16BIT);
- mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
- AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
+ try {
+ mMediaCodec.configure(format, null, crypto, flags);
+ if (playAudio) {
+ int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+ int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ int channelConfig = (channelCount == 1) ? AudioFormat.CHANNEL_OUT_MONO :
+ AudioFormat.CHANNEL_OUT_STEREO;
+ // Using 16bit PCM for output. Keep this value in sync with
+ // kBytesPerAudioOutputSample in media_codec_bridge.cc.
+ int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig,
+ AudioFormat.ENCODING_PCM_16BIT);
+ mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
+ AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
+ }
+ return true;
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Cannot configure the audio codec " + e.toString());
}
+ return false;
}
@CalledByNative
@@ -184,4 +284,19 @@ private void playOutputBuffer(byte[] buf) {
}
}
}
+
+ @CalledByNative
+ private void setVolume(double volume) {
+ if (mAudioTrack != null) {
+ mAudioTrack.setStereoVolume((float) volume, (float) volume);
+ }
+ }
+
+ private void resetLastPresentationTimeIfNeeded(long presentationTimeUs) {
+ if (mFlushed) {
+ mLastPresentationTimeUs =
+ Math.max(presentationTimeUs - MAX_PRESENTATION_TIMESTAMP_SHIFT_US, 0);
+ mFlushed = false;
+ }
+ }
}
diff --git a/src/org/chromium/media/MediaPlayerBridge.java b/src/org/chromium/media/MediaPlayerBridge.java
index 7274418..4b0a1aa 100644
--- a/src/org/chromium/media/MediaPlayerBridge.java
+++ b/src/org/chromium/media/MediaPlayerBridge.java
@@ -9,20 +9,142 @@
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
+import android.view.Surface;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
+import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
+import java.util.Map;
+// A wrapper around android.media.MediaPlayer that allows the native code to use it.
+// See media/base/android/media_player_bridge.cc for the corresponding native code.
@JNINamespace("media")
-class MediaPlayerBridge {
+public class MediaPlayerBridge {
private static final String TAG = "MediaPlayerBridge";
+ // Local player to forward this to. We don't initialize it here since the subclass might not
+ // want it.
+ private MediaPlayer mPlayer;
+
+ @CalledByNative
+ private static MediaPlayerBridge create() {
+ return new MediaPlayerBridge();
+ }
+
+ protected MediaPlayer getLocalPlayer() {
+ if (mPlayer == null) {
+ mPlayer = new MediaPlayer();
+ }
+ return mPlayer;
+ }
+
+ @CalledByNative
+ protected void setSurface(Surface surface) {
+ getLocalPlayer().setSurface(surface);
+ }
+
+ @CalledByNative
+ protected void prepareAsync() throws IllegalStateException {
+ getLocalPlayer().prepareAsync();
+ }
+
+ @CalledByNative
+ protected boolean isPlaying() {
+ return getLocalPlayer().isPlaying();
+ }
+
+ @CalledByNative
+ protected int getVideoWidth() {
+ return getLocalPlayer().getVideoWidth();
+ }
+
+ @CalledByNative
+ protected int getVideoHeight() {
+ return getLocalPlayer().getVideoHeight();
+ }
+
+ @CalledByNative
+ protected int getCurrentPosition() {
+ return getLocalPlayer().getCurrentPosition();
+ }
+
+ @CalledByNative
+ protected int getDuration() {
+ return getLocalPlayer().getDuration();
+ }
+
+ @CalledByNative
+ protected void release() {
+ getLocalPlayer().release();
+ }
+
+ @CalledByNative
+ protected void setVolume(double volume) {
+ getLocalPlayer().setVolume((float) volume, (float) volume);
+ }
+
+ @CalledByNative
+ protected void start() {
+ getLocalPlayer().start();
+ }
+
+ @CalledByNative
+ protected void pause() {
+ getLocalPlayer().pause();
+ }
+
+ @CalledByNative
+ protected void seekTo(int msec) throws IllegalStateException {
+ getLocalPlayer().seekTo(msec);
+ }
+
+ @CalledByNative
+ protected boolean setDataSource(
+ Context context, String url, String cookies, boolean hideUrlLog) {
+ Uri uri = Uri.parse(url);
+ HashMap headersMap = new HashMap();
+ if (hideUrlLog)
+ headersMap.put("x-hide-urls-from-log", "true");
+ if (!TextUtils.isEmpty(cookies))
+ headersMap.put("Cookie", cookies);
+ try {
+ getLocalPlayer().setDataSource(context, uri, headersMap);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ protected void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) {
+ getLocalPlayer().setOnBufferingUpdateListener(listener);
+ }
+
+ protected void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
+ getLocalPlayer().setOnCompletionListener(listener);
+ }
+
+ protected void setOnErrorListener(MediaPlayer.OnErrorListener listener) {
+ getLocalPlayer().setOnErrorListener(listener);
+ }
+
+ protected void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) {
+ getLocalPlayer().setOnPreparedListener(listener);
+ }
+
+ protected void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener) {
+ getLocalPlayer().setOnSeekCompleteListener(listener);
+ }
+
+ protected void setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener) {
+ getLocalPlayer().setOnVideoSizeChangedListener(listener);
+ }
+
private static class AllowedOperations {
private final boolean mCanPause;
private final boolean mCanSeekForward;
@@ -45,23 +167,6 @@ private AllowedOperations(boolean canPause, boolean canSeekForward,
private boolean canSeekBackward() { return mCanSeekBackward; }
}
- @CalledByNative
- private static boolean setDataSource(MediaPlayer player, Context context, String url,
- String cookies, boolean hideUrlLog) {
- Uri uri = Uri.parse(url);
- HashMap headersMap = new HashMap();
- if (hideUrlLog)
- headersMap.put("x-hide-urls-from-log", "true");
- if (!TextUtils.isEmpty(cookies))
- headersMap.put("Cookie", cookies);
- try {
- player.setDataSource(context, uri, headersMap);
- return true;
- } catch (Exception e) {
- return false;
- }
- }
-
/**
* Returns an AllowedOperations object to show all the operations that are
* allowed on the media player.
diff --git a/src/org/chromium/media/MediaPlayerListener.java b/src/org/chromium/media/MediaPlayerListener.java
index ba080c8..3c68589 100644
--- a/src/org/chromium/media/MediaPlayerListener.java
+++ b/src/org/chromium/media/MediaPlayerListener.java
@@ -124,19 +124,15 @@ public void releaseResources() {
@CalledByNative
private static MediaPlayerListener create(int nativeMediaPlayerListener,
- Context context, MediaPlayer mediaPlayer) {
+ Context context, MediaPlayerBridge mediaPlayerBridge) {
final MediaPlayerListener listener =
new MediaPlayerListener(nativeMediaPlayerListener, context);
- mediaPlayer.setOnBufferingUpdateListener(listener);
- mediaPlayer.setOnCompletionListener(listener);
- mediaPlayer.setOnErrorListener(listener);
- mediaPlayer.setOnPreparedListener(listener);
- mediaPlayer.setOnSeekCompleteListener(listener);
- mediaPlayer.setOnVideoSizeChangedListener(listener);
- if (PackageManager.PERMISSION_GRANTED ==
- context.checkCallingOrSelfPermission(permission.WAKE_LOCK)) {
- mediaPlayer.setWakeMode(context, android.os.PowerManager.FULL_WAKE_LOCK);
- }
+ mediaPlayerBridge.setOnBufferingUpdateListener(listener);
+ mediaPlayerBridge.setOnCompletionListener(listener);
+ mediaPlayerBridge.setOnErrorListener(listener);
+ mediaPlayerBridge.setOnPreparedListener(listener);
+ mediaPlayerBridge.setOnSeekCompleteListener(listener);
+ mediaPlayerBridge.setOnVideoSizeChangedListener(listener);
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(
@@ -144,7 +140,7 @@ private static MediaPlayerListener create(int nativeMediaPlayerListener,
AudioManager.STREAM_MUSIC,
// Request permanent focus.
- AudioManager.AUDIOFOCUS_GAIN);
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
return listener;
}
diff --git a/src/org/chromium/media/VideoCapture.java b/src/org/chromium/media/VideoCapture.java
index 150f3b6..f055f35 100644
--- a/src/org/chromium/media/VideoCapture.java
+++ b/src/org/chromium/media/VideoCapture.java
@@ -31,9 +31,35 @@ static class CaptureCapability {
public int mDesiredFps = 0;
}
+ // Some devices with OS older than JELLY_BEAN don't support YV12 format correctly.
+ // Some devices don't support YV12 format correctly even with JELLY_BEAN or newer OS.
+ // To work around the issues on those devices, we'd have to request NV21.
+ // This is a temporary hack till device manufacturers fix the problem or
+ // we don't need to support those devices any more.
+ private static class DeviceImageFormatHack {
+ private static final String[] sBUGGY_DEVICE_LIST = {
+ "SAMSUNG-SGH-I747",
+ };
+
+ static int getImageFormat() {
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ return ImageFormat.NV21;
+ }
+
+ for (String buggyDevice : sBUGGY_DEVICE_LIST) {
+ if (buggyDevice.contentEquals(android.os.Build.MODEL)) {
+ return ImageFormat.NV21;
+ }
+ }
+
+ return ImageFormat.YV12;
+ }
+ }
+
private Camera mCamera;
public ReentrantLock mPreviewBufferLock = new ReentrantLock();
- private int mPixelFormat = ImageFormat.YV12;
+ private int mImageFormat = ImageFormat.YV12;
+ private byte[] mColorPlane = null;
private Context mContext = null;
// True when native code has started capture.
private boolean mIsRunning = false;
@@ -87,26 +113,29 @@ public boolean allocate(int width, int height, int frameRate) {
// Calculate fps.
List listFpsRange = parameters.getSupportedPreviewFpsRange();
+ if (listFpsRange.size() == 0) {
+ Log.e(TAG, "allocate: no fps range found");
+ return false;
+ }
int frameRateInMs = frameRate * 1000;
- boolean fpsIsSupported = false;
- int fpsMin = 0;
- int fpsMax = 0;
Iterator itFpsRange = listFpsRange.iterator();
+ int[] fpsRange = (int[])itFpsRange.next();
+ // Use the first range as default.
+ int fpsMin = fpsRange[0];
+ int fpsMax = fpsRange[1];
+ int newFrameRate = (fpsMin + 999) / 1000;
while (itFpsRange.hasNext()) {
- int[] fpsRange = (int[])itFpsRange.next();
+ fpsRange = (int[])itFpsRange.next();
if (fpsRange[0] <= frameRateInMs &&
frameRateInMs <= fpsRange[1]) {
- fpsIsSupported = true;
fpsMin = fpsRange[0];
fpsMax = fpsRange[1];
+ newFrameRate = frameRate;
break;
}
}
-
- if (!fpsIsSupported) {
- Log.e(TAG, "allocate: fps " + frameRate + " is not supported");
- return false;
- }
+ frameRate = newFrameRate;
+ Log.d(TAG, "allocate: fps set to " + frameRate);
mCurrentCapability = new CaptureCapability();
mCurrentCapability.mDesiredFps = frameRate;
@@ -144,8 +173,10 @@ public boolean allocate(int width, int height, int frameRate) {
Log.d(TAG, "allocate: matched width=" + matchedWidth +
", height=" + matchedHeight);
+ calculateImageFormat(matchedWidth, matchedHeight);
+
parameters.setPreviewSize(matchedWidth, matchedHeight);
- parameters.setPreviewFormat(mPixelFormat);
+ parameters.setPreviewFormat(mImageFormat);
parameters.setPreviewFpsRange(fpsMin, fpsMax);
mCamera.setParameters(parameters);
@@ -171,7 +202,7 @@ public boolean allocate(int width, int height, int frameRate) {
mCamera.setPreviewTexture(mSurfaceTexture);
int bufSize = matchedWidth * matchedHeight *
- ImageFormat.getBitsPerPixel(mPixelFormat) / 8;
+ ImageFormat.getBitsPerPixel(mImageFormat) / 8;
for (int i = 0; i < NUM_CAPTURE_BUFFERS; i++) {
byte[] buffer = new byte[bufSize];
mCamera.addCallbackBuffer(buffer);
@@ -283,11 +314,14 @@ public void onPreviewFrame(byte[] data, Camera camera) {
if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
rotation = (mCameraOrientation + rotation) % 360;
rotation = (360 - rotation) % 360;
- flipHorizontal = (rotation == 180 || rotation == 0);
- flipVertical = !flipHorizontal;
+ flipHorizontal = (rotation == 270 || rotation == 90);
+ flipVertical = flipHorizontal;
} else {
rotation = (mCameraOrientation - rotation + 360) % 360;
}
+ if (mImageFormat == ImageFormat.NV21) {
+ convertNV21ToYV12(data);
+ }
nativeOnFrameAvailable(mNativeVideoCaptureDeviceAndroid,
data, mExpectedFrameSize,
rotation, flipVertical, flipHorizontal);
@@ -374,4 +408,22 @@ private int getDeviceOrientation() {
}
return orientation;
}
+
+ private void calculateImageFormat(int width, int height) {
+ mImageFormat = DeviceImageFormatHack.getImageFormat();
+ if (mImageFormat == ImageFormat.NV21) {
+ mColorPlane = new byte[width * height / 4];
+ }
+ }
+
+ private void convertNV21ToYV12(byte[] data) {
+ final int ySize = mCurrentCapability.mWidth * mCurrentCapability.mHeight;
+ final int uvSize = ySize / 4;
+ for (int i = 0; i < uvSize; i++) {
+ final int index = ySize + i * 2;
+ data[ySize + i] = data[index];
+ mColorPlane[i] = data[index + 1];
+ }
+ System.arraycopy(mColorPlane, 0, data, ySize + uvSize, uvSize);
+ }
}
diff --git a/src/org/chromium/media/WebAudioMediaCodecBridge.java b/src/org/chromium/media/WebAudioMediaCodecBridge.java
index 2559693..ac752d4 100644
--- a/src/org/chromium/media/WebAudioMediaCodecBridge.java
+++ b/src/org/chromium/media/WebAudioMediaCodecBridge.java
@@ -10,10 +10,11 @@
import android.media.MediaCodec.BufferInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
+import android.os.ParcelFileDescriptor;
import android.util.Log;
+import java.io.File;
import java.nio.ByteBuffer;
-import android.os.ParcelFileDescriptor;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
@@ -25,6 +26,13 @@ class WebAudioMediaCodecBridge {
// TODO(rtoy): What is the correct timeout value for reading
// from a file in memory?
static final long TIMEOUT_MICROSECONDS = 500;
+ @CalledByNative
+ private static String CreateTempFile(Context ctx) throws java.io.IOException {
+ File outputDirectory = ctx.getCacheDir();
+ File outputFile = File.createTempFile("webaudio", ".dat", outputDirectory);
+ return outputFile.getAbsolutePath();
+ }
+
@CalledByNative
private static boolean decodeAudioFile(Context ctx,
int nativeMediaCodecBridge,
diff --git a/src/org/chromium/net/AndroidNetworkLibrary.java b/src/org/chromium/net/AndroidNetworkLibrary.java
index bc82bb1..95752cc 100644
--- a/src/org/chromium/net/AndroidNetworkLibrary.java
+++ b/src/org/chromium/net/AndroidNetworkLibrary.java
@@ -30,7 +30,7 @@
*/
class AndroidNetworkLibrary {
- private static final String TAG = AndroidNetworkLibrary.class.getName();
+ private static final String TAG = "AndroidNetworkLibrary";
/**
* Stores the key pair through the CertInstaller activity.
diff --git a/src/org/chromium/net/CertVerifyResultAndroid.java b/src/org/chromium/net/CertVerifyResultAndroid.java
index 2363f9d..3eb7b78 100644
--- a/src/org/chromium/net/CertVerifyResultAndroid.java
+++ b/src/org/chromium/net/CertVerifyResultAndroid.java
@@ -1,10 +1,32 @@
+
+
+
+
package org.chromium.net;
+
public class CertVerifyResultAndroid {
+
+
+
+
public static final int VERIFY_OK = 0;
+
+
public static final int VERIFY_FAILED = -1;
+
+
public static final int VERIFY_NO_TRUSTED_ROOT = -2;
+
+
public static final int VERIFY_EXPIRED = -3;
+
+
public static final int VERIFY_NOT_YET_VALID = -4;
+
+
public static final int VERIFY_UNABLE_TO_PARSE = -5;
+
+
+
public static final int VERIFY_INCORRECT_KEY_USAGE = -6;
}
diff --git a/src/org/chromium/net/CertificateMimeType.java b/src/org/chromium/net/CertificateMimeType.java
index 95d38fa..bddac2f 100644
--- a/src/org/chromium/net/CertificateMimeType.java
+++ b/src/org/chromium/net/CertificateMimeType.java
@@ -1,5 +1,12 @@
+
+
+
+
package org.chromium.net;
+
public class CertificateMimeType {
+
+
public static final int UNKNOWN = 0;
public static final int X509_USER_CERT = 1;
public static final int X509_CA_CERT = 2;
diff --git a/src/org/chromium/net/NetError.java b/src/org/chromium/net/NetError.java
index 63fa793..cabbff2 100644
--- a/src/org/chromium/net/NetError.java
+++ b/src/org/chromium/net/NetError.java
@@ -1,4 +1,9 @@
+
+
+
+
package org.chromium.net;
+
public class NetError {
public static final int ERR_IO_PENDING = -1;
public static final int ERR_FAILED = -2;
@@ -74,6 +79,9 @@ public class NetError {
public static final int ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN = -150;
public static final int ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED = -151;
public static final int ERR_ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH = -152;
+public static final int ERR_SSL_DECRYPT_ERROR_ALERT = -153;
+public static final int ERR_WS_THROTTLE_QUEUE_TOO_LARGE = -154;
+public static final int ERR_TOO_MANY_SOCKET_STREAMS = -155;
public static final int ERR_CERT_COMMON_NAME_INVALID = -200;
public static final int ERR_CERT_DATE_INVALID = -201;
public static final int ERR_CERT_AUTHORITY_INVALID = -202;
@@ -129,6 +137,7 @@ public class NetError {
public static final int ERR_CONTENT_LENGTH_MISMATCH = -354;
public static final int ERR_INCOMPLETE_CHUNKED_ENCODING = -355;
public static final int ERR_QUIC_PROTOCOL_ERROR = -356;
+public static final int ERR_RESPONSE_HEADERS_TRUNCATED = -357;
public static final int ERR_CACHE_MISS = -400;
public static final int ERR_CACHE_READ_FAILURE = -401;
public static final int ERR_CACHE_WRITE_FAILURE = -402;
@@ -160,6 +169,7 @@ public class NetError {
public static final int ERR_KEY_GENERATION_FAILED = -710;
public static final int ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED = -711;
public static final int ERR_PRIVATE_KEY_EXPORT_FAILED = -712;
+public static final int ERR_SELF_SIGNED_CERT_GENERATION_FAILED = -713;
public static final int ERR_DNS_MALFORMED_RESPONSE = -800;
public static final int ERR_DNS_SERVER_REQUIRES_TCP = -801;
public static final int ERR_DNS_SERVER_FAILED = -802;
diff --git a/src/org/chromium/net/PrivateKeyType.java b/src/org/chromium/net/PrivateKeyType.java
index 671829c..8c4c33a 100644
--- a/src/org/chromium/net/PrivateKeyType.java
+++ b/src/org/chromium/net/PrivateKeyType.java
@@ -1,5 +1,11 @@
+
+
+
+
package org.chromium.net;
+
public class PrivateKeyType {
+
public static final int RSA = 0;
public static final int DSA = 1;
public static final int ECDSA = 2;
diff --git a/src/org/chromium/net/X509Util.java b/src/org/chromium/net/X509Util.java
index 1c0c045..30007ca 100644
--- a/src/org/chromium/net/X509Util.java
+++ b/src/org/chromium/net/X509Util.java
@@ -27,7 +27,7 @@
public class X509Util {
- private static final String TAG = X509Util.class.getName();
+ private static final String TAG = "X509Util";
private static CertificateFactory sCertificateFactory;
diff --git a/src/org/chromium/ui/ColorPickerAdvanced.java b/src/org/chromium/ui/ColorPickerAdvanced.java
new file mode 100644
index 0000000..91800ea
--- /dev/null
+++ b/src/org/chromium/ui/ColorPickerAdvanced.java
@@ -0,0 +1,252 @@
+// 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;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+/**
+ * Represents a more advanced way for the user to choose a color, based on selecting each of
+ * the Hue, Saturation and Value attributes.
+ */
+public class ColorPickerAdvanced extends LinearLayout implements OnSeekBarChangeListener {
+ private static final int HUE_SEEK_BAR_MAX = 360;
+
+ private static final int HUE_COLOR_COUNT = 7;
+
+ private static final int SATURATION_SEEK_BAR_MAX = 100;
+
+ private static final int SATURATION_COLOR_COUNT = 2;
+
+ private static final int VALUE_SEEK_BAR_MAX = 100;
+
+ private static final int VALUE_COLOR_COUNT = 2;
+
+ ColorPickerAdvancedComponent mHueDetails;
+
+ ColorPickerAdvancedComponent mSaturationDetails;
+
+ ColorPickerAdvancedComponent mValueDetails;
+
+ private OnColorChangedListener mOnColorChangedListener;
+
+ private int mCurrentColor;
+
+ private final float[] mCurrentHsvValues = new float[3];
+
+ public ColorPickerAdvanced(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ColorPickerAdvanced(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public ColorPickerAdvanced(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Initializes all the views and variables in the advanced view.
+ */
+ private void init() {
+ setOrientation(LinearLayout.VERTICAL);
+
+ mHueDetails = createAndAddNewGradient(R.string.color_picker_hue,
+ HUE_SEEK_BAR_MAX, this);
+ mSaturationDetails = createAndAddNewGradient(R.string.color_picker_saturation,
+ SATURATION_SEEK_BAR_MAX, this);
+ mValueDetails = createAndAddNewGradient(R.string.color_picker_value,
+ VALUE_SEEK_BAR_MAX, this);
+
+ refreshGradientComponents();
+ }
+
+ /**
+ * Creates a new GradientDetails object from the parameters provided, initializes it,
+ * and adds it to this advanced view.
+ *
+ * @param textResourceId The text to display for the label.
+ * @param seekBarMax The maximum value of the seek bar for the gradient.
+ * @param seekBarListener Object listening to when the user changes the seek bar.
+ *
+ * @return A new GradientDetails object initialized with the given parameters.
+ */
+ public ColorPickerAdvancedComponent createAndAddNewGradient(int textResourceId,
+ int seekBarMax,
+ OnSeekBarChangeListener seekBarListener) {
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View newComponent = inflater.inflate(R.layout.color_picker_advanced_component, null);
+ addView(newComponent);
+
+ return new ColorPickerAdvancedComponent(newComponent,
+ textResourceId,
+ seekBarMax,
+ seekBarListener);
+ }
+
+ /**
+ * Sets the listener for when the user changes the color.
+ *
+ * @param onColorChangedListener The object listening for the change in color.
+ */
+ public void setListener(OnColorChangedListener onColorChangedListener) {
+ mOnColorChangedListener = onColorChangedListener;
+ }
+
+ /**
+ * @return The color the user has currently chosen.
+ */
+ public int getColor() {
+ return mCurrentColor;
+ }
+
+ /**
+ * Sets the color that the user has currently chosen.
+ *
+ * @param color The currently chosen color.
+ */
+ public void setColor(int color) {
+ mCurrentColor = color;
+ Color.colorToHSV(mCurrentColor, mCurrentHsvValues);
+ refreshGradientComponents();
+ }
+
+ /**
+ * Notifies the listener, if there is one, of a change in the selected color.
+ */
+ private void notifyColorChanged() {
+ if (mOnColorChangedListener != null) {
+ mOnColorChangedListener.onColorChanged(getColor());
+ }
+ }
+
+ /**
+ * Callback for when a slider is updated on the advanced view.
+ *
+ * @param seekBar The color slider that was updated.
+ * @param progress The new value of the color slider.
+ * @param fromUser Whether it was the user the changed the value, or whether
+ * we were setting it up.
+ */
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+ mCurrentHsvValues[0] = mHueDetails.getValue();
+ mCurrentHsvValues[1] = mSaturationDetails.getValue() / 100.0f;
+ mCurrentHsvValues[2] = mValueDetails.getValue() / 100.0f;
+
+ mCurrentColor = Color.HSVToColor(mCurrentHsvValues);
+
+ updateHueGradient();
+ updateSaturationGradient();
+ updateValueGradient();
+
+ notifyColorChanged();
+ }
+ }
+
+ /**
+ * Updates only the hue gradient display with the hue value for the
+ * currently selected color.
+ */
+ private void updateHueGradient() {
+ float[] tempHsvValues = new float[3];
+ tempHsvValues[1] = mCurrentHsvValues[1];
+ tempHsvValues[2] = mCurrentHsvValues[2];
+
+ int[] newColors = new int[HUE_COLOR_COUNT];
+
+ for (int i = 0; i < HUE_COLOR_COUNT; ++i) {
+ tempHsvValues[0] = i * 60.0f;
+ newColors[i] = Color.HSVToColor(tempHsvValues);
+ }
+ mHueDetails.setGradientColors(newColors);
+ }
+
+ /**
+ * Updates only the saturation gradient display with the saturation value
+ * for the currently selected color.
+ */
+ private void updateSaturationGradient() {
+ float[] tempHsvValues = new float[3];
+ tempHsvValues[0] = mCurrentHsvValues[0];
+ tempHsvValues[1] = 0.0f;
+ tempHsvValues[2] = mCurrentHsvValues[2];
+
+ int[] newColors = new int[SATURATION_COLOR_COUNT];
+
+ newColors[0] = Color.HSVToColor(tempHsvValues);
+
+ tempHsvValues[1] = 1.0f;
+ newColors[1] = Color.HSVToColor(tempHsvValues);
+ mSaturationDetails.setGradientColors(newColors);
+ }
+
+ /**
+ * Updates only the Value gradient display with the Value amount for
+ * the currently selected color.
+ */
+ private void updateValueGradient() {
+ float[] tempHsvValues = new float[3];
+ tempHsvValues[0] = mCurrentHsvValues[0];
+ tempHsvValues[1] = mCurrentHsvValues[1];
+ tempHsvValues[2] = 0.0f;
+
+ int[] newColors = new int[VALUE_COLOR_COUNT];
+
+ newColors[0] = Color.HSVToColor(tempHsvValues);
+
+ tempHsvValues[2] = 1.0f;
+ newColors[1] = Color.HSVToColor(tempHsvValues);
+ mValueDetails.setGradientColors(newColors);
+ }
+
+ /**
+ * Updates all the gradient displays to show the currently selected color.
+ */
+ private void refreshGradientComponents() {
+ // Round and bound the saturation value.
+ int saturationValue = Math.round(mCurrentHsvValues[1] * 100.0f);
+ saturationValue = Math.min(saturationValue, SATURATION_SEEK_BAR_MAX);
+ saturationValue = Math.max(saturationValue, 0);
+
+ // Round and bound the Value amount.
+ int valueValue = Math.round(mCurrentHsvValues[2] * 100.0f);
+ valueValue = Math.min(valueValue, VALUE_SEEK_BAR_MAX);
+ valueValue = Math.max(valueValue, 0);
+
+ // Don't need to round the hue value since its possible values match the seek bar
+ // range directly.
+ mHueDetails.setValue(mCurrentHsvValues[0]);
+ mSaturationDetails.setValue(saturationValue);
+ mValueDetails.setValue(valueValue);
+
+ updateHueGradient();
+ updateSaturationGradient();
+ updateValueGradient();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing.
+ }
+}
diff --git a/src/org/chromium/ui/ColorPickerAdvancedComponent.java b/src/org/chromium/ui/ColorPickerAdvancedComponent.java
new file mode 100644
index 0000000..1e679d5
--- /dev/null
+++ b/src/org/chromium/ui/ColorPickerAdvancedComponent.java
@@ -0,0 +1,94 @@
+// 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;
+
+import android.content.Context;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.GradientDrawable.Orientation;
+import android.os.Build;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+import org.chromium.base.ApiCompatibilityUtils;
+
+/**
+ * Encapsulates a single gradient view of the HSV color display, including its label, gradient
+ * view and seek bar.
+ *
+ * Mirrors a "color_picker_advanced_component" layout.
+ */
+public class ColorPickerAdvancedComponent {
+ // The view that displays the gradient.
+ private final View mGradientView;
+ // The seek bar that allows the user to change the value of this component.
+ private final SeekBar mSeekBar;
+ // The set of colors to interpolate the gradient through.
+ private int[] mGradientColors;
+ // The Drawable that represents the gradient.
+ private GradientDrawable mGradientDrawable;
+ // The text label for the component.
+ private final TextView mText;
+
+ /**
+ * Initializes the views.
+ *
+ * @param rootView View that contains all the content, such as the label, gradient view, etc.
+ * @param textResourceId The resource ID of the text to show on the label.
+ * @param seekBarMax The range of the seek bar.
+ * @param seekBarListener The listener for when the seek bar value changes.
+ */
+ ColorPickerAdvancedComponent(final View rootView,
+ final int textResourceId,
+ final int seekBarMax,
+ final OnSeekBarChangeListener seekBarListener) {
+ mGradientView = rootView.findViewById(R.id.gradient);
+ mText = (TextView) rootView.findViewById(R.id.text);
+ mText.setText(textResourceId);
+ mGradientDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, null);
+ mSeekBar = (SeekBar) rootView.findViewById(R.id.seek_bar);
+ mSeekBar.setOnSeekBarChangeListener(seekBarListener);
+ mSeekBar.setMax(seekBarMax);
+ // Setting the thumb offset means the seek bar thumb can move all the way to each end
+ // of the gradient view.
+ Context context = rootView.getContext();
+ int offset = context.getResources()
+ .getDrawable(R.drawable.color_picker_advanced_select_handle)
+ .getIntrinsicWidth();
+ mSeekBar.setThumbOffset(offset / 2);
+ }
+
+ /**
+ * @return The value represented by this component, maintained by the seek bar progress.
+ */
+ public float getValue() {
+ return mSeekBar.getProgress();
+ }
+
+ /**
+ * Sets the value of the component (by setting the seek bar value).
+ *
+ * @param newValue The value to give the component.
+ */
+ public void setValue(float newValue) {
+ mSeekBar.setProgress((int) newValue);
+ }
+
+ /**
+ * Sets the colors for the gradient view to interpolate through.
+ *
+ * @param newColors The set of colors representing the interpolation points for the gradient.
+ */
+ public void setGradientColors(int[] newColors) {
+ mGradientColors = newColors.clone();
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ Orientation currentOrientation = Orientation.LEFT_RIGHT;
+ mGradientDrawable = new GradientDrawable(currentOrientation, mGradientColors);
+ } else {
+ mGradientDrawable.setColors(mGradientColors);
+ }
+ ApiCompatibilityUtils.setBackgroundForView(mGradientView, mGradientDrawable);
+ }
+}
diff --git a/src/org/chromium/ui/ColorPickerDialog.java b/src/org/chromium/ui/ColorPickerDialog.java
index d743a5c..58f1e0a 100644
--- a/src/org/chromium/ui/ColorPickerDialog.java
+++ b/src/org/chromium/ui/ColorPickerDialog.java
@@ -4,209 +4,163 @@
package org.chromium.ui;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.graphics.SweepGradient;
-import android.os.Bundle;
-import android.view.MotionEvent;
+import android.view.LayoutInflater;
import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
/**
- * UI for the color chooser that shows on the Android platform as a result
- * of <input type=color > form element.
- *
- * Note that this UI is only temporary and will be replaced once the UI
- * design in
- * https://code.google.com/p/chromium/issues/detail?id=162491 is finalized
+ * UI for the color chooser that shows on the Android platform as a result of
+ * <input type=color > form element.
*/
-public class ColorPickerDialog extends Dialog {
+public class ColorPickerDialog extends AlertDialog implements OnColorChangedListener {
+ private final ColorPickerAdvanced mAdvancedColorPicker;
- public interface OnColorChangedListener {
- void colorChanged(int color);
- }
+ private final ColorPickerSimple mSimpleColorPicker;
- private OnColorChangedListener mListener;
- private int mInitialColor;
-
- private static class ColorPickerView extends View {
- private static final int CENTER_RADIUS = 32;
- private static final int DIALOG_HEIGHT = 200;
- private static final int BOUNDING_BOX_EDGE = 100;
- private static final float PI = 3.1415926f;
-
- private final Paint mPaint;
- private final Paint mCenterPaint;
- private final int[] mColors;
- private final OnColorChangedListener mListener;
- private boolean mTrackingCenter;
- private boolean mHighlightCenter;
-
- private int center_x = -1;
- private int center_y = -1;
-
- ColorPickerView(Context c, OnColorChangedListener listener, int color) {
- super(c);
- mListener = listener;
- mColors = new int[] {
- 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
- 0xFFFFFF00, 0xFFFF0000
- };
- Shader shader = new SweepGradient(0, 0, mColors, null);
-
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setShader(shader);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(32);
-
- mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mCenterPaint.setColor(color);
- mCenterPaint.setStrokeWidth(5);
- }
+ private final Button mMoreButton;
- @Override
- protected void onDraw(Canvas canvas) {
- if (center_x == -1) {
- center_x = getWidth() / 2;
- }
- if (center_y == -1) {
- center_y = getHeight() / 2;
- }
+ // The view up in the corner that shows the user the color they've currently selected.
+ private final View mCurrentColorView;
- float r = BOUNDING_BOX_EDGE - mPaint.getStrokeWidth() * 0.5f;
+ private final OnColorChangedListener mListener;
- canvas.translate(center_x, center_y);
+ private final int mInitialColor;
- canvas.drawOval(new RectF(-r, -r, r, r), mPaint);
- canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);
+ private int mCurrentColor;
- if (mTrackingCenter) {
- int color = mCenterPaint.getColor();
- mCenterPaint.setStyle(Paint.Style.STROKE);
+ /**
+ * @param context The context the dialog is to run in.
+ * @param theme The theme to display the dialog in.
+ * @param listener The object to notify when the color is set.
+ * @param color The initial color to set.
+ */
+ public ColorPickerDialog(Context context, OnColorChangedListener listener, int color) {
+ super(context, 0);
- if (mHighlightCenter) {
- mCenterPaint.setAlpha(0xFF);
- } else {
- mCenterPaint.setAlpha(0x80);
- }
- canvas.drawCircle(0, 0,
- CENTER_RADIUS + mCenterPaint.getStrokeWidth(),
- mCenterPaint);
+ mListener = listener;
+ mInitialColor = color;
+ mCurrentColor = mInitialColor;
+
+ // Initialize title
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View title = inflater.inflate(R.layout.color_picker_dialog_title, null);
+ setCustomTitle(title);
+
+ mCurrentColorView = title.findViewById(R.id.selected_color_view);
+
+ TextView titleText = (TextView) title.findViewById(R.id.title);
+ titleText.setText(R.string.color_picker_dialog_title);
+
+ // Initialize Set/Cancel buttons
+ String positiveButtonText = context.getString(R.string.color_picker_button_set);
+ setButton(BUTTON_POSITIVE, positiveButtonText,
+ new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ tryNotifyColorSet(mCurrentColor);
+ }
+ });
+
+ // Note that with the color picker there's not really any such thing as
+ // "cancelled".
+ // The color picker flow only finishes when we return a color, so we
+ // have to always
+ // return something. The concept of "cancelled" in this case just means
+ // returning
+ // the color that we were initialized with.
+ String negativeButtonText = context.getString(R.string.color_picker_button_cancel);
+ setButton(BUTTON_NEGATIVE, negativeButtonText,
+ new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ tryNotifyColorSet(mInitialColor);
+ }
+ });
- mCenterPaint.setStyle(Paint.Style.FILL);
- mCenterPaint.setColor(color);
+ setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface arg0) {
+ tryNotifyColorSet(mInitialColor);
}
- }
+ });
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(widthMeasureSpec, DIALOG_HEIGHT);
- }
-
- private static int interpolate(int low, int high, float interPolant) {
- return low + java.lang.Math.round(interPolant * (high - low));
- }
+ // Initialize main content view
+ View content = inflater.inflate(R.layout.color_picker_dialog_content, null);
+ setView(content);
- static int interpolateColor(int colors[], float x, float y) {
- float angle = (float)java.lang.Math.atan2(y, x);
- float unit = angle / (2 * PI);
- if (unit < 0) {
- unit += 1;
- }
- if (unit <= 0) {
- return colors[0];
- }
- if (unit >= 1) {
- return colors[colors.length - 1];
+ // Initialize More button.
+ mMoreButton = (Button) content.findViewById(R.id.more_colors_button);
+ mMoreButton.setOnClickListener(new Button.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showAdvancedView();
}
+ });
- float p = unit * (colors.length - 1);
- int i = (int)p;
- p -= i;
-
- // Now p is just the fractional part [0...1) and i is the index.
- int c0 = colors[i];
- int c1 = colors[i+1];
- int a = interpolate(Color.alpha(c0), Color.alpha(c1), p);
- int r = interpolate(Color.red(c0), Color.red(c1), p);
- int g = interpolate(Color.green(c0), Color.green(c1), p);
- int b = interpolate(Color.blue(c0), Color.blue(c1), p);
+ // Initialize advanced color view (hidden initially).
+ mAdvancedColorPicker =
+ (ColorPickerAdvanced) content.findViewById(R.id.color_picker_advanced);
+ mAdvancedColorPicker.setVisibility(View.GONE);
- return Color.argb(a, r, g, b);
- }
+ // Initialize simple color view (default view).
+ mSimpleColorPicker = (ColorPickerSimple) content.findViewById(R.id.color_picker_simple);
+ mSimpleColorPicker.init(this);
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- float x = event.getX() - center_x;
- float y = event.getY() - center_y;
-
- // equivalent to sqrt(x * x + y * y) <= CENTER_RADIUS but cheaper
- boolean inCenter = (x * x + y * y) <= (CENTER_RADIUS * CENTER_RADIUS);
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mTrackingCenter = inCenter;
- if (inCenter) {
- mHighlightCenter = true;
- invalidate();
- break;
- }
- case MotionEvent.ACTION_MOVE:
- if (mTrackingCenter) {
- if (mHighlightCenter != inCenter) {
- mHighlightCenter = inCenter;
- invalidate();
- }
- } else {
- mCenterPaint.setColor(interpolateColor(mColors, x, y));
- invalidate();
- }
- break;
- case MotionEvent.ACTION_UP:
- if (mTrackingCenter) {
- if (inCenter) {
- mListener.colorChanged(mCenterPaint.getColor());
- }
-
- // Draw without the halo surrounding the central circle.
- mTrackingCenter = false;
- invalidate();
- }
- break;
- default:
- break;
- }
- return true;
- }
+ updateCurrentColor(mInitialColor);
}
- public ColorPickerDialog(Context context,
- OnColorChangedListener listener,
- int initialColor) {
- super(context);
-
- mListener = listener;
- mInitialColor = initialColor;
+ /**
+ * Listens to the ColorPicker for when the user has changed the selected color, and
+ * updates the current color (the color shown in the title) accordingly.
+ *
+ * @param color The new color chosen by the user.
+ */
+ @Override
+ public void onColorChanged(int color) {
+ updateCurrentColor(color);
+ }
- setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface arg0) {
- mListener.colorChanged(mInitialColor);
- }
- });
+ /**
+ * Hides the simple view (the default) and shows the advanced one instead, hiding the
+ * "More" button at the same time.
+ */
+ private void showAdvancedView() {
+ // Only need to hide the borders, not the Views themselves, since the Views are
+ // contained within the borders.
+ View buttonBorder = findViewById(R.id.more_colors_button_border);
+ buttonBorder.setVisibility(View.GONE);
+
+ View simpleViewBorder = findViewById(R.id.color_picker_simple_border);
+ simpleViewBorder.setVisibility(View.GONE);
+
+ mAdvancedColorPicker.setVisibility(View.VISIBLE);
+ mAdvancedColorPicker.setListener(this);
+ mAdvancedColorPicker.setColor(mCurrentColor);
}
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(new ColorPickerView(getContext(), mListener, mInitialColor));
+ /**
+ * Tries to notify any listeners that the color has been set.
+ */
+ private void tryNotifyColorSet(int color) {
+ if (mListener != null) {
+ mListener.onColorChanged(color);
+ }
+ }
- // TODO(miguelg): Internationalization
- setTitle("Select Color");
+ /**
+ * Updates the internal cache of the currently selected color, updating the colorful little
+ * box in the title at the same time.
+ */
+ private void updateCurrentColor(int color) {
+ mCurrentColor = color;
+ if (mCurrentColorView != null) {
+ mCurrentColorView.setBackgroundColor(color);
+ }
}
}
diff --git a/src/org/chromium/ui/ColorPickerMoreButton.java b/src/org/chromium/ui/ColorPickerMoreButton.java
new file mode 100644
index 0000000..a982afd
--- /dev/null
+++ b/src/org/chromium/ui/ColorPickerMoreButton.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;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * Simple class that draws a white border around a button, purely for a UI change.
+ */
+public class ColorPickerMoreButton extends Button {
+
+ // A cache for the paint used to draw the border, so it doesn't have to be created in
+ // every onDraw() call.
+ private Paint mBorderPaint;
+
+ public ColorPickerMoreButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ColorPickerMoreButton(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ /**
+ * Sets up the paint to use for drawing the border.
+ */
+ public void init() {
+ mBorderPaint = new Paint();
+ mBorderPaint.setStyle(Paint.Style.STROKE);
+ mBorderPaint.setColor(Color.WHITE);
+ // Set the width to one pixel.
+ mBorderPaint.setStrokeWidth(1.0f);
+ // And make sure the border doesn't bleed into the outside.
+ mBorderPaint.setAntiAlias(false);
+ }
+
+ /**
+ * Draws the border around the edge of the button.
+ *
+ * @param canvas The canvas to draw on.
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawRect(0.5f, 0.5f, getWidth() - 1.5f, getHeight() - 1.5f, mBorderPaint);
+ super.onDraw(canvas);
+ }
+}
diff --git a/src/org/chromium/ui/ColorPickerSimple.java b/src/org/chromium/ui/ColorPickerSimple.java
new file mode 100644
index 0000000..979405a
--- /dev/null
+++ b/src/org/chromium/ui/ColorPickerSimple.java
@@ -0,0 +1,189 @@
+// 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;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+
+/**
+ * Draws a grid of (predefined) colors and allows the user to choose one of
+ * those colors.
+ */
+public class ColorPickerSimple extends View {
+ private static final int ROW_COUNT = 2;
+
+ private static final int COLUMN_COUNT = 4;
+
+ private static final int GRID_CELL_COUNT = ROW_COUNT * COLUMN_COUNT;
+
+ private static final int[] COLORS = { Color.RED,
+ Color.CYAN,
+ Color.BLUE,
+ Color.GREEN,
+ Color.MAGENTA,
+ Color.YELLOW,
+ Color.BLACK,
+ Color.WHITE
+ };
+
+ private Paint mBorderPaint;
+
+ private Rect[] mBounds;
+
+ private Paint[] mPaints;
+
+ private OnColorChangedListener mOnColorTouchedListener;
+
+ private int mLastTouchedXPosition;
+
+ private int mLastTouchedYPosition;
+
+ public ColorPickerSimple(Context context) {
+ super(context);
+ }
+
+ public ColorPickerSimple(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ColorPickerSimple(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Initializes the listener and precalculates the grid and color positions.
+ *
+ * @param onColorChangedListener The listener that gets notified when the user touches
+ * a color.
+ */
+ public void init(OnColorChangedListener onColorChangedListener) {
+ mOnColorTouchedListener = onColorChangedListener;
+
+ // This will get calculated when the layout size is updated.
+ mBounds = null;
+
+ mPaints = new Paint[GRID_CELL_COUNT];
+ for (int i = 0; i < GRID_CELL_COUNT; ++i) {
+ Paint newPaint = new Paint();
+ newPaint.setColor(COLORS[i]);
+ mPaints[i] = newPaint;
+ }
+
+ mBorderPaint = new Paint();
+ int borderColor = getContext().getResources().getColor(R.color.color_picker_border_color);
+ mBorderPaint.setColor(borderColor);
+
+ // Responds to the user touching the grid and works out which color has been chosen as
+ // a result, depending on the X,Y coordinate. Note that we respond to the click event
+ // here, but the onClick() method doesn't provide us with the X,Y coordinates, so we
+ // track them in onTouchEvent() below. This way the grid reacts properly to touch events
+ // whereas if we put this onClick() code in onTouchEvent below then we get some strange
+ // interactions with the ScrollView in the parent ColorPickerDialog.
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOnColorTouchedListener != null && getWidth() > 0 && getHeight() > 0) {
+ int column = mLastTouchedXPosition * COLUMN_COUNT / getWidth();
+ int row = mLastTouchedYPosition * ROW_COUNT / getHeight();
+
+ int colorIndex = (row * COLUMN_COUNT) + column;
+ if (colorIndex >= 0 && colorIndex < COLORS.length) {
+ mOnColorTouchedListener.onColorChanged(COLORS[colorIndex]);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Draws the grid of colors, based on the rectangles calculated in onSizeChanged().
+ * Also draws borders in between the colored rectangles.
+ *
+ * @param canvas The canvas the colors are drawn onto.
+ */
+ @Override
+ public void onDraw(Canvas canvas) {
+ if (mBounds == null || mPaints == null) {
+ return;
+ }
+
+ canvas.drawColor(Color.WHITE);
+
+ // Draw the actual colored rectangles.
+ for (int i = 0; i < GRID_CELL_COUNT; ++i) {
+ canvas.drawRect(mBounds[i], mPaints[i]);
+ }
+
+ // Draw 1px borders between the rows.
+ for (int i = 0; i < ROW_COUNT - 1; ++i) {
+ canvas.drawLine(0,
+ mBounds[i * COLUMN_COUNT].bottom + 1,
+ getWidth(),
+ mBounds[i * COLUMN_COUNT].bottom + 1,
+ mBorderPaint);
+ }
+
+ // Draw 1px borders between the columns.
+ for (int j = 0; j < COLUMN_COUNT - 1; ++j) {
+ canvas.drawLine(mBounds[j].right + 1,
+ 0,
+ mBounds[j].right + 1,
+ getHeight(),
+ mBorderPaint);
+ }
+ }
+
+ /**
+ * Stores the X,Y coordinates of the touch so that we can use them in the onClick() listener
+ * above to work out where the click was on the grid.
+ *
+ * @param event The MotionEvent the X,Y coordinates are retrieved from.
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mLastTouchedXPosition = (int) event.getX();
+ mLastTouchedYPosition = (int) event.getY();
+ }
+ return super.onTouchEvent(event);
+ }
+
+ /**
+ * Recalculates the color grid with the new sizes.
+ */
+ @Override
+ protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+ calculateGrid(width, height);
+ }
+
+ /**
+ * Calculates the sizes and positions of the cells in the grid, splitting
+ * them up as evenly as possible. Leaves 3 pixels between each cell so that
+ * we can draw a border between them as well, and leaves a pixel around the
+ * edge.
+ */
+ private void calculateGrid(final int width, final int height) {
+ mBounds = new Rect[GRID_CELL_COUNT];
+
+ for (int i = 0; i < ROW_COUNT; ++i) {
+ for (int j = 0; j < COLUMN_COUNT; ++j) {
+ int left = j * (width + 1) / COLUMN_COUNT + 1;
+ int right = (j + 1) * (width + 1) / COLUMN_COUNT - 2;
+
+ int top = i * (height + 1) / ROW_COUNT + 1;
+ int bottom = (i + 1) * (height + 1) / ROW_COUNT - 2;
+
+ Rect rect = new Rect(left, top, right, bottom);
+ mBounds[(i * COLUMN_COUNT) + j] = rect;
+ }
+ }
+ }
+}
diff --git a/src/org/chromium/ui/OnColorChangedListener.java b/src/org/chromium/ui/OnColorChangedListener.java
new file mode 100644
index 0000000..4caa3cf
--- /dev/null
+++ b/src/org/chromium/ui/OnColorChangedListener.java
@@ -0,0 +1,17 @@
+// 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;
+
+/**
+ * The callback used to indicate the user changed the color.
+ */
+public interface OnColorChangedListener {
+
+ /**
+ * Called upon a color change.
+ *
+ * @param color The color that was set.
+ */
+ void onColorChanged(int color);
+}
\ No newline at end of file
diff --git a/src/org/chromium/ui/R.java b/src/org/chromium/ui/R.java
index 53f60cc..c722c14 100644
--- a/src/org/chromium/ui/R.java
+++ b/src/org/chromium/ui/R.java
@@ -18,5 +18,42 @@ public final class R {
public static final class string {
public static int low_memory_error;
public static int opening_file_error;
+ public static int color_picker_button_more;
+ public static int color_picker_hue;
+ public static int color_picker_saturation;
+ public static int color_picker_value;
+ public static int color_picker_button_set;
+ public static int color_picker_button_cancel;
+ public static int color_picker_dialog_title;
+ }
+ public static final class id {
+ public static int autofill_label;
+ public static int autofill_popup_window;
+ public static int autofill_sublabel;
+ public static int selected_color_view;
+ public static int title;
+ public static int more_colors_button;
+ public static int color_picker_advanced;
+ public static int color_picker_simple;
+ public static int more_colors_button_border;
+ public static int color_picker_simple_border;
+ public static int gradient;
+ public static int text;
+ public static int seek_bar;
+ }
+ public static final class layout {
+ public static int autofill_text;
+ public static int color_picker_dialog_title;
+ public static int color_picker_dialog_content;
+ public static int color_picker_advanced_component;
+ }
+ public static final class drawable {
+ public static int color_picker_advanced_select_handle;
+ }
+ public static final class style {
+ public static int AutofillPopupWindow;
+ }
+ public static final class color {
+ public static int color_picker_border_color;
}
}
diff --git a/src/org/chromium/ui/SelectFileDialog.java b/src/org/chromium/ui/SelectFileDialog.java
index a9b117b..9aa11e4 100644
--- a/src/org/chromium/ui/SelectFileDialog.java
+++ b/src/org/chromium/ui/SelectFileDialog.java
@@ -11,6 +11,7 @@
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
+import android.text.TextUtils;
import java.io.File;
import java.util.ArrayList;
@@ -34,15 +35,11 @@ class SelectFileDialog implements WindowAndroid.IntentCallback{
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_CAMERA = "camera";
- private static final String CAPTURE_CAMCORDER = "camcorder";
- private static final String CAPTURE_MICROPHONE = "microphone";
- private static final String CAPTURE_FILESYSTEM = "filesystem";
private static final String CAPTURE_IMAGE_DIRECTORY = "browser-photos";
private final int mNativeSelectFileDialog;
private List mFileTypes;
- private String mCapture; // May be null if no capture parameter was set.
+ private boolean mCapture;
private Uri mCameraOutputUri;
private SelectFileDialog(int nativeSelectFileDialog) {
@@ -56,7 +53,7 @@ private SelectFileDialog(int nativeSelectFileDialog) {
* @param window The WindowAndroid that can show intents
*/
@CalledByNative
- private void selectFile(String[] fileTypes, String capture, WindowAndroid window) {
+ private void selectFile(String[] fileTypes, boolean capture, WindowAndroid window) {
mFileTypes = new ArrayList(Arrays.asList(fileTypes));
mCapture = capture;
@@ -69,9 +66,9 @@ private void selectFile(String[] fileTypes, String capture, WindowAndroid window
MediaStore.Audio.Media.RECORD_SOUND_ACTION);
String lowMemoryError = window.getContext().getString(R.string.low_memory_error);
- // Quick check - if a capture parameter other than filesystem (the default) is specified we
- // should just launch the appropriate intent. Otherwise build up a chooser based on the
- // accept type and then display that to the user.
+ // 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, lowMemoryError)) return;
} else if (captureCamcorder()) {
@@ -89,19 +86,19 @@ private void selectFile(String[] fileTypes, String capture, WindowAndroid window
// chooser above.
if (shouldShowImageTypes()) {
extraIntents.add(camera);
- getContentIntent.setType("image/*");
+ getContentIntent.setType(ALL_IMAGE_TYPES);
} else if (shouldShowVideoTypes()) {
extraIntents.add(camcorder);
- getContentIntent.setType("video/*");
+ getContentIntent.setType(ALL_VIDEO_TYPES);
} else if (shouldShowAudioTypes()) {
extraIntents.add(soundRecorder);
- getContentIntent.setType("audio/*");
+ getContentIntent.setType(ALL_AUDIO_TYPES);
}
}
if (extraIntents.isEmpty()) {
// We couldn't resolve an accept type, so fallback to a generic chooser.
- getContentIntent.setType("*/*");
+ getContentIntent.setType(ANY_TYPES);
extraIntents.add(camera);
extraIntents.add(camcorder);
extraIntents.add(soundRecorder);
@@ -181,8 +178,7 @@ public void onIntentCompleted(WindowAndroid window, int resultCode,
}
if (!success) {
onFileNotSelected();
- String openingFileError = window.getContext().getString(R.string.opening_file_error);
- window.showError(openingFileError);
+ window.showError(R.string.opening_file_error);
}
}
@@ -215,22 +211,20 @@ 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 shouldShowImageTypes() && mCapture != null && mCapture.startsWith(CAPTURE_CAMERA);
+ return mCapture && acceptsSpecificType(ALL_IMAGE_TYPES);
}
private boolean captureCamcorder() {
- return shouldShowVideoTypes() && mCapture != null &&
- mCapture.startsWith(CAPTURE_CAMCORDER);
+ return mCapture && acceptsSpecificType(ALL_VIDEO_TYPES);
}
private boolean captureMicrophone() {
- return shouldShowAudioTypes() && mCapture != null &&
- mCapture.startsWith(CAPTURE_MICROPHONE);
- }
-
- private boolean captureFilesystem() {
- return mCapture != null && mCapture.startsWith(CAPTURE_FILESYSTEM);
+ return mCapture && acceptsSpecificType(ALL_AUDIO_TYPES);
}
private boolean acceptSpecificType(String accept) {
diff --git a/src/org/chromium/ui/WindowAndroid.java b/src/org/chromium/ui/WindowAndroid.java
index 58716d8..477e5e6 100644
--- a/src/org/chromium/ui/WindowAndroid.java
+++ b/src/org/chromium/ui/WindowAndroid.java
@@ -11,6 +11,7 @@
import android.content.Intent;
import android.os.Bundle;
import android.util.SparseArray;
+import android.view.WindowManager;
import android.widget.Toast;
import java.util.HashMap;
@@ -80,6 +81,14 @@ public void showError(String error) {
}
}
+ /**
+ * Displays an error message from the given resource id.
+ * @param resId The error message string's resource id.
+ */
+ public void showError(int resId) {
+ showError(mActivity.getString(resId));
+ }
+
/**
* Displays an error message for a nonexistent callback.
* @param error The error message string to be displayed.
@@ -99,6 +108,7 @@ public void sendBroadcast(Intent intent) {
* TODO(nileshagrawal): Stop returning Activity Context crbug.com/233440.
* @return Activity context.
*/
+ @Deprecated
public Context getContext() {
return mActivity;
}
@@ -190,6 +200,14 @@ public int getNativePointer() {
return mNativeWindowAndroid;
}
+ public void keepScreenOn(boolean screenOn) {
+ if (screenOn) {
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
private native int nativeInit();
private native void nativeDestroy(int nativeWindowAndroid);
diff --git a/src/org/chromium/ui/autofill/AutofillListAdapter.java b/src/org/chromium/ui/autofill/AutofillListAdapter.java
new file mode 100644
index 0000000..62203dc
--- /dev/null
+++ b/src/org/chromium/ui/autofill/AutofillListAdapter.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.autofill;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+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.ArrayList;
+
+/**
+ * Autofill suggestion adapter for AutofillWindow.
+ */
+public class AutofillListAdapter extends ArrayAdapter {
+ private Context mContext;
+
+ AutofillListAdapter(Context context, ArrayList objects) {
+ super(context, R.layout.autofill_text, objects);
+ mContext = context;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View layout = convertView;
+ if (convertView == null) {
+ LayoutInflater inflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ layout = inflater.inflate(R.layout.autofill_text, null);
+ }
+ TextView labelView = (TextView) layout.findViewById(R.id.autofill_label);
+ labelView.setText(getItem(position).mLabel);
+
+ TextView sublabelView = (TextView) layout.findViewById(R.id.autofill_sublabel);
+ CharSequence sublabel = getItem(position).mSublabel;
+ if (TextUtils.isEmpty(sublabel)) {
+ sublabelView.setVisibility(View.GONE);
+ } else {
+ sublabelView.setText(sublabel);
+ sublabelView.setVisibility(View.VISIBLE);
+ }
+
+ return layout;
+ }
+}
diff --git a/src/org/chromium/ui/autofill/AutofillPopup.java b/src/org/chromium/ui/autofill/AutofillPopup.java
new file mode 100644
index 0000000..0483b02
--- /dev/null
+++ b/src/org/chromium/ui/autofill/AutofillPopup.java
@@ -0,0 +1,214 @@
+// 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.autofill;
+
+import android.content.Context;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.AdapterView;
+import android.widget.ListPopupWindow;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import org.chromium.ui.R;
+import org.chromium.ui.ViewAndroidDelegate;
+
+/**
+ * The Autofill suggestion popup that lists relevant suggestions.
+ */
+public class AutofillPopup extends ListPopupWindow implements AdapterView.OnItemClickListener {
+
+ /**
+ * Constants defining types of Autofill suggestion entries.
+ * Has to be kept in sync with enum in WebAutofillClient.h
+ *
+ * Not supported: MenuItemIDWarningMessage, MenuItemIDSeparator, MenuItemIDClearForm, and
+ * MenuItemIDAutofillOptions.
+ */
+ private static final int ITEM_ID_AUTOCOMPLETE_ENTRY = 0;
+ private static final int ITEM_ID_PASSWORD_ENTRY = -2;
+ private static final int ITEM_ID_DATA_LIST_ENTRY = -6;
+
+ private static final int TEXT_PADDING_DP = 30;
+
+ private final AutofillPopupDelegate mAutofillCallback;
+ private final Context mContext;
+ private final ViewAndroidDelegate mViewAndroidDelegate;
+ private View mAnchorView;
+ private float mAnchorWidth;
+ private float mAnchorHeight;
+ private float mAnchorX;
+ private float mAnchorY;
+ private Paint mLabelViewPaint;
+ private Paint mSublabelViewPaint;
+ private OnLayoutChangeListener mLayoutChangeListener;
+
+ /**
+ * An interface to handle the touch interaction with an AutofillPopup object.
+ */
+ public interface AutofillPopupDelegate {
+ /**
+ * Requests the controller to hide AutofillPopup.
+ */
+ public void requestHide();
+
+ /**
+ * Handles the selection of an Autofill suggestion from an AutofillPopup.
+ * @param listIndex The index of the selected Autofill suggestion.
+ */
+ public void suggestionSelected(int listIndex);
+ }
+
+ /**
+ * Creates an AutofillWindow with specified parameters.
+ * @param context Application context.
+ * @param viewAndroidDelegate View delegate used to add and remove views.
+ * @param autofillCallback A object that handles the calls to the native AutofillPopupView.
+ */
+ public AutofillPopup(Context context, ViewAndroidDelegate viewAndroidDelegate,
+ AutofillPopupDelegate autofillCallback) {
+ super(context, null, 0, R.style.AutofillPopupWindow);
+ mContext = context;
+ mViewAndroidDelegate = viewAndroidDelegate ;
+ mAutofillCallback = autofillCallback;
+
+ setOnItemClickListener(this);
+
+ mAnchorView = mViewAndroidDelegate.acquireAnchorView();
+ mAnchorView.setId(R.id.autofill_popup_window);
+ mAnchorView.setTag(this);
+
+ mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
+ mAnchorHeight);
+
+ mLayoutChangeListener = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (v == mAnchorView) AutofillPopup.this.show();
+ }
+ };
+
+ mAnchorView.addOnLayoutChangeListener(mLayoutChangeListener);
+ setAnchorView(mAnchorView);
+ }
+
+ @Override
+ public void show() {
+ // An ugly hack to keep the popup from expanding on top of the keyboard.
+ setInputMethodMode(INPUT_METHOD_NEEDED);
+ super.show();
+ }
+
+ /**
+ * Sets the location and the size of the anchor view that the AutofillPopup will use to attach
+ * itself.
+ * @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.
+ */
+ public void setAnchorRect(float x, float y, float width, float height) {
+ mAnchorWidth = width;
+ mAnchorHeight = height;
+ mAnchorX = x;
+ mAnchorY = y;
+ if (mAnchorView != null) {
+ mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY,
+ mAnchorWidth, mAnchorHeight);
+ }
+ }
+
+ /**
+ * Sets the Autofill suggestions to display in the popup and shows the popup.
+ * @param suggestions Autofill suggestion data.
+ */
+ public void show(AutofillSuggestion[] suggestions) {
+ // Remove the AutofillSuggestions with IDs that are not supported by Android
+ ArrayList cleanedData = new ArrayList();
+ for (int i = 0; i < suggestions.length; i++) {
+ int itemId = suggestions[i].mUniqueId;
+ if (itemId > 0 || itemId == ITEM_ID_AUTOCOMPLETE_ENTRY ||
+ itemId == ITEM_ID_PASSWORD_ENTRY || itemId == ITEM_ID_DATA_LIST_ENTRY) {
+ cleanedData.add(suggestions[i]);
+ }
+ }
+ setAdapter(new AutofillListAdapter(mContext, cleanedData));
+ // Once the mAnchorRect is resized and placed correctly, it will show the Autofill popup.
+ mAnchorWidth = Math.max(getDesiredWidth(cleanedData), mAnchorWidth);
+ mViewAndroidDelegate.setAnchorViewPosition(mAnchorView, mAnchorX, mAnchorY, mAnchorWidth,
+ mAnchorHeight);
+ }
+
+ /**
+ * Overrides the default dismiss behavior to request the controller to dismiss the view.
+ */
+ @Override
+ public void dismiss() {
+ mAutofillCallback.requestHide();
+ }
+
+ /**
+ * Hides the popup and removes the anchor view from the ContainerView.
+ */
+ public void hide() {
+ super.dismiss();
+ mAnchorView.removeOnLayoutChangeListener(mLayoutChangeListener);
+ mAnchorView.setTag(null);
+ mViewAndroidDelegate.releaseAnchorView(mAnchorView);
+ }
+
+ /**
+ * Get desired popup window width by calculating the maximum text length from Autofill data.
+ * @param data Autofill suggestion data.
+ * @return The popup window width in DIP.
+ */
+ private float getDesiredWidth(ArrayList data) {
+ if (mLabelViewPaint == null || mSublabelViewPaint == null) {
+ LayoutInflater inflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View layout = inflater.inflate(R.layout.autofill_text, null);
+ TextView labelView = (TextView) layout.findViewById(R.id.autofill_label);
+ mLabelViewPaint = labelView.getPaint();
+ TextView sublabelView = (TextView) layout.findViewById(R.id.autofill_sublabel);
+ mSublabelViewPaint = sublabelView.getPaint();
+ }
+
+ float maxTextWidth = 0;
+ Rect bounds = new Rect();
+ for (int i = 0; i < data.size(); ++i) {
+ bounds.setEmpty();
+ String label = data.get(i).mLabel;
+ if (!TextUtils.isEmpty(label)) {
+ mLabelViewPaint.getTextBounds(label, 0, label.length(), bounds);
+ }
+ float labelWidth = bounds.width();
+
+ bounds.setEmpty();
+ String sublabel = data.get(i).mSublabel;
+ if (!TextUtils.isEmpty(sublabel)) {
+ mSublabelViewPaint.getTextBounds(sublabel, 0, sublabel.length(), bounds);
+ }
+
+ float localMax = Math.max(labelWidth, bounds.width());
+ maxTextWidth = Math.max(maxTextWidth, localMax);
+ }
+ // Scale it down to make it unscaled by screen density.
+ maxTextWidth = maxTextWidth / mContext.getResources().getDisplayMetrics().density;
+ // Adding padding.
+ return maxTextWidth + TEXT_PADDING_DP;
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ mAutofillCallback.suggestionSelected(position);
+ }
+
+}
diff --git a/src/org/chromium/ui/autofill/AutofillSuggestion.java b/src/org/chromium/ui/autofill/AutofillSuggestion.java
new file mode 100644
index 0000000..51669ea
--- /dev/null
+++ b/src/org/chromium/ui/autofill/AutofillSuggestion.java
@@ -0,0 +1,26 @@
+// 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.autofill;
+
+/**
+ * Autofill suggestion container used to store information needed for each Autofill popup entry.
+ */
+public class AutofillSuggestion {
+ final String mLabel;
+ final String mSublabel;
+ final int mUniqueId;
+
+ /**
+ * Constructs a Autofill suggestion container.
+ * @param name The name of the Autofill suggestion.
+ * @param label The describing label of the Autofill suggestion.
+ * @param uniqueId The unique id used to identify the Autofill suggestion.
+ */
+ public AutofillSuggestion(String name, String label, int uniqueId) {
+ mLabel = name;
+ mSublabel = label;
+ mUniqueId = uniqueId;
+ }
+}
\ No newline at end of file
diff --git a/src/org/chromium/ui/gfx/BitmapHelper.java b/src/org/chromium/ui/gfx/BitmapHelper.java
index 5e237f7..97511a1 100644
--- a/src/org/chromium/ui/gfx/BitmapHelper.java
+++ b/src/org/chromium/ui/gfx/BitmapHelper.java
@@ -17,11 +17,53 @@ public static Bitmap createBitmap(int width, int height) {
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
+ /**
+ * 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
- public static Bitmap decodeDrawableResource(String name) {
+ private static Bitmap decodeDrawableResource(String name,
+ int reqWidth,
+ int reqHeight) {
Resources res = Resources.getSystem();
- int resource_id = res.getIdentifier(name, null, null);
+ int resId = res.getIdentifier(name, null, null);
- return BitmapFactory.decodeResource(res, resource_id);
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, options);
+
+ options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
+
+ options.inJustDecodeBounds = false;
+ 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;
}
}
diff --git a/src/org/chromium/ui/gfx/DeviceDisplayInfo.java b/src/org/chromium/ui/gfx/DeviceDisplayInfo.java
index b133931..088c9c2 100644
--- a/src/org/chromium/ui/gfx/DeviceDisplayInfo.java
+++ b/src/org/chromium/ui/gfx/DeviceDisplayInfo.java
@@ -7,7 +7,6 @@
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
-import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.WindowManager;
@@ -25,11 +24,13 @@
@JNINamespace("gfx")
public class DeviceDisplayInfo {
- private WindowManager mWinManager;
+
+ private final Context mAppContext;
+ private final WindowManager mWinManager;
private DeviceDisplayInfo(Context context) {
- Context appContext = context.getApplicationContext();
- mWinManager = (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE);
+ mAppContext = context.getApplicationContext();
+ mWinManager = (WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE);
}
/**
@@ -114,24 +115,12 @@ public double getDIPScale() {
return getMetrics().density;
}
- /**
- * @return Display refresh rate in frames per second.
- */
- @CalledByNative
- public double getRefreshRate() {
- double result = getDisplay().getRefreshRate();
- // Sanity check.
- return (result >= 61 || result < 30) ? 0 : result;
- }
-
private Display getDisplay() {
return mWinManager.getDefaultDisplay();
}
private DisplayMetrics getMetrics() {
- DisplayMetrics metrics = new DisplayMetrics();
- getDisplay().getMetrics(metrics);
- return metrics;
+ return mAppContext.getResources().getDisplayMetrics();
}
/**
diff --git a/src/us/costan/chrome/ChromeView.java b/src/us/costan/chrome/ChromeView.java
index 3d5f724..b2e70f5 100644
--- a/src/us/costan/chrome/ChromeView.java
+++ b/src/us/costan/chrome/ChromeView.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -60,6 +61,7 @@ public class ChromeView extends FrameLayout {
public ChromeView(Context context) {
this(context, null);
+ Log.i("chromeview", "ChromeView ctor");
}
/** Constructor for inflating via XML. */
@@ -78,6 +80,7 @@ public ChromeView(Context context, AttributeSet attrs) {
} catch(ClassCastException e) {
// Hope that hardware acceleration is enabled.
+ Log.e("chromeview", e.getMessage());
}
SharedPreferences sharedPreferences = context.getSharedPreferences(
@@ -1164,5 +1167,22 @@ public boolean requestDrawGL(Canvas canvas) {
}
}
}
+ @Override
+ public void overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY, int maxOverScrollX,
+ int maxOverScrollY, boolean isTouchEvent) {
+
+ // FIXME (davis): no clue if this is right
+ ChromeView.super.overScrollBy( deltaX, deltaY, scrollX, scrollY,
+ scrollRangeX, scrollRangeY, maxOverScrollX,
+ maxOverScrollY, isTouchEvent);
+
+ }
+ @Override
+ public void super_scrollTo(int scrollX, int scrollY) {
+
+ // FIXME (davis): no clue if this is right
+
+ }
}
}