summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-02 22:54:33 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-02 22:54:33 -0800
commit3dec7d563a2f3e1eb967ce2054a00b6620e3558c (patch)
treeaa3b0365c47cb3c1607c0dc76c8d32b4046fc287 /core/java
parent15ab3eae2ec3d73b3e8aa60b33ae41445bf83f4b (diff)
downloadframeworks_base-3dec7d563a2f3e1eb967ce2054a00b6620e3558c.zip
frameworks_base-3dec7d563a2f3e1eb967ce2054a00b6620e3558c.tar.gz
frameworks_base-3dec7d563a2f3e1eb967ce2054a00b6620e3558c.tar.bz2
auto import from //depot/cupcake/@137055
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/ApplicationContext.java39
-rw-r--r--core/java/android/app/ExpandableListActivity.java23
-rw-r--r--core/java/android/app/IntentService.java74
-rw-r--r--core/java/android/app/ListActivity.java26
-rw-r--r--core/java/android/app/NotificationManager.java4
-rw-r--r--core/java/android/app/SearchDialog.java2
-rw-r--r--core/java/android/app/SearchManager.java8
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java4
-rw-r--r--core/java/android/content/ContentProvider.java75
-rw-r--r--core/java/android/content/ContentProviderNative.java43
-rw-r--r--core/java/android/content/ContentResolver.java320
-rw-r--r--core/java/android/content/ContentServiceNative.java7
-rw-r--r--core/java/android/content/IContentProvider.java4
-rw-r--r--core/java/android/content/Intent.java8
-rw-r--r--core/java/android/content/pm/PackageManager.java20
-rw-r--r--core/java/android/content/res/AssetFileDescriptor.java271
-rw-r--r--core/java/android/content/res/ColorStateList.java19
-rw-r--r--core/java/android/content/res/Resources.java180
-rw-r--r--core/java/android/content/res/StringBlock.java62
-rw-r--r--core/java/android/content/res/TypedArray.java28
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java22
-rw-r--r--core/java/android/gadget/GadgetHost.java12
-rw-r--r--core/java/android/gadget/GadgetHostView.java249
-rw-r--r--core/java/android/gadget/GadgetManager.java16
-rw-r--r--core/java/android/hardware/Camera.java1
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java7
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java266
-rwxr-xr-xcore/java/android/inputmethodservice/KeyboardView.java22
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java72
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java46
-rw-r--r--core/java/android/net/http/CertificateChainValidator.java154
-rw-r--r--core/java/android/os/BatteryStats.java137
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java18
-rw-r--r--core/java/android/pim/ICalendar.java15
-rw-r--r--core/java/android/provider/Checkin.java10
-rw-r--r--core/java/android/provider/Im.java33
-rw-r--r--core/java/android/provider/Settings.java49
-rw-r--r--core/java/android/server/BluetoothA2dpService.java82
-rw-r--r--core/java/android/server/BluetoothDeviceService.java24
-rw-r--r--core/java/android/server/search/SearchableInfo.java18
-rw-r--r--core/java/android/speech/srec/package.html1
-rw-r--r--core/java/android/text/InputType.java5
-rw-r--r--core/java/android/text/Styled.java249
-rw-r--r--core/java/android/text/format/DateUtils.java216
-rw-r--r--core/java/android/text/method/NumberKeyListener.java5
-rw-r--r--core/java/android/view/GestureDetector.java91
-rw-r--r--core/java/android/view/KeyEvent.java6
-rw-r--r--core/java/android/view/View.java47
-rw-r--r--core/java/android/view/ViewConfiguration.java18
-rw-r--r--core/java/android/view/ViewRoot.java39
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java7
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java106
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java14
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java18
-rw-r--r--core/java/android/webkit/CallbackProxy.java8
-rw-r--r--core/java/android/webkit/TextDialog.java103
-rw-r--r--core/java/android/webkit/WebView.java102
-rw-r--r--core/java/android/webkit/WebViewCore.java4
-rw-r--r--core/java/android/widget/AbsListView.java29
-rw-r--r--core/java/android/widget/AbsSeekBar.java45
-rw-r--r--core/java/android/widget/AnalogClock.java17
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java103
-rw-r--r--core/java/android/widget/BaseAdapter.java4
-rw-r--r--core/java/android/widget/Chronometer.java19
-rw-r--r--core/java/android/widget/CursorAdapter.java20
-rw-r--r--core/java/android/widget/Filter.java14
-rw-r--r--core/java/android/widget/GridView.java29
-rw-r--r--core/java/android/widget/HorizontalScrollView.java2
-rw-r--r--core/java/android/widget/ImageView.java2
-rw-r--r--core/java/android/widget/ListView.java85
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java4
-rw-r--r--core/java/android/widget/PopupWindow.java63
-rw-r--r--core/java/android/widget/ProgressBar.java1
-rw-r--r--core/java/android/widget/ResourceCursorAdapter.java22
-rw-r--r--core/java/android/widget/TextView.java355
-rw-r--r--core/java/android/widget/ZoomButtonsController.java478
-rw-r--r--core/java/android/widget/ZoomRing.java418
-rw-r--r--core/java/android/widget/ZoomRingController.java462
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl2
-rw-r--r--core/java/com/android/internal/app/UsbStorageStopActivity.java2
-rw-r--r--core/java/com/android/internal/gadget/IGadgetService.aidl1
-rw-r--r--core/java/com/android/internal/logging/AndroidConfig.java11
-rw-r--r--core/java/com/android/internal/logging/AndroidHandler.java69
-rw-r--r--core/java/com/android/internal/net/DbSSLSessionCache.java269
-rw-r--r--core/java/com/android/internal/net/SSLSessionCache.java101
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java323
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java83
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java16
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl2
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java9
-rw-r--r--core/java/com/android/internal/view/menu/ExpandedMenuView.java5
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java15
-rw-r--r--core/java/com/android/internal/widget/TextProgressBar.java4
-rw-r--r--core/java/com/google/android/gdata/client/AndroidGDataClient.java21
-rw-r--r--core/java/com/google/android/net/GoogleHttpClient.java38
96 files changed, 5117 insertions, 1539 deletions
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index 394b8e3..3b5ad86 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -1487,7 +1487,7 @@ class ApplicationContext extends Context {
static final class ApplicationPackageManager extends PackageManager {
@Override
public PackageInfo getPackageInfo(String packageName, int flags)
- throws NameNotFoundException {
+ throws NameNotFoundException {
try {
PackageInfo pi = mPM.getPackageInfo(packageName, flags);
if (pi != null) {
@@ -1500,6 +1500,43 @@ class ApplicationContext extends Context {
throw new NameNotFoundException(packageName);
}
+ public Intent getLaunchIntentForPackage(String packageName)
+ throws NameNotFoundException {
+ // First see if the package has an INFO activity; the existence of
+ // such an activity is implied to be the desired front-door for the
+ // overall package (such as if it has multiple launcher entries).
+ Intent intent = getLaunchIntentForPackageCategory(this, packageName,
+ Intent.CATEGORY_INFO);
+ if (intent != null) {
+ return intent;
+ }
+
+ // Otherwise, try to find a main launcher activity.
+ return getLaunchIntentForPackageCategory(this, packageName,
+ Intent.CATEGORY_LAUNCHER);
+ }
+
+ // XXX This should be implemented as a call to the package manager,
+ // to reduce the work needed.
+ static Intent getLaunchIntentForPackageCategory(PackageManager pm,
+ String packageName, String category) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Intent intentToResolve = new Intent(Intent.ACTION_MAIN, null);
+ intentToResolve.addCategory(category);
+ final List<ResolveInfo> apps =
+ pm.queryIntentActivities(intentToResolve, 0);
+ // I wish there were a way to directly get the "main" activity of a
+ // package but ...
+ for (ResolveInfo app : apps) {
+ if (app.activityInfo.packageName.equals(packageName)) {
+ intent.setClassName(packageName, app.activityInfo.name);
+ return intent;
+ }
+ }
+ return null;
+ }
+
@Override
public int[] getPackageGids(String packageName)
throws NameNotFoundException {
diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java
index 75dfcae..a2e048f 100644
--- a/core/java/android/app/ExpandableListActivity.java
+++ b/core/java/android/app/ExpandableListActivity.java
@@ -63,21 +63,21 @@ import java.util.Map;
*
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
- * android:paddingLeft=&quot;8&quot;
- * android:paddingRight=&quot;8&quot;&gt;
+ * android:paddingLeft=&quot;8dp&quot;
+ * android:paddingRight=&quot;8dp&quot;&gt;
*
- * &lt;ExpandableListView id=&quot;android:list&quot;
+ * &lt;ExpandableListView android:id=&quot;@id/android:list&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
*
- * &lt;TextView id=&quot;android:empty&quot;
+ * &lt;TextView android:id=&quot;@id/android:empty&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#FF0000&quot;
@@ -113,19 +113,19 @@ import java.util.Map;
*
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
*
- * &lt;TextView id=&quot;text1&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text1&quot;
+ * android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
*
- * &lt;TextView id=&quot;text2&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text2&quot;
+ * android:textSize=&quot;16sp&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
@@ -162,7 +162,8 @@ public class ExpandableListActivity extends Activity implements
/**
* Override this to populate the context menu when an item is long pressed. menuInfo
- * will contain a {@link AdapterContextMenuInfo} whose position is a packed position
+ * will contain an {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo}
+ * whose packedPosition is a packed position
* that should be used with {@link ExpandableListView#getPackedPositionType(long)} and
* the other similar methods.
* <p>
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
new file mode 100644
index 0000000..2b12a2a
--- /dev/null
+++ b/core/java/android/app/IntentService.java
@@ -0,0 +1,74 @@
+package android.app;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * An abstract {@link Service} that serializes the handling of the Intents passed upon service
+ * start and handles them on a handler thread.
+ *
+ * <p>To use this class extend it and implement {@link #onHandleIntent}. The {@link Service} will
+ * automatically be stopped when the last enqueued {@link Intent} is handled.
+ */
+public abstract class IntentService extends Service {
+ private volatile Looper mServiceLooper;
+ private volatile ServiceHandler mServiceHandler;
+ private String mName;
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ onHandleIntent((Intent)msg.obj);
+ stopSelf(msg.arg1);
+ }
+ }
+
+ public IntentService(String name) {
+ super();
+ mName = name;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
+ thread.start();
+
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = intent;
+ mServiceHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onDestroy() {
+ mServiceLooper.quit();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * Invoked on the Handler thread with the {@link Intent} that is passed to {@link #onStart}.
+ * Note that this will be invoked from a different thread than the one that handles the
+ * {@link #onStart} call.
+ */
+ protected abstract void onHandleIntent(Intent intent);
+}
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
index 2818937..5523c18 100644
--- a/core/java/android/app/ListActivity.java
+++ b/core/java/android/app/ListActivity.java
@@ -53,22 +53,22 @@ import android.widget.ListView;
* </p>
*
* <pre>
- * &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
- * android:paddingLeft=&quot;8&quot;
- * android:paddingRight=&quot;8&quot;&gt;
+ * android:paddingLeft=&quot;8dp&quot;
+ * android:paddingRight=&quot;8dp&quot;&gt;
*
- * &lt;ListView id=&quot;android:list&quot;
+ * &lt;ListView android:id=&quot;@id/android:list&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
*
- * &lt;TextView id=&quot;android:empty&quot;
+ * &lt;TextView id=&quot;@id/android:empty&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#FF0000&quot;
@@ -99,19 +99,19 @@ import android.widget.ListView;
*
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
*
- * &lt;TextView id=&quot;text1&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text1&quot;
+ * android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
*
- * &lt;TextView id=&quot;text2&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text2&quot;
+ * android:textSize=&quot;16sp&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
@@ -142,8 +142,8 @@ import android.widget.ListView;
* public class MyListAdapter extends ListActivity {
*
* &#064;Override
- * protected void onCreate(Bundle icicle){
- * super.onCreate(icicle);
+ * protected void onCreate(Bundle savedInstanceState){
+ * super.onCreate(savedInstanceState);
*
* // We'll define a custom screen layout here (the one shown above), but
* // typically, you could just use the standard ListActivity layout.
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index afb3827..39edab7 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -82,9 +82,7 @@ public class NotificationManager
* @param id An identifier for this notification unique within your
* application.
* @param notification A {@link Notification} object describing how to
- * notify the user, other than the view you're providing. If you
- * pass null, there will be no persistent notification and no
- * flashing, vibration, etc.
+ * notify the user, other than the view you're providing. Must not be null.
*/
public void notify(int id, Notification notification)
{
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 7b8256c..d447eb2 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -448,6 +448,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
mSearchTextField.setInputType(inputType);
+ mSearchTextField.setImeOptions(mSearchable.getImeOptions());
}
}
@@ -793,7 +794,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// otherwise, dispatch an "edit view" key
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
if (event.getAction() == KeyEvent.ACTION_UP) {
v.cancelLongPress();
launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 2cc6de9..c1d66f4 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -747,6 +747,14 @@ import android.view.KeyEvent;
* <a href="../R.attr.html#inputType">inputType</a> attribute.</td>
* <td align="center">No</td>
* </tr>
+ * <tr><th>android:imeOptions</th>
+ * <td>If provided, supplies additional options for the input method.
+ * For most searches, in which free form text is expected, this attribute
+ * need not be provided, and will default to "actionSearch".
+ * Suitable values for this attribute are described in the
+ * <a href="../R.attr.html#imeOptions">imeOptions</a> attribute.</td>
+ * <td align="center">No</td>
+ * </tr>
*
* </tbody>
* </table>
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 56b231f..1ba1c1e 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -63,8 +63,10 @@ public class BluetoothDevice {
public static final int UNBOND_REASON_AUTH_CANCELED = 3;
/** A bond attempt failed because we could not contact the remote device */
public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
+ /** A bond attempt failed because a discovery is in progress */
+ public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
/** An existing bond was explicitly revoked */
- public static final int UNBOND_REASON_REMOVED = 5;
+ public static final int UNBOND_REASON_REMOVED = 6;
private static final String TAG = "BluetoothDevice";
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 3a64cee..25544de 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,6 +18,7 @@ package android.content;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.CursorToBulkCursorAdaptor;
@@ -162,6 +163,13 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ContentProvider.this.openFile(uri, mode);
}
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
+ else checkReadPermission(uri);
+ return ContentProvider.this.openAssetFile(uri, mode);
+ }
+
public ISyncAdapter getSyncAdapter() {
checkWritePermission(null);
return ContentProvider.this.getSyncAdapter().getISyncAdapter();
@@ -438,8 +446,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* of this method should create a new ParcelFileDescriptor for each call.
*
* @param uri The URI whose file is to be opened.
- * @param mode Access mode for the file. May be "r" for read-only access
- * or "rw" for read and write access.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "rw" for read and write access, or "rwt" for read and write access
+ * that truncates any existing file.
*
* @return Returns a new ParcelFileDescriptor which you can use to access
* the file.
@@ -448,19 +457,66 @@ public abstract class ContentProvider implements ComponentCallbacks {
* no file associated with the given URI or the mode is invalid.
* @throws SecurityException Throws SecurityException if the caller does
* not have permission to access the file.
- */
+ *
+ * @see #openAssetFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ */
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
throw new FileNotFoundException("No files supported by provider at "
+ uri);
}
+
+ /**
+ * This is like {@link #openFile}, but can be implemented by providers
+ * that need to be able to return sub-sections of files, often assets
+ * inside of their .apk. Note that when implementing this your clients
+ * must be able to deal with such files, either directly with
+ * {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor}, or by using the higher-level
+ * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+ * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+ * methods.
+ *
+ * <p><em>Note: if you are implementing this to return a full file, you
+ * should create the AssetFileDescriptor with
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
+ * applications that can not handle sub-sections of files.</em></p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
+ *
+ * @return Returns a new AssetFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the file.
+ *
+ * @see #openFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ */
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ ParcelFileDescriptor fd = openFile(uri, mode);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
/**
* Convenience for subclasses that wish to implement {@link #openFile}
* by looking up a column named "_data" at the given URI.
*
* @param uri The URI to be opened.
- * @param mode The file mode.
+ * @param mode The file mode. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
*
* @return Returns a new ParcelFileDescriptor that can be used by the
* client to access the file.
@@ -489,16 +545,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
throw new FileNotFoundException("Column _data not found.");
}
- int modeBits;
- if ("r".equals(mode)) {
- modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
- } else if ("rw".equals(mode)) {
- modeBits = ParcelFileDescriptor.MODE_READ_WRITE
- | ParcelFileDescriptor.MODE_CREATE;
- } else {
- throw new FileNotFoundException("Bad mode for " + uri + ": "
- + mode);
- }
+ int modeBits = ContentResolver.modeToMode(uri, mode);
return ParcelFileDescriptor.open(new File(path), modeBits);
}
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index ede2c9b..e5e3f74 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.res.AssetFileDescriptor;
import android.database.BulkCursorNative;
import android.database.BulkCursorToCursorAdaptor;
import android.database.Cursor;
@@ -187,6 +188,25 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
return true;
}
+ case OPEN_ASSET_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+
+ AssetFileDescriptor fd;
+ fd = openAssetFile(url, mode);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case GET_SYNC_ADAPTER_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
@@ -413,6 +433,29 @@ final class ContentProviderProxy implements IContentProvider
return fd;
}
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+
+ mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+ data.recycle();
+ reply.recycle();
+
+ return fd;
+ }
+
public ISyncAdapter getSyncAdapter() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 52f55b6..0d886ee 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -17,6 +17,7 @@
package android.content;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -28,6 +29,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.text.TextUtils;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -170,119 +172,100 @@ public abstract class ContentResolver {
* <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
* <li>file ({@link #SCHEME_FILE})</li>
* </ul>
- * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
- * <p>
- * A Uri object can be used to reference a resource in an APK file. The
- * Uri should be one of the following formats:
- * <ul>
- * <li><code>android.resource://package_name/id_number</code><br/>
- * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
- * For example <code>com.example.myapp</code><br/>
- * <code>id_number</code> is the int form of the ID.<br/>
- * The easiest way to construct this form is
- * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
- * </li>
- * <li><code>android.resource://package_name/type/name</code><br/>
- * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
- * For example <code>com.example.myapp</code><br/>
- * <code>type</code> is the string form of the resource type. For example, <code>raw</code>
- * or <code>drawable</code>.
- * <code>name</code> is the string form of the resource name. That is, whatever the file
- * name was in your res directory, without the type extension.
- * The easiest way to construct this form is
- * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
- * </li>
- * </ul>
- * @param uri The desired "content:" URI.
+ *
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
+ * @param uri The desired URI.
* @return InputStream
* @throws FileNotFoundException if the provided URI could not be opened.
+ * @see #openAssetFileDescriptor(Uri, String)
*/
public final InputStream openInputStream(Uri uri)
throws FileNotFoundException {
String scheme = uri.getScheme();
- if (SCHEME_CONTENT.equals(scheme)) {
- ParcelFileDescriptor fd = openFileDescriptor(uri, "r");
- return fd != null ? new ParcelFileDescriptor.AutoCloseInputStream(fd) : null;
- } else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- String authority = uri.getAuthority();
- Resources r;
- if (TextUtils.isEmpty(authority)) {
- throw new FileNotFoundException("No authority: " + uri);
- } else {
- try {
- r = mContext.getPackageManager().getResourcesForApplication(authority);
- } catch (NameNotFoundException ex) {
- throw new FileNotFoundException("No package found for authority: " + uri);
- }
- }
- List<String> path = uri.getPathSegments();
- if (path == null) {
- throw new FileNotFoundException("No path: " + uri);
- }
- int len = path.size();
- int id;
- if (len == 1) {
- try {
- id = Integer.parseInt(path.get(0));
- } catch (NumberFormatException e) {
- throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
- }
- } else if (len == 2) {
- id = r.getIdentifier(path.get(1), path.get(0), authority);
- } else {
- throw new FileNotFoundException("More than two path segments: " + uri);
- }
- if (id == 0) {
- throw new FileNotFoundException("No resource found for: " + uri);
- }
+ if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ // Note: left here to avoid breaking compatibility. May be removed
+ // with sufficient testing.
+ OpenResourceIdResult r = getResourceId(uri);
try {
- InputStream stream = r.openRawResource(id);
+ InputStream stream = r.r.openRawResource(r.id);
return stream;
} catch (Resources.NotFoundException ex) {
- throw new FileNotFoundException("Resource ID does not exist: " + uri);
+ throw new FileNotFoundException("Resource does not exist: " + uri);
}
} else if (SCHEME_FILE.equals(scheme)) {
+ // Note: left here to avoid breaking compatibility. May be removed
+ // with sufficient testing.
return new FileInputStream(uri.getPath());
} else {
- throw new FileNotFoundException("Unknown scheme: " + uri);
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r");
+ try {
+ return fd != null ? fd.createInputStream() : null;
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
+ }
}
}
/**
+ * Synonym for {@link #openOutputStream(Uri, String)
+ * openOutputStream(uri, "w")}.
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ */
+ public final OutputStream openOutputStream(Uri uri)
+ throws FileNotFoundException {
+ return openOutputStream(uri, "w");
+ }
+
+ /**
* Open a stream on to the content associated with a content URI. If there
* is no data associated with the URI, FileNotFoundException is thrown.
*
* <h5>Accepts the following URI schemes:</h5>
* <ul>
* <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
* </ul>
*
- * @param uri The desired "content:" URI.
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
+ * @param uri The desired URI.
+ * @param mode May be "w", "wa", "rw", or "rwt".
* @return OutputStream
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ * @see #openAssetFileDescriptor(Uri, String)
*/
- public final OutputStream openOutputStream(Uri uri)
+ public final OutputStream openOutputStream(Uri uri, String mode)
throws FileNotFoundException {
- String scheme = uri.getScheme();
- if (SCHEME_CONTENT.equals(scheme)) {
- ParcelFileDescriptor fd = openFileDescriptor(uri, "rw");
- return fd != null
- ? new ParcelFileDescriptor.AutoCloseOutputStream(fd) : null;
- } else {
- throw new FileNotFoundException("Unknown scheme: " + uri);
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode);
+ try {
+ return fd != null ? fd.createOutputStream() : null;
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
}
}
/**
* Open a raw file descriptor to access data under a "content:" URI. This
- * interacts with the underlying {@link ContentProvider#openFile}
- * ContentProvider.openFile()} method of the provider associated with the
- * given URI, to retrieve any file stored there.
+ * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
+ * underlying {@link ContentProvider#openFile}
+ * ContentProvider.openFile()} method, so will <em>not</em> work with
+ * providers that return sub-sections of files. If at all possible,
+ * you should use {@link #openAssetFileDescriptor(Uri, String)}. You
+ * will receive a FileNotFoundException exception if the provider returns a
+ * sub-section of a file.
*
* <h5>Accepts the following URI schemes:</h5>
* <ul>
* <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
* </ul>
*
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
* @param uri The desired URI to open.
* @param mode The file mode to use, as per {@link ContentProvider#openFile
* ContentProvider.openFile}.
@@ -290,32 +273,189 @@ public abstract class ContentResolver {
* own this descriptor and are responsible for closing it when done.
* @throws FileNotFoundException Throws FileNotFoundException of no
* file exists under the URI or the mode is invalid.
+ * @see #openAssetFileDescriptor(Uri, String)
*/
public final ParcelFileDescriptor openFileDescriptor(Uri uri,
String mode) throws FileNotFoundException {
- IContentProvider provider = acquireProvider(uri);
- if (provider == null) {
- throw new FileNotFoundException("No content provider: " + uri);
+ AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode);
+ if (afd == null) {
+ return null;
+ }
+
+ if (afd.getDeclaredLength() < 0) {
+ // This is a full file!
+ return afd.getParcelFileDescriptor();
}
+
+ // Client can't handle a sub-section of a file, so close what
+ // we got and bail with an exception.
try {
- ParcelFileDescriptor fd = provider.openFile(uri, mode);
- if(fd == null) {
+ afd.close();
+ } catch (IOException e) {
+ }
+
+ throw new FileNotFoundException("Not a whole file");
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a "content:" URI. This
+ * interacts with the underlying {@link ContentProvider#openAssetFile}
+ * ContentProvider.openAssetFile()} method of the provider associated with the
+ * given URI, to retrieve any file stored there.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
+ * <p>
+ * A Uri object can be used to reference a resource in an APK file. The
+ * Uri should be one of the following formats:
+ * <ul>
+ * <li><code>android.resource://package_name/id_number</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>id_number</code> is the int form of the ID.<br/>
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
+ * </li>
+ * <li><code>android.resource://package_name/type/name</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>type</code> is the string form of the resource type. For example, <code>raw</code>
+ * or <code>drawable</code>.
+ * <code>name</code> is the string form of the resource name. That is, whatever the file
+ * name was in your res directory, without the type extension.
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
+ * </li>
+ * </ul>
+ *
+ * @param uri The desired URI to open.
+ * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
+ * ContentProvider.openAssetFile}.
+ * @return Returns a new ParcelFileDescriptor pointing to the file. You
+ * own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * file exists under the URI or the mode is invalid.
+ */
+ public final AssetFileDescriptor openAssetFileDescriptor(Uri uri,
+ String mode) throws FileNotFoundException {
+ String scheme = uri.getScheme();
+ if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ if (!"r".equals(mode)) {
+ throw new FileNotFoundException("Can't write resources: " + uri);
+ }
+ OpenResourceIdResult r = getResourceId(uri);
+ try {
+ return r.r.openRawResourceFd(r.id);
+ } catch (Resources.NotFoundException ex) {
+ throw new FileNotFoundException("Resource does not exist: " + uri);
+ }
+ } else if (SCHEME_FILE.equals(scheme)) {
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+ new File(uri.getPath()), modeToMode(uri, mode));
+ return new AssetFileDescriptor(pfd, 0, -1);
+ } else {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ try {
+ AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+ if(fd == null) {
+ releaseProvider(provider);
+ return null;
+ }
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), provider);
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+ } catch (RemoteException e) {
releaseProvider(provider);
- return null;
+ throw new FileNotFoundException("Dead content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ releaseProvider(provider);
+ throw e;
+ } catch (RuntimeException e) {
+ releaseProvider(provider);
+ throw e;
}
- return new ParcelFileDescriptorInner(fd, provider);
- } catch (RemoteException e) {
- releaseProvider(provider);
- throw new FileNotFoundException("Dead content provider: " + uri);
- } catch (FileNotFoundException e) {
- releaseProvider(provider);
- throw e;
- } catch (RuntimeException e) {
- releaseProvider(provider);
- throw e;
}
}
+ class OpenResourceIdResult {
+ Resources r;
+ int id;
+ }
+
+ OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException {
+ String authority = uri.getAuthority();
+ Resources r;
+ if (TextUtils.isEmpty(authority)) {
+ throw new FileNotFoundException("No authority: " + uri);
+ } else {
+ try {
+ r = mContext.getPackageManager().getResourcesForApplication(authority);
+ } catch (NameNotFoundException ex) {
+ throw new FileNotFoundException("No package found for authority: " + uri);
+ }
+ }
+ List<String> path = uri.getPathSegments();
+ if (path == null) {
+ throw new FileNotFoundException("No path: " + uri);
+ }
+ int len = path.size();
+ int id;
+ if (len == 1) {
+ try {
+ id = Integer.parseInt(path.get(0));
+ } catch (NumberFormatException e) {
+ throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
+ }
+ } else if (len == 2) {
+ id = r.getIdentifier(path.get(1), path.get(0), authority);
+ } else {
+ throw new FileNotFoundException("More than two path segments: " + uri);
+ }
+ if (id == 0) {
+ throw new FileNotFoundException("No resource found for: " + uri);
+ }
+ OpenResourceIdResult res = new OpenResourceIdResult();
+ res.r = r;
+ res.id = id;
+ return res;
+ }
+
+ /** @hide */
+ static public int modeToMode(Uri uri, String mode) throws FileNotFoundException {
+ int modeBits;
+ if ("r".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+ } else if ("w".equals(mode) || "wt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else if ("wa".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_APPEND;
+ } else if ("rw".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE;
+ } else if ("rwt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else {
+ throw new FileNotFoundException("Bad mode for " + uri + ": "
+ + mode);
+ }
+ return modeBits;
+ }
+
/**
* Inserts a row into a table at the given URL.
*
diff --git a/core/java/android/content/ContentServiceNative.java b/core/java/android/content/ContentServiceNative.java
index f050501..364f9ee 100644
--- a/core/java/android/content/ContentServiceNative.java
+++ b/core/java/android/content/ContentServiceNative.java
@@ -75,6 +75,13 @@ abstract class ContentServiceNative extends Binder implements IContentService
{
try {
switch (code) {
+ case 5038: {
+ data.readString(); // ignore the interface token that service generated
+ Uri uri = Uri.parse(data.readString());
+ notifyChange(uri, null, false, false);
+ return true;
+ }
+
case REGISTER_CONTENT_OBSERVER_TRANSACTION: {
Uri uri = Uri.CREATOR.createFromParcel(data);
boolean notifyForDescendents = data.readInt() != 0;
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index a6ef46f..0606956 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.IBulkCursor;
@@ -52,6 +53,8 @@ public interface IContentProvider extends IInterface {
String[] selectionArgs) throws RemoteException;
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException;
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException;
public ISyncAdapter getSyncAdapter() throws RemoteException;
/* IPC constants */
@@ -65,4 +68,5 @@ public interface IContentProvider extends IInterface {
static final int GET_SYNC_ADAPTER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12;
static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
+ static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c1c3b49..e1c1f64 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -522,6 +522,7 @@ import java.util.Set;
* <li> {@link #CATEGORY_ALTERNATIVE}
* <li> {@link #CATEGORY_SELECTED_ALTERNATIVE}
* <li> {@link #CATEGORY_LAUNCHER}
+ * <li> {@link #CATEGORY_INFO}
* <li> {@link #CATEGORY_HOME}
* <li> {@link #CATEGORY_PREFERENCE}
* <li> {@link #CATEGORY_GADGET}
@@ -1546,6 +1547,13 @@ public class Intent implements Parcelable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER";
/**
+ * Provides information about the package it is in; typically used if
+ * a package does not contain a {@link #CATEGORY_LAUNCHER} to provide
+ * a front-door to the user without having to be shown in the all apps list.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_INFO = "android.intent.category.INFO";
+ /**
* This is the home activity, that is the first activity that is displayed
* when the device boots.
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 698f27f..7287d9c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -480,6 +480,26 @@ public abstract class PackageManager {
throws NameNotFoundException;
/**
+ * Return a "good" intent to launch a front-door activity in a package,
+ * for use for example to implement an "open" button when browsing through
+ * packages. The current implementation will look first for a main
+ * activity in the category {@link Intent#CATEGORY_INFO}, next for a
+ * main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
+ * null if neither are found.
+ *
+ * <p>Throws {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @param packageName The name of the package to inspect.
+ *
+ * @return Returns either a fully-qualified Intent that can be used to
+ * launch the main activity in the package, or null if the package does
+ * not contain such an activity.
+ */
+ public abstract Intent getLaunchIntentForPackage(String packageName)
+ throws NameNotFoundException;
+
+ /**
* Return an array of all of the secondary group-ids that have been
* assigned to a package.
*
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 4a073f7..231e3e2 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,9 +16,13 @@
package android.content.res;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
/**
@@ -26,16 +30,32 @@ import java.io.IOException;
* opened FileDescriptor that can be used to read the data, as well as the
* offset and length of that entry's data in the file.
*/
-public class AssetFileDescriptor {
+public class AssetFileDescriptor implements Parcelable {
+ /**
+ * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
+ * and {@link #getDeclaredLength} when a length has not been declared. This means
+ * the data extends to the end of the file.
+ */
+ public static final long UNKNOWN_LENGTH = -1;
+
private final ParcelFileDescriptor mFd;
private final long mStartOffset;
private final long mLength;
/**
* Create a new AssetFileDescriptor from the given values.
+ * @param fd The underlying file descriptor.
+ * @param startOffset The location within the file that the asset starts.
+ * This must be 0 if length is UNKNOWN_LENGTH.
+ * @param length The number of bytes of the asset, or
+ * {@link #UNKNOWN_LENGTH if it extends to the end of the file.
*/
public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
long length) {
+ if (length < 0 && startOffset != 0) {
+ throw new IllegalArgumentException(
+ "startOffset must be 0 when using UNKNOWN_LENGTH");
+ }
mFd = fd;
mStartOffset = startOffset;
mLength = length;
@@ -66,9 +86,33 @@ public class AssetFileDescriptor {
}
/**
- * Returns the total number of bytes of this asset entry's data.
+ * Returns the total number of bytes of this asset entry's data. May be
+ * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
+ * If the AssetFileDescriptor was constructed with {@link #UNKNOWN_LENGTH},
+ * this will use {@link ParcelFileDescriptor#getStatSize()
+ * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
+ * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
+ * not be determined.
+ *
+ * @see #getDeclaredLength()
*/
public long getLength() {
+ if (mLength >= 0) {
+ return mLength;
+ }
+ long len = mFd.getStatSize();
+ return len >= 0 ? len : UNKNOWN_LENGTH;
+ }
+
+ /**
+ * Return the actual number of bytes that were declared when the
+ * AssetFileDescriptor was constructed. Will be
+ * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
+ * should be read to the end of the file.
+ *
+ * @see #getDeclaredLength()
+ */
+ public long getDeclaredLength() {
return mLength;
}
@@ -78,4 +122,227 @@ public class AssetFileDescriptor {
public void close() throws IOException {
mFd.close();
}
+
+ /**
+ * Create and return a new auto-close input stream for this asset. This
+ * will either return a full asset {@link AutoCloseInputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
+ * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileInputStream createInputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
+ }
+ return new AutoCloseInputStream(this);
+ }
+
+ /**
+ * Create and return a new auto-close output stream for this asset. This
+ * will either return a full asset {@link AutoCloseOutputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
+ * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileOutputStream createOutputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
+ }
+ return new AutoCloseOutputStream(this);
+ }
+
+ @Override
+ public String toString() {
+ return "{AssetFileDescriptor: " + mFd
+ + " start=" + mStartOffset + " len=" + mLength + "}";
+ }
+
+ /**
+ * An InputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescritor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseInputStream
+ extends ParcelFileDescriptor.AutoCloseInputStream {
+ private long mRemaining;
+
+ public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ super.skip(fd.getStartOffset());
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mRemaining >= 0
+ ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
+ : super.available();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int res = super.read();
+ if (res >= 0) mRemaining--;
+ return res;
+ }
+
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, offset, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer, offset, count);
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, 0, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer);
+ }
+
+ @Override
+ public long skip(long count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = mRemaining;
+ long res = super.skip(count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ // TODO Auto-generated method stub
+ return super.skip(count);
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.mark(readlimit);
+ }
+
+ @Override
+ public boolean markSupported() {
+ if (mRemaining >= 0) {
+ return false;
+ }
+ return super.markSupported();
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.reset();
+ }
+ }
+
+ /**
+ * An OutputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescritor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseOutputStream
+ extends ParcelFileDescriptor.AutoCloseOutputStream {
+ private long mRemaining;
+
+ public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
+ throw new IOException("Unable to seek");
+ }
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer, offset, count);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer, offset, count);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ super.write(oneByte);
+ mRemaining--;
+ return;
+ }
+
+ super.write(oneByte);
+ }
+ }
+
+
+ /* Parcelable interface */
+ public int describeContents() {
+ return mFd.describeContents();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ mFd.writeToParcel(out, flags);
+ out.writeLong(mStartOffset);
+ out.writeLong(mLength);
+ }
+
+ AssetFileDescriptor(Parcel src) {
+ mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
+ mStartOffset = src.readLong();
+ mLength = src.readLong();
+ }
+
+ public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
+ = new Parcelable.Creator<AssetFileDescriptor>() {
+ public AssetFileDescriptor createFromParcel(Parcel in) {
+ return new AssetFileDescriptor(in);
+ }
+ public AssetFileDescriptor[] newArray(int size) {
+ return new AssetFileDescriptor[size];
+ }
+ };
}
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 17cb687..0f3f270 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,8 +16,6 @@
package android.content.res;
-import com.google.android.collect.Lists;
-
import com.android.internal.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -113,7 +111,8 @@ public class ColorStateList implements Parcelable {
* Create a ColorStateList from an XML document, given a set of {@link Resources}.
*/
public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
- throws XmlPullParserException, IOException {
+ throws XmlPullParserException, IOException {
+
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -125,19 +124,16 @@ public class ColorStateList implements Parcelable {
throw new XmlPullParserException("No start tag found");
}
- final ColorStateList colorStateList = createFromXmlInner(r, parser, attrs);
-
- return colorStateList;
+ return createFromXmlInner(r, parser, attrs);
}
/* Create from inside an XML document. Called on a parser positioned at
* a tag in an XML document, tries to create a ColorStateList from that tag.
* Returns null if the tag is not a valid ColorStateList.
*/
- private static ColorStateList createFromXmlInner(Resources r,
- XmlPullParser parser,
- AttributeSet attrs)
- throws XmlPullParserException, IOException {
+ private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
+ AttributeSet attrs) throws XmlPullParserException, IOException {
+
ColorStateList colorStateList;
final String name = parser.getName();
@@ -146,8 +142,7 @@ public class ColorStateList implements Parcelable {
colorStateList = new ColorStateList();
} else {
throw new XmlPullParserException(
- parser.getPositionDescription() + ": invalid drawable tag "
- + name);
+ parser.getPositionDescription() + ": invalid drawable tag " + name);
}
colorStateList.inflate(r, parser, attrs);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 5a0daea..1a963f6 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -46,6 +46,7 @@ public class Resources {
static final String TAG = "Resources";
private static final boolean DEBUG_LOAD = false;
private static final boolean DEBUG_CONFIG = false;
+ private static final boolean TRACE_FOR_PRELOAD = false;
private static final int sSdkVersion = SystemProperties.getInt(
"ro.build.version.sdk", 0);
@@ -57,6 +58,8 @@ public class Resources {
// single-threaded, and after that these are immutable.
private static final SparseArray<Drawable.ConstantState> mPreloadedDrawables
= new SparseArray<Drawable.ConstantState>();
+ private static final SparseArray<ColorStateList> mPreloadedColorStateLists
+ = new SparseArray<ColorStateList>();
private static boolean mPreloaded;
/*package*/ final TypedValue mTmpValue = new TypedValue();
@@ -78,7 +81,7 @@ public class Resources {
private final Configuration mConfiguration = new Configuration();
/*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
PluralRules mPluralRule;
-
+
/**
* This exception is thrown by the resource APIs when a requested resource
* can not be found.
@@ -90,7 +93,7 @@ public class Resources {
public NotFoundException(String name) {
super(name);
}
- };
+ }
/**
* Create a new Resources object on top of an existing set of assets in an
@@ -1229,7 +1232,9 @@ public class Resources {
width = mMetrics.widthPixels;
height = mMetrics.heightPixels;
} else {
+ //noinspection SuspiciousNameCombination
width = mMetrics.heightPixels;
+ //noinspection SuspiciousNameCombination
height = mMetrics.widthPixels;
}
int keyboardHidden = mConfiguration.keyboardHidden;
@@ -1342,6 +1347,7 @@ public class Resources {
try {
return Integer.parseInt(name);
} catch (Exception e) {
+ // Ignore
}
return mAssets.getResourceIdentifier(name, defType, defPackage);
}
@@ -1575,21 +1581,18 @@ public class Resources {
/*package*/ Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
- && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- // Should we be caching these? If we use constant colors much
- // at all, most likely...
- //System.out.println("Creating drawable for color: #" +
- // Integer.toHexString(value.data));
- Drawable dr = new ColorDrawable(value.data);
- dr.setChangingConfigurations(value.changingConfigurations);
- return dr;
+
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) android.util.Log.d("PreloadDrawable", name);
+ }
}
- final int key = (value.assetCookie<<24)|value.data;
+ final int key = (value.assetCookie << 24) | value.data;
Drawable dr = getCachedDrawable(key);
- //System.out.println("Cached drawable @ #" +
- // Integer.toHexString(key.intValue()) + ": " + dr);
+
if (dr != null) {
return dr;
}
@@ -1597,46 +1600,52 @@ public class Resources {
Drawable.ConstantState cs = mPreloadedDrawables.get(key);
if (cs != null) {
dr = cs.newDrawable();
-
} else {
- if (value.string == null) {
- throw new NotFoundException(
- "Resource is not a Drawable (color or path): " + value);
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ dr = new ColorDrawable(value.data);
}
-
- String file = value.string.toString();
-
- if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
- + value.assetCookie + ": " + file);
-
- if (file.endsWith(".xml")) {
- try {
- XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXml(this, rp);
- rp.close();
- } catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+
+ if (dr == null) {
+ if (value.string == null) {
+ throw new NotFoundException(
+ "Resource is not a Drawable (color or path): " + value);
}
-
- } else {
- try {
- InputStream is = mAssets.openNonAsset(
- value.assetCookie, file, AssetManager.ACCESS_BUFFER);
- // System.out.println("Opened file " + file + ": " + is);
- dr = Drawable.createFromResourceStream(this, value, is, file);
- is.close();
- // System.out.println("Created stream: " + dr);
- } catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+
+ String file = value.string.toString();
+
+ if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
+ + value.assetCookie + ": " + file);
+
+ if (file.endsWith(".xml")) {
+ try {
+ XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXml(this, rp);
+ rp.close();
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+
+ } else {
+ try {
+ InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_BUFFER);
+ // System.out.println("Opened file " + file + ": " + is);
+ dr = Drawable.createFromResourceStream(this, value, is, file);
+ is.close();
+ // System.out.println("Created stream: " + dr);
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
}
}
}
@@ -1647,13 +1656,13 @@ public class Resources {
if (cs != null) {
if (mPreloading) {
mPreloadedDrawables.put(key, cs);
- }
- synchronized (mTmpValue) {
- //Log.i(TAG, "Saving cached drawable @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + cs);
- mDrawableCache.put(
- key, new WeakReference<Drawable.ConstantState>(cs));
+ } else {
+ synchronized (mTmpValue) {
+ //Log.i(TAG, "Saving cached drawable @ #" +
+ // Integer.toHexString(key.intValue())
+ // + " in " + this + ": " + cs);
+ mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
+ }
}
}
}
@@ -1661,7 +1670,7 @@ public class Resources {
return dr;
}
- private final Drawable getCachedDrawable(int key) {
+ private Drawable getCachedDrawable(int key) {
synchronized (mTmpValue) {
WeakReference<Drawable.ConstantState> wr = mDrawableCache.get(key);
if (wr != null) { // we have the key
@@ -1682,13 +1691,40 @@ public class Resources {
/*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
throws NotFoundException {
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
- && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- return ColorStateList.valueOf(value.data);
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) android.util.Log.d("PreloadColorStateList", name);
+ }
+ }
+
+ final int key = (value.assetCookie << 24) | value.data;
+
+ ColorStateList csl;
+
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+
+ csl = mPreloadedColorStateLists.get(key);
+ if (csl != null) {
+ return csl;
+ }
+
+ csl = ColorStateList.valueOf(value.data);
+ if (mPreloading) {
+ mPreloadedColorStateLists.put(key, csl);
+ }
+
+ return csl;
}
- final int key = (value.assetCookie<<24)|value.data;
- ColorStateList csl = getCachedColorStateList(key);
+ csl = getCachedColorStateList(key);
+ if (csl != null) {
+ return csl;
+ }
+
+ csl = mPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -1720,12 +1756,16 @@ public class Resources {
}
if (csl != null) {
- synchronized (mTmpValue) {
- //Log.i(TAG, "Saving cached color state list @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + csl);
- mColorStateListCache.put(
- key, new WeakReference<ColorStateList>(csl));
+ if (mPreloading) {
+ mPreloadedColorStateLists.put(key, csl);
+ } else {
+ synchronized (mTmpValue) {
+ //Log.i(TAG, "Saving cached color state list @ #" +
+ // Integer.toHexString(key.intValue())
+ // + " in " + this + ": " + csl);
+ mColorStateListCache.put(
+ key, new WeakReference<ColorStateList>(csl));
+ }
}
}
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 3df7708..e684cb8 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -141,6 +141,8 @@ final class StringBlock {
int type = style[i];
if (localLOGV) Log.v(TAG, "Applying style span id=" + type
+ ", start=" + style[i+1] + ", end=" + style[i+2]);
+
+
if (type == ids.boldId) {
buffer.setSpan(new StyleSpan(Typeface.BOLD),
style[i+1], style[i+2]+1,
@@ -178,9 +180,8 @@ final class StringBlock {
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.listItemId) {
- buffer.setSpan(new BulletSpan(10),
- style[i+1], style[i+2]+1,
- Spannable.SPAN_PARAGRAPH);
+ addParagraphSpan(buffer, new BulletSpan(10),
+ style[i+1], style[i+2]+1);
} else if (type == ids.marqueeId) {
buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
style[i+1], style[i+2]+1,
@@ -194,9 +195,8 @@ final class StringBlock {
sub = subtag(tag, ";height=");
if (sub != null) {
int size = Integer.parseInt(sub);
- buffer.setSpan(new Height(size),
- style[i+1], style[i+2]+1,
- Spannable.SPAN_PARAGRAPH);
+ addParagraphSpan(buffer, new Height(size),
+ style[i+1], style[i+2]+1);
}
sub = subtag(tag, ";size=");
@@ -231,6 +231,28 @@ final class StringBlock {
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ } else if (tag.startsWith("annotation;")) {
+ int len = tag.length();
+ int next;
+
+ for (int t = tag.indexOf(';'); t < len; t = next) {
+ int eq = tag.indexOf('=', t);
+ if (eq < 0) {
+ break;
+ }
+
+ next = tag.indexOf(';', eq);
+ if (next < 0) {
+ next = len;
+ }
+
+ String key = tag.substring(t + 1, eq);
+ String value = tag.substring(eq + 1, next);
+
+ buffer.setSpan(new Annotation(key, value),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
}
}
@@ -239,6 +261,34 @@ final class StringBlock {
return new SpannedString(buffer);
}
+ /**
+ * If a translator has messed up the edges of paragraph-level markup,
+ * fix it to actually cover the entire paragraph that it is attached to
+ * instead of just whatever range they put it on.
+ */
+ private static void addParagraphSpan(Spannable buffer, Object what,
+ int start, int end) {
+ int len = buffer.length();
+
+ if (start != 0 && start != len && buffer.charAt(start - 1) != '\n') {
+ for (start--; start > 0; start--) {
+ if (buffer.charAt(start - 1) == '\n') {
+ break;
+ }
+ }
+ }
+
+ if (end != 0 && end != len && buffer.charAt(end - 1) != '\n') {
+ for (end++; end < len; end++) {
+ if (buffer.charAt(end - 1) == '\n') {
+ break;
+ }
+ }
+ }
+
+ buffer.setSpan(what, start, end, Spannable.SPAN_PARAGRAPH);
+ }
+
private static String subtag(String full, String attribute) {
int start = full.indexOf(attribute);
if (start < 0) {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 82a57dd..3a32c03 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -438,6 +438,34 @@ public class TypedArray {
throw new RuntimeException(getPositionDescription()
+ ": You must supply a " + name + " attribute.");
}
+
+ /**
+ * Special version of {@link #getDimensionPixelSize} for retrieving
+ * {@link android.view.ViewGroup}'s layout_width and layout_height
+ * attributes. This is only here for performance reasons; applications
+ * should use {@link #getDimensionPixelSize}.
+ *
+ * @param index Index of the attribute to retrieve.
+ * @param defValue The default value to return if this attribute is not
+ * default or contains the wrong type of data.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ */
+ public int getLayoutDimension(int index, int defValue) {
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA];
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(
+ data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+ }
+
+ return defValue;
+ }
/**
* Retrieve a fractional unit attribute at <var>index</var>.
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 87bb277..2af080a 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -778,9 +778,9 @@ public class SQLiteDatabase extends SQLiteClosable {
}
/**
- * Returns the maximum size the database may grow to.
+ * Returns the current database page size, in bytes.
*
- * @return the new maximum database size
+ * @return the database page size, in bytes
*/
public long getPageSize() {
SQLiteStatement prog = null;
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index d169259..5889ad9 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -17,6 +17,7 @@
package android.database.sqlite;
import android.os.SystemClock;
+import android.util.Log;
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
@@ -26,6 +27,10 @@ import android.os.SystemClock;
*/
public class SQLiteStatement extends SQLiteProgram
{
+ private static final String TAG = "SQLiteStatement";
+
+ private final String mSql;
+
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
@@ -34,6 +39,11 @@ public class SQLiteStatement extends SQLiteProgram
*/
/* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
super(db, sql);
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ mSql = sql;
+ } else {
+ mSql = null;
+ }
}
/**
@@ -50,6 +60,9 @@ public class SQLiteStatement extends SQLiteProgram
acquireReference();
try {
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "execute() for [" + mSql + "]");
+ }
native_execute();
if (logStats) {
mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
@@ -77,6 +90,9 @@ public class SQLiteStatement extends SQLiteProgram
acquireReference();
try {
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "executeInsert() for [" + mSql + "]");
+ }
native_execute();
if (logStats) {
mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
@@ -103,6 +119,9 @@ public class SQLiteStatement extends SQLiteProgram
acquireReference();
try {
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]");
+ }
long retValue = native_1x1_long();
if (logStats) {
mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
@@ -129,6 +148,9 @@ public class SQLiteStatement extends SQLiteProgram
acquireReference();
try {
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "simpleQueryForString() for [" + mSql + "]");
+ }
String retValue = native_1x1_string();
if (logStats) {
mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
diff --git a/core/java/android/gadget/GadgetHost.java b/core/java/android/gadget/GadgetHost.java
index 31aed32..3d88b58 100644
--- a/core/java/android/gadget/GadgetHost.java
+++ b/core/java/android/gadget/GadgetHost.java
@@ -19,6 +19,7 @@ package android.gadget;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -62,7 +63,11 @@ public class GadgetHost {
}
}
- Handler mHandler = new Handler() {
+ class UpdateHandler extends Handler {
+ public UpdateHandler(Looper looper) {
+ super(looper);
+ }
+
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_UPDATE: {
@@ -75,7 +80,9 @@ public class GadgetHost {
}
}
}
- };
+ }
+
+ Handler mHandler;
int mHostId;
Callbacks mCallbacks = new Callbacks();
@@ -84,6 +91,7 @@ public class GadgetHost {
public GadgetHost(Context context, int hostId) {
mContext = context;
mHostId = hostId;
+ mHandler = new UpdateHandler(context.getMainLooper());
synchronized (sServiceLock) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.GADGET_SERVICE);
diff --git a/core/java/android/gadget/GadgetHostView.java b/core/java/android/gadget/GadgetHostView.java
index a985bd4..5cbd988 100644
--- a/core/java/android/gadget/GadgetHostView.java
+++ b/core/java/android/gadget/GadgetHostView.java
@@ -18,7 +18,13 @@ package android.gadget;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
import android.util.Config;
import android.util.Log;
import android.view.Gravity;
@@ -29,16 +35,23 @@ import android.view.animation.Animation;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
-import android.widget.ViewAnimator;
/**
* Provides the glue to show gadget views. This class offers automatic animation
* between updates, and will try recycling old views for each incoming
* {@link RemoteViews}.
*/
-public class GadgetHostView extends ViewAnimator implements Animation.AnimationListener {
+public class GadgetHostView extends FrameLayout {
static final String TAG = "GadgetHostView";
- static final boolean LOGD = Config.LOGD || true;
+ static final boolean LOGD = false;
+ static final boolean CROSSFADE = false;
+
+ static final int VIEW_MODE_NOINIT = 0;
+ static final int VIEW_MODE_CONTENT = 1;
+ static final int VIEW_MODE_ERROR = 2;
+ static final int VIEW_MODE_DEFAULT = 3;
+
+ static final int FADE_DURATION = 1000;
// When we're inflating the initialLayout for a gadget, we only allow
// views that are allowed in RemoteViews.
@@ -47,28 +60,17 @@ public class GadgetHostView extends ViewAnimator implements Animation.AnimationL
return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
}
};
-
- Context mLocalContext;
+
+ Context mContext;
int mGadgetId;
GadgetProviderInfo mInfo;
-
- View mActiveView = null;
- View mStaleView = null;
-
- int mActiveLayoutId = -1;
- int mStaleLayoutId = -1;
-
- /**
- * Last set of {@link RemoteViews} applied to {@link #mActiveView}
- */
- RemoteViews mActiveActions = null;
-
- /**
- * Flag indicating that {@link #mActiveActions} has been applied to
- * {@link #mStaleView}, meaning it's readyto recycle.
- */
- boolean mStalePrepared = false;
+ View mView;
+ int mViewMode = VIEW_MODE_NOINIT;
+ int mLayoutId = -1;
+ long mFadeStartTime = -1;
+ Bitmap mOld;
+ Paint mOldPaint = new Paint();
/**
* Create a host view. Uses default fade animations.
@@ -86,27 +88,13 @@ public class GadgetHostView extends ViewAnimator implements Animation.AnimationL
*/
public GadgetHostView(Context context, int animationIn, int animationOut) {
super(context);
- mLocalContext = context;
-
- // Prepare our default transition animations
- setAnimateFirstView(true);
- setInAnimation(context, animationIn);
- setOutAnimation(context, animationOut);
-
- // Watch for animation events to prepare recycling
- Animation inAnimation = getInAnimation();
- if (inAnimation != null) {
- inAnimation.setAnimationListener(this);
- }
+ mContext = context;
}
/**
* Set the gadget that will be displayed by this view.
*/
public void setGadget(int gadgetId, GadgetProviderInfo info) {
- if (mInfo != null) {
- // TODO: remove the old view, or whatever
- }
mGadgetId = gadgetId;
mInfo = info;
}
@@ -119,92 +107,141 @@ public class GadgetHostView extends ViewAnimator implements Animation.AnimationL
return mInfo;
}
- public void onAnimationEnd(Animation animation) {
- // When our transition animation finishes, we should try bringing our
- // newly-stale view up to the current view.
- if (mActiveActions != null &&
- mStaleLayoutId == mActiveActions.getLayoutId()) {
- if (LOGD) Log.d(TAG, "after animation, layoutId matched so we're recycling old view");
- mActiveActions.reapply(mLocalContext, mStaleView);
- mStalePrepared = true;
- }
- }
-
- public void onAnimationRepeat(Animation animation) {
- }
-
- public void onAnimationStart(Animation animation) {
- }
-
/**
* Process a set of {@link RemoteViews} coming in as an update from the
* gadget provider. Will animate into these new views as needed.
*/
public void updateGadget(RemoteViews remoteViews) {
- if (LOGD) Log.d(TAG, "updateGadget called");
+ if (LOGD) Log.d(TAG, "updateGadget called mOld=" + mOld);
boolean recycled = false;
- View newContent = null;
+ View content = null;
Exception exception = null;
- if (remoteViews == null) {
- newContent = getDefaultView();
+ // Capture the old view into a bitmap so we can do the crossfade.
+ if (CROSSFADE) {
+ if (mFadeStartTime < 0) {
+ if (mView != null) {
+ final int width = mView.getWidth();
+ final int height = mView.getHeight();
+ try {
+ mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ } catch (OutOfMemoryError e) {
+ // we just won't do the fade
+ mOld = null;
+ }
+ if (mOld != null) {
+ //mView.drawIntoBitmap(mOld);
+ }
+ }
+ }
}
- // If our stale view has been prepared to match active, and the new
- // layout matches, try recycling it
- if (newContent == null && mStalePrepared &&
- remoteViews.getLayoutId() == mStaleLayoutId) {
- try {
- remoteViews.reapply(mLocalContext, mStaleView);
- newContent = mStaleView;
- recycled = true;
- if (LOGD) Log.d(TAG, "was able to recycled existing layout");
- } catch (RuntimeException e) {
- exception = e;
+ if (remoteViews == null) {
+ if (mViewMode == VIEW_MODE_DEFAULT) {
+ // We've already done this -- nothing to do.
+ return;
+ }
+ content = getDefaultView();
+ mLayoutId = -1;
+ mViewMode = VIEW_MODE_DEFAULT;
+ } else {
+ int layoutId = remoteViews.getLayoutId();
+
+ // If our stale view has been prepared to match active, and the new
+ // layout matches, try recycling it
+ if (content == null && layoutId == mLayoutId) {
+ try {
+ remoteViews.reapply(mContext, mView);
+ content = mView;
+ recycled = true;
+ if (LOGD) Log.d(TAG, "was able to recycled existing layout");
+ } catch (RuntimeException e) {
+ exception = e;
+ }
+ }
+
+ // Try normal RemoteView inflation
+ if (content == null) {
+ try {
+ content = remoteViews.apply(mContext, this);
+ if (LOGD) Log.d(TAG, "had to inflate new layout");
+ } catch (RuntimeException e) {
+ exception = e;
+ }
}
+
+ mLayoutId = layoutId;
+ mViewMode = VIEW_MODE_CONTENT;
}
- // Try normal RemoteView inflation
- if (newContent == null) {
- try {
- newContent = remoteViews.apply(mLocalContext, this);
- if (LOGD) Log.d(TAG, "had to inflate new layout");
- } catch (RuntimeException e) {
- exception = e;
+ if (content == null) {
+ if (mViewMode == VIEW_MODE_ERROR) {
+ // We've already done this -- nothing to do.
+ return ;
}
+ Log.w(TAG, "updateGadget couldn't find any view, using error view", exception);
+ content = getErrorView();
+ mViewMode = VIEW_MODE_ERROR;
}
- if (exception != null && LOGD) {
- Log.w(TAG, "Error inflating gadget " + getGadgetInfo(), exception);
+ if (!recycled) {
+ prepareView(content);
+ addView(content);
}
-
- if (newContent == null) {
- // TODO: Should we throw an exception here for the host activity to catch?
- // Maybe we should show a generic error widget.
- if (LOGD) Log.d(TAG, "updateGadget couldn't find any view, so inflating error");
- newContent = getErrorView();
+
+ if (mView != content) {
+ removeView(mView);
+ mView = content;
}
-
- if (!recycled) {
- prepareView(newContent);
- addView(newContent);
+
+ if (CROSSFADE) {
+ if (mFadeStartTime < 0) {
+ // if there is already an animation in progress, don't do anything --
+ // the new view will pop in on top of the old one during the cross fade,
+ // and that looks okay.
+ mFadeStartTime = SystemClock.uptimeMillis();
+ invalidate();
+ }
}
-
- showNext();
-
- if (!recycled) {
- removeView(mStaleView);
+ }
+
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (CROSSFADE) {
+ int alpha;
+ int l = child.getLeft();
+ int t = child.getTop();
+ if (mFadeStartTime > 0) {
+ alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
+ if (alpha > 255) {
+ alpha = 255;
+ }
+ Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
+ + " w=" + child.getWidth());
+ if (alpha != 255 && mOld != null) {
+ mOldPaint.setAlpha(255-alpha);
+ //canvas.drawBitmap(mOld, l, t, mOldPaint);
+ }
+ } else {
+ alpha = 255;
+ }
+ int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ boolean rv = super.drawChild(canvas, child, drawingTime);
+ canvas.restoreToCount(restoreTo);
+ if (alpha < 255) {
+ invalidate();
+ } else {
+ mFadeStartTime = -1;
+ if (mOld != null) {
+ mOld.recycle();
+ mOld = null;
+ }
+ }
+ return rv;
+ } else {
+ return super.drawChild(canvas, child, drawingTime);
}
-
- mStalePrepared = false;
- mActiveActions = remoteViews;
-
- mStaleView = mActiveView;
- mActiveView = newContent;
-
- mStaleLayoutId = mActiveLayoutId;
- mActiveLayoutId = (remoteViews == null) ? -1 : remoteViews.getLayoutId();
}
/**
@@ -234,7 +271,7 @@ public class GadgetHostView extends ViewAnimator implements Animation.AnimationL
try {
if (mInfo != null) {
- Context theirContext = mLocalContext.createPackageContext(
+ Context theirContext = mContext.createPackageContext(
mInfo.provider.getPackageName(), 0 /* no flags */);
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -266,9 +303,9 @@ public class GadgetHostView extends ViewAnimator implements Animation.AnimationL
* Inflate and return a view that represents an error state.
*/
protected View getErrorView() {
- TextView tv = new TextView(mLocalContext);
- // TODO: move this error string and background color into resources
- tv.setText("Error inflating gadget");
+ TextView tv = new TextView(mContext);
+ tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
+ // TODO: get this color from somewhere.
tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
return tv;
}
diff --git a/core/java/android/gadget/GadgetManager.java b/core/java/android/gadget/GadgetManager.java
index a9a2c80..d2c4055 100644
--- a/core/java/android/gadget/GadgetManager.java
+++ b/core/java/android/gadget/GadgetManager.java
@@ -300,5 +300,21 @@ public class GadgetManager {
throw new RuntimeException("system server dead?", e);
}
}
+
+ /**
+ * Get the list of gadgetIds that have been bound to the given gadget
+ * provider.
+ *
+ * @param provider The {@link android.content.BroadcastReceiver} that is the
+ * gadget provider to find gadgetIds for.
+ */
+ public int[] getGadgetIds(ComponentName provider) {
+ try {
+ return sService.getGadgetIds(provider);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 40a5b47..106c920 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -48,7 +48,6 @@ public class Camera {
private static final int ERROR_CALLBACK = 5;
private int mNativeContext; // accessed by native methods
- private int mListenerContext;
private EventHandler mEventHandler;
private ShutterCallback mShutterCallback;
private PictureCallback mRawImageCallback;
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 52f8209..0295f69 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -98,6 +98,13 @@ public class ExtractEditText extends EditText {
}
/**
+ * Return true if the edit text is currently showing a scroll bar.
+ */
+ public boolean hasVerticalScrollBar() {
+ return computeVerticalScrollRange() > computeVerticalScrollExtent();
+ }
+
+ /**
* Pretend like the window this view is in always has focus, so its
* highlight and cursor will be displayed.
*/
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4be1fc7..1e2e2f3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -26,7 +26,9 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.SystemClock;
import android.provider.Settings;
+import android.text.InputType;
import android.text.Layout;
import android.text.Spannable;
import android.text.method.MovementMethod;
@@ -49,6 +51,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
import android.widget.FrameLayout;
import java.io.FileDescriptor;
@@ -241,6 +244,8 @@ public class InputMethodService extends AbstractInputMethodService {
boolean mIsFullscreen;
View mExtractView;
ExtractEditText mExtractEditText;
+ ViewGroup mExtractAccessories;
+ Button mExtractAction;
ExtractedText mExtractedText;
int mExtractedToken;
@@ -271,6 +276,21 @@ public class InputMethodService extends AbstractInputMethodService {
}
};
+ final View.OnClickListener mActionClickListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ final EditorInfo ei = getCurrentInputEditorInfo();
+ final InputConnection ic = getCurrentInputConnection();
+ if (ei != null && ic != null) {
+ if (ei.actionId != 0) {
+ ic.performEditorAction(ei.actionId);
+ } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION)
+ != EditorInfo.IME_ACTION_NONE) {
+ ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
+ }
+ }
+ }
+ };
+
/**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides
@@ -522,6 +542,8 @@ public class InputMethodService extends AbstractInputMethodService {
mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
mExtractView = null;
mExtractEditText = null;
+ mExtractAccessories = null;
+ mExtractAction = null;
mFullscreenApplied = false;
mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
@@ -703,7 +725,7 @@ public class InputMethodService extends AbstractInputMethodService {
setExtractView(v);
}
}
- startExtractingText();
+ startExtractingText(false);
}
}
@@ -907,9 +929,17 @@ public class InputMethodService extends AbstractInputMethodService {
mExtractEditText = (ExtractEditText)view.findViewById(
com.android.internal.R.id.inputExtractEditText);
mExtractEditText.setIME(this);
- startExtractingText();
+ mExtractAction = (Button)view.findViewById(
+ com.android.internal.R.id.inputExtractAction);
+ if (mExtractAction != null) {
+ mExtractAccessories = (ViewGroup)view.findViewById(
+ com.android.internal.R.id.inputExtractAccessories);
+ }
+ startExtractingText(false);
} else {
mExtractEditText = null;
+ mExtractAccessories = null;
+ mExtractAction = null;
}
}
@@ -1166,7 +1196,7 @@ public class InputMethodService extends AbstractInputMethodService {
}
if (doShowInput) {
- startExtractingText();
+ startExtractingText(false);
}
if (!wasVisible) {
@@ -1276,7 +1306,7 @@ public class InputMethodService extends AbstractInputMethodService {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
mInputViewStarted = true;
onStartInputView(mInputEditorInfo, restarting);
- startExtractingText();
+ startExtractingText(true);
} else if (mCandidatesVisibility == View.VISIBLE) {
if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
mCandidatesViewStarted = true;
@@ -1453,6 +1483,25 @@ public class InputMethodService extends AbstractInputMethodService {
static final int MOVEMENT_DOWN = -1;
static final int MOVEMENT_UP = -2;
+ void reportExtractedMovement(int keyCode, int count) {
+ int dx = 0, dy = 0;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ dx = -count;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ dx = count;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ dy = -count;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ dy = count;
+ break;
+ }
+ onExtractedCursorMovement(dx, dy);
+ }
+
boolean doMovementKey(int keyCode, KeyEvent event, int count) {
final ExtractEditText eet = mExtractEditText;
if (isFullscreenMode() && isInputViewShown() && eet != null) {
@@ -1467,6 +1516,7 @@ public class InputMethodService extends AbstractInputMethodService {
if (count == MOVEMENT_DOWN) {
if (movement.onKeyDown(eet,
(Spannable)eet.getText(), keyCode, event)) {
+ reportExtractedMovement(keyCode, 1);
return true;
}
} else if (count == MOVEMENT_UP) {
@@ -1475,7 +1525,9 @@ public class InputMethodService extends AbstractInputMethodService {
return true;
}
} else {
- if (!movement.onKeyOther(eet, (Spannable)eet.getText(), event)) {
+ if (movement.onKeyOther(eet, (Spannable)eet.getText(), event)) {
+ reportExtractedMovement(keyCode, count);
+ } else {
KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN);
if (movement.onKeyDown(eet,
(Spannable)eet.getText(), keyCode, down)) {
@@ -1488,6 +1540,7 @@ public class InputMethodService extends AbstractInputMethodService {
movement.onKeyUp(eet,
(Spannable)eet.getText(), keyCode, up);
}
+ reportExtractedMovement(keyCode, count);
}
}
}
@@ -1507,6 +1560,97 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * Send the given key event code (as defined by {@link KeyEvent}) to the
+ * current input connection is a key down + key up event pair. The sent
+ * events have {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+ * set, so that the recipient can identify them as coming from a software
+ * input method, and
+ * {@link KeyEvent#FLAG_KEEP_TOUCH_MODE KeyEvent.FLAG_KEEP_TOUCH_MODE}, so
+ * that they don't impact the current touch mode of the UI.
+ *
+ * @param keyEventCode The raw key code to send, as defined by
+ * {@link KeyEvent}.
+ */
+ public void sendDownUpKeyEvents(int keyEventCode) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ long eventTime = SystemClock.uptimeMillis();
+ ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ }
+
+ /**
+ * Ask the input target to execute its default action via
+ * {@link InputConnection#performEditorAction
+ * InputConnection.performEditorAction()}.
+ *
+ * @param fromEnterKey If true, this will be executed as if the user had
+ * pressed an enter key on the keyboard, that is it will <em>not</em>
+ * be done if the editor has set {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION
+ * EditorInfo.IME_FLAG_NO_ENTER_ACTION}. If false, the action will be
+ * sent regardless of how the editor has set that flag.
+ *
+ * @return Returns a boolean indicating whether an action has been sent.
+ * If false, either the editor did not specify a default action or it
+ * does not want an action from the enter key. If true, the action was
+ * sent (or there was no input connection at all).
+ */
+ public boolean sendDefaultEditorAction(boolean fromEnterKey) {
+ EditorInfo ei = getCurrentInputEditorInfo();
+ if (ei != null &&
+ (!fromEnterKey || (ei.imeOptions &
+ EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0) &&
+ (ei.imeOptions & EditorInfo.IME_MASK_ACTION) !=
+ EditorInfo.IME_ACTION_NONE) {
+ // If the enter key was pressed, and the editor has a default
+ // action associated with pressing enter, then send it that
+ // explicit action instead of the key event.
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Send the given UTF-16 character to the current input connection. Most
+ * characters will be delivered simply by calling
+ * {@link InputConnection#commitText InputConnection.commitText()} with
+ * the character; some, however, may be handled different. In particular,
+ * the enter character ('\n') will either be delivered as an action code
+ * or a raw key event, as appropriate.
+ *
+ * @param charCode The UTF-16 character code to send.
+ */
+ public void sendKeyChar(char charCode) {
+ switch (charCode) {
+ case '\n': // Apps may be listening to an enter key to perform an action
+ if (!sendDefaultEditorAction(true)) {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
+ }
+ break;
+ default:
+ // Make sure that digits go through any text watcher on the client side.
+ if (charCode >= '0' && charCode <= '9') {
+ sendDownUpKeyEvents(charCode - '0' + KeyEvent.KEYCODE_0);
+ } else {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.commitText(String.valueOf((char) charCode), 1);
+ }
+ }
+ break;
+ }
+ }
+
+ /**
* This is called when the user has moved the cursor in the extracted
* text view, when running in fullsreen mode. The default implementation
* performs the corresponding selection change on the underlying text
@@ -1522,11 +1666,36 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* This is called when the user has clicked on the extracted text view,
* when running in fullscreen mode. The default implementation hides
- * the candidates view when this happens. Re-implement this to provide
- * whatever behavior you want.
+ * the candidates view when this happens, but only if the extracted text
+ * editor has a vertical scroll bar because its text doesn't fit.
+ * Re-implement this to provide whatever behavior you want.
*/
public void onExtractedTextClicked() {
- setCandidatesViewShown(false);
+ if (mExtractEditText == null) {
+ return;
+ }
+ if (mExtractEditText.hasVerticalScrollBar()) {
+ setCandidatesViewShown(false);
+ }
+ }
+
+ /**
+ * This is called when the user has performed a cursor movement in the
+ * extracted text view, when it is running in fullscreen mode. The default
+ * implementation hides the candidates view when a vertical movement
+ * happens, but only if the extracted text editor has a vertical scroll bar
+ * because its text doesn't fit.
+ * Re-implement this to provide whatever behavior you want.
+ * @param dx The amount of cursor movement in the x dimension.
+ * @param dy The amount of cursor movement in the y dimension.
+ */
+ public void onExtractedCursorMovement(int dx, int dy) {
+ if (mExtractEditText == null || dy == 0) {
+ return;
+ }
+ if (mExtractEditText.hasVerticalScrollBar()) {
+ setCandidatesViewShown(false);
+ }
}
/**
@@ -1545,7 +1714,74 @@ public class InputMethodService extends AbstractInputMethodService {
return true;
}
- void startExtractingText() {
+ /**
+ * Return text that can be used as a button label for the given
+ * {@link EditorInfo#imeOptions EditorInfo.imeOptions}. Returns null
+ * if there is no action requested. Note that there is no guarantee that
+ * the returned text will be relatively short, so you probably do not
+ * want to use it as text on a soft keyboard key label.
+ *
+ * @param imeOptions The value from @link EditorInfo#imeOptions EditorInfo.imeOptions}.
+ *
+ * @return Returns a label to use, or null if there is no action.
+ */
+ public CharSequence getTextForImeAction(int imeOptions) {
+ switch (imeOptions&EditorInfo.IME_MASK_ACTION) {
+ case EditorInfo.IME_ACTION_NONE:
+ return null;
+ case EditorInfo.IME_ACTION_GO:
+ return getText(com.android.internal.R.string.ime_action_go);
+ case EditorInfo.IME_ACTION_SEARCH:
+ return getText(com.android.internal.R.string.ime_action_search);
+ case EditorInfo.IME_ACTION_SEND:
+ return getText(com.android.internal.R.string.ime_action_send);
+ case EditorInfo.IME_ACTION_NEXT:
+ return getText(com.android.internal.R.string.ime_action_next);
+ default:
+ return getText(com.android.internal.R.string.ime_action_default);
+ }
+ }
+
+ /**
+ * Called when it is time to update the actions available from a full-screen
+ * IME. You do not need to deal with this if you are using the standard
+ * full screen extract UI. If replacing it, you will need to re-implement
+ * this to put the action in your own UI and handle it.
+ */
+ public void onUpdateExtractingAccessories(EditorInfo ei) {
+ if (mExtractAccessories == null) {
+ return;
+ }
+ final boolean hasAction = ei.actionLabel != null || (
+ (ei.imeOptions&EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE &&
+ (ei.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0);
+ if (hasAction) {
+ mExtractAccessories.setVisibility(View.VISIBLE);
+ if (ei.actionLabel != null) {
+ mExtractAction.setText(ei.actionLabel);
+ } else {
+ mExtractAction.setText(getTextForImeAction(ei.imeOptions));
+ }
+ mExtractAction.setOnClickListener(mActionClickListener);
+ } else {
+ mExtractAccessories.setVisibility(View.GONE);
+ mExtractAction.setOnClickListener(null);
+ }
+ }
+
+ /**
+ * This is called when, while currently displayed in extract mode, the
+ * current input target changes. The default implementation will
+ * auto-hide the IME if the new target is not a full editor, since this
+ * can be an confusing experience for the user.
+ */
+ public void onExtractingInputChanged(EditorInfo ei) {
+ if (ei.inputType == InputType.TYPE_NULL) {
+ dismissSoftInput(InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+ }
+
+ void startExtractingText(boolean inputChanged) {
final ExtractEditText eet = mExtractEditText;
if (eet != null && getCurrentInputStarted()
&& isFullscreenMode()) {
@@ -1557,9 +1793,13 @@ public class InputMethodService extends AbstractInputMethodService {
req.hintMaxChars = 10000;
mExtractedText = getCurrentInputConnection().getExtractedText(req,
InputConnection.GET_EXTRACTED_TEXT_MONITOR);
+
+ final EditorInfo ei = getCurrentInputEditorInfo();
+
try {
eet.startInternalChanges();
- int inputType = getCurrentInputEditorInfo().inputType;
+ onUpdateExtractingAccessories(ei);
+ int inputType = ei.inputType;
if ((inputType&EditorInfo.TYPE_MASK_CLASS)
== EditorInfo.TYPE_CLASS_TEXT) {
if ((inputType&EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE) != 0) {
@@ -1567,7 +1807,7 @@ public class InputMethodService extends AbstractInputMethodService {
}
}
eet.setInputType(inputType);
- eet.setHint(mInputEditorInfo.hintText);
+ eet.setHint(ei.hintText);
if (mExtractedText != null) {
eet.setEnabled(true);
eet.setExtractedText(mExtractedText);
@@ -1578,6 +1818,10 @@ public class InputMethodService extends AbstractInputMethodService {
} finally {
eet.finishInternalChanges();
}
+
+ if (inputChanged) {
+ onExtractingInputChanged(ei);
+ }
}
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 886e688..c838779 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -74,7 +74,6 @@ public class KeyboardView extends View implements View.OnClickListener {
* For keys that repeat, this is only called once.
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
* key, the value will be zero.
- * @hide Pending API Council approval
*/
void onPress(int primaryCode);
@@ -82,7 +81,6 @@ public class KeyboardView extends View implements View.OnClickListener {
* Called when the user releases a key. This is sent after the {@link #onKey} is called.
* For keys that repeat, this is only called once.
* @param primaryCode the code of the key that was released
- * @hide Pending API Council approval
*/
void onRelease(int primaryCode);
@@ -99,6 +97,12 @@ public class KeyboardView extends View implements View.OnClickListener {
void onKey(int primaryCode, int[] keyCodes);
/**
+ * Sends a sequence of characters to the listener.
+ * @param text the sequence of characters to be displayed.
+ */
+ void onText(CharSequence text);
+
+ /**
* Called when the user quickly moves the finger from right to left.
*/
void swipeLeft();
@@ -394,6 +398,7 @@ public class KeyboardView extends View implements View.OnClickListener {
requestLayout();
invalidate();
computeProximityThreshold(keyboard);
+ mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
}
/**
@@ -699,9 +704,7 @@ public class KeyboardView extends View implements View.OnClickListener {
if (index != NOT_A_KEY && index < mKeys.length) {
final Key key = mKeys[index];
if (key.text != null) {
- for (int i = 0; i < key.text.length(); i++) {
- mKeyboardActionListener.onKey(key.text.charAt(i), key.codes);
- }
+ mKeyboardActionListener.onText(key.text);
mKeyboardActionListener.onRelease(NOT_A_KEY);
} else {
int code = key.codes[0];
@@ -792,7 +795,7 @@ public class KeyboardView extends View implements View.OnClickListener {
mPreviewText.setCompoundDrawables(null, null, null, null);
mPreviewText.setText(getPreviewText(key));
if (key.label.length() > 1 && key.codes.length < 2) {
- mPreviewText.setTextSize(mLabelTextSize);
+ mPreviewText.setTextSize(mKeyTextSize);
mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
} else {
mPreviewText.setTextSize(mPreviewTextSizeLarge);
@@ -896,6 +899,11 @@ public class KeyboardView extends View implements View.OnClickListener {
dismissPopupKeyboard();
}
+ public void onText(CharSequence text) {
+ mKeyboardActionListener.onText(text);
+ dismissPopupKeyboard();
+ }
+
public void swipeLeft() { }
public void swipeRight() { }
public void swipeUp() { }
@@ -1102,6 +1110,8 @@ public class KeyboardView extends View implements View.OnClickListener {
mHandler.removeMessages(MSG_SHOW_PREVIEW);
dismissPopupKeyboard();
+
+ mMiniKeyboardCache.clear();
}
@Override
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index f816caa..ccef97e 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -16,39 +16,46 @@
package android.net;
-import android.util.Log;
-import android.util.Config;
+import android.content.Context;
import android.net.http.DomainNameChecker;
import android.os.SystemProperties;
+import android.util.Config;
+import android.util.Log;
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
+import com.android.internal.net.SSLSessionCache;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
-import java.security.NoSuchAlgorithmException;
+import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
-import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * SSLSocketFactory that allows skipping the certificate chain validation
+ * based on system setting (socket.relaxsslcheck=yes, ro.secure=1 - for
+ * testing only).
+ *
+ * It also adds a readTimeout that will be set on each created socket.
+ * The factory will use SSL session persistence if enabled by config.
+ */
public class SSLCertificateSocketFactory extends SSLSocketFactory {
- private static final boolean DBG = true;
private static final String LOG_TAG = "SSLCertificateSocketFactory";
private static X509TrustManager sDefaultTrustManager;
- private final int socketReadTimeoutForSslHandshake;
-
static {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
@@ -83,14 +90,36 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
}
};
- private SSLSocketFactory factory;
+ private static SSLSocketFactory factory;
+
+ /**
+ * Initialize a single default factory to be used for all returned
+ * sockets.
+ *
+ * Because of the signature of getDefault(int timeout) it needs to create
+ * a new instance which encapsulates the timeout on each call. We want
+ * to share a single SSLContext and SSLSessionCache.
+ *
+ * Can be called multiple times - but only the first will initialize the factory.
+ *
+ * @param androidContext will be used for SSL session persistence. Null for backward
+ * compatibility, no SSL persistence.
+ * @hide
+ */
+ public static synchronized void setupDefaultFactory(Context androidContext) {
+ if ( factory != null) {
+ // Can only be initialized once, to avoid having multiple caches.
+ return;
+ }
+ factory = SSLSessionCache.getSocketFactory(androidContext, TRUST_MANAGER);
+ }
+
+ private final int socketReadTimeoutForSslHandshake;
public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake)
throws NoSuchAlgorithmException, KeyManagementException {
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, TRUST_MANAGER, new java.security.SecureRandom());
- factory = (SSLSocketFactory) context.getSocketFactory();
- this.socketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake;
+ this.socketReadTimeoutForSslHandshake
+ = socketReadTimeoutForSslHandshake;
}
/**
@@ -103,6 +132,11 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
*/
public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake) {
try {
+ if (factory == null) {
+ // The delegated factory was not initialized explicitely with a context.
+ // Use a default one.
+ setupDefaultFactory(null);
+ }
return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake);
} catch (NoSuchAlgorithmException e) {
Log.e(LOG_TAG,
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index 4fb1499..0c4fcda 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -47,6 +47,8 @@ import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
import java.io.IOException;
import java.io.InputStream;
@@ -55,6 +57,7 @@ import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.net.URI;
+import java.security.KeyManagementException;
import android.util.Log;
import android.content.ContentResolver;
@@ -98,10 +101,13 @@ public final class AndroidHttpClient implements HttpClient {
/**
* Create a new HttpClient with reasonable defaults (which you can update).
+ *
* @param userAgent to report in your HTTP requests.
+ * @param sessionCache persistent session cache
* @return AndroidHttpClient for you to use for all your requests.
*/
- public static AndroidHttpClient newInstance(String userAgent) {
+ public static AndroidHttpClient newInstance(String userAgent,
+ SSLClientSessionCache sessionCache) {
HttpParams params = new BasicHttpParams();
// Turn off stale checking. Our connections break all the time anyway,
@@ -123,7 +129,8 @@ public final class AndroidHttpClient implements HttpClient {
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https",
- SSLSocketFactory.getSocketFactory(), 443));
+ socketFactoryWithCache(sessionCache), 443));
+
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
@@ -132,6 +139,41 @@ public final class AndroidHttpClient implements HttpClient {
return new AndroidHttpClient(manager, params);
}
+ /**
+ * Returns a socket factory backed by the given persistent session cache.
+ *
+ * @param sessionCache to retrieve sessions from, null for no cache
+ */
+ private static SSLSocketFactory socketFactoryWithCache(
+ SSLClientSessionCache sessionCache) {
+ if (sessionCache == null) {
+ // Use the default factory which doesn't support persistent
+ // caching.
+ return SSLSocketFactory.getSocketFactory();
+ }
+
+ // Create a new SSL context backed by the cache.
+ // TODO: Keep a weak *identity* hash map of caches to engines. In the
+ // mean time, if we have two engines for the same cache, they'll still
+ // share sessions but will have to do so through the persistent cache.
+ SSLContextImpl sslContext = new SSLContextImpl();
+ try {
+ sslContext.engineInit(null, null, null, sessionCache, null);
+ } catch (KeyManagementException e) {
+ throw new AssertionError(e);
+ }
+ return new SSLSocketFactory(sslContext.engineGetSocketFactory());
+ }
+
+ /**
+ * Create a new HttpClient with reasonable defaults (which you can update).
+ * @param userAgent to report in your HTTP requests.
+ * @return AndroidHttpClient for you to use for all your requests.
+ */
+ public static AndroidHttpClient newInstance(String userAgent) {
+ return newInstance(userAgent, null /* session cache */);
+ }
+
private final HttpClient delegate;
private RuntimeException mLeakedException = new IllegalStateException(
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index b7f7368..0edbe5b 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -16,8 +16,6 @@
package android.net.http;
-import android.os.SystemClock;
-
import java.io.IOException;
import java.security.cert.Certificate;
@@ -28,23 +26,13 @@ import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Enumeration;
-
-import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
-import org.apache.http.HttpHost;
-
-import org.bouncycastle.asn1.x509.X509Name;
-
/**
* Class responsible for all server certificate validation functionality
*
@@ -52,9 +40,6 @@ import org.bouncycastle.asn1.x509.X509Name;
*/
class CertificateChainValidator {
- private static long sTotal = 0;
- private static long sTotalReused = 0;
-
/**
* The singleton instance of the certificate chain validator
*/
@@ -110,91 +95,42 @@ class CertificateChainValidator {
* @return An SSL error object if there is an error and null otherwise
*/
public SslError doHandshakeAndValidateServerCertificates(
- HttpsConnection connection, SSLSocket sslSocket, String domain)
- throws SSLHandshakeException, IOException {
-
- ++sTotal;
-
- SSLContext sslContext = HttpsConnection.getContext();
- if (sslContext == null) {
- closeSocketThrowException(sslSocket, "SSL context is null");
- }
-
+ HttpsConnection connection, SSLSocket sslSocket, String domain)
+ throws IOException {
X509Certificate[] serverCertificates = null;
- long sessionBeforeHandshakeLastAccessedTime = 0;
- byte[] sessionBeforeHandshakeId = null;
-
- SSLSession sessionAfterHandshake = null;
-
- synchronized(sslContext) {
- // get SSL session before the handshake
- SSLSession sessionBeforeHandshake =
- getSSLSession(sslContext, connection.getHost());
- if (sessionBeforeHandshake != null) {
- sessionBeforeHandshakeLastAccessedTime =
- sessionBeforeHandshake.getLastAccessedTime();
+ // start handshake, close the socket if we fail
+ try {
+ sslSocket.setUseClientMode(true);
+ sslSocket.startHandshake();
+ } catch (IOException e) {
+ closeSocketThrowException(
+ sslSocket, e.getMessage(),
+ "failed to perform SSL handshake");
+ }
- sessionBeforeHandshakeId =
- sessionBeforeHandshake.getId();
- }
+ // retrieve the chain of the server peer certificates
+ Certificate[] peerCertificates =
+ sslSocket.getSession().getPeerCertificates();
- // start handshake, close the socket if we fail
- try {
- sslSocket.setUseClientMode(true);
- sslSocket.startHandshake();
- } catch (IOException e) {
- closeSocketThrowException(
- sslSocket, e.getMessage(),
- "failed to perform SSL handshake");
+ if (peerCertificates == null || peerCertificates.length <= 0) {
+ closeSocketThrowException(
+ sslSocket, "failed to retrieve peer certificates");
+ } else {
+ serverCertificates =
+ new X509Certificate[peerCertificates.length];
+ for (int i = 0; i < peerCertificates.length; ++i) {
+ serverCertificates[i] =
+ (X509Certificate)(peerCertificates[i]);
}
- // retrieve the chain of the server peer certificates
- Certificate[] peerCertificates =
- sslSocket.getSession().getPeerCertificates();
-
- if (peerCertificates == null || peerCertificates.length <= 0) {
- closeSocketThrowException(
- sslSocket, "failed to retrieve peer certificates");
- } else {
- serverCertificates =
- new X509Certificate[peerCertificates.length];
- for (int i = 0; i < peerCertificates.length; ++i) {
- serverCertificates[i] =
- (X509Certificate)(peerCertificates[i]);
- }
-
- // update the SSL certificate associated with the connection
- if (connection != null) {
- if (serverCertificates[0] != null) {
- connection.setCertificate(
- new SslCertificate(serverCertificates[0]));
- }
+ // update the SSL certificate associated with the connection
+ if (connection != null) {
+ if (serverCertificates[0] != null) {
+ connection.setCertificate(
+ new SslCertificate(serverCertificates[0]));
}
}
-
- // get SSL session after the handshake
- sessionAfterHandshake =
- getSSLSession(sslContext, connection.getHost());
- }
-
- if (sessionBeforeHandshakeLastAccessedTime != 0 &&
- sessionAfterHandshake != null &&
- Arrays.equals(
- sessionBeforeHandshakeId, sessionAfterHandshake.getId()) &&
- sessionBeforeHandshakeLastAccessedTime <
- sessionAfterHandshake.getLastAccessedTime()) {
-
- if (HttpLog.LOGV) {
- HttpLog.v("SSL session was reused: total reused: "
- + sTotalReused
- + " out of total of: " + sTotal);
-
- ++sTotalReused;
- }
-
- // no errors!!!
- return null;
}
// check if the first certificate in the chain is for this site
@@ -216,7 +152,6 @@ class CertificateChainValidator {
}
}
- //
// first, we validate the chain using the standard validation
// solution; if we do not find any errors, we are done; if we
// fail the standard validation, we re-validate again below,
@@ -393,14 +328,14 @@ class CertificateChainValidator {
}
private void closeSocketThrowException(
- SSLSocket socket, String errorMessage, String defaultErrorMessage)
- throws SSLHandshakeException, IOException {
+ SSLSocket socket, String errorMessage, String defaultErrorMessage)
+ throws IOException {
closeSocketThrowException(
socket, errorMessage != null ? errorMessage : defaultErrorMessage);
}
- private void closeSocketThrowException(SSLSocket socket, String errorMessage)
- throws SSLHandshakeException, IOException {
+ private void closeSocketThrowException(SSLSocket socket,
+ String errorMessage) throws IOException {
if (HttpLog.LOGV) {
HttpLog.v("validation error: " + errorMessage);
}
@@ -416,29 +351,4 @@ class CertificateChainValidator {
throw new SSLHandshakeException(errorMessage);
}
-
- /**
- * @param sslContext The SSL context shared accross all the SSL sessions
- * @param host The host associated with the session
- * @return A suitable SSL session from the SSL context
- */
- private SSLSession getSSLSession(SSLContext sslContext, HttpHost host) {
- if (sslContext != null && host != null) {
- Enumeration en = sslContext.getClientSessionContext().getIds();
- while (en.hasMoreElements()) {
- byte[] id = (byte[]) en.nextElement();
- if (id != null) {
- SSLSession session =
- sslContext.getClientSessionContext().getSession(id);
- if (session.isValid() &&
- host.getHostName().equals(session.getPeerHost()) &&
- host.getPort() == session.getPeerPort()) {
- return session;
- }
- }
- }
- }
-
- return null;
- }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 6f9d6c6..7590bfe 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -16,6 +16,8 @@ import android.util.SparseArray;
*/
public abstract class BatteryStats implements Parcelable {
+ private static final boolean LOCAL_LOGV = false;
+
/**
* A constant indicating a partial wake lock timer.
*/
@@ -72,6 +74,7 @@ public abstract class BatteryStats implements Parcelable {
private static final String WAKELOCK_DATA = "wakelock";
private static final String NETWORK_DATA = "network";
private static final String BATTERY_DATA = "battery";
+ private static final String MISC_DATA = "misc";
private final StringBuilder mFormatBuilder = new StringBuilder(8);
private final Formatter mFormatter = new Formatter(mFormatBuilder);
@@ -93,11 +96,11 @@ public abstract class BatteryStats implements Parcelable {
* Returns the total time in microseconds associated with this Timer for the
* selected type of statistics.
*
- * @param now system uptime time in microseconds
+ * @param batteryRealtime system realtime on battery in microseconds
* @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
* @return a time in microseconds
*/
- public abstract long getTotalTime(long now, int which);
+ public abstract long getTotalTime(long batteryRealtime, int which);
/**
* Temporary for debugging.
@@ -222,11 +225,11 @@ public abstract class BatteryStats implements Parcelable {
/**
* Returns the amount of time spent started.
*
- * @param now elapsed realtime in microseconds.
+ * @param batteryUptime elapsed uptime on battery in microseconds.
* @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
* @return
*/
- public abstract long getStartTime(long now, int which);
+ public abstract long getStartTime(long batteryUptime, int which);
/**
* Returns the total number of times startService() has been called.
@@ -256,16 +259,16 @@ public abstract class BatteryStats implements Parcelable {
*
* {@hide}
*/
- public abstract long getBatteryScreenOnTime();
+ public abstract long getScreenOnTime(long batteryRealtime, int which);
/**
- * Returns the time in milliseconds that the screen has been on while the device was
- * plugged in.
+ * Returns the time in milliseconds that the phone has been on while the device was
+ * running on battery.
*
* {@hide}
*/
- public abstract long getPluggedScreenOnTime();
-
+ public abstract long getPhoneOnTime(long batteryRealtime, int which);
+
/**
* Return whether we are currently running on battery.
*/
@@ -382,18 +385,18 @@ public abstract class BatteryStats implements Parcelable {
*
* @param sb a StringBuilder object.
* @param timer a Timer object contining the wakelock times.
- * @param now the current time in microseconds.
+ * @param batteryRealtime the current on-battery time in microseconds.
* @param name the name of the wakelock.
* @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
* @param linePrefix a String to be prepended to each line of output.
* @return the line prefix
*/
- private static final String printWakeLock(StringBuilder sb, Timer timer, long now,
- String name, int which, String linePrefix) {
+ private static final String printWakeLock(StringBuilder sb, Timer timer,
+ long batteryRealtime, String name, int which, String linePrefix) {
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTimeMicros = timer.getTotalTime(now, which);
+ long totalTimeMicros = timer.getTotalTime(batteryRealtime, which);
long totalTimeMillis = (totalTimeMicros + 500) / 1000;
int count = timer.getCount(which);
@@ -470,26 +473,30 @@ public abstract class BatteryStats implements Parcelable {
* @param which
*/
private final void dumpCheckinLocked(PrintWriter pw, int which) {
- long uSecTime = SystemClock.elapsedRealtime() * 1000;
- final long uSecNow = getBatteryUptime(uSecTime);
+ final long rawUptime = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryUptime = getBatteryUptime(rawUptime);
+ final long batteryRealtime = getBatteryRealtime(rawRealtime);
+ final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+ final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+ final long totalRealtime = computeRealtime(rawRealtime, which);
+ final long totalUptime = computeUptime(rawUptime, which);
+ final long screenOnTime = getScreenOnTime(batteryRealtime, which);
+ final long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
StringBuilder sb = new StringBuilder(128);
- long batteryUptime = computeBatteryUptime(uSecNow, which);
- long batteryRealtime = computeBatteryRealtime(getBatteryRealtime(uSecTime), which);
- long elapsedRealtime = computeRealtime(uSecTime, which);
- long uptime = computeUptime(SystemClock.uptimeMillis() * 1000, which);
String category = STAT_NAMES[which];
// Dump "battery" stat
dumpLine(pw, 0 /* uid */, category, BATTERY_DATA,
which == STATS_TOTAL ? getStartCount() : "N/A",
- batteryUptime / 1000,
- formatRatioLocked(batteryUptime, elapsedRealtime),
- batteryRealtime / 1000,
- formatRatioLocked(batteryRealtime, elapsedRealtime),
- uptime / 1000,
- elapsedRealtime / 1000);
+ whichBatteryUptime / 1000, whichBatteryRealtime / 1000,
+ totalUptime / 1000, totalRealtime / 1000);
+
+ // Dump misc stats
+ dumpLine(pw, 0 /* uid */, category, MISC_DATA,
+ screenOnTime / 1000, phoneOnTime / 1000);
SparseArray<? extends Uid> uidStats = getUidStats();
final int NU = uidStats.size();
@@ -508,11 +515,11 @@ public abstract class BatteryStats implements Parcelable {
Uid.Wakelock wl = ent.getValue();
String linePrefix = "";
sb.setLength(0);
- linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL), uSecNow,
+ linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime,
"full", which, linePrefix);
- linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), uSecNow,
+ linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime,
"partial", which, linePrefix);
- linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), uSecNow,
+ linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime,
"window", which, linePrefix);
// Only log if we had at lease one wakelock...
@@ -531,7 +538,7 @@ public abstract class BatteryStats implements Parcelable {
Timer timer = se.getSensorTime();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTime = (timer.getTotalTime(uSecNow, which) + 500) / 1000;
+ long totalTime = (timer.getTotalTime(batteryRealtime, which) + 500) / 1000;
int count = timer.getCount(which);
if (totalTime != 0) {
dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count);
@@ -571,7 +578,7 @@ public abstract class BatteryStats implements Parcelable {
for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent
: serviceStats.entrySet()) {
BatteryStats.Uid.Pkg.Serv ss = sent.getValue();
- long startTime = ss.getStartTime(uSecNow, which);
+ long startTime = ss.getStartTime(batteryUptime, which);
int starts = ss.getStarts(which);
int launches = ss.getLaunches(which);
if (startTime != 0 || starts != 0 || launches != 0) {
@@ -591,29 +598,40 @@ public abstract class BatteryStats implements Parcelable {
@SuppressWarnings("unused")
private final void dumpLocked(Printer pw, String prefix, int which) {
- long uSecTime = SystemClock.elapsedRealtime() * 1000;
- final long uSecNow = getBatteryUptime(uSecTime);
-
+ final long rawUptime = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryUptime = getBatteryUptime(rawUptime);
+ final long batteryRealtime = getBatteryUptime(rawRealtime);
+
+ final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+ final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+ final long totalRealtime = computeRealtime(rawRealtime, which);
+ final long totalUptime = computeUptime(rawUptime, which);
+
StringBuilder sb = new StringBuilder(128);
- long batteryUptime = computeBatteryUptime(uSecNow, which);
- long batteryRealtime = computeBatteryRealtime(getBatteryRealtime(uSecTime), which);
- long elapsedRealtime = computeRealtime(uSecTime, which);
- long uptime = computeUptime(SystemClock.uptimeMillis() * 1000, which);
pw.println(prefix
- + " Time on battery: " + formatTimeMs(batteryUptime / 1000) + "("
- + formatRatioLocked(batteryUptime, elapsedRealtime)
+ + " Time on battery: " + formatTimeMs(whichBatteryUptime / 1000)
+ + "(" + formatRatioLocked(whichBatteryUptime, totalRealtime)
+ ") uptime, "
- + formatTimeMs(batteryRealtime / 1000) + "("
- + formatRatioLocked(batteryRealtime, elapsedRealtime)
+ + formatTimeMs(whichBatteryRealtime / 1000) + "("
+ + formatRatioLocked(whichBatteryRealtime, totalRealtime)
+ ") realtime");
pw.println(prefix
+ " Total: "
- + formatTimeMs(uptime / 1000)
+ + formatTimeMs(totalUptime / 1000)
+ "uptime, "
- + formatTimeMs(elapsedRealtime / 1000)
+ + formatTimeMs(totalRealtime / 1000)
+ "realtime");
+ long screenOnTime = getScreenOnTime(batteryRealtime, which);
+ long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
+ pw.println(prefix
+ + " Time with screen on: " + formatTimeMs(screenOnTime / 1000)
+ + "(" + formatRatioLocked(screenOnTime, whichBatteryRealtime)
+ + "), time with phone on: " + formatTimeMs(phoneOnTime / 1000)
+ + "(" + formatRatioLocked(phoneOnTime, whichBatteryRealtime) + ")");
+
pw.println(" ");
SparseArray<? extends Uid> uidStats = getUidStats();
@@ -641,11 +659,11 @@ public abstract class BatteryStats implements Parcelable {
sb.append(prefix);
sb.append(" Wake lock ");
sb.append(ent.getKey());
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), uSecNow,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime,
"full", which, linePrefix);
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), uSecNow,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime,
"partial", which, linePrefix);
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), uSecNow,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime,
"window", which, linePrefix);
if (!linePrefix.equals(": ")) {
sb.append(" realtime");
@@ -677,7 +695,7 @@ public abstract class BatteryStats implements Parcelable {
Timer timer = se.getSensorTime();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTime = (timer.getTotalTime(uSecNow, which) + 500) / 1000;
+ long totalTime = (timer.getTotalTime(batteryRealtime, which) + 500) / 1000;
int count = timer.getCount(which);
//timer.logState();
if (totalTime != 0) {
@@ -737,7 +755,7 @@ public abstract class BatteryStats implements Parcelable {
for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent
: serviceStats.entrySet()) {
BatteryStats.Uid.Pkg.Serv ss = sent.getValue();
- long startTime = ss.getStartTime(uSecNow, which);
+ long startTime = ss.getStartTime(batteryUptime, which);
int starts = ss.getStarts(which);
int launches = ss.getLaunches(which);
if (startTime != 0 || starts != 0 || launches != 0) {
@@ -787,9 +805,24 @@ public abstract class BatteryStats implements Parcelable {
@SuppressWarnings("unused")
public void dumpCheckinLocked(PrintWriter pw, String[] args) {
- dumpCheckinLocked(pw, STATS_TOTAL);
- dumpCheckinLocked(pw, STATS_LAST);
- dumpCheckinLocked(pw, STATS_UNPLUGGED);
- dumpCheckinLocked(pw, STATS_CURRENT);
+ boolean isUnpluggedOnly = false;
+
+ for (String arg : args) {
+ if ("-u".equals(arg)) {
+ if (LOCAL_LOGV) Log.v("BatteryStats", "Dumping unplugged data");
+ isUnpluggedOnly = true;
+ }
+ }
+
+ if (isUnpluggedOnly) {
+ dumpCheckinLocked(pw, STATS_UNPLUGGED);
+ }
+ else {
+ dumpCheckinLocked(pw, STATS_TOTAL);
+ dumpCheckinLocked(pw, STATS_LAST);
+ dumpCheckinLocked(pw, STATS_UNPLUGGED);
+ dumpCheckinLocked(pw, STATS_CURRENT);
+ }
}
+
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index ed138cb..3fcb18e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -76,6 +76,11 @@ public class ParcelFileDescriptor implements Parcelable {
public static final int MODE_TRUNCATE = 0x04000000;
/**
+ * For use with {@link #open}: append to end of file while writing.
+ */
+ public static final int MODE_APPEND = 0x02000000;
+
+ /**
* Create a new ParcelFileDescriptor accessing a given file.
*
* @param file The file to be opened.
@@ -138,6 +143,19 @@ public class ParcelFileDescriptor implements Parcelable {
}
/**
+ * Return the total size of the file representing this fd, as determined
+ * by stat(). Returns -1 if the fd is not a file.
+ */
+ public native long getStatSize();
+
+ /**
+ * This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream,
+ * and I really don't think we want it to be public.
+ * @hide
+ */
+ public native long seekTo(long pos);
+
+ /**
* Close the ParcelFileDescriptor. This implementation closes the underlying
* OS resources allocated to represent this stream.
*
diff --git a/core/java/android/pim/ICalendar.java b/core/java/android/pim/ICalendar.java
index 4a5d7e4..cc0f45e 100644
--- a/core/java/android/pim/ICalendar.java
+++ b/core/java/android/pim/ICalendar.java
@@ -405,13 +405,15 @@ public class ICalendar {
// TODO: get rid of this -- handle all of the parsing in one pass through
// the text.
private static String normalizeText(String text) {
- // first we deal with line folding, by replacing all "\r\n " strings
- // with nothing
- text = text.replaceAll("\r\n ", "");
-
// it's supposed to be \r\n, but not everyone does that
text = text.replaceAll("\r\n", "\n");
text = text.replaceAll("\r", "\n");
+
+ // we deal with line folding, by replacing all "\n " strings
+ // with nothing. The RFC specifies "\r\n " to be folded, but
+ // we handle "\n " and "\r " too because we can get those.
+ text = text.replaceAll("\n ", "");
+
return text;
}
@@ -440,7 +442,7 @@ public class ICalendar {
current = parseLine(line, state, current);
// if the provided component was null, we will return the root
// NOTE: in this case, if the first line is not a BEGIN, a
- // FormatException will get thrown.
+ // FormatException will get thrown.
if (component == null) {
component = current;
}
@@ -524,8 +526,7 @@ public class ICalendar {
private static String extractValue(ParserState state)
throws FormatException {
String line = state.line;
- char c = line.charAt(state.index);
- if (c != ':') {
+ if (state.index >= line.length() || line.charAt(state.index) != ':') {
throw new FormatException("Expected ':' before end of line in "
+ line);
}
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
index 0cdac53..1454089 100644
--- a/core/java/android/provider/Checkin.java
+++ b/core/java/android/provider/Checkin.java
@@ -62,6 +62,9 @@ public final class Checkin {
AUTOTEST_FAILURE,
AUTOTEST_SEQUENCE_BEGIN,
AUTOTEST_SUITE_BEGIN,
+ AUTOTEST_TCPDUMP_BEGIN,
+ AUTOTEST_TCPDUMP_DATA,
+ AUTOTEST_TCPDUMP_END,
AUTOTEST_TEST_BEGIN,
AUTOTEST_TEST_FAILURE,
AUTOTEST_TEST_SUCCESS,
@@ -98,14 +101,14 @@ public final class Checkin {
SETUP_SERVER_ERROR,
SETUP_SERVER_TIMEOUT,
SETUP_NO_DATA_NETWORK,
- SYSTEM_APP_NOT_RESPONDING,
SYSTEM_BOOT,
SYSTEM_LAST_KMSG,
SYSTEM_RECOVERY_LOG,
SYSTEM_RESTART,
SYSTEM_SERVICE_LOOPING,
SYSTEM_TOMBSTONE,
- TEST,
+ TEST,
+ BATTERY_DISCHARGE_INFO,
}
}
@@ -190,6 +193,9 @@ public final class Checkin {
// The category is used for GTalk service messages
public static final String CATEGORY = "android.server.checkin.CHECKIN";
+
+ // If true indicates that the checkin should only transfer market related data
+ public static final String EXTRA_MARKET_ONLY = "market_only";
}
private static final String TAG = "Checkin";
diff --git a/core/java/android/provider/Im.java b/core/java/android/provider/Im.java
index bea857f..19ad158 100644
--- a/core/java/android/provider/Im.java
+++ b/core/java/android/provider/Im.java
@@ -2044,4 +2044,37 @@ public class Im {
*/
public static final Uri CONTENT_URI = Uri.parse("content://im/lastRmqId");
}
+
+ /**
+ * Columns for IM branding resource map cache table. This table caches the result of
+ * loading the branding resources to speed up IM landing page start.
+ */
+ public interface BrandingResourceMapCacheColumns {
+ /**
+ * The provider ID
+ * <P>Type: INTEGER</P>
+ */
+ String PROVIDER_ID = "provider_id";
+ /**
+ * The application resource ID
+ * <P>Type: INTEGER</P>
+ */
+ String APP_RES_ID = "app_res_id";
+ /**
+ * The plugin resource ID
+ * <P>Type: INTEGER</P>
+ */
+ String PLUGIN_RES_ID = "plugin_res_id";
+ }
+
+ /**
+ * The table for caching the result of loading IM branding resources.
+ */
+ public static final class BrandingResourceMapCache
+ implements BaseColumns, BrandingResourceMapCacheColumns {
+ /**
+ * The content:// style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://im/brandingResMapCache");
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4a784c8..10ca5d5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2183,6 +2183,12 @@ public final class Settings {
public static final String CHECKIN_INTERVAL = "checkin_interval";
/**
+ * Boolean indicating if the market app should force market only checkins on
+ * install/uninstall. Any non-0 value is considered true.
+ */
+ public static final String MARKET_FORCE_CHECKIN = "market_force_checkin";
+
+ /**
* How frequently (in seconds) to check the memory status of the
* device.
*/
@@ -2455,6 +2461,14 @@ public final class Settings {
"gtalk_ssl_handshake_timeout_ms";
/**
+ * Enable use of ssl session caching.
+ * 'db' - save each session in a per-process database
+ * not set or any other value - normal java in-memory caching.
+ * Other cache types may be added.
+ */
+ public static final String SSL_SESSION_CACHE = "ssl_session_cache";
+
+ /**
* How many bytes long a message has to be, in order to be gzipped.
*/
public static final String SYNC_MIN_GZIP_BYTES =
@@ -2766,11 +2780,29 @@ public final class Settings {
public static final String VOICE_SEARCH_ENCODING_WIFI = "voice_search_encoding_wifi";
/**
- * Prefix for rules that launch automatic instrumentation test cycles.
- * The format is the InstrumentationTestRunner (or compatible) package and class,
- * followed optionally by space-separated key value pairs to pass to it.
+ * Whether to use automatic gain control in voice search (0 = disable, 1 = enable).
+ * To be factored out of this class.
*/
- public static final String AUTOTEST_PREFIX = "autotest:";
+ public static final String VOICE_SEARCH_ENABLE_AGC = "voice_search_enable_agc";
+
+ /**
+ * Whether to use noise suppression in voice search (0 = disable, 1 = enable).
+ * To be factored out of this class.
+ */
+ public static final String VOICE_SEARCH_ENABLE_NS = "voice_search_enable_ns";
+
+ /**
+ * Whether to use the IIR filter in voice search (0 = disable, 1 = enable).
+ * To be factored out of this class.
+ */
+ public static final String VOICE_SEARCH_ENABLE_IIR = "voice_search_enable_iir";
+
+ /**
+ * List of test suites (local disk filename) for the automatic instrumentation test runner.
+ * The file format is similar to automated_suites.xml, see AutoTesterService.
+ * If this setting is missing or empty, the automatic test runner will not start.
+ */
+ public static final String AUTOTEST_SUITES_FILE = "autotest_suites_file";
/**
* Interval between synchronous checkins forced by the automatic test runner.
@@ -2785,6 +2817,15 @@ public final class Settings {
*/
public static final String AUTOTEST_REBOOT_SECONDS = "autotest_reboot_seconds";
+
+ /**
+ * Threshold values for the duration and level of a discharge cycle, under
+ * which we log discharge cycle info.
+ */
+ public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
+ "battery_discharge_duration_threshold";
+ public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
+
/**
* @deprecated
* @hide
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 58f9491..f8bc765 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -15,8 +15,7 @@
*/
/**
- * TODO: Move this to
- * java/services/com/android/server/BluetoothA2dpService.java
+ * TODO: Move this to services.jar
* and make the contructor package private again.
* @hide
*/
@@ -35,15 +34,16 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.List;
import java.util.HashMap;
-import java.util.Iterator;
+import java.util.List;
public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String TAG = "BluetoothA2dpService";
@@ -57,6 +57,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String A2DP_SINK_ADDRESS = "a2dp_sink_address";
private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
+ private static final int MESSAGE_CONNECT_TO = 1;
+
private final Context mContext;
private final IntentFilter mIntentFilter;
private HashMap<String, SinkState> mAudioDevices;
@@ -86,6 +88,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
mIntentFilter = new IntentFilter(BluetoothIntent.ENABLED_ACTION);
mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
mIntentFilter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
mContext.registerReceiver(mReceiver, mIntentFilter);
if (device.isEnabled()) {
@@ -123,6 +126,37 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
setSinkPriority(address, BluetoothA2dp.PRIORITY_OFF);
break;
}
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) {
+ if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF) {
+ // This device is a preferred sink. Make an A2DP connection
+ // after a delay. We delay to avoid connection collisions,
+ // and to give other profiles such as HFP a chance to
+ // connect first.
+ Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, address);
+ mHandler.sendMessageDelayed(msg, 6000);
+ }
+ }
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_CONNECT_TO:
+ String address = (String)msg.obj;
+ // check device is still preferred, and nothing is currently
+ // connected
+ if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF &&
+ lookupSinksMatchingStates(new int[] {
+ BluetoothA2dp.STATE_CONNECTING,
+ BluetoothA2dp.STATE_CONNECTED,
+ BluetoothA2dp.STATE_PLAYING,
+ BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) {
+ log("Auto-connecting A2DP to sink " + address);
+ connectSink(address);
+ }
+ break;
}
}
};
@@ -142,7 +176,10 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private synchronized void onBluetoothDisable() {
if (mAudioDevices != null) {
- for (String path : mAudioDevices.keySet()) {
+ // copy to allow modification during iteration
+ String[] paths = new String[mAudioDevices.size()];
+ paths = mAudioDevices.keySet().toArray(paths);
+ for (String path : paths) {
switch (mAudioDevices.get(path).state) {
case BluetoothA2dp.STATE_CONNECTING:
case BluetoothA2dp.STATE_CONNECTED:
@@ -234,17 +271,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
public synchronized List<String> listConnectedSinks() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<String> connectedSinks = new ArrayList<String>();
- if (mAudioDevices == null) {
- return connectedSinks;
- }
- for (SinkState sink : mAudioDevices.values()) {
- if (sink.state == BluetoothA2dp.STATE_CONNECTED ||
- sink.state == BluetoothA2dp.STATE_PLAYING) {
- connectedSinks.add(sink.address);
- }
- }
- return connectedSinks;
+ return lookupSinksMatchingStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
+ BluetoothA2dp.STATE_PLAYING});
}
public synchronized int getSinkState(String address) {
@@ -298,7 +326,11 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
// bluez 3.36 quietly disconnects the previous sink when a new sink
// is connected, so we need to mark all previously connected sinks as
// disconnected
- for (String oldPath : mAudioDevices.keySet()) {
+
+ // copy to allow modification during iteration
+ String[] paths = new String[mAudioDevices.size()];
+ paths = mAudioDevices.keySet().toArray(paths);
+ for (String oldPath : paths) {
if (path.equals(oldPath)) {
continue;
}
@@ -350,6 +382,22 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return null;
}
+ private synchronized List<String> lookupSinksMatchingStates(int[] states) {
+ List<String> sinks = new ArrayList<String>();
+ if (mAudioDevices == null) {
+ return sinks;
+ }
+ for (SinkState sink : mAudioDevices.values()) {
+ for (int state : states) {
+ if (sink.state == state) {
+ sinks.add(sink.address);
+ break;
+ }
+ }
+ }
+ return sinks;
+ }
+
private synchronized void updateState(String path, int state) {
if (mAudioDevices == null) return;
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java
index fa53a60..950ff3a 100644
--- a/core/java/android/server/BluetoothDeviceService.java
+++ b/core/java/android/server/BluetoothDeviceService.java
@@ -141,6 +141,20 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
mBondState.setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
}
+
+ // Remove remoteServiceChannelCallbacks
+ HashMap<String, IBluetoothDeviceCallback> callbacksMap =
+ mEventLoop.getRemoteServiceChannelCallbacks();
+ IBluetoothDeviceCallback callback;
+
+ for (String address : callbacksMap.keySet()) {
+ callback = callbacksMap.get(address);
+ try {
+ callback.onGetRemoteServiceChannelResult(address, BluetoothError.ERROR_DISABLED);
+ } catch (RemoteException e) {}
+ callbacksMap.remove(address);
+ }
+
// update mode
Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
intent.putExtra(BluetoothIntent.SCAN_MODE, BluetoothDevice.SCAN_MODE_NONE);
@@ -569,10 +583,18 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
address = address.toUpperCase();
+ String[] bonding = mBondState.listInState(BluetoothDevice.BOND_BONDING);
+ if (bonding.length > 0 && !bonding[0].equals(address)) {
+ log("Ignoring createBond(): another device is bonding");
+ // a different device is currently bonding, fail
+ return false;
+ }
+
// Check for bond state only if we are not performing auto
// pairing exponential back-off attempts.
if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
- mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) {
+ mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) {
+ log("Ignoring createBond(): this device is already bonding or bonded");
return false;
}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index c18675e..0c04839 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -35,6 +35,7 @@ import android.text.InputType;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
+import android.view.inputmethod.EditorInfo;
import java.io.IOException;
import java.util.ArrayList;
@@ -77,6 +78,7 @@ public final class SearchableInfo implements Parcelable {
private int mIconId = 0;
private int mSearchButtonText = 0;
private int mSearchInputType = 0;
+ private int mSearchImeOptions = 0;
private String mSuggestAuthority = null;
private String mSuggestPath = null;
private String mSuggestSelection = null;
@@ -429,8 +431,9 @@ public final class SearchableInfo implements Parcelable {
com.android.internal.R.styleable.Searchable_searchButtonText, 0);
mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType,
InputType.TYPE_CLASS_TEXT |
- InputType.TYPE_TEXT_FLAG_SEARCH |
InputType.TYPE_TEXT_VARIATION_NORMAL);
+ mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions,
+ EditorInfo.IME_ACTION_SEARCH);
setSearchModeFlags();
if (DBG_INHIBIT_SUGGESTIONS == 0) {
@@ -743,6 +746,17 @@ public final class SearchableInfo implements Parcelable {
}
/**
+ * Return the input method options specified in the searchable attributes.
+ * This will default to EditorInfo.ACTION_SEARCH if not specified (which is
+ * appropriate for a search box).
+ *
+ * @return the input type
+ */
+ public int getImeOptions() {
+ return mSearchImeOptions;
+ }
+
+ /**
* Return the list of searchable activities, for use in the drop-down.
*/
public static ArrayList<SearchableInfo> getSearchablesList() {
@@ -782,6 +796,7 @@ public final class SearchableInfo implements Parcelable {
mIconId = in.readInt();
mSearchButtonText = in.readInt();
mSearchInputType = in.readInt();
+ mSearchImeOptions = in.readInt();
setSearchModeFlags();
mSuggestAuthority = in.readString();
@@ -818,6 +833,7 @@ public final class SearchableInfo implements Parcelable {
dest.writeInt(mIconId);
dest.writeInt(mSearchButtonText);
dest.writeInt(mSearchInputType);
+ dest.writeInt(mSearchImeOptions);
dest.writeString(mSuggestAuthority);
dest.writeString(mSuggestPath);
diff --git a/core/java/android/speech/srec/package.html b/core/java/android/speech/srec/package.html
index 723b30b..9a99df8 100644
--- a/core/java/android/speech/srec/package.html
+++ b/core/java/android/speech/srec/package.html
@@ -1,5 +1,6 @@
<HTML>
<BODY>
Simple, synchronous SREC speech recognition API.
+@hide
</BODY>
</HTML>
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index a073cf4..f643f92 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -128,11 +128,6 @@ public interface InputType {
*/
public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000;
- /**
- * Flag for {@link #TYPE_CLASS_TEXT}: flags any text being used as a search string
- */
- public static final int TYPE_TEXT_FLAG_SEARCH = 0x00080000;
-
// ----------------------------------------------------------------------
/**
diff --git a/core/java/android/text/Styled.java b/core/java/android/text/Styled.java
index 05c27ea..0aa2004 100644
--- a/core/java/android/text/Styled.java
+++ b/core/java/android/text/Styled.java
@@ -16,25 +16,26 @@
package android.text;
-import android.graphics.Paint;
import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.graphics.MaskFilter;
-import android.graphics.Rasterizer;
-import android.graphics.LayerRasterizer;
-import android.text.style.*;
-
-/* package */ class Styled
+import android.graphics.Paint;
+import android.text.style.CharacterStyle;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+
+/**
+ * This class provides static methods for drawing and measuring styled texts, like
+ * {@link android.text.Spanned} object with {@link android.text.style.ReplacementSpan}.
+ * @hide
+ */
+public class Styled
{
private static float each(Canvas canvas,
Spanned text, int start, int end,
int dir, boolean reverse,
float x, int top, int y, int bottom,
- Paint.FontMetricsInt fm,
- TextPaint realPaint,
+ Paint.FontMetricsInt fmi,
TextPaint paint,
+ TextPaint workPaint,
boolean needwid) {
boolean havewid = false;
@@ -43,9 +44,9 @@ import android.text.style.*;
ReplacementSpan replacement = null;
- realPaint.bgColor = 0;
- realPaint.baselineShift = 0;
- paint.set(realPaint);
+ paint.bgColor = 0;
+ paint.baselineShift = 0;
+ workPaint.set(paint);
if (spans.length > 0) {
for (int i = 0; i < spans.length; i++) {
@@ -55,7 +56,7 @@ import android.text.style.*;
replacement = (ReplacementSpan)span;
}
else {
- span.updateDrawState(paint);
+ span.updateDrawState(workPaint);
}
}
}
@@ -74,66 +75,66 @@ import android.text.style.*;
tmpend = end;
}
- if (fm != null) {
- paint.getFontMetricsInt(fm);
+ if (fmi != null) {
+ workPaint.getFontMetricsInt(fmi);
}
if (canvas != null) {
- if (paint.bgColor != 0) {
- int c = paint.getColor();
- Paint.Style s = paint.getStyle();
- paint.setColor(paint.bgColor);
- paint.setStyle(Paint.Style.FILL);
+ if (workPaint.bgColor != 0) {
+ int c = workPaint.getColor();
+ Paint.Style s = workPaint.getStyle();
+ workPaint.setColor(workPaint.bgColor);
+ workPaint.setStyle(Paint.Style.FILL);
if (!havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
if (dir == Layout.DIR_RIGHT_TO_LEFT)
- canvas.drawRect(x - ret, top, x, bottom, paint);
+ canvas.drawRect(x - ret, top, x, bottom, workPaint);
else
- canvas.drawRect(x, top, x + ret, bottom, paint);
+ canvas.drawRect(x, top, x + ret, bottom, workPaint);
- paint.setStyle(s);
- paint.setColor(c);
+ workPaint.setStyle(s);
+ workPaint.setColor(c);
}
if (dir == Layout.DIR_RIGHT_TO_LEFT) {
if (!havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
canvas.drawText(tmp, tmpstart, tmpend,
- x - ret, y + paint.baselineShift, paint);
+ x - ret, y + workPaint.baselineShift, workPaint);
} else {
if (needwid) {
if (!havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
}
canvas.drawText(tmp, tmpstart, tmpend,
- x, y + paint.baselineShift, paint);
+ x, y + workPaint.baselineShift, workPaint);
}
} else {
if (needwid && !havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
}
} else {
- ret = replacement.getSize(paint, text, start, end, fm);
+ ret = replacement.getSize(workPaint, text, start, end, fmi);
if (canvas != null) {
if (dir == Layout.DIR_RIGHT_TO_LEFT)
replacement.draw(canvas, text, start, end,
- x - ret, top, y, bottom, paint);
+ x - ret, top, y, bottom, workPaint);
else
replacement.draw(canvas, text, start, end,
- x, top, y, bottom, paint);
+ x, top, y, bottom, workPaint);
}
}
@@ -143,15 +144,29 @@ import android.text.style.*;
return ret;
}
- public static int getTextWidths(TextPaint realPaint,
- TextPaint paint,
- Spanned text, int start, int end,
- float[] widths, Paint.FontMetricsInt fm) {
-
+ /**
+ * Return the advance widths for the characters in the string.
+ * See also {@link android.graphics.Paint#getTextWidths(CharSequence, int, int, float[])}.
+ *
+ * @param paint The main {@link TextPaint} object.
+ * @param workPaint The {@link TextPaint} object used for temporal workspace.
+ * @param text The text to measure
+ * @param start The index of the first char to to measure
+ * @param end The end of the text slice to measure
+ * @param widths Array to receive the advance widths of the characters.
+ * Must be at least a large as (end - start).
+ * @param fmi FontMetrics information. Can be null.
+ * @return The actual number of widths returned.
+ */
+ public static int getTextWidths(TextPaint paint,
+ TextPaint workPaint,
+ Spanned text, int start, int end,
+ float[] widths, Paint.FontMetricsInt fmi) {
+ // Keep workPaint as is so that developers reuse the workspace.
MetricAffectingSpan[] spans = text.getSpans(start, end, MetricAffectingSpan.class);
ReplacementSpan replacement = null;
- paint.set(realPaint);
+ workPaint.set(paint);
for (int i = 0; i < spans.length; i++) {
MetricAffectingSpan span = spans[i];
@@ -159,15 +174,15 @@ import android.text.style.*;
replacement = (ReplacementSpan)span;
}
else {
- span.updateMeasureState(paint);
+ span.updateMeasureState(workPaint);
}
}
if (replacement == null) {
- paint.getFontMetricsInt(fm);
- paint.getTextWidths(text, start, end, widths);
+ workPaint.getFontMetricsInt(fmi);
+ workPaint.getTextWidths(text, start, end, widths);
} else {
- int wid = replacement.getSize(paint, text, start, end, fm);
+ int wid = replacement.getSize(workPaint, text, start, end, fmi);
if (end > start) {
widths[0] = wid;
@@ -183,10 +198,10 @@ import android.text.style.*;
CharSequence text, int start, int end,
int dir, boolean reverse,
float x, int top, int y, int bottom,
- Paint.FontMetricsInt fm,
+ Paint.FontMetricsInt fmi,
TextPaint paint,
TextPaint workPaint,
- boolean needwid) {
+ boolean needWidth) {
if (! (text instanceof Spanned)) {
float ret = 0;
@@ -194,22 +209,22 @@ import android.text.style.*;
CharSequence tmp = TextUtils.getReverse(text, start, end);
int tmpend = end - start;
- if (canvas != null || needwid)
+ if (canvas != null || needWidth)
ret = paint.measureText(tmp, 0, tmpend);
if (canvas != null)
canvas.drawText(tmp, 0, tmpend,
x - ret, y, paint);
} else {
- if (needwid)
+ if (needWidth)
ret = paint.measureText(text, start, end);
if (canvas != null)
canvas.drawText(text, start, end, x, y, paint);
}
- if (fm != null) {
- paint.getFontMetricsInt(fm);
+ if (fmi != null) {
+ paint.getFontMetricsInt(fmi);
}
return ret * dir; //Layout.DIR_RIGHT_TO_LEFT == -1
@@ -232,67 +247,129 @@ import android.text.style.*;
next = sp.nextSpanTransition(i, end, division);
x += each(canvas, sp, i, next, dir, reverse,
- x, top, y, bottom, fm, paint, workPaint,
- needwid || next != end);
-
- if (fm != null) {
- if (fm.ascent < asc)
- asc = fm.ascent;
- if (fm.descent > desc)
- desc = fm.descent;
-
- if (fm.top < ftop)
- ftop = fm.top;
- if (fm.bottom > fbot)
- fbot = fm.bottom;
+ x, top, y, bottom, fmi, paint, workPaint,
+ needWidth || next != end);
+
+ if (fmi != null) {
+ if (fmi.ascent < asc)
+ asc = fmi.ascent;
+ if (fmi.descent > desc)
+ desc = fmi.descent;
+
+ if (fmi.top < ftop)
+ ftop = fmi.top;
+ if (fmi.bottom > fbot)
+ fbot = fmi.bottom;
}
}
- if (fm != null) {
+ if (fmi != null) {
if (start == end) {
- paint.getFontMetricsInt(fm);
+ paint.getFontMetricsInt(fmi);
} else {
- fm.ascent = asc;
- fm.descent = desc;
- fm.top = ftop;
- fm.bottom = fbot;
+ fmi.ascent = asc;
+ fmi.descent = desc;
+ fmi.top = ftop;
+ fmi.bottom = fbot;
}
}
return x - ox;
}
- public static float drawText(Canvas canvas,
- CharSequence text, int start, int end,
- int dir, boolean reverse,
- float x, int top, int y, int bottom,
- TextPaint paint,
- TextPaint workPaint,
- boolean needwid) {
- if ((dir == Layout.DIR_RIGHT_TO_LEFT && !reverse)||(reverse && dir == Layout.DIR_LEFT_TO_RIGHT)) {
+
+ /* package */ static float drawText(Canvas canvas,
+ CharSequence text, int start, int end,
+ int direction, boolean reverse,
+ float x, int top, int y, int bottom,
+ TextPaint paint,
+ TextPaint workPaint,
+ boolean needWidth) {
+ if ((direction == Layout.DIR_RIGHT_TO_LEFT && !reverse) ||
+ (reverse && direction == Layout.DIR_LEFT_TO_RIGHT)) {
float ch = foreach(null, text, start, end, Layout.DIR_LEFT_TO_RIGHT,
false, 0, 0, 0, 0, null, paint, workPaint,
true);
- ch *= dir; // DIR_RIGHT_TO_LEFT == -1
- foreach(canvas, text, start, end, -dir,
+ ch *= direction; // DIR_RIGHT_TO_LEFT == -1
+ foreach(canvas, text, start, end, -direction,
reverse, x + ch, top, y, bottom, null, paint,
workPaint, true);
return ch;
}
- return foreach(canvas, text, start, end, dir, reverse,
+ return foreach(canvas, text, start, end, direction, reverse,
x, top, y, bottom, null, paint, workPaint,
- needwid);
+ needWidth);
}
-
+
+ /**
+ * Draw the specified range of text, specified by start/end, with its origin at (x,y),
+ * in the specified Paint. The origin is interpreted based on the Align setting in the
+ * Paint.
+ *
+ * This method considers style information in the text
+ * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
+ * correctly draws the text).
+ * See also
+ * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float, float, Paint)}
+ * and
+ * {@link android.graphics.Canvas#drawRect(float, float, float, float, Paint)}.
+ *
+ * @param canvas The target canvas.
+ * @param text The text to be drawn
+ * @param start The index of the first character in text to draw
+ * @param end (end - 1) is the index of the last character in text to draw
+ * @param direction The direction of the text. This must be
+ * {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
+ * {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
+ * @param x The x-coordinate of origin for where to draw the text
+ * @param top The top side of the rectangle to be drawn
+ * @param y The y-coordinate of origin for where to draw the text
+ * @param bottom The bottom side of the rectangle to be drawn
+ * @param paint The main {@link TextPaint} object.
+ * @param workPaint The {@link TextPaint} object used for temporal workspace.
+ * @param needWidth If true, this method returns the width of drawn text.
+ * @return Width of the drawn text if needWidth is true.
+ */
+ public static float drawText(Canvas canvas,
+ CharSequence text, int start, int end,
+ int direction,
+ float x, int top, int y, int bottom,
+ TextPaint paint,
+ TextPaint workPaint,
+ boolean needWidth) {
+ // For safety.
+ direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+ /*
+ * Hided "reverse" parameter since it is meaningless for external developers.
+ * Kept workPaint as is so that developers reuse the workspace.
+ */
+ return drawText(canvas, text, start, end, direction, false,
+ x, top, y, bottom, paint, workPaint, needWidth);
+ }
+
+ /**
+ * Return the width of the text, considering style information in the text
+ * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
+ * correctly mesures the width of the text).
+ *
+ * @param paint The main {@link TextPaint} object.
+ * @param workPaint The {@link TextPaint} object used for temporal workspace.
+ * @param text The text to measure
+ * @param start The index of the first character to start measuring
+ * @param end 1 beyond the index of the last character to measure
+ * @param fmi FontMetrics information. Can be null
+ * @return The width of the text
+ */
public static float measureText(TextPaint paint,
TextPaint workPaint,
CharSequence text, int start, int end,
- Paint.FontMetricsInt fm) {
+ Paint.FontMetricsInt fmi) {
+ // Keep workPaint as is so that developers reuse the workspace.
return foreach(null, text, start, end,
Layout.DIR_LEFT_TO_RIGHT, false,
- 0, 0, 0, 0, fm, paint, workPaint, true);
+ 0, 0, 0, 0, fmi, paint, workPaint, true);
}
}
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index feae6cf..8a7cdd9 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -595,6 +595,17 @@ public class DateUtils
* @param elapsedSeconds the elapsed time in seconds.
*/
public static String formatElapsedTime(long elapsedSeconds) {
+ return formatElapsedTime(null, elapsedSeconds);
+ }
+
+ /**
+ * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
+ * for display on the call-in-progress screen.
+ *
+ * @param recycle {@link StringBuilder} to recycle, if possible
+ * @param elapsedSeconds the elapsed time in seconds.
+ */
+ public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) {
initFormatStrings();
long hours = 0;
@@ -613,18 +624,24 @@ public class DateUtils
String result;
if (hours > 0) {
- return formatElapsedTime(sElapsedFormatHMMSS, hours, minutes, seconds);
+ return formatElapsedTime(recycle, sElapsedFormatHMMSS, hours, minutes, seconds);
} else {
- return formatElapsedTime(sElapsedFormatMMSS, minutes, seconds);
+ return formatElapsedTime(recycle, sElapsedFormatMMSS, minutes, seconds);
}
}
/**
* Fast formatting of h:mm:ss
*/
- private static String formatElapsedTime(String format, long hours, long minutes, long seconds) {
+ private static String formatElapsedTime(StringBuilder recycle, String format, long hours,
+ long minutes, long seconds) {
if (FAST_FORMAT_HMMSS.equals(format)) {
- StringBuffer sb = new StringBuffer(16);
+ StringBuilder sb = recycle;
+ if (sb == null) {
+ sb = new StringBuilder(8);
+ } else {
+ sb.setLength(0);
+ }
sb.append(hours);
sb.append(TIME_SEPARATOR);
if (minutes < 10) {
@@ -649,9 +666,15 @@ public class DateUtils
/**
* Fast formatting of m:ss
*/
- private static String formatElapsedTime(String format, long minutes, long seconds) {
+ private static String formatElapsedTime(StringBuilder recycle, String format, long minutes,
+ long seconds) {
if (FAST_FORMAT_MMSS.equals(format)) {
- StringBuffer sb = new StringBuffer(16);
+ StringBuilder sb = recycle;
+ if (sb == null) {
+ sb = new StringBuilder(8);
+ } else {
+ sb.setLength(0);
+ }
if (minutes < 10) {
sb.append(TIME_PADDING);
} else {
@@ -1028,8 +1051,9 @@ public class DateUtils
* If FORMAT_NO_YEAR is set, then the year is not shown.
* If neither FORMAT_SHOW_YEAR nor FORMAT_NO_YEAR are set, then the year
* is shown only if it is different from the current year, or if the start
- * and end dates fall on different years.
- *
+ * and end dates fall on different years. If both are set,
+ * FORMAT_SHOW_YEAR takes precedence.
+ *
* <p>
* Normally the date is shown unless the start and end day are the same.
* If FORMAT_SHOW_DATE is set, then the date is always shown, even for
@@ -1120,24 +1144,28 @@ public class DateUtils
boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0;
boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
-
- Time startDate;
+
+ // If we're getting called with a single instant in time (from
+ // e.g. formatDateTime(), below), then we can skip a lot of
+ // computation below that'd otherwise be thrown out.
+ boolean isInstant = (startMillis == endMillis);
+
+ Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+ startDate.set(startMillis);
+
Time endDate;
-
- if (useUTC) {
- startDate = new Time(Time.TIMEZONE_UTC);
- endDate = new Time(Time.TIMEZONE_UTC);
+ int dayDistance;
+ if (isInstant) {
+ endDate = startDate;
+ dayDistance = 0;
} else {
- startDate = new Time();
- endDate = new Time();
+ endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+ endDate.set(endMillis);
+ int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
+ int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
+ dayDistance = endJulianDay - startJulianDay;
}
-
- startDate.set(startMillis);
- endDate.set(endMillis);
- int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
- int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
- int dayDistance = endJulianDay - startJulianDay;
-
+
// If the end date ends at 12am at the beginning of a day,
// then modify it to make it look like it ends at midnight on
// the previous day. This will allow us to display "8pm - midnight",
@@ -1152,20 +1180,21 @@ public class DateUtils
// and an end date of Nov 12 at 00:00.
// If the start and end time are the same, then skip this and don't
// adjust the date.
- if ((endDate.hour | endDate.minute | endDate.second) == 0
- && (!showTime || dayDistance <= 1) && (startMillis != endMillis)) {
+ if (!isInstant
+ && (endDate.hour | endDate.minute | endDate.second) == 0
+ && (!showTime || dayDistance <= 1)) {
endDate.monthDay -= 1;
endDate.normalize(true /* ignore isDst */);
}
-
+
int startDay = startDate.monthDay;
int startMonthNum = startDate.month;
int startYear = startDate.year;
-
+
int endDay = endDate.monthDay;
int endMonthNum = endDate.month;
int endYear = endDate.year;
-
+
String startWeekDayString = "";
String endWeekDayString = "";
if (showWeekDay) {
@@ -1176,9 +1205,9 @@ public class DateUtils
weekDayFormat = WEEKDAY_FORMAT;
}
startWeekDayString = startDate.format(weekDayFormat);
- endWeekDayString = endDate.format(weekDayFormat);
+ endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat);
}
-
+
String startTimeString = "";
String endTimeString = "";
if (showTime) {
@@ -1204,7 +1233,7 @@ public class DateUtils
boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0;
boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0;
-
+
boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0;
boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
if (abbrevTime && startOnTheHour) {
@@ -1220,20 +1249,41 @@ public class DateUtils
startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
}
}
- if (abbrevTime && endOnTheHour) {
- if (capAMPM) {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+
+ // Don't waste time on setting endTimeFormat when
+ // we're dealing with an instant, where we'll never
+ // need the end point. (It's the same as the start
+ // point)
+ if (!isInstant) {
+ if (abbrevTime && endOnTheHour) {
+ if (capAMPM) {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+ }
} else {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+ if (capAMPM) {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+ }
}
- } else {
- if (capAMPM) {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+
+ if (endDate.hour == 12 && endOnTheHour && !noNoon) {
+ if (capNoon) {
+ endTimeFormat = res.getString(com.android.internal.R.string.Noon);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.noon);
+ }
+ } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
+ if (capMidnight) {
+ endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.midnight);
+ }
}
}
-
+
if (startDate.hour == 12 && startOnTheHour && !noNoon) {
if (capNoon) {
startTimeFormat = res.getString(com.android.internal.R.string.Noon);
@@ -1243,37 +1293,32 @@ public class DateUtils
// Don't show the start time starting at midnight. Show
// 12am instead.
}
-
- if (endDate.hour == 12 && endOnTheHour && !noNoon) {
- if (capNoon) {
- endTimeFormat = res.getString(com.android.internal.R.string.Noon);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.noon);
- }
- } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
- if (capMidnight) {
- endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.midnight);
- }
- }
}
+
startTimeString = startDate.format(startTimeFormat);
- endTimeString = endDate.format(endTimeFormat);
+ endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat);
}
-
- // Get the current year
- long millis = System.currentTimeMillis();
- Time time = new Time();
- time.set(millis);
- int currentYear = time.year;
-
+
// Show the year if the user specified FORMAT_SHOW_YEAR or if
// the starting and end years are different from each other
// or from the current year. But don't show the year if the
- // user specified FORMAT_NO_YEAR;
- showYear = showYear || (!noYear && (startYear != endYear || startYear != currentYear));
-
+ // user specified FORMAT_NO_YEAR.
+ if (showYear) {
+ // No code... just a comment for clarity. Keep showYear
+ // on, as they enabled it with FORMAT_SHOW_YEAR. This
+ // takes precedence over them setting FORMAT_NO_YEAR.
+ } else if (noYear) {
+ // They explicitly didn't want a year.
+ showYear = false;
+ } else if (startYear != endYear) {
+ showYear = true;
+ } else {
+ // Show the year if it's not equal to the current year.
+ Time currentTime = new Time();
+ currentTime.setToNow();
+ showYear = startYear != currentTime.year;
+ }
+
String defaultDateFormat, fullFormat, dateRange;
if (numericDate) {
defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date);
@@ -1306,7 +1351,7 @@ public class DateUtils
}
}
}
-
+
if (showWeekDay) {
if (showTime) {
fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2);
@@ -1320,20 +1365,20 @@ public class DateUtils
fullFormat = res.getString(com.android.internal.R.string.date1_date2);
}
}
-
+
if (noMonthDay && startMonthNum == endMonthNum) {
// Example: "January, 2008"
String startDateString = startDate.format(defaultDateFormat);
return startDateString;
}
-
+
if (startYear != endYear || noMonthDay) {
// Different year or we are not showing the month day number.
// Example: "December 31, 2007 - January 1, 2008"
// Or: "January - February, 2008"
String startDateString = startDate.format(defaultDateFormat);
String endDateString = endDate.format(defaultDateFormat);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1341,7 +1386,7 @@ public class DateUtils
endWeekDayString, endDateString, endTimeString);
return dateRange;
}
-
+
// Get the month, day, and year strings for the start and end dates
String monthFormat;
if (numericDate) {
@@ -1354,16 +1399,17 @@ public class DateUtils
String startMonthString = startDate.format(monthFormat);
String startMonthDayString = startDate.format(MONTH_DAY_FORMAT);
String startYearString = startDate.format(YEAR_FORMAT);
- String endMonthString = endDate.format(monthFormat);
- String endMonthDayString = endDate.format(MONTH_DAY_FORMAT);
- String endYearString = endDate.format(YEAR_FORMAT);
-
+
+ String endMonthString = isInstant ? null : endDate.format(monthFormat);
+ String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
+ String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
+
if (startMonthNum != endMonthNum) {
// Same year, different month.
// Example: "October 28 - November 3"
// or: "Wed, Oct 31 - Sat, Nov 3, 2007"
// or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm"
-
+
int index = 0;
if (showWeekDay) index = 1;
if (showYear) index += 2;
@@ -1371,7 +1417,7 @@ public class DateUtils
if (numericDate) index += 8;
int resId = sameYearTable[index];
fullFormat = res.getString(resId);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1381,7 +1427,7 @@ public class DateUtils
endYearString, endTimeString);
return dateRange;
}
-
+
if (startDay != endDay) {
// Same month, different day.
int index = 0;
@@ -1391,7 +1437,7 @@ public class DateUtils
if (numericDate) index += 8;
int resId = sameMonthTable[index];
fullFormat = res.getString(resId);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1401,19 +1447,19 @@ public class DateUtils
endYearString, endTimeString);
return dateRange;
}
-
+
// Same start and end day
boolean showDate = (flags & FORMAT_SHOW_DATE) != 0;
-
+
// If nothing was specified, then show the date.
if (!showTime && !showDate && !showWeekDay) showDate = true;
-
+
// Compute the time string (example: "10:00 - 11:00 am")
String timeString = "";
if (showTime) {
// If the start and end time are the same, then just show the
// start time.
- if (startMillis == endMillis) {
+ if (isInstant) {
// Same start and end time.
// Example: "10:15 AM"
timeString = startTimeString;
@@ -1423,7 +1469,7 @@ public class DateUtils
timeString = String.format(timeFormat, startTimeString, endTimeString);
}
}
-
+
// Figure out which full format to use.
fullFormat = "";
String dateString = "";
@@ -1457,7 +1503,7 @@ public class DateUtils
} else if (showTime) {
return timeString;
}
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat, timeString, startWeekDayString, dateString);
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
index e500fae..9270ca5 100644
--- a/core/java/android/text/method/NumberKeyListener.java
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -101,6 +101,11 @@ public abstract class NumberKeyListener extends BaseKeyListener
selEnd = Math.max(a, b);
}
+ if (selStart < 0 || selEnd < 0) {
+ selStart = selEnd = 0;
+ Selection.setSelection(content, 0);
+ }
+
int i = event != null ? lookup(event, content) : 0;
int repeatCount = event != null ? event.getRepeatCount() : 0;
if (repeatCount == 0) {
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 679c683..e0231a7 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -113,20 +113,48 @@ public class GestureDetector {
}
/**
- * @hide pending API council
+ * The listener that is used to notify when a double-tap or a confirmed
+ * single-tap occur.
*/
public interface OnDoubleTapListener {
+ /**
+ * Notified when a single-tap occurs.
+ * <p>
+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
+ * will only be called after the detector is confident that the user's
+ * first tap is not followed by a second tap leading to a double-tap
+ * gesture.
+ *
+ * @param e The down motion event of the single-tap.
+ * @return true if the event is consumed, else false
+ */
boolean onSingleTapConfirmed(MotionEvent e);
+
+ /**
+ * Notified when a double-tap occurs.
+ *
+ * @param e The down motion event of the first tap of the double-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap(MotionEvent e);
+
+ /**
+ * Notified when an event within a double-tap gesture occurs, including
+ * the down, move, and up events.
+ *
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event is consumed, else false
+ */
boolean onDoubleTapEvent(MotionEvent e);
}
-
+
/**
- * A convenience class to extend when you only want to listen for a
- * subset of all the gestures. This implements all methods in the
- * {@link OnGestureListener} but does nothing and return {@code false}
- * for all applicable methods.
+ * A convenience class to extend when you only want to listen for a subset
+ * of all the gestures. This implements all methods in the
+ * {@link OnGestureListener} and {@link OnDoubleTapListener} but does
+ * nothing and return {@code false} for all applicable methods.
*/
- public static class SimpleOnGestureListener implements OnGestureListener {
+ public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@@ -150,13 +178,25 @@ public class GestureDetector {
public boolean onDown(MotionEvent e) {
return false;
}
+
+ public boolean onDoubleTap(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
}
// TODO: ViewConfiguration
private int mBiggerTouchSlopSquare = 20 * 20;
-
+
private int mTouchSlopSquare;
- private int mDoubleTapSlopSquare;
+ private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
@@ -164,7 +204,7 @@ public class GestureDetector {
// TODO make new double-tap timeout, and define its events (i.e. either time
// between down-down or time between up-down)
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
-
+
// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;
private static final int LONG_PRESS = 2;
@@ -181,13 +221,13 @@ public class GestureDetector {
private MotionEvent mCurrentDownEvent;
private MotionEvent mPreviousUpEvent;
-
+
/**
* True when the user is still touching for the second tap (down, move, and
* up events). Can only be true if there is a double tap listener attached.
*/
private boolean mIsDoubleTapping;
-
+
private float mLastMotionY;
private float mLastMotionX;
@@ -226,7 +266,7 @@ public class GestureDetector {
break;
default:
- throw new RuntimeException("Unknown message " + msg); //never
+ throw new RuntimeException("Unknown message " + msg); //never
}
}
}
@@ -303,6 +343,9 @@ public class GestureDetector {
mHandler = new GestureHandler();
}
mListener = listener;
+ if (listener instanceof OnDoubleTapListener) {
+ setOnDoubleTapListener((OnDoubleTapListener) listener);
+ }
init(context);
}
@@ -331,8 +374,11 @@ public class GestureDetector {
}
/**
- * @hide pending API council
- * @param onDoubleTapListener
+ * Sets the listener which will be called for double-tap and related
+ * gestures.
+ *
+ * @param onDoubleTapListener the listener invoked for all the callbacks, or
+ * null to stop listening for double-tap gestures.
*/
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
@@ -387,7 +433,10 @@ public class GestureDetector {
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
- handled = mDoubleTapListener.onDoubleTapEvent(ev);
+ // Give a callback with the first tap of the double-tap
+ handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
+ // Give a callback with down event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
@@ -418,7 +467,8 @@ public class GestureDetector {
final float scrollX = mLastMotionX - x;
final float scrollY = mLastMotionY - y;
if (mIsDoubleTapping) {
- handled = mDoubleTapListener.onDoubleTapEvent(ev);
+ // Give the move events of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (x - mCurrentDownEvent.getX());
final int deltaY = (int) (y - mCurrentDownEvent.getY());
@@ -446,7 +496,8 @@ public class GestureDetector {
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
- handled = mDoubleTapListener.onDoubleTapEvent(ev);
+ // Finally, give the up event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
mIsDoubleTapping = false;
break;
} else if (mInLongPress) {
@@ -495,7 +546,7 @@ public class GestureDetector {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
-
+
if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
return false;
}
@@ -504,7 +555,7 @@ public class GestureDetector {
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
-
+
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
mInLongPress = true;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index d6ea91c..430cc71 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -229,6 +229,12 @@ public class KeyEvent implements Parcelable {
public static final int FLAG_SOFT_KEYBOARD = 0x2;
/**
+ * This mask is set if we don't want the key event to cause us to leave
+ * touch mode.
+ */
+ public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
+
+ /**
* Returns the maximum keycode.
*/
public static int getMaxKeyCode() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5ed3a7e..406fad8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -57,6 +57,7 @@ import com.android.internal.view.menu.MenuBuilder;
import java.util.ArrayList;
import java.util.Arrays;
+import java.lang.ref.SoftReference;
/**
* <p>
@@ -1563,7 +1564,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private int[] mDrawableState = null;
- private Bitmap mDrawingCache;
+ private SoftReference<Bitmap> mDrawingCache;
/**
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
@@ -3950,25 +3951,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ destroyDrawingCache();
}
if ((changed & DRAWING_CACHE_ENABLED) != 0) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ destroyDrawingCache();
mPrivateFlags &= ~DRAWING_CACHE_VALID;
}
if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ destroyDrawingCache();
mPrivateFlags &= ~DRAWING_CACHE_VALID;
}
@@ -5415,11 +5407,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
return null;
}
- if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED &&
- ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null)) {
+ if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
buildDrawingCache();
}
- return mDrawingCache;
+ return mDrawingCache == null ? null : mDrawingCache.get();
}
/**
@@ -5434,7 +5425,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
public void destroyDrawingCache() {
if (mDrawingCache != null) {
- mDrawingCache.recycle();
+ final Bitmap bitmap = mDrawingCache.get();
+ if (bitmap != null) bitmap.recycle();
mDrawingCache = null;
}
}
@@ -5474,7 +5466,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @see #destroyDrawingCache()
*/
public void buildDrawingCache() {
- if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null) {
+ if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null ||
+ mDrawingCache.get() == null) {
+
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
}
@@ -5492,15 +5486,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (width <= 0 || height <= 0 ||
(width * height * (opaque ? 2 : 4) >= // Projected bitmap size in bytes
ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ destroyDrawingCache();
return;
}
boolean clear = true;
- Bitmap bitmap = mDrawingCache;
+ Bitmap bitmap = mDrawingCache == null ? null : mDrawingCache.get();
if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
@@ -5525,12 +5516,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
// Try to cleanup memory
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
+ if (bitmap != null) bitmap.recycle();
try {
- mDrawingCache = bitmap = Bitmap.createBitmap(width, height, quality);
+ bitmap = Bitmap.createBitmap(width, height, quality);
+ mDrawingCache = new SoftReference<Bitmap>(bitmap);
} catch (OutOfMemoryError e) {
// If there is not enough memory to create the bitmap cache, just
// ignore the issue as bitmap caches are not required to draw the
@@ -8060,7 +8050,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
shader = new LinearGradient(0, 0, 0, 1, color, 0, Shader.TileMode.CLAMP);
paint.setShader(shader);
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
+ // Restore the default transfer mode (src_over)
+ paint.setXfermode(null);
}
}
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 2f7b0d1..d3f48c6 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -56,7 +56,7 @@ public class ViewConfiguration {
/**
* Defines the duration in milliseconds we will wait to see if a touch event
- * is a top or a scroll. If the user does not move within this interval, it is
+ * is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
*/
private static final int TAP_TIMEOUT = 100;
@@ -213,7 +213,7 @@ public class ViewConfiguration {
}
/**
- * @return Defines the length of the fading edges in pixels
+ * @return the length of the fading edges in pixels
*
* @deprecated Use {@link #getScaledFadingEdgeLength()} instead.
*/
@@ -223,14 +223,14 @@ public class ViewConfiguration {
}
/**
- * @return Defines the length of the fading edges in pixels
+ * @return the length of the fading edges in pixels
*/
public int getScaledFadingEdgeLength() {
return mFadingEdgeLength;
}
/**
- * @return Defines the duration in milliseconds of the pressed state in child
+ * @return the duration in milliseconds of the pressed state in child
* components.
*/
public static int getPressedStateDuration() {
@@ -238,7 +238,7 @@ public class ViewConfiguration {
}
/**
- * @return Defines the duration in milliseconds before a press turns into
+ * @return the duration in milliseconds before a press turns into
* a long press
*/
public static int getLongPressTimeout() {
@@ -246,8 +246,8 @@ public class ViewConfiguration {
}
/**
- * @return Defines the duration in milliseconds we will wait to see if a touch event
- * is a top or a scroll. If the user does not move within this interval, it is
+ * @return the duration in milliseconds we will wait to see if a touch event
+ * is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
*/
public static int getTapTimeout() {
@@ -255,7 +255,7 @@ public class ViewConfiguration {
}
/**
- * @return Defines the duration in milliseconds we will wait to see if a touch event
+ * @return the duration in milliseconds we will wait to see if a touch event
* is a jump tap. If the user does not move within this interval, it is
* considered to be a tap.
*/
@@ -264,7 +264,7 @@ public class ViewConfiguration {
}
/**
- * @return Defines the duration in milliseconds between the first tap's up event and
+ * @return the duration in milliseconds between the first tap's up event and
* the second tap's down event for an interaction to be considered a
* double-tap.
* @hide pending API council
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 3cfaf1b..de64d0e 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -143,6 +143,7 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded;
boolean mHasHadWindowFocus;
+ boolean mLastWasImTarget;
boolean mWindowAttributesChanged = false;
@@ -998,6 +999,21 @@ public final class ViewRoot extends Handler implements ViewParent,
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
+ if (mAttachInfo.mHasWindowFocus) {
+ final boolean imTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
+ if (imTarget != mLastWasImTarget) {
+ mLastWasImTarget = imTarget;
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && imTarget) {
+ imm.startGettingWindowFocus(mView);
+ imm.onWindowFocus(mView, mView.findFocus(),
+ mWindowAttributes.softInputMode,
+ !mHasHadWindowFocus, mWindowAttributes.flags);
+ }
+ }
+ }
+
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
if (!cancelDraw && !newSurface) {
@@ -1176,7 +1192,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
if (!canvas.isOpaque()) {
- canvas.drawColor(0xff0000ff, PorterDuff.Mode.CLEAR);
+ canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
} else if (yoff != 0) {
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
@@ -1608,10 +1624,13 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
+ mLastWasImTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
+
InputMethodManager imm = InputMethodManager.peekInstance();
if (mView != null) {
- if (hasWindowFocus && imm != null) {
- imm.startGettingWindowFocus();
+ if (hasWindowFocus && imm != null && mLastWasImTarget) {
+ imm.startGettingWindowFocus(mView);
}
mView.dispatchWindowFocusChanged(hasWindowFocus);
}
@@ -1619,7 +1638,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
- if (imm != null) {
+ if (imm != null && mLastWasImTarget) {
imm.onWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
@@ -1976,6 +1995,9 @@ public final class ViewRoot extends Handler implements ViewParent,
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
+ if ((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
+ return false;
+ }
// only relevant if we are in touch mode
if (!mAttachInfo.mInTouchMode) {
@@ -2095,8 +2117,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// If it is possible for this window to interact with the input
// method window, then we want to first dispatch our key events
// to the input method.
- if (WindowManager.LayoutParams.mayUseInputMethod(
- mWindowAttributes.flags)) {
+ if (mLastWasImTarget) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && mView != null) {
int seq = enqueuePendingEvent(event, sendDone);
@@ -2126,6 +2147,10 @@ public final class ViewRoot extends Handler implements ViewParent,
sWindowSession.finishKey(mWindow);
} catch (RemoteException e) {
}
+ } else {
+ Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq
+ + " handled=" + handled + " ev=" + event
+ + ") neither delivering nor finishing key");
}
}
}
@@ -2448,6 +2473,8 @@ public final class ViewRoot extends Handler implements ViewParent,
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
viewRoot.dispatchKey(event);
+ } else {
+ Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!");
}
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 6fbc174..52b4107 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -341,6 +341,13 @@ public class BaseInputConnection implements InputConnection {
/**
* The default implementation does nothing.
*/
+ public boolean performEditorAction(int actionCode) {
+ return false;
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
public boolean performContextMenuAction(int id) {
return false;
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index b2f26d7..0405371 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -25,17 +25,100 @@ public class EditorInfo implements InputType, Parcelable {
public int inputType = TYPE_NULL;
/**
- * A string supplying additional information about the content type that
- * is private to a particular IME implementation. The string must be
+ * Set of bits in {@link #imeOptions} that provide alternative actions
+ * associated with the "enter" key. This both helps the IME provide
+ * better feedback about what the enter key will do, and also allows it
+ * to provide alternative mechanisms for providing that command.
+ */
+ public static final int IME_MASK_ACTION = 0x000000ff;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: there is no special action
+ * associated with this editor.
+ */
+ public static final int IME_ACTION_NONE = 0x00000000;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
+ * operation to take the user to the target of the text they typed.
+ * Typically used, for example, when entering a URL.
+ */
+ public static final int IME_ACTION_GO = 0x00000001;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
+ * operation, taking the user to the results of searching for the text
+ * the have typed (in whatever context is appropriate).
+ */
+ public static final int IME_ACTION_SEARCH = 0x00000002;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
+ * operation, delivering the text to its target. This is typically used
+ * when composing a message.
+ */
+ public static final int IME_ACTION_SEND = 0x00000003;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
+ * operation, taking the user to the next field that will accept text.
+ */
+ public static final int IME_ACTION_NEXT = 0x00000004;
+
+ /**
+ * Flag of {@link #imeOptions}: used in conjunction with
+ * {@link #IME_MASK_ACTION}, this indicates that the action should not
+ * be available in-line as the same as a "enter" key. Typically this is
+ * because the action has such a significant impact or is not recoverable
+ * enough that accidentally hitting it should be avoided, such as sending
+ * a message.
+ */
+ public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000;
+
+ /**
+ * Generic non-special type for {@link #imeOptions}.
+ */
+ public static final int IME_NORMAL = 0x00000000;
+
+ /**
+ * Special code for when the ime option has been undefined. This is not
+ * used with the EditorInfo structure, but can be used elsewhere.
+ */
+ public static final int IME_UNDEFINED = 0x80000000;
+
+ /**
+ * Extended type information for the editor, to help the IME better
+ * integrate with it.
+ */
+ public int imeOptions = IME_NORMAL;
+
+ /**
+ * A string supplying additional information options that are
+ * private to a particular IME implementation. The string must be
* scoped to a package owned by the implementation, to ensure there are
* no conflicts between implementations, but other than that you can put
* whatever you want in it to communicate with the IME. For example,
* you could have a string that supplies an argument like
* <code>"com.example.myapp.SpecialMode=3"</code>. This field is can be
- * filled in from the {@link android.R.attr#editorPrivateContentType}
+ * filled in from the {@link android.R.attr#privateImeOptions}
* attribute of a TextView.
*/
- public String privateContentType = null;
+ public String privateImeOptions = null;
+
+ /**
+ * In some cases an IME may be able to display an arbitrary label for
+ * a command the user can perform, which you can specify here. You can
+ * not count on this being used.
+ */
+ public CharSequence actionLabel = null;
+
+ /**
+ * If {@link #actionLabel} has been given, this is the id for that command
+ * when the user presses its button that is delivered back with
+ * {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction()}.
+ */
+ public int actionId = 0;
/**
* The text offset of the start of the selection at the time editing
@@ -106,7 +189,10 @@ public class EditorInfo implements InputType, Parcelable {
*/
public void dump(Printer pw, String prefix) {
pw.println(prefix + "inputType=0x" + Integer.toHexString(inputType)
- + " privateContentType=" + privateContentType);
+ + " imeOptions=0x" + Integer.toHexString(imeOptions)
+ + " privateImeOptions=" + privateImeOptions);
+ pw.println(prefix + "actionLabel=" + actionLabel
+ + " actionId=" + actionId);
pw.println(prefix + "initialSelStart=" + initialSelStart
+ " initialSelEnd=" + initialSelEnd
+ " initialCapsMode=0x"
@@ -127,7 +213,10 @@ public class EditorInfo implements InputType, Parcelable {
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(inputType);
- dest.writeString(privateContentType);
+ dest.writeInt(imeOptions);
+ dest.writeString(privateImeOptions);
+ TextUtils.writeToParcel(actionLabel, dest, flags);
+ dest.writeInt(actionId);
dest.writeInt(initialSelStart);
dest.writeInt(initialSelEnd);
dest.writeInt(initialCapsMode);
@@ -146,7 +235,10 @@ public class EditorInfo implements InputType, Parcelable {
public EditorInfo createFromParcel(Parcel source) {
EditorInfo res = new EditorInfo();
res.inputType = source.readInt();
- res.privateContentType = source.readString();
+ res.imeOptions = source.readInt();
+ res.privateImeOptions = source.readString();
+ res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.actionId = source.readInt();
res.initialSelStart = source.readInt();
res.initialSelEnd = source.readInt();
res.initialCapsMode = source.readInt();
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 530127d..32cce35 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -208,10 +208,24 @@ public interface InputConnection {
/**
* Set the selection of the text editor. To set the cursor position,
* start and end should have the same value.
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
*/
public boolean setSelection(int start, int end);
/**
+ * Have the editor perform an action it has said it can do.
+ *
+ * @param editorAction This must be one of the action constants for
+ * {@link EditorInfo#imeOptions EditorInfo.editorType}, such as
+ * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean performEditorAction(int editorAction);
+
+ /**
* Perform a context menu action on the field. The given id may be one of:
* {@link android.R.id#selectAll},
* {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 91fa211..e9e4703 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -222,6 +222,11 @@ public final class InputMethodManager {
// -----------------------------------------------------------
/**
+ * This is the root view of the overall window that currently has input
+ * method focus.
+ */
+ View mCurRootView;
+ /**
* This is the view that should currently be served by an input method,
* regardless of the state of setting that up.
*/
@@ -840,6 +845,13 @@ public final class InputMethodManager {
void focusInLocked(View view) {
if (DEBUG) Log.v(TAG, "focusIn: " + view);
+
+ if (mCurRootView != view.getRootView()) {
+ // This is a request from a window that isn't in the window with
+ // IME focus, so ignore it.
+ return;
+ }
+
// Okay we have a new view that is being served.
if (mServedView != view) {
mCurrentTextBoxAttribute = null;
@@ -913,7 +925,7 @@ public final class InputMethodManager {
}
/**
- * Called by ViewRoot the first time it gets window focus.
+ * Called by ViewRoot when its window gets input focus.
* @hide
*/
public void onWindowFocus(View rootView, View focusedView, int softInputMode,
@@ -946,9 +958,10 @@ public final class InputMethodManager {
}
/** @hide */
- public void startGettingWindowFocus() {
+ public void startGettingWindowFocus(View rootView) {
synchronized (mH) {
mWindowFocusedView = null;
+ mCurRootView = rootView;
}
}
@@ -1165,6 +1178,7 @@ public final class InputMethodManager {
+ " mBindSequence=" + mBindSequence
+ " mCurId=" + mCurId);
p.println(" mCurMethod=" + mCurMethod);
+ p.println(" mCurRootView=" + mCurRootView);
p.println(" mServedView=" + mServedView);
p.println(" mLastServedView=" + mLastServedView);
p.println(" mServedConnecting=" + mServedConnecting);
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 4f8e5e4..84aeb83 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -907,10 +907,12 @@ class CallbackProxy extends Handler {
}
public void onReceivedIcon(Bitmap icon) {
- if (Config.DEBUG && mBackForwardList.getCurrentItem() == null) {
- throw new AssertionError();
+ // The current item might be null if the icon was already stored in the
+ // database and this is a new WebView.
+ WebHistoryItem i = mBackForwardList.getCurrentItem();
+ if (i != null) {
+ i.setFavicon(icon);
}
- mBackForwardList.getCurrentItem().setFavicon(icon);
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index c2620a5..8a82411 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -25,8 +25,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
-import android.os.Handler;
-import android.os.Message;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
@@ -43,7 +41,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewConfiguration;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
@@ -82,22 +79,6 @@ import java.util.ArrayList;
// FIXME: This can be replaced with TextView.NO_FILTERS if that
// is made public/protected.
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
- // The time of the last enter down, so we know whether to perform a long
- // press.
- private long mDownTime;
-
- private boolean mTrackballDown = false;
- private static int LONGPRESS = 1;
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == LONGPRESS) {
- if (mTrackballDown) {
- performLongClick();
- mTrackballDown = false;
- }
- }
- }
- };
/**
* Create a new TextDialog.
@@ -135,6 +116,13 @@ import java.util.ArrayList;
}
@Override
+ protected boolean shouldAdvanceFocusOnEnter() {
+ // In the browser, single line textfields use enter as a form submit,
+ // so we never want to advance the focus on enter.
+ return false;
+ }
+
+ @Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.isSystem()) {
return super.dispatchKeyEvent(event);
@@ -152,43 +140,33 @@ import java.util.ArrayList;
return true;
}
- // For single-line textfields, return key should not be handled
- // here. Instead, the WebView is passed the key up, so it may fire a
- // submit/onClick.
- // Center key should always be passed to a potential onClick
- if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)
- || KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)) {
if (isPopupShowing()) {
- super.dispatchKeyEvent(event);
- return true;
+ return super.dispatchKeyEvent(event);
}
- if (down) {
- if (event.getRepeatCount() == 0) {
- mGotEnterDown = true;
- mDownTime = event.getEventTime();
- // Send the keydown when the up comes, so that we have
- // a chance to handle a long press.
- } else if (mGotEnterDown && event.getEventTime() - mDownTime >
- ViewConfiguration.getLongPressTimeout()) {
- performLongClick();
- mGotEnterDown = false;
- }
- } else if (mGotEnterDown) {
- mGotEnterDown = false;
- if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
- mWebView.shortPressOnTextField();
- return true;
- }
- // If we reached here, then this is a single line textfield, and
- // the user pressed ENTER. In this case, we want to hide the
- // soft input method.
+ if (!down) {
+ // Hide the keyboard, since the user has just submitted this
+ // form. The submission happens thanks to the two calls
+ // to sendDomEvent.
InputMethodManager.getInstance(mContext)
.hideSoftInputFromWindow(getWindowToken(), 0);
sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
sendDomEvent(event);
}
- return true;
+ return super.dispatchKeyEvent(event);
+ } else if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ // Note that this handles center key and trackball.
+ if (isPopupShowing()) {
+ return super.dispatchKeyEvent(event);
+ }
+ // Center key should be passed to a potential onClick
+ if (!down) {
+ mWebView.shortPressOnTextField();
+ }
+ // Pass to super to handle longpress.
+ return super.dispatchKeyEvent(event);
}
+
// Ensure there is a layout so arrow keys are handled properly.
if (getLayout() == null) {
measure(mWidthSpec, mHeightSpec);
@@ -225,9 +203,8 @@ import java.util.ArrayList;
case KeyEvent.KEYCODE_DPAD_DOWN:
isArrowKey = true;
break;
- case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
- // For multi-line text boxes, newlines and dpad center will
+ // For multi-line text boxes, newlines will
// trigger onTextChanged for key down (which will send both
// key up and key down) but not key up.
mGotEnterDown = true;
@@ -269,7 +246,7 @@ import java.util.ArrayList;
// with WebCore's notion of the current selection, reset the selection
// to what it was before the key event.
Selection.setSelection(text, oldStart, oldEnd);
- // Ignore the key up event for newlines or dpad center. This prevents
+ // Ignore the key up event for newlines. This prevents
// multiple newlines in the native textarea.
if (mGotEnterDown && !down) {
return true;
@@ -391,27 +368,8 @@ import java.util.ArrayList;
if (isPopupShowing()) {
return super.onTrackballEvent(event);
}
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (!mTrackballDown) {
- mTrackballDown = true;
- mHandler.sendEmptyMessageDelayed(LONGPRESS,
- ViewConfiguration.getLongPressTimeout());
- }
- return true;
- case MotionEvent.ACTION_UP:
- if (mTrackballDown) {
- mWebView.shortPressOnTextField();
- mTrackballDown = false;
- mHandler.removeMessages(LONGPRESS);
- }
- return true;
- case MotionEvent.ACTION_CANCEL:
- mTrackballDown = false;
- return true;
- case MotionEvent.ACTION_MOVE:
- // fall through
+ if (event.getAction() != MotionEvent.ACTION_MOVE) {
+ return false;
}
Spannable text = (Spannable) getText();
MovementMethod move = getMovementMethod();
@@ -442,7 +400,6 @@ import java.util.ArrayList;
// hide the soft keyboard when the edit text is out of focus
InputMethodManager.getInstance(mContext).hideSoftInputFromWindow(
getWindowToken(), 0);
- mHandler.removeMessages(LONGPRESS);
mWebView.removeView(this);
mWebView.requestFocus();
mScrollToAccommodateCursor = false;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index c59a5fc..5126ef0 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -71,6 +71,7 @@ import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.Toast;
+import android.widget.ZoomButtonsController;
import android.widget.ZoomControls;
import android.widget.ZoomRingController;
import android.widget.FrameLayout;
@@ -284,8 +285,10 @@ public class WebView extends AbsoluteLayout
/**
* Customizable constant
*/
- // pre-computed square of ViewConfiguration.getTouchSlop()
+ // pre-computed square of ViewConfiguration.getScaledTouchSlop()
private int mTouchSlopSquare;
+ // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
+ private int mDoubleTapSlopSquare;
// This should be ViewConfiguration.getTapTimeout()
// But system time out is 100ms, which is too short for the browser.
// In the browser, if it switches out of tap too soon, jump tap won't work.
@@ -321,6 +324,8 @@ public class WebView extends AbsoluteLayout
private int mContentHeight; // cache of value from WebViewCore
static int MAX_FLOAT_CONTENT_WIDTH = 480;
+ // the calculated minimum content width for calculating the minimum scale.
+ // If it is 0, it means don't use it.
private int mMinContentWidth;
// Need to have the separate control for horizontal and vertical scrollbar
@@ -553,7 +558,9 @@ public class WebView extends AbsoluteLayout
return mExtra;
}
}
-
+
+ private ZoomButtonsController mZoomButtonsController;
+
private ZoomRingController mZoomRingController;
private ImageView mZoomRingOverview;
private Animation mZoomRingOverviewExitAnimation;
@@ -617,6 +624,9 @@ public class WebView extends AbsoluteLayout
/ ZOOM_RING_STEPS;
}
mZoomRingController.setThumbAngle(angle * MAX_ZOOM_RING_ANGLE);
+
+ // Don't show a thumb if the user cannot zoom
+ mZoomRingController.setThumbVisible(mMinZoomScale != mMaxZoomScale);
// Show the zoom overview tab on the ring
setZoomOverviewVisible(true);
@@ -733,6 +743,26 @@ public class WebView extends AbsoluteLayout
mZoomRingController.setPannerAcceleration(160);
mZoomRingController.setPannerStartAcceleratingDuration(700);
createZoomRingOverviewTab();
+ mZoomButtonsController = new ZoomButtonsController(context, this);
+ mZoomButtonsController.setOverviewVisible(true);
+ mZoomButtonsController.setCallback(new ZoomButtonsController.OnZoomListener() {
+ public void onCenter(int x, int y) {
+ mZoomListener.onCenter(x, y);
+ }
+
+ public void onOverview() {
+ mZoomButtonsController.setVisible(false);
+ zoomScrollOut();
+ }
+
+ public void onVisibilityChanged(boolean visible) {
+ mZoomListener.onVisibilityChanged(visible);
+ }
+
+ public void onZoom(boolean zoomIn) {
+ mZoomListener.onSimpleZoom(zoomIn);
+ }
+ });
}
private void init() {
@@ -745,6 +775,9 @@ public class WebView extends AbsoluteLayout
final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mTouchSlopSquare = slop * slop;
mMinLockSnapReverseDistance = slop;
+ final int doubleTapslop = ViewConfiguration.get(getContext())
+ .getScaledDoubleTapSlop();
+ mDoubleTapSlopSquare = doubleTapslop * doubleTapslop;
}
private void createZoomRingOverviewTab() {
@@ -763,7 +796,7 @@ public class WebView extends AbsoluteLayout
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER);
// TODO: magic constant that's based on the zoom ring radius + some offset
- lp.topMargin = 208;
+ lp.topMargin = 200;
mZoomRingOverview.setLayoutParams(lp);
mZoomRingOverview.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
@@ -2305,8 +2338,19 @@ public class WebView extends AbsoluteLayout
/**
* Use this function to bind an object to Javascript so that the
* methods can be accessed from Javascript.
- * IMPORTANT, the object that is bound runs in another thread and
- * not in the thread that it was constructed in.
+ * <p><strong>IMPORTANT:</strong>
+ * <ul>
+ * <li> Using addJavascriptInterface() allows JavaScript to control your
+ * application. This can be a very useful feature or a dangerous security
+ * issue. When the HTML in the WebView is untrustworthy (for example, part
+ * or all of the HTML is provided by some person or process), then an
+ * attacker could inject HTML that will execute your code and possibly any
+ * code of the attacker's choosing.<br>
+ * Do not use addJavascriptInterface() unless all of the HTML in this
+ * WebView was written by you.</li>
+ * <li> The Java object that is bound runs in another thread and not in
+ * the thread that it was constructed in.</li>
+ * </ul></p>
* @param obj The class instance to bind to Javascript
* @param interfaceName The name to used to expose the class in Javascript
*/
@@ -2969,8 +3013,8 @@ public class WebView extends AbsoluteLayout
if (lp != null) {
// Take the last touch and adjust for the location of the
// TextDialog.
- float x = mLastTouchX - lp.x;
- float y = mLastTouchY - lp.y;
+ float x = mLastTouchX + (float) (mScrollX - lp.x);
+ float y = mLastTouchY + (float) (mScrollY - lp.y);
mTextEntry.fakeTouchEvent(x, y);
}
}
@@ -3164,6 +3208,9 @@ public class WebView extends AbsoluteLayout
mSelectX = mScrollX + (int) mLastTouchX;
mSelectY = mScrollY + (int) mLastTouchY;
}
+ int contentX = viewToContent((int) mLastTouchX + mScrollX);
+ int contentY = viewToContent((int) mLastTouchY + mScrollY);
+ nativeClearFocus(contentX, contentY);
}
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
@@ -3355,6 +3402,9 @@ public class WebView extends AbsoluteLayout
public void emulateShiftHeld() {
mExtendSelection = false;
mShiftIsPressed = true;
+ int contentX = viewToContent((int) mLastTouchX + mScrollX);
+ int contentY = viewToContent((int) mLastTouchY + mScrollY);
+ nativeClearFocus(contentX, contentY);
}
private boolean commitCopy() {
@@ -3401,6 +3451,7 @@ public class WebView extends AbsoluteLayout
// Clean up the zoom ring
mZoomRingController.setVisible(false);
+ mZoomButtonsController.setVisible(false);
}
// Implementation for OnHierarchyChangeListener
@@ -3449,8 +3500,17 @@ public class WebView extends AbsoluteLayout
// false for the first parameter
}
} else {
- // If our window has lost focus, stop drawing the focus ring
- mDrawFocusRing = false;
+ if (!mZoomButtonsController.isVisible()) {
+ /*
+ * The zoom controls come in their own window, so our window
+ * loses focus. Our policy is to not draw the focus ring if
+ * our window is not focused, but this is an exception since
+ * the user can still navigate the web page with the zoom
+ * controls showing.
+ */
+ // If our window has lost focus, stop drawing the focus ring
+ mDrawFocusRing = false;
+ }
mGotKeyDown = false;
mShiftIsPressed = false;
if (mNativeClass != 0) {
@@ -3592,7 +3652,8 @@ public class WebView extends AbsoluteLayout
+ mTouchMode);
}
- if (mZoomRingController.isVisible() && mInZoomTapDragMode) {
+ if ((mZoomRingController.isVisible() || mZoomButtonsController.isVisible())
+ && mInZoomTapDragMode) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
// Just released the second tap, no longer in tap-drag mode
mInZoomTapDragMode = false;
@@ -3630,6 +3691,9 @@ public class WebView extends AbsoluteLayout
mLastSentTouchTime = eventTime;
}
+ int deltaX = (int) (mLastTouchX - x);
+ int deltaY = (int) (mLastTouchY - y);
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN
@@ -3655,16 +3719,23 @@ public class WebView extends AbsoluteLayout
, viewToContent(mSelectY), false);
mTouchSelection = mExtendSelection = true;
} else if (!ZoomRingController.useOldZoom(mContext) &&
- mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
+ mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP) &&
+ (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare)) {
// Found doubletap, invoke the zoom controller
mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
- mZoomRingController.setVisible(true);
+ int contentX = viewToContent((int) mLastTouchX + mScrollX);
+ int contentY = viewToContent((int) mLastTouchY + mScrollY);
+ if (inEditingMode()) {
+ mTextEntry.updateCachedTextfield();
+ }
+ nativeClearFocus(contentX, contentY);
mInZoomTapDragMode = true;
if (mLogEvent) {
EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
(eventTime - mLastTouchUpTime), eventTime);
}
- return mZoomRingController.handleDoubleTapEvent(ev);
+ return mZoomRingController.handleDoubleTapEvent(ev) ||
+ mZoomButtonsController.handleDoubleTapEvent(ev);
} else {
mTouchMode = TOUCH_INIT_MODE;
mPreventDrag = mForwardTouchEvents;
@@ -3701,9 +3772,6 @@ public class WebView extends AbsoluteLayout
}
mVelocityTracker.addMovement(ev);
- int deltaX = (int) (mLastTouchX - x);
- int deltaY = (int) (mLastTouchY - y);
-
if (mTouchMode != TOUCH_DRAG_MODE) {
if (mTouchMode == TOUCH_SELECT_MODE) {
mSelectX = mScrollX + (int) x;
@@ -5092,6 +5160,8 @@ public class WebView extends AbsoluteLayout
});
if (mSelection != -1) {
listView.setSelection(mSelection);
+ listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ listView.setItemChecked(mSelection, true);
}
}
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index a7261c5..6ab088d 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1294,7 +1294,9 @@ final class WebViewCore {
draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
Message.obtain(mWebView.mPrivateHandler,
- WebView.NEW_PICTURE_MSG_ID, nativeGetContentMinPrefWidth(),
+ WebView.NEW_PICTURE_MSG_ID,
+ mViewportMinimumScale == 0 ? nativeGetContentMinPrefWidth()
+ : 0,
0, draw).sendToTarget();
nativeCheckNavCache();
if (mWebkitScrollX != 0 || mWebkitScrollY != 0) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f362e22..9da78d0 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -17,7 +17,6 @@
package android.widget;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -28,6 +27,7 @@ import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -893,25 +893,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mSyncMode = SYNC_FIRST_POSITION;
}
- // Don't restore the type filter window when there is no keyboard
- if (acceptFilter()) {
- String filterText = ss.filter;
- setFilterText(filterText);
- }
+ setFilterText(ss.filter);
requestLayout();
}
private boolean acceptFilter() {
final Context context = mContext;
- final Configuration configuration = context.getResources().getConfiguration();
- final boolean keyboardShowing = configuration.keyboardHidden !=
- Configuration.KEYBOARDHIDDEN_YES;
- final boolean hasKeyboard = configuration.keyboard != Configuration.KEYBOARD_NOKEYS;
final InputMethodManager inputManager = (InputMethodManager)
context.getSystemService(Context.INPUT_METHOD_SERVICE);
- return (hasKeyboard && keyboardShowing) ||
- (!hasKeyboard && !inputManager.isFullscreenMode());
+ return !inputManager.isFullscreenMode();
}
/**
@@ -922,7 +913,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
public void setFilterText(String filterText) {
// TODO: Should we check for acceptFilter()?
- if (mTextFilterEnabled && filterText != null && filterText.length() > 0) {
+ if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
createTextFilter(false);
// This is going to call our listener onTextChanged, but we might not
// be ready to bring up a window yet
@@ -942,6 +933,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /**
+ * Returns the list's text filter, if available.
+ * @return the list's text filter or null if filtering isn't enabled
+ * @hide pending API Council approval
+ */
+ public CharSequence getTextFilter() {
+ if (mTextFilterEnabled && mTextFilter != null) {
+ return mTextFilter.getText();
+ }
+ return null;
+ }
+
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index b046a6b..1d553f1 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -40,9 +40,15 @@ public abstract class AbsSeekBar extends ProgressBar {
* Whether this is user seekable.
*/
boolean mIsUserSeekable = true;
+
+ /**
+ * On key presses (right or left), the amount to increment/decrement the
+ * progress.
+ */
+ private int mKeyProgressIncrement = 1;
private static final int NO_ALPHA = 0xFF;
- float mDisabledAlpha;
+ private float mDisabledAlpha;
public AbsSeekBar(Context context) {
super(context);
@@ -101,6 +107,39 @@ public abstract class AbsSeekBar extends ProgressBar {
invalidate();
}
+ /**
+ * Sets the amount of progress changed via the arrow keys.
+ *
+ * @param increment The amount to increment or decrement when the user
+ * presses the arrow keys.
+ */
+ public void setKeyProgressIncrement(int increment) {
+ mKeyProgressIncrement = increment < 0 ? -increment : increment;
+ }
+
+ /**
+ * Returns the amount of progress changed via the arrow keys.
+ * <p>
+ * By default, this will be a value that is derived from the max progress.
+ *
+ * @return The amount to increment or decrement when the user presses the
+ * arrow keys. This will be positive.
+ */
+ public int getKeyProgressIncrement() {
+ return mKeyProgressIncrement;
+ }
+
+ @Override
+ public synchronized void setMax(int max) {
+ super.setMax(max);
+
+ if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
+ // It will take the user too long to change this via keys, change it
+ // to something more reasonable
+ setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
+ }
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mThumb || super.verifyDrawable(who);
@@ -321,12 +360,12 @@ public abstract class AbsSeekBar extends ProgressBar {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (progress <= 0) break;
- setProgress(progress - 1, true);
+ setProgress(progress - mKeyProgressIncrement, true);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (progress >= getMax()) break;
- setProgress(progress + 1, true);
+ setProgress(progress + mKeyProgressIncrement, true);
return true;
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index cf9c588..f847bc3 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -48,7 +48,6 @@ public class AnalogClock extends View {
private int mDialHeight;
private boolean mAttached;
- private long mLastTime;
private final Handler mHandler = new Handler();
private float mMinutes;
@@ -96,7 +95,6 @@ public class AnalogClock extends View {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- onTimeChanged();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();
@@ -107,6 +105,15 @@ public class AnalogClock extends View {
getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
+
+ // NOTE: It's safe to do these after registering the receiver since the receiver always runs
+ // in the main thread, therefore the receiver can't run before this method returns.
+
+ // The time zone may have changed while the receiver wasn't registered, so update the Time
+ mCalendar = new Time();
+
+ // Make sure we update to the current time
+ onTimeChanged();
}
@Override
@@ -212,9 +219,7 @@ public class AnalogClock extends View {
}
private void onTimeChanged() {
- long time = System.currentTimeMillis();
- mCalendar.set(time);
- mLastTime = time;
+ mCalendar.setToNow();
int hour = mCalendar.hour;
int minute = mCalendar.minute;
@@ -231,8 +236,6 @@ public class AnalogClock extends View {
if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
String tz = intent.getStringExtra("time-zone");
mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
- } else {
- mCalendar = new Time();
}
onTimeChanged();
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 7a51676..0c1c72a 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -78,6 +78,8 @@ import com.android.internal.R;
* @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
* @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
static final boolean DEBUG = false;
@@ -96,6 +98,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private DropDownListView mDropDownList;
private int mDropDownVerticalOffset;
private int mDropDownHorizontalOffset;
+ private int mDropDownAnchorId;
+ private View mDropDownAnchorView; // view is retrieved lazily from id once needed
+ private int mDropDownWidth;
private Drawable mDropDownListHighlight;
@@ -147,6 +152,18 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
mDropDownHorizontalOffset = (int)
a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
+
+ // Get the anchor's id now, but the view won't be ready, so wait to actually get the
+ // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
+ // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
+ // this TextView, as a default anchoring point.
+ mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
+ View.NO_ID);
+
+ // For dropdown width, the developer can specify a specific width, or FILL_PARENT
+ // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
+ mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
R.layout.simple_dropdown_hint);
@@ -187,6 +204,49 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void setCompletionHint(CharSequence hint) {
mHintText = hint;
}
+
+ /**
+ * <p>Returns the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+ *
+ * @return the width for the drop down list
+ */
+ public int getDropDownWidth() {
+ return mDropDownWidth;
+ }
+
+ /**
+ * <p>Sets the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+ *
+ * @param width the width to use
+ */
+ public void setDropDownWidth(int width) {
+ mDropDownWidth = width;
+ }
+
+ /**
+ * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
+ *
+ * @return the view's id, or {@link View#NO_ID} if none specified
+ */
+ public int getDropDownAnchor() {
+ return mDropDownAnchorId;
+ }
+
+ /**
+ * <p>Sets the view to which the auto-complete drop down list should anchor. The view
+ * corresponding to this id will not be loaded until the next time it is needed to avoid
+ * loading a view which is not yet instantiated.</p>
+ *
+ * @param id the id to anchor the drop down list view to
+ */
+ public void setDropDownAnchor(int id) {
+ mDropDownAnchorId = id;
+ mDropDownAnchorView = null;
+ }
/**
* <p>Returns the number of characters the user must type before the drop
@@ -741,6 +801,18 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return result;
}
+
+ /**
+ * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
+ * the id is NO_ID or we can't find a view for the given id, we return this TextView as
+ * the default anchoring point.</p>
+ */
+ private View getDropDownAnchorView() {
+ if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
+ mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
+ }
+ return mDropDownAnchorView == null ? this : mDropDownAnchorView;
+ }
/**
* <p>Displays the drop down on screen.</p>
@@ -748,16 +820,37 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void showDropDown() {
int height = buildDropDown();
if (mPopup.isShowing()) {
- mPopup.update(this, mDropDownHorizontalOffset, mDropDownVerticalOffset,
- getWidth(), height);
+ int widthSpec;
+ if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ // The call to PopupWindow's update method below can accept -1 for any
+ // value you do not want to update.
+ widthSpec = -1;
+ } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ widthSpec = getDropDownAnchorView().getWidth();
+ } else {
+ widthSpec = mDropDownWidth;
+ }
+ mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
+ mDropDownVerticalOffset, widthSpec, height);
} else {
- mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
- mPopup.setWidth(getWidth());
+ int widthSpec;
+ if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ } else {
+ mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
+ if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ mPopup.setWidth(getDropDownAnchorView().getWidth());
+ } else {
+ mPopup.setWidth(mDropDownWidth);
+ }
+ }
mPopup.setHeight(height);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
mPopup.setOutsideTouchable(true);
mPopup.setTouchInterceptor(new PopupTouchIntercepter());
- mPopup.showAsDropDown(this, mDropDownHorizontalOffset, mDropDownVerticalOffset);
+ mPopup.showAsDropDown(getDropDownAnchorView(),
+ mDropDownHorizontalOffset, mDropDownVerticalOffset);
mDropDownList.setSelection(ListView.INVALID_POSITION);
mDropDownList.hideSelector();
mDropDownList.requestFocus();
diff --git a/core/java/android/widget/BaseAdapter.java b/core/java/android/widget/BaseAdapter.java
index 1921d73..532fd76 100644
--- a/core/java/android/widget/BaseAdapter.java
+++ b/core/java/android/widget/BaseAdapter.java
@@ -42,6 +42,10 @@ public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
mDataSetObservable.unregisterObserver(observer);
}
+ /**
+ * Notifies the attached View that the underlying data has been changed
+ * and it should refresh itself.
+ */
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 369221e..91add58 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -69,7 +69,10 @@ public class Chronometer extends TextView {
private Object[] mFormatterArgs = new Object[1];
private StringBuilder mFormatBuilder;
private OnChronometerTickListener mOnChronometerTickListener;
-
+ private StringBuilder mRecycle = new StringBuilder(8);
+
+ private static final int TICK_WHAT = 2;
+
/**
* Initialize this Chronometer object.
* Sets the base to the current time.
@@ -115,6 +118,7 @@ public class Chronometer extends TextView {
@android.view.RemotableViewMethod
public void setBase(long base) {
mBase = base;
+ dispatchChronometerTick();
updateText(SystemClock.elapsedRealtime());
}
@@ -216,10 +220,10 @@ public class Chronometer extends TextView {
updateRunning();
}
- private void updateText(long now) {
+ private synchronized void updateText(long now) {
long seconds = now - mBase;
seconds /= 1000;
- String text = DateUtils.formatElapsedTime(seconds);
+ String text = DateUtils.formatElapsedTime(mRecycle, seconds);
if (mFormat != null) {
Locale loc = Locale.getDefault();
@@ -247,7 +251,10 @@ public class Chronometer extends TextView {
if (running != mRunning) {
if (running) {
updateText(SystemClock.elapsedRealtime());
- mHandler.sendMessageDelayed(Message.obtain(), 1000);
+ dispatchChronometerTick();
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), 1000);
+ } else {
+ mHandler.removeMessages(TICK_WHAT);
}
mRunning = running;
}
@@ -255,10 +262,10 @@ public class Chronometer extends TextView {
private Handler mHandler = new Handler() {
public void handleMessage(Message m) {
- if (mStarted) {
+ if (mRunning) {
updateText(SystemClock.elapsedRealtime());
dispatchChronometerTick();
- sendMessageDelayed(Message.obtain(), 1000);
+ sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
}
}
};
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 3d758e7..898e501 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -348,6 +348,21 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
mFilterQueryProvider = filterQueryProvider;
}
+ /**
+ * Called when the {@link ContentObserver} on the cursor receives a change notification.
+ * The default implementation provides the auto-requery logic, but may be overridden by
+ * sub classes.
+ *
+ * @see ContentObserver#onChange(boolean)
+ * @hide pending API Council approval
+ */
+ protected void onContentChanged() {
+ if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
+ if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
+ mDataValid = mCursor.requery();
+ }
+ }
+
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
@@ -360,10 +375,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
@Override
public void onChange(boolean selfChange) {
- if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
- mDataValid = mCursor.requery();
- }
+ onContentChanged();
}
}
diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java
index a2316cf..1d0fd5e 100644
--- a/core/java/android/widget/Filter.java
+++ b/core/java/android/widget/Filter.java
@@ -45,8 +45,6 @@ public abstract class Filter {
private Handler mThreadHandler;
private Handler mResultHandler;
- private String mConstraint;
- private boolean mConstraintIsValid = false;
/**
* <p>Creates a new asynchronous filter.</p>
@@ -84,13 +82,6 @@ public abstract class Filter {
*/
public final void filter(CharSequence constraint, FilterListener listener) {
synchronized (this) {
- String constraintAsString = constraint != null ? constraint.toString() : null;
- if (mConstraintIsValid && (
- (constraintAsString == null && mConstraint == null) ||
- (constraintAsString != null && constraintAsString.equals(mConstraint)))) {
- // nothing to do
- return;
- }
if (mThreadHandler == null) {
HandlerThread thread = new HandlerThread(THREAD_NAME);
@@ -103,16 +94,13 @@ public abstract class Filter {
RequestArguments args = new RequestArguments();
// make sure we use an immutable copy of the constraint, so that
// it doesn't change while the filter operation is in progress
- args.constraint = constraintAsString;
+ args.constraint = constraint != null ? constraint.toString() : null;
args.listener = listener;
message.obj = args;
mThreadHandler.removeMessages(FILTER_TOKEN);
mThreadHandler.removeMessages(FINISH_TOKEN);
mThreadHandler.sendMessage(message);
-
- mConstraint = constraintAsString;
- mConstraintIsValid = true;
}
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 38bfc7c..6bbf062 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -924,32 +924,23 @@ public class GridView extends AbsListView {
final int count = mItemCount;
if (count > 0) {
final View child = obtainView(0);
- final int childViewType = mAdapter.getItemViewType(0);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+ if (p == null) {
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
-
- final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- mListPadding.left + mListPadding.right, lp.width);
-
- int lpHeight = lp.height;
-
- int childHeightSpec;
- if (lpHeight > 0) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
+ p.viewType = mAdapter.getItemViewType(0);
+ int childHeightSpec = getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
+ int childWidthSpec = getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
child.measure(childWidthSpec, childHeightSpec);
+
childHeight = child.getMeasuredHeight();
- if (mRecycler.shouldRecycleViewType(childViewType)) {
+ if (mRecycler.shouldRecycleViewType(p.viewType)) {
mRecycler.addScrapView(child);
}
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 96fe595..652e30c 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -875,7 +875,7 @@ public class HorizontalScrollView extends FrameLayout {
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
- final int childHeightMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 94d1bd1..a4523b9 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -840,7 +840,7 @@ public class ImageView extends View {
@Override
public int getBaseline() {
- return mBaselineAligned ? getHeight() : -1;
+ return mBaselineAligned ? getMeasuredHeight() : -1;
}
/**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 4e5989c..6df72d4 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1011,34 +1011,13 @@ public class ListView extends AbsListView {
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0);
- final int childViewType = mAdapter.getItemViewType(0);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
-
- final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- mListPadding.left + mListPadding.right, lp.width);
-
- int lpHeight = lp.height;
-
- int childHeightSpec;
- if (lpHeight > 0) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- }
-
- child.measure(childWidthSpec, childHeightSpec);
+ measureScrapChild(child, 0, widthMeasureSpec);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
- if (mRecycler.shouldRecycleViewType(childViewType)) {
+ if (recycleOnMeasure()) {
mRecycler.addScrapView(child);
}
}
@@ -1055,13 +1034,40 @@ public class ListView extends AbsListView {
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
- heightSize = measureHeightOfChildren(
- MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
- 0, NO_POSITION, heightSize, -1);
+ heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
- mWidthMeasureSpec = widthMeasureSpec;
+ mWidthMeasureSpec = widthMeasureSpec;
+ }
+
+ private void measureScrapChild(View child, int position, int widthMeasureSpec) {
+ LayoutParams p = (LayoutParams) child.getLayoutParams();
+ if (p == null) {
+ p = new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ }
+ p.viewType = mAdapter.getItemViewType(position);
+
+ int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+ mListPadding.left + mListPadding.right, p.width);
+ int lpHeight = p.height;
+ int childHeightSpec;
+ if (lpHeight > 0) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ /**
+ * @return True to recycle the views used to measure this ListView in
+ * UNSPECIFIED/AT_MOST modes, false otherwise.
+ * @hide
+ */
+ protected boolean recycleOnMeasure() {
+ return true;
}
/**
@@ -1090,8 +1096,8 @@ public class ListView extends AbsListView {
* startPosition is 0).
* @return The height of this ListView with the given children.
*/
- final int measureHeightOfChildren(final int widthMeasureSpec, final int startPosition,
- int endPosition, final int maxHeight, int disallowPartialChildPosition) {
+ final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
+ final int maxHeight, int disallowPartialChildPosition) {
final ListAdapter adapter = mAdapter;
if (adapter == null) {
@@ -1110,29 +1116,20 @@ public class ListView extends AbsListView {
// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
+ final boolean recyle = recycleOnMeasure();
+
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i);
- final int childViewType = adapter.getItemViewType(i);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
+ measureScrapChild(child, i, widthMeasureSpec);
if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}
- child.measure(widthMeasureSpec, lp.height >= 0
- ? MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY)
- : MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-
// Recycle the view before we possibly return from the method
- if (recycleBin.shouldRecycleViewType(childViewType)) {
+ if (recyle) {
recycleBin.addScrapView(child);
}
@@ -1656,7 +1653,7 @@ public class ListView extends AbsListView {
// Respect layout params that are already in the view. Otherwise make some up...
// noinspection unchecked
- AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+ AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
if (p == null) {
p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
@@ -1675,7 +1672,7 @@ public class ListView extends AbsListView {
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
if (child instanceof Checkable) {
- ((Checkable)child).setChecked(mCheckStates.get(position));
+ ((Checkable) child).setChecked(mCheckStates.get(position));
}
}
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index 59a9310..05abc26 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -126,7 +126,7 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
Editable text = getText();
int end = getSelectionEnd();
- if (end < 0) {
+ if (end < 0 || mTokenizer == null) {
return false;
}
@@ -147,7 +147,7 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
public void performValidation() {
Validator v = getValidator();
- if (v == null) {
+ if (v == null || mTokenizer == null) {
return;
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 4a5cea1..53db77e 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -30,6 +30,7 @@ import android.view.View.OnTouchListener;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
import android.os.IBinder;
import android.content.Context;
import android.content.res.TypedArray;
@@ -102,6 +103,8 @@ public class PopupWindow {
private Rect mTempRect = new Rect();
private Drawable mBackground;
+ private Drawable mAboveAnchorBackgroundDrawable;
+ private Drawable mBelowAnchorBackgroundDrawable;
private boolean mAboveAnchor;
@@ -164,6 +167,43 @@ public class PopupWindow {
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+ // If this is a StateListDrawable, try to find and store the drawable to be
+ // used when the drop-down is placed above its anchor view, and the one to be
+ // used when the drop-down is placed below its anchor view. We extract
+ // the drawables ourselves to work around a problem with using refreshDrawableState
+ // that it will take into account the padding of all drawables specified in a
+ // StateListDrawable, thus adding superfluous padding to drop-down views.
+ //
+ // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
+ // at least one other drawable, intended for the 'below-anchor state'.
+ if (mBackground instanceof StateListDrawable) {
+ StateListDrawable background = (StateListDrawable) mBackground;
+
+ // Find the above-anchor view - this one's easy, it should be labeled as such.
+ int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
+
+ // Now, for the below-anchor view, look for any other drawable specified in the
+ // StateListDrawable which is not for the above-anchor state and use that.
+ int count = background.getStateCount();
+ int belowAnchorStateIndex = -1;
+ for (int i = 0; i < count; i++) {
+ if (i != aboveAnchorStateIndex) {
+ belowAnchorStateIndex = i;
+ break;
+ }
+ }
+
+ // Store the drawables we found, if we found them. Otherwise, set them both
+ // to null so that we'll just use refreshDrawableState.
+ if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
+ mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex);
+ mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex);
+ } else {
+ mBelowAnchorBackgroundDrawable = null;
+ mAboveAnchorBackgroundDrawable = null;
+ }
+ }
+
a.recycle();
}
@@ -661,7 +701,18 @@ public class PopupWindow {
mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
if (mBackground != null) {
- mPopupView.refreshDrawableState();
+ // If the background drawable provided was a StateListDrawable with above-anchor
+ // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
+ // do the job.
+ if (mAboveAnchorBackgroundDrawable != null) {
+ if (mAboveAnchor) {
+ mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
+ } else {
+ mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
+ }
+ } else {
+ mPopupView.refreshDrawableState();
+ }
}
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
@@ -697,12 +748,18 @@ public class PopupWindow {
*/
private void preparePopup(WindowManager.LayoutParams p) {
if (mBackground != null) {
+ final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+ int height = ViewGroup.LayoutParams.FILL_PARENT;
+ if (layoutParams != null &&
+ layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+
// when a background is available, we embed the content view
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT
+ ViewGroup.LayoutParams.FILL_PARENT, height
);
popupViewContainer.setBackgroundDrawable(mBackground);
popupViewContainer.addView(mContentView, listParams);
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index dd2570a..f646ab5 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -526,6 +526,7 @@ public class ProgressBar extends View {
* @see #getProgress()
* @see #incrementProgressBy(int)
*/
+ @android.view.RemotableViewMethod
public synchronized void setProgress(int progress) {
setProgress(progress, false);
}
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index 9052ae3..a5dbd98 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -46,10 +46,30 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
public ResourceCursorAdapter(Context context, int layout, Cursor c) {
super(context, c);
mLayout = mDropDownLayout = layout;
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
+ * Constructor.
+ *
+ * @param context The context where the ListView associated with this
+ * SimpleListItemFactory is running
+ * @param layout resource identifier of a layout file that defines the views
+ * for this list item. Unless you override them later, this will
+ * define both the item views and the drop down views.
+ * @param c The cursor from which to get the data.
+ * @param autoRequery If true the adapter will call requery() on the
+ * cursor whenever it changes so the most recent
+ * data is always displayed.
+ * @hide Pending API Council approval
+ */
+ public ResourceCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
+ super(context, c, autoRequery);
+ mLayout = mDropDownLayout = layout;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /**
* Inflates view(s) from the specified XML file.
*
* @see android.widget.CursorAdapter#newView(android.content.Context,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c852be5..88b2a01 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -84,6 +84,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
+import android.view.ViewRoot;
import android.view.ViewTreeObserver;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
@@ -215,7 +216,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
int mDrawablePadding;
- };
+ }
private Drawables mDrawables;
private CharSequence mError;
@@ -239,8 +240,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mMarqueeRepeatLimit = 3;
class InputContentType {
- String privateContentType;
+ int imeOptions = EditorInfo.IME_UNDEFINED;
+ String privateImeOptions;
+ CharSequence imeActionLabel;
+ int imeActionId;
Bundle extras;
+ OnEditorActionListener onEditorActionListener;
+ boolean enterDown;
}
InputContentType mInputContentType;
@@ -268,6 +274,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
p.measureText("H");
}
+ /**
+ * Interface definition for a callback to be invoked when an action is
+ * performed on the editor.
+ */
+ public interface OnEditorActionListener {
+ /**
+ * Called when an action is being performed.
+ *
+ * @param v The view that was clicked.
+ * @param actionId Identifier of the action. This will be either the
+ * identifier you supplied, or {@link EditorInfo#IME_UNDEFINED
+ * EditorInfo.IME_UNDEFINED} if being called due to the enter key
+ * being pressed.
+ * @param event If triggered by an enter key, this is the event;
+ * otherwise, this is null.
+ * @return Return true if you have consumed the action, else false.
+ */
+ boolean onEditorAction(TextView v, int actionId, KeyEvent event);
+ }
+
public TextView(Context context) {
this(context, null);
}
@@ -376,7 +402,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int shadowcolor = 0;
float dx = 0, dy = 0, r = 0;
boolean password = false;
- int contentType = EditorInfo.TYPE_NULL;
+ int inputType = EditorInfo.TYPE_NULL;
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
@@ -610,11 +636,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_inputType:
- contentType = a.getInt(attr, mInputType);
+ inputType = a.getInt(attr, mInputType);
break;
- case com.android.internal.R.styleable.TextView_editorPrivateContentType:
- setPrivateContentType(a.getString(attr));
+ case com.android.internal.R.styleable.TextView_imeOptions:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeOptions = a.getInt(attr,
+ mInputContentType.imeOptions);
+ break;
+
+ case com.android.internal.R.styleable.TextView_imeActionLabel:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionLabel = a.getText(attr);
+ break;
+
+ case com.android.internal.R.styleable.TextView_imeActionId:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionId = a.getInt(attr,
+ mInputContentType.imeActionId);
+ break;
+
+ case com.android.internal.R.styleable.TextView_privateImeOptions:
+ setPrivateImeOptions(a.getString(attr));
break;
case com.android.internal.R.styleable.TextView_editorExtras:
@@ -632,7 +681,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BufferType bufferType = BufferType.EDITABLE;
- if ((contentType&(EditorInfo.TYPE_MASK_CLASS
+ if ((inputType&(EditorInfo.TYPE_MASK_CLASS
|EditorInfo.TYPE_MASK_VARIATION))
== (EditorInfo.TYPE_CLASS_TEXT
|EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
@@ -656,57 +705,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
throw new RuntimeException(ex);
}
try {
- mInputType = contentType != EditorInfo.TYPE_NULL
- ? contentType
+ mInputType = inputType != EditorInfo.TYPE_NULL
+ ? inputType
: mInput.getInputType();
} catch (IncompatibleClassChangeError e) {
mInputType = EditorInfo.TYPE_CLASS_TEXT;
}
} else if (digits != null) {
mInput = DigitsKeyListener.getInstance(digits.toString());
- mInputType = contentType;
- } else if (contentType != EditorInfo.TYPE_NULL) {
- setInputType(contentType, true);
- singleLine = (contentType&(EditorInfo.TYPE_MASK_CLASS
+ mInputType = inputType;
+ } else if (inputType != EditorInfo.TYPE_NULL) {
+ setInputType(inputType, true);
+ singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
(EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
} else if (phone) {
mInput = DialerKeyListener.getInstance();
- contentType = EditorInfo.TYPE_CLASS_PHONE;
+ inputType = EditorInfo.TYPE_CLASS_PHONE;
} else if (numeric != 0) {
mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
(numeric & DECIMAL) != 0);
- contentType = EditorInfo.TYPE_CLASS_NUMBER;
+ inputType = EditorInfo.TYPE_CLASS_NUMBER;
if ((numeric & SIGNED) != 0) {
- contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
+ inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
}
if ((numeric & DECIMAL) != 0) {
- contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
+ inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
}
- mInputType = contentType;
+ mInputType = inputType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
- contentType = EditorInfo.TYPE_CLASS_TEXT;
+ inputType = EditorInfo.TYPE_CLASS_TEXT;
if (!singleLine) {
- contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
}
switch (autocap) {
case 1:
cap = TextKeyListener.Capitalize.SENTENCES;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
break;
case 2:
cap = TextKeyListener.Capitalize.WORDS;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
break;
case 3:
cap = TextKeyListener.Capitalize.CHARACTERS;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
break;
default:
@@ -715,7 +764,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mInput = TextKeyListener.getInstance(autotext, cap);
- mInputType = contentType;
+ mInputType = inputType;
} else if (editable) {
mInput = TextKeyListener.getInstance();
mInputType = EditorInfo.TYPE_CLASS_TEXT;
@@ -1075,6 +1124,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_singleLine
*/
public final void setTransformationMethod(TransformationMethod method) {
+ if (method == mTransformation) {
+ // Avoid the setText() below if the transformation is
+ // the same.
+ return;
+ }
if (mTransformation != null) {
if (mText instanceof Spannable) {
((Spannable) mText).removeSpan(mTransformation);
@@ -2778,7 +2832,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Directly change the content type integer of the text view, without
* modifying any other state.
- * @see #setContentType
+ * @see #setInputType(int)
* @see android.text.InputType
* @attr ref android.R.styleable#TextView_inputType
*/
@@ -2842,28 +2896,159 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Change the editor type integer associated with the text view, which
+ * will be reported to an IME with {@link EditorInfo#imeOptions} when it
+ * has focus.
+ * @see #getImeOptions
+ * @see android.view.inputmethod.EditorInfo
+ * @attr ref android.R.styleable#TextView_imeOptions
+ */
+ public void setImeOptions(int imeOptions) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeOptions = imeOptions;
+ }
+
+ /**
+ * Get the type of the IME editor.
+ *
+ * @see #setImeOptions(int)
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public int getImeOptions() {
+ return mInputContentType != null
+ ? mInputContentType.imeOptions : EditorInfo.IME_UNDEFINED;
+ }
+
+ /**
+ * Change the custom IME action associated with the text view, which
+ * will be reported to an IME with {@link EditorInfo#actionLabel}
+ * and {@link EditorInfo#actionId} when it has focus.
+ * @see #getImeActionLabel
+ * @see #getImeActionId
+ * @see android.view.inputmethod.EditorInfo
+ * @attr ref android.R.styleable#TextView_imeActionLabel
+ * @attr ref android.R.styleable#TextView_imeActionId
+ */
+ public void setImeActionLabel(CharSequence label, int actionId) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionLabel = label;
+ mInputContentType.imeActionId = actionId;
+ }
+
+ /**
+ * Get the IME action label previous set with {@link #setImeActionLabel}.
+ *
+ * @see #setImeActionLabel
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public CharSequence getImeActionLabel() {
+ return mInputContentType != null
+ ? mInputContentType.imeActionLabel : null;
+ }
+
+ /**
+ * Get the IME action ID previous set with {@link #setImeActionLabel}.
+ *
+ * @see #setImeActionLabel
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public int getImeActionId() {
+ return mInputContentType != null
+ ? mInputContentType.imeActionId : 0;
+ }
+
+ /**
+ * Set a special OnClickListener to be called when an action is performed
+ * on the text view. This will be called when the enter key is pressed,
+ * or when an action supplied to the IME is selected by the user.
+ */
+ public void setOnEditorActionListener(OnEditorActionListener l) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.onEditorActionListener = l;
+ }
+
+ /**
+ * Called when an attached input method calls
+ * {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction()}
+ * for this text view. The default implementation will call your click
+ * listener supplied to {@link #setOnEditorActionListener},
+ * or generate an enter key down/up pair to invoke the action if not.
+ *
+ * @param actionCode The code of the action being performed.
+ *
+ * @see #setOnEditorActionListener
+ */
+ public void onEditorAction(int actionCode) {
+ final InputContentType ict = mInputContentType;
+ if (ict != null) {
+ if (ict.onEditorActionListener != null) {
+ if (ict.onEditorActionListener.onEditorAction(this,
+ actionCode, null)) {
+ return;
+ }
+ }
+ }
+
+ if (actionCode == EditorInfo.IME_ACTION_NEXT &&
+ (ict != null || !shouldAdvanceFocusOnEnter())) {
+ // This is the default handling for the NEXT action, to advance
+ // focus. Note that for backwards compatibility we don't do this
+ // default handling if explicit ime options have not been given,
+ // and we do not advance by default on an enter key -- in that
+ // case, we want to turn this into the normal enter key codes that
+ // an app may be expecting.
+ View v = focusSearch(FOCUS_DOWN);
+ if (v != null) {
+ if (!v.requestFocus(FOCUS_DOWN)) {
+ throw new IllegalStateException("focus search returned a view " +
+ "that wasn't able to take focus!");
+ }
+ }
+ return;
+ }
+
+ Handler h = getHandler();
+ long eventTime = SystemClock.uptimeMillis();
+ h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ }
+
+ /**
* Set the private content type of the text, which is the
- * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType}
+ * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
* field that will be filled in when creating an input connection.
*
- * @see #getPrivateContentType()
- * @see EditorInfo#privateContentType
- * @attr ref android.R.styleable#TextView_editorPrivateContentType
+ * @see #getPrivateImeOptions()
+ * @see EditorInfo#privateImeOptions
+ * @attr ref android.R.styleable#TextView_privateImeOptions
*/
- public void setPrivateContentType(String type) {
+ public void setPrivateImeOptions(String type) {
if (mInputContentType == null) mInputContentType = new InputContentType();
- mInputContentType.privateContentType = type;
+ mInputContentType.privateImeOptions = type;
}
/**
* Get the private type of the content.
*
- * @see #setPrivateContentType(String)
- * @see EditorInfo#privateContentType
+ * @see #setPrivateImeOptions(String)
+ * @see EditorInfo#privateImeOptions
*/
- public String getPrivateContentType() {
+ public String getPrivateImeOptions() {
return mInputContentType != null
- ? mInputContentType.privateContentType : null;
+ ? mInputContentType.privateImeOptions : null;
}
/**
@@ -3807,7 +3992,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* but also in mail addresses and subjects which will display on multiple
* lines but where it doesn't make sense to insert newlines.
*/
- private boolean advanceFocusOnEnter() {
+ protected boolean shouldAdvanceFocusOnEnter() {
if (mInput == null) {
return false;
}
@@ -3828,15 +4013,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
+ private boolean isInterestingEnter(KeyEvent event) {
+ if ((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 &&
+ mInputContentType != null &&
+ (mInputContentType.imeOptions &
+ EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+ // If this enter key came from a soft keyboard, and the
+ // text editor has been configured to not do a default
+ // action for software enter keys, then we aren't interested.
+ return false;
+ }
+ return true;
+ }
+
private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
if (!isEnabled()) {
return 0;
}
switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
- if (advanceFocusOnEnter()) {
+ if (!isInterestingEnter(event)) {
+ // Ignore enter key we aren't interested in.
+ return -1;
+ }
+ if (mInputContentType != null
+ && mInputContentType.onEditorActionListener != null) {
+ mInputContentType.enterDown = true;
+ }
+ // fall through...
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (shouldAdvanceFocusOnEnter()) {
return 0;
}
}
@@ -3939,7 +4146,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
case KeyEvent.KEYCODE_ENTER:
- if (advanceFocusOnEnter()) {
+ if (mInputContentType != null
+ && mInputContentType.onEditorActionListener != null
+ && mInputContentType.enterDown) {
+ mInputContentType.enterDown = false;
+ if (mInputContentType.onEditorActionListener.onEditorAction(
+ this, EditorInfo.IME_UNDEFINED, event)) {
+ return true;
+ }
+ }
+
+ if (shouldAdvanceFocusOnEnter()) {
/*
* If there is a click listener, just call through to
* super, which will invoke it.
@@ -3994,11 +4211,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mInputMethodState = new InputMethodState();
}
outAttrs.inputType = mInputType;
- outAttrs.hintText = mHint;
if (mInputContentType != null) {
- outAttrs.privateContentType = mInputContentType.privateContentType;
+ outAttrs.imeOptions = mInputContentType.imeOptions;
+ outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
+ outAttrs.actionLabel = mInputContentType.imeActionLabel;
+ outAttrs.actionId = mInputContentType.imeActionId;
outAttrs.extras = mInputContentType.extras;
+ } else {
+ outAttrs.imeOptions = EditorInfo.IME_UNDEFINED;
+ }
+ if (outAttrs.imeOptions == EditorInfo.IME_UNDEFINED) {
+ if (focusSearch(FOCUS_DOWN) != null) {
+ // An action has not been set, but the enter key will move to
+ // the next focus, so set the action to that.
+ outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
+ if (!shouldAdvanceFocusOnEnter()) {
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+ }
+ }
}
+ outAttrs.hintText = mHint;
if (mText instanceof Editable) {
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = Selection.getSelectionStart(mText);
@@ -5787,6 +6019,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Don't leave us in the middle of a batch edit.
onEndBatchEdit();
+ if (mInputContentType != null) {
+ mInputContentType.enterDown = false;
+ }
}
startStopMarquee(hasWindowFocus);
@@ -5880,8 +6115,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mScroller = s;
}
- private static class Blink extends Handler
- implements Runnable {
+ private static class Blink extends Handler implements Runnable {
private WeakReference<TextView> mView;
private boolean mCancelled;
@@ -6139,23 +6373,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
int start = end;
- char c;
int len = mText.length();
- while (start > 0 && (((c = mTransformed.charAt(start - 1)) == '\'') ||
- (Character.isLetterOrDigit(c)))) {
- start--;
+ for (; start > 0; start--) {
+ char c = mTransformed.charAt(start - 1);
+ int type = Character.getType(c);
+
+ if (c != '\'' &&
+ type != Character.UPPERCASE_LETTER &&
+ type != Character.LOWERCASE_LETTER &&
+ type != Character.TITLECASE_LETTER &&
+ type != Character.MODIFIER_LETTER &&
+ type != Character.DECIMAL_DIGIT_NUMBER) {
+ break;
+ }
}
- while (end < len && (((c = mTransformed.charAt(end)) == '\'') ||
- (Character.isLetterOrDigit(c)))) {
- end++;
+ for (; end < len; end++) {
+ char c = mTransformed.charAt(end);
+ int type = Character.getType(c);
+
+ if (c != '\'' &&
+ type != Character.UPPERCASE_LETTER &&
+ type != Character.LOWERCASE_LETTER &&
+ type != Character.TITLECASE_LETTER &&
+ type != Character.MODIFIER_LETTER &&
+ type != Character.DECIMAL_DIGIT_NUMBER) {
+ break;
+ }
}
if (start == end) {
return null;
}
+ if (end - start > 48) {
+ return null;
+ }
+
return TextUtils.substring(mTransformed, start, end);
}
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
new file mode 100644
index 0000000..ec45e23
--- /dev/null
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.view.WindowManager.LayoutParams;
+
+// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration)
+
+/**
+ * TODO: Docs
+ *
+ * If you are using this with a custom View, please call
+ * {@link #setVisible(boolean) setVisible(false)} from the
+ * {@link View#onDetachedFromWindow}.
+ *
+ * @hide
+ */
+public class ZoomButtonsController implements View.OnTouchListener {
+
+ private static final String TAG = "ZoomButtonsController";
+
+ private static final int ZOOM_CONTROLS_TIMEOUT =
+ (int) ViewConfiguration.getZoomControlsTimeout();
+
+ // TODO: scaled to density
+ private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
+
+ private Context mContext;
+ private WindowManager mWindowManager;
+
+ /**
+ * The view that is being zoomed by this zoom ring.
+ */
+ private View mOwnerView;
+
+ /**
+ * The bounds of the owner view in global coordinates. This is recalculated
+ * each time the zoom ring is shown.
+ */
+ private Rect mOwnerViewBounds = new Rect();
+
+ /**
+ * The container that is added as a window.
+ */
+ private FrameLayout mContainer;
+ private LayoutParams mContainerLayoutParams;
+ private int[] mContainerLocation = new int[2];
+
+ private ZoomControls mControls;
+
+ /**
+ * The view (or null) that should receive touch events. This will get set if
+ * the touch down hits the container. It will be reset on the touch up.
+ */
+ private View mTouchTargetView;
+ /**
+ * The {@link #mTouchTargetView}'s location in window, set on touch down.
+ */
+ private int[] mTouchTargetLocationInWindow = new int[2];
+ /**
+ * If the zoom ring is dismissed but the user is still in a touch
+ * interaction, we set this to true. This will ignore all touch events until
+ * up/cancel, and then set the owner's touch listener to null.
+ */
+ private boolean mReleaseTouchListenerOnUp;
+
+ private boolean mIsVisible;
+
+ private Rect mTempRect = new Rect();
+
+ private OnZoomListener mCallback;
+
+ /**
+ * When showing the zoom, we add the view as a new window. However, there is
+ * logic that needs to know the size of the zoom which is determined after
+ * it's laid out. Therefore, we must post this logic onto the UI thread so
+ * it will be exceuted AFTER the layout. This is the logic.
+ */
+ private Runnable mPostedVisibleInitializer;
+
+ private IntentFilter mConfigurationChangedFilter =
+ new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+
+ private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!mIsVisible) return;
+
+ mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
+ mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
+ }
+ };
+
+ /** When configuration changes, this is called after the UI thread is idle. */
+ private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
+ /** Used to delay the zoom ring dismissal. */
+ private static final int MSG_DISMISS_ZOOM_RING = 3;
+ /**
+ * If setVisible(true) is called and the owner view's window token is null,
+ * we delay the setVisible(true) call until it is not null.
+ */
+ private static final int MSG_POST_SET_VISIBLE = 4;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_POST_CONFIGURATION_CHANGED:
+ onPostConfigurationChanged();
+ break;
+
+ case MSG_DISMISS_ZOOM_RING:
+ setVisible(false);
+ break;
+
+ case MSG_POST_SET_VISIBLE:
+ if (mOwnerView.getWindowToken() == null) {
+ // Doh, it is still null, throw an exception
+ throw new IllegalArgumentException(
+ "Cannot make the zoom ring visible if the owner view is " +
+ "not attached to a window.");
+ }
+ setVisible(true);
+ break;
+ }
+
+ }
+ };
+
+ public ZoomButtonsController(Context context, View ownerView) {
+ mContext = context;
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mOwnerView = ownerView;
+
+ mContainer = createContainer();
+ }
+
+ private FrameLayout createContainer() {
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.BOTTOM | Gravity.CENTER;
+ lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
+ LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ lp.width = LayoutParams.FILL_PARENT;
+ lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
+ lp.format = PixelFormat.TRANSPARENT;
+ // TODO: make a new animation for this
+ lp.windowAnimations = com.android.internal.R.style.Animation_InputMethodFancy;
+ mContainerLayoutParams = lp;
+
+ FrameLayout container = new FrameLayout(mContext);
+ container.setLayoutParams(lp);
+ container.setMeasureAllChildren(true);
+
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(com.android.internal.R.layout.zoom_magnify, container);
+
+ mControls = (ZoomControls) container.findViewById(com.android.internal.R.id.zoomControls);
+ mControls.setOnZoomInClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onZoom(true);
+ }
+ });
+ mControls.setOnZoomOutClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onZoom(false);
+ }
+ });
+
+ View overview = container.findViewById(com.android.internal.R.id.zoomMagnify);
+ overview.setVisibility(View.GONE);
+ overview.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onOverview();
+ }
+ });
+
+ return container;
+ }
+
+ public void setCallback(OnZoomListener callback) {
+ mCallback = callback;
+ }
+
+ public void setFocusable(boolean focusable) {
+ if (focusable) {
+ mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+
+ if (mIsVisible) {
+ mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ }
+ }
+
+ public void setOverviewVisible(boolean visible) {
+ mContainer.findViewById(com.android.internal.R.id.zoomMagnify)
+ .setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ public boolean isVisible() {
+ return mIsVisible;
+ }
+
+ public void setVisible(boolean visible) {
+
+ if (!useThisZoom(mContext)) return;
+
+ if (visible) {
+ if (mOwnerView.getWindowToken() == null) {
+ /*
+ * We need a window token to show ourselves, maybe the owner's
+ * window hasn't been created yet but it will have been by the
+ * time the looper is idle, so post the setVisible(true) call.
+ */
+ if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
+ mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
+ }
+ return;
+ }
+
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ }
+
+ if (mIsVisible == visible) {
+ return;
+ }
+ mIsVisible = visible;
+
+ if (visible) {
+ if (mContainerLayoutParams.token == null) {
+ mContainerLayoutParams.token = mOwnerView.getWindowToken();
+ }
+
+ mWindowManager.addView(mContainer, mContainerLayoutParams);
+
+ if (mPostedVisibleInitializer == null) {
+ mPostedVisibleInitializer = new Runnable() {
+ public void run() {
+ refreshPositioningVariables();
+
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(true);
+ }
+ }
+ };
+ }
+
+ mHandler.post(mPostedVisibleInitializer);
+
+ // Handle configuration changes when visible
+ mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
+
+ // Steal touches events from the owner
+ mOwnerView.setOnTouchListener(this);
+ mReleaseTouchListenerOnUp = false;
+
+ } else {
+ // Don't want to steal any more touches
+ if (mTouchTargetView != null) {
+ // We are still stealing the touch events for this touch
+ // sequence, so release the touch listener later
+ mReleaseTouchListenerOnUp = true;
+ } else {
+ mOwnerView.setOnTouchListener(null);
+ }
+
+ // No longer care about configuration changes
+ mContext.unregisterReceiver(mConfigurationChangedReceiver);
+
+ mWindowManager.removeView(mContainer);
+ mHandler.removeCallbacks(mPostedVisibleInitializer);
+
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(false);
+ }
+ }
+
+ }
+
+ /**
+ * TODO: docs
+ *
+ * Notes:
+ * - Please ensure you set your View to INVISIBLE not GONE when hiding it.
+ *
+ * @return TODO
+ */
+ public FrameLayout getContainer() {
+ return mContainer;
+ }
+
+ public int getZoomRingId() {
+ return mControls.getId();
+ }
+
+ private void dismissControlsDelayed(int delay) {
+ mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+ mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay);
+ }
+
+ /**
+ * Should be called by the client for each event belonging to the second tap
+ * (the down, move, up, and cancel events).
+ *
+ * @param event The event belonging to the second tap.
+ * @return Whether the event was consumed.
+ */
+ public boolean handleDoubleTapEvent(MotionEvent event) {
+ if (!useThisZoom(mContext)) return false;
+
+ int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ setVisible(true);
+ centerPoint(x, y);
+ }
+
+ return true;
+ }
+
+ private void refreshPositioningVariables() {
+ // Calculate the owner view's bounds
+ mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
+ mContainer.getLocationOnScreen(mContainerLocation);
+ }
+
+ /**
+ * Centers the point (in owner view's coordinates).
+ */
+ private void centerPoint(int x, int y) {
+ if (mCallback != null) {
+ mCallback.onCenter(x, y);
+ }
+ }
+
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getAction();
+
+ if (mReleaseTouchListenerOnUp) {
+ // The ring was dismissed but we need to throw away all events until the up
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mOwnerView.setOnTouchListener(null);
+ setTouchTargetView(null);
+ mReleaseTouchListenerOnUp = false;
+ }
+
+ // Eat this event
+ return true;
+ }
+
+ // TODO: optimize this (it ends up removing message and queuing another)
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+
+ View targetView = mTouchTargetView;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY());
+ setTouchTargetView(targetView);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ setTouchTargetView(null);
+ break;
+ }
+
+ if (targetView != null) {
+ // The upperleft corner of the target view in raw coordinates
+ int targetViewRawX = mContainerLocation[0] + mTouchTargetLocationInWindow[0];
+ int targetViewRawY = mContainerLocation[1] + mTouchTargetLocationInWindow[1];
+
+ MotionEvent containerEvent = MotionEvent.obtain(event);
+ // Convert the motion event into the target view's coordinates (from
+ // owner view's coordinates)
+ containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX,
+ mOwnerViewBounds.top - targetViewRawY);
+ boolean retValue = targetView.dispatchTouchEvent(containerEvent);
+ containerEvent.recycle();
+ return retValue;
+
+ } else {
+ return false;
+ }
+ }
+
+ private void setTouchTargetView(View view) {
+ mTouchTargetView = view;
+ if (view != null) {
+ view.getLocationInWindow(mTouchTargetLocationInWindow);
+ }
+ }
+
+ /**
+ * Returns the View that should receive a touch at the given coordinates.
+ *
+ * @param rawX The raw X.
+ * @param rawY The raw Y.
+ * @return The view that should receive the touches, or null if there is not one.
+ */
+ private View getViewForTouch(int rawX, int rawY) {
+ // Reverse order so the child drawn on top gets first dibs.
+ int containerCoordsX = rawX - mContainerLocation[0];
+ int containerCoordsY = rawY - mContainerLocation[1];
+ Rect frame = mTempRect;
+ for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mContainer.getChildAt(i);
+ if (child.getVisibility() != View.VISIBLE) {
+ continue;
+ }
+
+ child.getHitRect(frame);
+ // Expand the touch region
+ frame.top -= ZOOM_CONTROLS_TOUCH_PADDING;
+ if (frame.contains(containerCoordsX, containerCoordsY)) {
+ return child;
+ }
+ }
+
+ return null;
+ }
+
+ private void onPostConfigurationChanged() {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ refreshPositioningVariables();
+ }
+
+ public static boolean useThisZoom(Context context) {
+ return ZoomRingController.getZoomType(context) == 2;
+ }
+
+ public interface OnZoomListener {
+ void onCenter(int x, int y);
+ void onVisibilityChanged(boolean visible);
+ void onZoom(boolean zoomIn);
+ void onOverview();
+ }
+}
diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java
index a29e1a0..a5a867b 100644
--- a/core/java/android/widget/ZoomRing.java
+++ b/core/java/android/widget/ZoomRing.java
@@ -77,7 +77,7 @@ public class ZoomRing extends View {
private int mPreviousWidgetDragX;
private int mPreviousWidgetDragY;
- private boolean mDrawThumb = true;
+ private boolean mThumbVisible = true;
private Drawable mThumbDrawable;
/** Shown beneath the thumb if we can still zoom in. */
@@ -91,6 +91,13 @@ public class ZoomRing extends View {
private static final int THUMB_ARROWS_FADE_DURATION = 300;
private long mThumbArrowsFadeStartTime;
private int mThumbArrowsAlpha = 255;
+
+ private static final int THUMB_PLUS_MINUS_DISTANCE = 69;
+ private static final int THUMB_PLUS_MINUS_OFFSET_ANGLE = TWO_PI_INT_MULTIPLIED / 11;
+ /** Drawn (without rotation) on top of the arrow. */
+ private Drawable mThumbPlusDrawable;
+ /** Drawn (without rotation) on top of the arrow. */
+ private Drawable mThumbMinusDrawable;
private static final int MODE_IDLE = 0;
@@ -99,7 +106,7 @@ public class ZoomRing extends View {
* are waiting for him to move the slop amount before considering him in the
* drag thumb state.
*/
- private static final int MODE_WAITING_FOR_DRAG_THUMB = 5;
+ private static final int MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP = 5;
private static final int MODE_DRAG_THUMB = 1;
/**
* User has his finger down, but we are waiting for him to pass the touch
@@ -109,11 +116,14 @@ public class ZoomRing extends View {
private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4;
private static final int MODE_MOVE_ZOOM_RING = 2;
private static final int MODE_TAP_DRAG = 3;
- /** Ignore the touch interaction. Reset to MODE_IDLE after up/cancel. */
- private static final int MODE_IGNORE_UNTIL_UP = 6;
+ /** Ignore the touch interaction until the user touches the thumb again. */
+ private static final int MODE_IGNORE_UNTIL_TOUCHES_THUMB = 6;
private int mMode;
-
- private long mPreviousUpTime;
+
+ /** Records the last mode the user was in. */
+ private int mPreviousMode;
+
+ private long mPreviousCenterUpTime;
private int mPreviousDownX;
private int mPreviousDownY;
@@ -122,7 +132,9 @@ public class ZoomRing extends View {
private OnZoomRingCallback mCallback;
private int mPreviousCallbackAngle;
private int mCallbackThreshold = Integer.MAX_VALUE;
-
+ /** If the user drags to within __% of a tick, snap to that tick. */
+ private int mFuzzyCallbackThreshold = Integer.MAX_VALUE;
+
private boolean mResetThumbAutomatically = true;
private int mThumbDragStartAngle;
@@ -133,6 +145,8 @@ public class ZoomRing extends View {
private Scroller mThumbScroller;
+ private boolean mVibration = true;
+
private static final int MSG_THUMB_SCROLLER_TICK = 1;
private static final int MSG_THUMB_ARROWS_FADE_TICK = 2;
private Handler mHandler = new Handler() {
@@ -163,6 +177,8 @@ public class ZoomRing extends View {
mutate();
mThumbMinusArrowDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus_arrow_rotatable).
mutate();
+ mThumbPlusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_plus);
+ mThumbMinusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus);
if (DRAW_TRAIL) {
mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate();
}
@@ -175,7 +191,7 @@ public class ZoomRing extends View {
mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2;
mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2;
- mCallbackThreshold = PI_INT_MULTIPLIED / 6;
+ setCallbackThreshold(PI_INT_MULTIPLIED / 6);
}
public ZoomRing(Context context, AttributeSet attrs) {
@@ -193,8 +209,20 @@ public class ZoomRing extends View {
// TODO: rename
public void setCallbackThreshold(int callbackThreshold) {
mCallbackThreshold = callbackThreshold;
+ mFuzzyCallbackThreshold = (int) (callbackThreshold * 0.65f);
}
+ public void setVibration(boolean vibrate) {
+ mVibration = vibrate;
+ }
+
+ public void setThumbVisible(boolean thumbVisible) {
+ if (mThumbVisible != thumbVisible) {
+ mThumbVisible = thumbVisible;
+ invalidate();
+ }
+ }
+
// TODO: from XML too
public void setRingBounds(int innerRadius, int outerRadius) {
mBoundInnerRadiusSquared = innerRadius * innerRadius;
@@ -306,15 +334,7 @@ public class ZoomRing extends View {
public void setThumbAngleAnimated(int angle, int duration) {
// The angle when going from the current angle to the new angle
int deltaAngle = getDelta(mThumbAngle, angle);
- // Counter clockwise if the new angle is more the current angle
- boolean counterClockwise = deltaAngle > 0;
-
- if (deltaAngle > PI_INT_MULTIPLIED || deltaAngle < -PI_INT_MULTIPLIED) {
- // It's quicker to go the other direction
- counterClockwise = !counterClockwise;
- }
-
- setThumbAngleAnimated(angle, duration, counterClockwise);
+ setThumbAngleAnimated(angle, duration, deltaAngle > 0);
}
public void setThumbAngleAnimated(int angle, int duration, boolean counterClockwise) {
@@ -354,14 +374,10 @@ public class ZoomRing extends View {
return mThumbScroller.getCurrX() % TWO_PI_INT_MULTIPLIED;
}
- public void resetThumbAngle(int angle) {
- mPreviousCallbackAngle = angle;
- setThumbAngleInt(angle);
- }
-
public void resetThumbAngle() {
if (mResetThumbAutomatically) {
- resetThumbAngle(0);
+ mPreviousCallbackAngle = 0;
+ setThumbAngleInt(0);
}
}
@@ -394,101 +410,119 @@ public class ZoomRing extends View {
mTrail.setBounds(0, 0, right - left, bottom - top);
}
+ // These drawables are the same size as the track
mThumbPlusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
mThumbMinusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
+// Log.d(TAG, "History size: " + event.getHistorySize());
+
return handleTouch(event.getAction(), event.getEventTime(),
(int) event.getX(), (int) event.getY(), (int) event.getRawX(),
(int) event.getRawY());
}
- private void resetState() {
- mMode = MODE_IDLE;
+ private void resetToIdle() {
+ setMode(MODE_IDLE);
mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE;
mAcculumalatedTrailAngle = 0.0;
}
public void setTapDragMode(boolean tapDragMode, int x, int y) {
- resetState();
- mMode = tapDragMode ? MODE_TAP_DRAG : MODE_IDLE;
-
+ resetToIdle();
if (tapDragMode) {
+ setMode(MODE_TAP_DRAG);
+ mCallback.onUserInteractionStarted();
onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY));
+ } else {
+ onTouchUp(SystemClock.elapsedRealtime(), true);
}
}
public boolean handleTouch(int action, long time, int x, int y, int rawX, int rawY) {
- switch (action) {
+ // local{X,Y} will be where the center of the widget is (0,0)
+ int localX = x - mCenterX;
+ int localY = y - mCenterY;
+
+ /*
+ * If we are not drawing the thumb, there is no way for the user to be
+ * touching the thumb. Also, if this is the case, assume they are not
+ * touching the ring (so the user cannot absolute set the thumb, and
+ * there will be a larger touch region for going into the move-ring
+ * mode).
+ */
+ boolean isTouchingThumb = mThumbVisible;
+ boolean isTouchingRing = mThumbVisible;
+
+ int touchAngle = getAngle(localX, localY);
+// printAngle("touchAngle", touchAngle);
+// printAngle("mThumbAngle", mThumbAngle);
+// printAngle("mPreviousCallbackAngle", mPreviousCallbackAngle);
+// Log.d(TAG, "");
+
+
+ int radiusSquared = localX * localX + localY * localY;
+ if (radiusSquared < mBoundInnerRadiusSquared ||
+ radiusSquared > mBoundOuterRadiusSquared) {
+ // Out-of-bounds
+ isTouchingThumb = false;
+ isTouchingRing = false;
+ }
+
+ if (isTouchingThumb) {
+ int deltaThumbAndTouch = getDelta(mThumbAngle, touchAngle);
+ int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ?
+ deltaThumbAndTouch : -deltaThumbAndTouch;
+ if (absoluteDeltaThumbAndTouch > THUMB_GRAB_SLOP) {
+ // Didn't grab close enough to the thumb
+ isTouchingThumb = false;
+ }
+ }
+ switch (action) {
case MotionEvent.ACTION_DOWN:
- if (time - mPreviousUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT) {
+ if (!isTouchingRing &&
+ (time - mPreviousCenterUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT)) {
+ // Make sure the double-tap is in the center of the widget (and not on the ring)
mCallback.onZoomRingDismissed(true);
- onTouchUp(time);
+ onTouchUp(time, isTouchingRing);
// Dismissing, so halt here
return true;
}
+ resetToIdle();
mCallback.onUserInteractionStarted();
mPreviousDownX = x;
mPreviousDownY = y;
- resetState();
// Fall through to code below switch (since the down is used for
// jumping to the touched tick)
break;
case MotionEvent.ACTION_MOVE:
- if (mMode == MODE_IGNORE_UNTIL_UP) return true;
-
// Fall through to code below switch
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- onTouchUp(time);
+ onTouchUp(time, isTouchingRing);
return true;
default:
return false;
}
- // local{X,Y} will be where the center of the widget is (0,0)
- int localX = x - mCenterX;
- int localY = y - mCenterY;
- boolean isTouchingThumb = true;
- boolean isInRingBounds = true;
-
- int touchAngle = getAngle(localX, localY);
- int radiusSquared = localX * localX + localY * localY;
- if (radiusSquared < mBoundInnerRadiusSquared ||
- radiusSquared > mBoundOuterRadiusSquared) {
- // Out-of-bounds
- isTouchingThumb = false;
- isInRingBounds = false;
- }
-
- int deltaThumbAndTouch = getDelta(mThumbAngle, touchAngle);
- int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ?
- deltaThumbAndTouch : -deltaThumbAndTouch;
- if (isTouchingThumb &&
- absoluteDeltaThumbAndTouch > THUMB_GRAB_SLOP) {
- // Didn't grab close enough to the thumb
- isTouchingThumb = false;
- }
-
if (mMode == MODE_IDLE) {
if (isTouchingThumb) {
// They grabbed the thumb
- mMode = MODE_DRAG_THUMB;
+ setMode(MODE_DRAG_THUMB);
onThumbDragStarted(touchAngle);
- } else if (isInRingBounds) {
+ } else if (isTouchingRing) {
// They tapped somewhere else on the ring
int tickAngle = getClosestTickAngle(touchAngle);
-
int deltaThumbAndTick = getDelta(mThumbAngle, tickAngle);
int boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
@@ -497,12 +531,12 @@ public class ZoomRing extends View {
if (deltaThumbAndTick > MAX_ABS_JUMP_DELTA_ANGLE ||
deltaThumbAndTick < -MAX_ABS_JUMP_DELTA_ANGLE) {
// Trying to jump too far, ignore this touch interaction
- mMode = MODE_IGNORE_UNTIL_UP;
+ setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
return true;
}
- // Make sure we only let them jump within bounds
if (boundAngle != Integer.MIN_VALUE) {
+ // Cap the user's jump to the bound
tickAngle = boundAngle;
}
} else {
@@ -515,47 +549,59 @@ public class ZoomRing extends View {
deltaThumbAndTick = getDelta(mThumbAngle, tickAngle, !oldDirectionIsCcw);
boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
if (boundAngle != Integer.MIN_VALUE) {
- // Not allowed to be here, it is between two bounds
- mMode = MODE_IGNORE_UNTIL_UP;
+ // Cannot get to the tapped location because it is out-of-bounds
+ setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
return true;
}
}
}
- mMode = MODE_WAITING_FOR_DRAG_THUMB;
+ setMode(MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP);
mWaitingForDragThumbDownAngle = touchAngle;
boolean ccw = deltaThumbAndTick > 0;
setThumbAngleAnimated(tickAngle, 0, ccw);
- // Our thumb scrolling animation takes us from mThumbAngle to tickAngle
+ /*
+ * Our thumb scrolling animation takes us from mThumbAngle to
+ * tickAngle, so manifest that as the user dragging the thumb
+ * there.
+ */
onThumbDragStarted(mThumbAngle);
+ // We know which direction we want to go
onThumbDragged(tickAngle, true, ccw);
} else {
- // They tapped somewhere else
- mMode = MODE_WAITING_FOR_MOVE_ZOOM_RING;
+ // They tapped somewhere else on the widget
+ setMode(MODE_WAITING_FOR_MOVE_ZOOM_RING);
mCallback.onZoomRingSetMovableHintVisible(true);
}
- } else if (mMode == MODE_WAITING_FOR_DRAG_THUMB) {
+ } else if (mMode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) {
int deltaDownAngle = getDelta(mWaitingForDragThumbDownAngle, touchAngle);
if ((deltaDownAngle < -THUMB_DRAG_SLOP || deltaDownAngle > THUMB_DRAG_SLOP) &&
isDeltaInBounds(mWaitingForDragThumbDownAngle, deltaDownAngle)) {
- mMode = MODE_DRAG_THUMB;
+ setMode(MODE_DRAG_THUMB);
+
+ // No need to call onThumbDragStarted, since that was done when they tapped-to-jump
}
} else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
if (Math.abs(x - mPreviousDownX) > mTouchSlop ||
Math.abs(y - mPreviousDownY) > mTouchSlop) {
/* Make sure the user has moved the slop amount before going into that mode. */
- mMode = MODE_MOVE_ZOOM_RING;
+ setMode(MODE_MOVE_ZOOM_RING);
mCallback.onZoomRingMovingStarted();
}
+ } else if (mMode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) {
+ if (isTouchingThumb) {
+ // The user is back on the thumb, let's go back to the previous mode
+ setMode(mPreviousMode);
+ }
}
// Purposefully not an "else if"
if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) {
- if (isInRingBounds) {
+ if (isTouchingRing) {
onThumbDragged(touchAngle, false, false);
}
} else if (mMode == MODE_MOVE_ZOOM_RING) {
@@ -565,24 +611,39 @@ public class ZoomRing extends View {
return true;
}
- private void onTouchUp(long time) {
- if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
+ private void onTouchUp(long time, boolean isTouchingRing) {
+ int mode = mMode;
+ if (mode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) {
+ // For cleaning up, pretend like the user was still in the previous mode
+ mode = mPreviousMode;
+ }
+
+ if (mode == MODE_MOVE_ZOOM_RING || mode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
mCallback.onZoomRingSetMovableHintVisible(false);
- if (mMode == MODE_MOVE_ZOOM_RING) {
+ if (mode == MODE_MOVE_ZOOM_RING) {
mCallback.onZoomRingMovingStopped();
}
- } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG ||
- mMode == MODE_WAITING_FOR_DRAG_THUMB) {
+ } else if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG ||
+ mode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) {
onThumbDragStopped();
- if (mMode == MODE_DRAG_THUMB) {
+ if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG) {
// Animate back to a tick
setThumbAngleAnimated(mPreviousCallbackAngle, 0);
}
}
-
- mPreviousUpTime = time;
mCallback.onUserInteractionStopped();
+
+ if (!isTouchingRing) {
+ mPreviousCenterUpTime = time;
+ }
+ }
+
+ private void setMode(int mode) {
+ if (mode != mMode) {
+ mPreviousMode = mMode;
+ mMode = mode;
+ }
}
private boolean isDeltaInBounds(int startAngle, int deltaAngle) {
@@ -681,9 +742,8 @@ public class ZoomRing extends View {
int totalDeltaAngle;
totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
- int fuzzyCallbackThreshold = (int) (mCallbackThreshold * 0.65f);
- if (totalDeltaAngle >= fuzzyCallbackThreshold
- || totalDeltaAngle <= -fuzzyCallbackThreshold) {
+ if (totalDeltaAngle >= mFuzzyCallbackThreshold
+ || totalDeltaAngle <= -mFuzzyCallbackThreshold) {
if (!useDirection) {
// Set ccw to match the direction found by getDelta
@@ -737,7 +797,7 @@ public class ZoomRing extends View {
// We bounded the touch angle
totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
animateThumbToNewAngle = true;
- mMode = MODE_IGNORE_UNTIL_UP;
+ setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
}
@@ -764,11 +824,13 @@ public class ZoomRing extends View {
boolean canStillZoom = mCallback.onZoomRingThumbDragged(
deltaLevels, mThumbDragStartAngle, touchAngle);
- // TODO: we're trying the haptics to see how it goes with
- // users, so we're ignoring the settings (for now)
- performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ if (mVibration) {
+ // TODO: we're trying the haptics to see how it goes with
+ // users, so we're ignoring the settings (for now)
+ performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
// Set the callback angle to the actual angle based on how many delta levels we gave
mPreviousCallbackAngle = getValidAngle(
@@ -791,6 +853,134 @@ public class ZoomRing extends View {
setThumbAngleAuto(touchAngle, useDirection, ccw);
}
}
+// private void onThumbDragged(int touchAngle, boolean useDirection, boolean ccw) {
+// int deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+//
+// if (!useDirection) {
+// // Set ccw to match the direction found by getDelta
+// ccw = deltaPrevCbAndTouch > 0;
+// useDirection = true;
+// }
+//
+// boolean animateThumbToNewAngle = false;
+// boolean animationCcw = ccw;
+//
+// if (deltaPrevCbAndTouch >= mFuzzyCallbackThreshold
+// || deltaPrevCbAndTouch <= -mFuzzyCallbackThreshold) {
+//
+// /*
+// * When the user slides the thumb through the tick that corresponds
+// * to a zoom bound, we don't want to abruptly stop there. Instead,
+// * let the user slide it to the next tick, and then animate it back
+// * to the original zoom bound tick. Because of this, we make sure
+// * the delta from the bound is more than halfway to the next tick.
+// * We make sure the bound is between the touch and the previous
+// * callback to ensure we JUST passed the bound.
+// */
+// int oldTouchAngle = touchAngle;
+// if (ccw && mThumbCcwBound != Integer.MIN_VALUE) {
+// int deltaCcwBoundAndTouch =
+// getDelta(mThumbCcwBound, touchAngle, true, ccw);
+// if (deltaCcwBoundAndTouch >= mCallbackThreshold / 2) {
+// // The touch has past far enough from the bound
+// int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+// touchAngle, true, ccw);
+// if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) {
+// // The bound is between the previous callback angle and the touch
+// // Cap to the bound
+// touchAngle = mThumbCcwBound;
+// /*
+// * We're moving the touch BACK to the bound, so animate
+// * back in the opposite direction that passed the bound.
+// */
+// animationCcw = false;
+// }
+// }
+// } else if (!ccw && mThumbCwBound != Integer.MIN_VALUE) {
+// // See block above for general comments
+// int deltaCwBoundAndTouch =
+// getDelta(mThumbCwBound, touchAngle, true, ccw);
+// if (deltaCwBoundAndTouch <= -mCallbackThreshold / 2) {
+// int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+// touchAngle, true, ccw);
+// /*
+// * Both of these will be negative since we got delta in
+// * clockwise direction, and we want the magnitude of
+// * deltaPreviousCbAndTouch to be greater than the magnitude
+// * of deltaCwBoundAndTouch
+// */
+// if (deltaPreviousCbAndTouch <= deltaCwBoundAndTouch) {
+// touchAngle = mThumbCwBound;
+// animationCcw = true;
+// }
+// }
+// }
+// if (touchAngle != oldTouchAngle) {
+// // We bounded the touch angle
+// deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, true, ccw);
+// // Animate back to the bound
+// animateThumbToNewAngle = true;
+// // Disallow movement now
+// setMode(MODE_IGNORE_UNTIL_UP);
+// }
+//
+//
+// /*
+// * Prevent it from jumping too far (this could happen if the user
+// * goes through the center)
+// */
+//
+// if (mEnforceMaxAbsJump) {
+// if (deltaPrevCbAndTouch <= -MAX_ABS_JUMP_DELTA_ANGLE) {
+// deltaPrevCbAndTouch = -MAX_ABS_JUMP_DELTA_ANGLE;
+// animateThumbToNewAngle = true;
+// } else if (deltaPrevCbAndTouch >= MAX_ABS_JUMP_DELTA_ANGLE) {
+// deltaPrevCbAndTouch = MAX_ABS_JUMP_DELTA_ANGLE;
+// animateThumbToNewAngle = true;
+// }
+// }
+//
+// /*
+// * We need to cover the edge case of a user grabbing the thumb,
+// * going into the center of the widget, and then coming out from the
+// * center to an angle that's slightly below the angle he's trying to
+// * hit. If we do int division, we'll end up with one level lower
+// * than the one he was going for.
+// */
+// int deltaLevels = Math.round((float) deltaPrevCbAndTouch / mCallbackThreshold);
+// if (deltaLevels != 0) {
+// boolean canStillZoom = mCallback.onZoomRingThumbDragged(
+// deltaLevels, mThumbDragStartAngle, touchAngle);
+//
+// if (mVibration) {
+// // TODO: we're trying the haptics to see how it goes with
+// // users, so we're ignoring the settings (for now)
+// performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
+// HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
+// HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+//
+// }
+// // Set the callback angle to the actual angle based on how many delta levels we gave
+// mPreviousCallbackAngle = getValidAngle(
+// mPreviousCallbackAngle + (deltaLevels * mCallbackThreshold));
+// }
+// }
+//
+// if (DRAW_TRAIL) {
+// int deltaAngle = getDelta(mThumbAngle, touchAngle, true, ccw);
+// mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER);
+// }
+//
+// if (animateThumbToNewAngle) {
+// setThumbAngleAnimated(touchAngle, 0, animationCcw);
+// } else {
+// /*
+// * Use regular ccw here because animationCcw will never have been
+// * changed if animateThumbToNewAngle is false
+// */
+// setThumbAngleAuto(touchAngle, true, ccw);
+// }
+// }
private int getValidAngle(int invalidAngle) {
if (invalidAngle < 0) {
@@ -818,16 +1008,16 @@ public class ZoomRing extends View {
mCallback.onZoomRingThumbDraggingStopped();
}
- private void onZoomRingMoved(int x, int y) {
+ private void onZoomRingMoved(int rawX, int rawY) {
if (mPreviousWidgetDragX != Integer.MIN_VALUE) {
- int deltaX = x - mPreviousWidgetDragX;
- int deltaY = y - mPreviousWidgetDragY;
+ int deltaX = rawX - mPreviousWidgetDragX;
+ int deltaY = rawY - mPreviousWidgetDragY;
- mCallback.onZoomRingMoved(deltaX, deltaY);
+ mCallback.onZoomRingMoved(deltaX, deltaY, rawX, rawY);
}
- mPreviousWidgetDragX = x;
- mPreviousWidgetDragY = y;
+ mPreviousWidgetDragX = rawX;
+ mPreviousWidgetDragY = rawY;
}
@Override
@@ -859,15 +1049,17 @@ public class ZoomRing extends View {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- if (mDrawThumb) {
+ if (mThumbVisible) {
if (DRAW_TRAIL) {
mTrail.draw(canvas);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
mThumbPlusArrowDrawable.draw(canvas);
+ mThumbPlusDrawable.draw(canvas);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
mThumbMinusArrowDrawable.draw(canvas);
+ mThumbMinusDrawable.draw(canvas);
}
mThumbDrawable.draw(canvas);
}
@@ -877,6 +1069,28 @@ public class ZoomRing extends View {
int level = -angle * 10000 / ZoomRing.TWO_PI_INT_MULTIPLIED;
mThumbPlusArrowDrawable.setLevel(level);
mThumbMinusArrowDrawable.setLevel(level);
+
+ // Assume it is a square
+ int halfSideLength = mThumbPlusDrawable.getIntrinsicHeight() / 2;
+ int unoffsetAngle = angle + mZeroAngle;
+
+ int plusCenterX = (int) (Math.cos(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
+ int plusCenterY = (int) (Math.sin(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
+ mThumbPlusDrawable.setBounds(plusCenterX - halfSideLength,
+ plusCenterY - halfSideLength,
+ plusCenterX + halfSideLength,
+ plusCenterY + halfSideLength);
+
+ int minusCenterX = (int) (Math.cos(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
+ int minusCenterY = (int) (Math.sin(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
+ mThumbMinusDrawable.setBounds(minusCenterX - halfSideLength,
+ minusCenterY - halfSideLength,
+ minusCenterX + halfSideLength,
+ minusCenterY + halfSideLength);
}
public void setThumbArrowsVisible(boolean visible) {
@@ -886,6 +1100,7 @@ public class ZoomRing extends View {
if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR ||
callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) {
mThumbPlusArrowDrawable.setAlpha(255);
+ mThumbPlusDrawable.setAlpha(255);
mThumbArrowsToDraw |= THUMB_ARROW_PLUS;
} else {
mThumbArrowsToDraw &= ~THUMB_ARROW_PLUS;
@@ -893,6 +1108,7 @@ public class ZoomRing extends View {
if (callbackAngle < mThumbCcwBound - RADIAN_INT_ERROR ||
callbackAngle > mThumbCcwBound + RADIAN_INT_ERROR) {
mThumbMinusArrowDrawable.setAlpha(255);
+ mThumbMinusDrawable.setAlpha(255);
mThumbArrowsToDraw |= THUMB_ARROW_MINUS;
} else {
mThumbArrowsToDraw &= ~THUMB_ARROW_MINUS;
@@ -917,10 +1133,14 @@ public class ZoomRing extends View {
if (mThumbArrowsAlpha < 0) mThumbArrowsAlpha = 0;
if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
mThumbPlusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ mThumbPlusDrawable.setAlpha(mThumbArrowsAlpha);
+ invalidateDrawable(mThumbPlusDrawable);
invalidateDrawable(mThumbPlusArrowDrawable);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
mThumbMinusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ mThumbMinusDrawable.setAlpha(mThumbArrowsAlpha);
+ invalidateDrawable(mThumbMinusDrawable);
invalidateDrawable(mThumbMinusArrowDrawable);
}
@@ -941,7 +1161,7 @@ public class ZoomRing extends View {
void onZoomRingSetMovableHintVisible(boolean visible);
void onZoomRingMovingStarted();
- boolean onZoomRingMoved(int deltaX, int deltaY);
+ boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY);
void onZoomRingMovingStopped();
void onZoomRingThumbDraggingStarted();
diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java
index 2e97fda..19f66a0 100644
--- a/core/java/android/widget/ZoomRingController.java
+++ b/core/java/android/widget/ZoomRingController.java
@@ -33,6 +33,7 @@ import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -47,30 +48,36 @@ import android.view.animation.DecelerateInterpolator;
/**
* TODO: Docs
- *
+ *
* If you are using this with a custom View, please call
* {@link #setVisible(boolean) setVisible(false)} from the
* {@link View#onDetachedFromWindow}.
- *
+ *
* @hide
*/
public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
View.OnTouchListener, View.OnKeyListener {
-
+
private static final int ZOOM_RING_RADIUS_INSET = 24;
private static final int ZOOM_RING_RECENTERING_DURATION = 500;
private static final String TAG = "ZoomRing";
- public static final boolean USE_OLD_ZOOM = false;
+ public static final boolean USE_OLD_ZOOM = false;
+ static int getZoomType(Context context) {
+ return Settings.System.getInt(context.getContentResolver(), "zoom", 1);
+ }
public static boolean useOldZoom(Context context) {
- return Settings.System.getInt(context.getContentResolver(), "zoom", 1) == 0;
+ return getZoomType(context) == 0;
}
-
+ private static boolean useThisZoom(Context context) {
+ return getZoomType(context) == 1;
+ }
+
private static final int ZOOM_CONTROLS_TIMEOUT =
(int) ViewConfiguration.getZoomControlsTimeout();
-
+
// TODO: move these to ViewConfiguration or re-use existing ones
// TODO: scale px values based on latest from ViewConfiguration
private static final int SECOND_TAP_TIMEOUT = 500;
@@ -80,12 +87,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private static final int MAX_INITIATE_PAN_GAP = 10;
// TODO view config
private static final int INITIATE_PAN_DELAY = 300;
-
+
private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast";
-
+
private Context mContext;
private WindowManager mWindowManager;
-
+
/**
* The view that is being zoomed by this zoom ring.
*/
@@ -111,15 +118,15 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/**
* The {@link #mTouchTargetView}'s location in window, set on touch down.
*/
- private int[] mTouchTargetLocationInWindow = new int[2];
+ private int[] mTouchTargetLocationInWindow = new int[2];
/**
* If the zoom ring is dismissed but the user is still in a touch
* interaction, we set this to true. This will ignore all touch events until
* up/cancel, and then set the owner's touch listener to null.
*/
private boolean mReleaseTouchListenerOnUp;
-
-
+
+
/*
* Tap-drag is an interaction where the user first taps and then (quickly)
* does the clockwise or counter-clockwise drag. In reality, this is: (down,
@@ -132,30 +139,40 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
*/
private int mTapDragStartX;
private int mTapDragStartY;
-
+
private static final int TOUCH_MODE_IDLE = 0;
private static final int TOUCH_MODE_WAITING_FOR_SECOND_TAP = 1;
private static final int TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT = 2;
private static final int TOUCH_MODE_FORWARDING_FOR_TAP_DRAG = 3;
private int mTouchMode;
-
+
private boolean mIsZoomRingVisible;
-
+
private ZoomRing mZoomRing;
private int mZoomRingWidth;
private int mZoomRingHeight;
-
+
/** Invokes panning of owner view if the zoom ring is touching an edge. */
private Panner mPanner;
private long mTouchingEdgeStartTime;
private boolean mPanningEnabledForThisInteraction;
-
+
+ /**
+ * When the finger moves the zoom ring to an edge, this is the horizontal
+ * accumulator for how much the finger has moved off of its original touch
+ * point on the zoom ring (OOB = out-of-bounds). If < 0, the finger has
+ * moved that many px to the left of its original touch point on the ring.
+ */
+ private int mMovingZoomRingOobX;
+ /** Vertical accumulator, see {@link #mMovingZoomRingOobX} */
+ private int mMovingZoomRingOobY;
+
private ImageView mPanningArrows;
private Animation mPanningArrowsEnterAnimation;
private Animation mPanningArrowsExitAnimation;
-
+
private Rect mTempRect = new Rect();
-
+
private OnZoomListener mCallback;
private ViewConfiguration mViewConfig;
@@ -171,7 +188,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* for the container's layout params.
*/
private int mCenteredContainerY = Integer.MIN_VALUE;
-
+
/**
* Scroller used to re-center the zoom ring if the user had dragged it to a
* corner and then double-taps any point on the owner view (the owner view
@@ -181,7 +198,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* The (x,y) of the scroller is the (x,y) of the container's layout params.
*/
private Scroller mScroller;
-
+
/**
* When showing the zoom ring, we add the view as a new window. However,
* there is logic that needs to know the size of the zoom ring which is
@@ -189,7 +206,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* the UI thread so it will be exceuted AFTER the layout. This is the logic.
*/
private Runnable mPostedVisibleInitializer;
-
+
/**
* Only touch from the main thread.
*/
@@ -199,23 +216,29 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
-
+
private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mIsZoomRingVisible) return;
-
+
mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
}
};
-
+
/** Keeps the scroller going (or starts it). */
private static final int MSG_SCROLLER_TICK = 1;
/** When configuration changes, this is called after the UI thread is idle. */
private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
/** Used to delay the zoom ring dismissal. */
private static final int MSG_DISMISS_ZOOM_RING = 3;
+
+ /**
+ * If setVisible(true) is called and the owner view's window token is null,
+ * we delay the setVisible(true) call until it is not null.
+ */
+ private static final int MSG_POST_SET_VISIBLE = 4;
private Handler mHandler = new Handler() {
@Override
@@ -224,26 +247,36 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
case MSG_SCROLLER_TICK:
onScrollerTick();
break;
-
+
case MSG_POST_CONFIGURATION_CHANGED:
onPostConfigurationChanged();
break;
-
+
case MSG_DISMISS_ZOOM_RING:
setVisible(false);
break;
+
+ case MSG_POST_SET_VISIBLE:
+ if (mOwnerView.getWindowToken() == null) {
+ // Doh, it is still null, throw an exception
+ throw new IllegalArgumentException(
+ "Cannot make the zoom ring visible if the owner view is " +
+ "not attached to a window.");
+ }
+ setVisible(true);
+ break;
}
-
- }
+
+ }
};
-
+
public ZoomRingController(Context context, View ownerView) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mPanner = new Panner();
mOwnerView = ownerView;
-
+
mZoomRing = new ZoomRing(context);
mZoomRing.setId(com.android.internal.R.id.zoomControls);
mZoomRing.setLayoutParams(new FrameLayout.LayoutParams(
@@ -251,7 +284,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
mZoomRing.setCallback(this);
-
+
createPanningArrows();
mContainerLayoutParams = new LayoutParams();
@@ -269,12 +302,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mContainer = new FrameLayout(context);
mContainer.setLayoutParams(mContainerLayoutParams);
mContainer.setMeasureAllChildren(true);
-
+
mContainer.addView(mZoomRing);
mContainer.addView(mPanningArrows);
-
+
mScroller = new Scroller(context, new DecelerateInterpolator());
-
+
mViewConfig = ViewConfiguration.get(context);
}
@@ -287,7 +320,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
mPanningArrows.setVisibility(View.INVISIBLE);
-
+
mPanningArrowsEnterAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.fade_in);
mPanningArrowsExitAnimation = AnimationUtils.loadAnimation(mContext,
@@ -299,7 +332,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* get a callback. Once there is a callback, the accumulator resets. For
* example, if you set this to PI/6, it will give a callback every time the
* user moves PI/6 amount on the ring.
- *
+ *
* @param callbackThreshold The angle for the callback threshold, in radians
*/
public void setZoomCallbackThreshold(float callbackThreshold) {
@@ -308,7 +341,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/**
* Sets a drawable for the zoom ring track.
- *
+ *
* @param drawable The drawable to use for the track.
* @hide Need a better way of doing this, but this one-off for browser so it
* can have its final look for the usability study
@@ -316,11 +349,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void setZoomRingTrack(int drawable) {
mZoomRing.setBackgroundResource(drawable);
}
-
+
public void setCallback(OnZoomListener callback) {
mCallback = callback;
}
-
+
public void setThumbAngle(float angle) {
mZoomRing.setThumbAngle((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER));
}
@@ -328,13 +361,21 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void setThumbAngleAnimated(float angle) {
mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0);
}
-
+
public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
mZoomRing.setResetThumbAutomatically(resetThumbAutomatically);
}
+
+ public void setVibration(boolean vibrate) {
+ mZoomRing.setVibration(vibrate);
+ }
+
+ public void setThumbVisible(boolean thumbVisible) {
+ mZoomRing.setThumbVisible(thumbVisible);
+ }
public void setThumbClockwiseBound(float angle) {
- mZoomRing.setThumbClockwiseBound(angle >= 0 ?
+ mZoomRing.setThumbClockwiseBound(angle >= 0 ?
(int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
Integer.MIN_VALUE);
}
@@ -351,14 +392,26 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void setVisible(boolean visible) {
- if (useOldZoom(mContext)) return;
+ if (!useThisZoom(mContext)) return;
if (visible) {
+ if (mOwnerView.getWindowToken() == null) {
+ /*
+ * We need a window token to show ourselves, maybe the owner's
+ * window hasn't been created yet but it will have been by the
+ * time the looper is idle, so post the setVisible(true) call.
+ */
+ if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
+ mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
+ }
+ return;
+ }
+
dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
} else {
mPanner.stop();
}
-
+
if (mIsZoomRingVisible == visible) {
return;
}
@@ -368,40 +421,40 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
if (mContainerLayoutParams.token == null) {
mContainerLayoutParams.token = mOwnerView.getWindowToken();
}
-
+
mWindowManager.addView(mContainer, mContainerLayoutParams);
-
+
if (mPostedVisibleInitializer == null) {
mPostedVisibleInitializer = new Runnable() {
public void run() {
refreshPositioningVariables();
resetZoomRing();
-
+
// TODO: remove this 'update' and just center zoom ring before the
// 'add', but need to make sure we have the width and height (which
// probably can only be retrieved after it's measured, which happens
// after it's added).
mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
-
+
if (mCallback != null) {
mCallback.onVisibilityChanged(true);
}
}
- };
+ };
}
-
+
mPanningArrows.setAnimation(null);
-
+
mHandler.post(mPostedVisibleInitializer);
-
+
// Handle configuration changes when visible
mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
-
+
// Steal key/touches events from the owner
mOwnerView.setOnKeyListener(this);
mOwnerView.setOnTouchListener(this);
mReleaseTouchListenerOnUp = false;
-
+
} else {
// Don't want to steal any more keys/touches
mOwnerView.setOnKeyListener(null);
@@ -415,45 +468,45 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
// No longer care about configuration changes
mContext.unregisterReceiver(mConfigurationChangedReceiver);
-
+
mWindowManager.removeView(mContainer);
mHandler.removeCallbacks(mPostedVisibleInitializer);
-
+
if (mCallback != null) {
mCallback.onVisibilityChanged(false);
}
}
-
+
}
-
+
/**
* TODO: docs
- *
+ *
* Notes:
* - Touch dispatching is different. Only direct children who are clickable are eligble for touch events.
* - Please ensure you set your View to INVISIBLE not GONE when hiding it.
- *
+ *
* @return
*/
public FrameLayout getContainer() {
return mContainer;
}
-
+
public int getZoomRingId() {
return mZoomRing.getId();
}
-
+
private void dismissZoomRingDelayed(int delay) {
mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay);
}
-
+
private void resetZoomRing() {
mScroller.abortAnimation();
-
+
mContainerLayoutParams.x = mCenteredContainerX;
mContainerLayoutParams.y = mCenteredContainerY;
-
+
// Reset the thumb
mZoomRing.resetThumbAngle();
}
@@ -461,13 +514,15 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/**
* Should be called by the client for each event belonging to the second tap
* (the down, move, up, and cancel events).
- *
+ *
* @param event The event belonging to the second tap.
* @return Whether the event was consumed.
*/
public boolean handleDoubleTapEvent(MotionEvent event) {
- int action = event.getAction();
+ if (!useThisZoom(mContext)) return false;
+ int action = event.getAction();
+
// TODO: make sure this works well with the
// ownerView.setOnTouchListener(this) instead of window receiving
// touches
@@ -475,19 +530,19 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mTouchMode = TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT;
int x = (int) event.getX();
int y = (int) event.getY();
-
+
refreshPositioningVariables();
setVisible(true);
centerPoint(x, y);
- ensureZoomRingIsCentered();
-
+ ensureZoomRingIsCentered();
+
// Tap drag mode stuff
mTapDragStartX = x;
mTapDragStartY = y;
} else if (action == MotionEvent.ACTION_CANCEL) {
mTouchMode = TOUCH_MODE_IDLE;
-
+
} else { // action is move or up
switch (mTouchMode) {
case TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT: {
@@ -503,29 +558,29 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
setTouchTargetView(mZoomRing);
}
return true;
-
+
case MotionEvent.ACTION_UP:
mTouchMode = TOUCH_MODE_IDLE;
break;
}
break;
}
-
+
case TOUCH_MODE_FORWARDING_FOR_TAP_DRAG: {
switch (action) {
case MotionEvent.ACTION_MOVE:
giveTouchToZoomRing(event);
return true;
-
+
case MotionEvent.ACTION_UP:
mTouchMode = TOUCH_MODE_IDLE;
-
+
/*
* This is a power-user feature that only shows the
* zoom while the user is performing the tap-drag.
* That means once it is released, the zoom ring
* should disappear.
- */
+ */
mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY());
dismissZoomRingDelayed(0);
break;
@@ -534,13 +589,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
}
}
-
+
return true;
}
-
+
private void ensureZoomRingIsCentered() {
LayoutParams lp = mContainerLayoutParams;
-
+
if (lp.x != mCenteredContainerX || lp.y != mCenteredContainerY) {
int width = mContainer.getWidth();
int height = mContainer.getHeight();
@@ -549,21 +604,21 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
}
}
-
+
private void refreshPositioningVariables() {
mZoomRingWidth = mZoomRing.getWidth();
mZoomRingHeight = mZoomRing.getHeight();
-
+
// Calculate the owner view's bounds
mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
-
+
// Get the center
Gravity.apply(Gravity.CENTER, mContainer.getWidth(), mContainer.getHeight(),
mOwnerViewBounds, mTempRect);
mCenteredContainerX = mTempRect.left;
mCenteredContainerY = mTempRect.top;
}
-
+
/**
* Centers the point (in owner view's coordinates).
*/
@@ -572,7 +627,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mCallback.onCenter(x, y);
}
}
-
+
private void giveTouchToZoomRing(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
@@ -580,11 +635,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
int y = rawY - mContainerLayoutParams.y - mZoomRing.getTop();
mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY);
}
-
+
public void onZoomRingSetMovableHintVisible(boolean visible) {
- setPanningArrowsVisible(visible);
+ setPanningArrowsVisible(visible);
}
-
+
public void onUserInteractionStarted() {
mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
}
@@ -596,24 +651,62 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void onZoomRingMovingStarted() {
mScroller.abortAnimation();
mTouchingEdgeStartTime = 0;
+ mMovingZoomRingOobX = 0;
+ mMovingZoomRingOobY = 0;
if (mCallback != null) {
mCallback.onBeginPan();
}
}
-
+
private void setPanningArrowsVisible(boolean visible) {
mPanningArrows.startAnimation(visible ? mPanningArrowsEnterAnimation
: mPanningArrowsExitAnimation);
mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
-
- public boolean onZoomRingMoved(int deltaX, int deltaY) {
+
+ public boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY) {
+
+ if (mMovingZoomRingOobX != 0) {
+ /*
+ * The finger has moved off the point where it originally touched
+ * the zidget.
+ */
+ boolean wasOobLeft = mMovingZoomRingOobX < 0;
+ mMovingZoomRingOobX += deltaX;
+ if ((wasOobLeft && mMovingZoomRingOobX > 0) ||
+ (!wasOobLeft && mMovingZoomRingOobX < 0)) {
+ /*
+ * Woot, the finger is back on the original point. Infact, it
+ * went PAST its original point, so take the amount it passed
+ * and use that as the delta to move the zoom ring.
+ */
+ deltaX = mMovingZoomRingOobX;
+ // No longer out-of-bounds, reset
+ mMovingZoomRingOobX = 0;
+ } else {
+ // The finger is still not back, eat this movement
+ deltaX = 0;
+ }
+ }
+
+ if (mMovingZoomRingOobY != 0) {
+ // See above for comments
+ boolean wasOobUp = mMovingZoomRingOobY < 0;
+ mMovingZoomRingOobY += deltaY;
+ if ((wasOobUp && mMovingZoomRingOobY > 0) || (!wasOobUp && mMovingZoomRingOobY < 0)) {
+ deltaY = mMovingZoomRingOobY;
+ mMovingZoomRingOobY = 0;
+ } else {
+ deltaY = 0;
+ }
+ }
+
WindowManager.LayoutParams lp = mContainerLayoutParams;
Rect ownerBounds = mOwnerViewBounds;
-
+
int zoomRingLeft = mZoomRing.getLeft();
int zoomRingTop = mZoomRing.getTop();
-
+
int newX = lp.x + deltaX;
int newZoomRingX = newX + zoomRingLeft;
newZoomRingX = (newZoomRingX <= ownerBounds.left) ? ownerBounds.left :
@@ -627,19 +720,26 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
(newZoomRingY + mZoomRingHeight > ownerBounds.bottom) ?
ownerBounds.bottom - mZoomRingHeight : newZoomRingY;
lp.y = newZoomRingY - zoomRingTop;
-
+
mWindowManager.updateViewLayout(mContainer, lp);
-
+
// Check for pan
boolean horizontalPanning = true;
int leftGap = newZoomRingX - ownerBounds.left;
if (leftGap < MAX_PAN_GAP) {
+ if (leftGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
+ // Future moves in this direction should be accumulated in mMovingZoomRingOobX
+ mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
+ }
if (shouldPan(leftGap)) {
mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap));
}
} else {
int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft);
if (rightGap < MAX_PAN_GAP) {
+ if (rightGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
+ mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
+ }
if (shouldPan(rightGap)) {
mPanner.setHorizontalStrength(getStrengthFromGap(rightGap));
}
@@ -648,15 +748,21 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
horizontalPanning = false;
}
}
-
+
int topGap = newZoomRingY - ownerBounds.top;
if (topGap < MAX_PAN_GAP) {
+ if (topGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
+ mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
+ }
if (shouldPan(topGap)) {
mPanner.setVerticalStrength(-getStrengthFromGap(topGap));
}
} else {
int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop);
if (bottomGap < MAX_PAN_GAP) {
+ if (bottomGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
+ mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
+ }
if (shouldPan(bottomGap)) {
mPanner.setVerticalStrength(getStrengthFromGap(bottomGap));
}
@@ -670,13 +776,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
}
}
-
+
return true;
}
-
+
private boolean shouldPan(int gap) {
if (mPanningEnabledForThisInteraction) return true;
-
+
if (gap < MAX_INITIATE_PAN_GAP) {
long time = SystemClock.elapsedRealtime();
if (mTouchingEdgeStartTime != 0 &&
@@ -693,7 +799,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
return false;
}
-
+
public void onZoomRingMovingStopped() {
mPanner.stop();
setPanningArrowsVisible(false);
@@ -701,18 +807,18 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mCallback.onEndPan();
}
}
-
+
private int getStrengthFromGap(int gap) {
return gap > MAX_PAN_GAP ? 0 :
(MAX_PAN_GAP - gap) * 100 / MAX_PAN_GAP;
}
-
+
public void onZoomRingThumbDraggingStarted() {
if (mCallback != null) {
mCallback.onBeginDrag();
}
}
-
+
public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) {
if (mCallback != null) {
int deltaZoomLevel = -numLevels;
@@ -720,17 +826,17 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mZoomRingWidth / 2;
int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() +
mZoomRingHeight / 2;
-
+
return mCallback.onDragZoom(deltaZoomLevel,
globalZoomCenterX - mOwnerViewBounds.left,
globalZoomCenterY - mOwnerViewBounds.top,
(float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER,
(float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER);
}
-
+
return false;
}
-
+
public void onZoomRingThumbDraggingStopped() {
if (mCallback != null) {
mCallback.onEndDrag();
@@ -740,35 +846,35 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void onZoomRingDismissed(boolean dismissImmediately) {
if (dismissImmediately) {
mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
- setVisible(false);
+ setVisible(false);
} else {
dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
}
}
-
+
public void onRingDown(int tickAngle, int touchAngle) {
}
-
+
public boolean onTouch(View v, MotionEvent event) {
if (sTutorialDialog != null && sTutorialDialog.isShowing() &&
SystemClock.elapsedRealtime() - sTutorialShowTime >= TUTORIAL_MIN_DISPLAY_TIME) {
finishZoomTutorial();
}
-
+
int action = event.getAction();
if (mReleaseTouchListenerOnUp) {
- // The ring was dismissed but we need to throw away all events until the up
+ // The ring was dismissed but we need to throw away all events until the up
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mOwnerView.setOnTouchListener(null);
setTouchTargetView(null);
mReleaseTouchListenerOnUp = false;
}
-
+
// Eat this event
return true;
}
-
+
View targetView = mTouchTargetView;
switch (action) {
@@ -776,7 +882,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY());
setTouchTargetView(targetView);
break;
-
+
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setTouchTargetView(null);
@@ -787,7 +893,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
// The upperleft corner of the target view in raw coordinates
int targetViewRawX = mContainerLayoutParams.x + mTouchTargetLocationInWindow[0];
int targetViewRawY = mContainerLayoutParams.y + mTouchTargetLocationInWindow[1];
-
+
MotionEvent containerEvent = MotionEvent.obtain(event);
// Convert the motion event into the target view's coordinates (from
// owner view's coordinates)
@@ -796,32 +902,32 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
boolean retValue = targetView.dispatchTouchEvent(containerEvent);
containerEvent.recycle();
return retValue;
-
+
} else {
if (action == MotionEvent.ACTION_DOWN) {
dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
}
-
+
return false;
}
}
-
+
private void setTouchTargetView(View view) {
mTouchTargetView = view;
if (view != null) {
view.getLocationInWindow(mTouchTargetLocationInWindow);
}
}
-
+
/**
* Returns the View that should receive a touch at the given coordinates.
- *
+ *
* @param rawX The raw X.
* @param rawY The raw Y.
* @return The view that should receive the touches, or null if there is not one.
*/
private View getViewForTouch(int rawX, int rawY) {
- // Check to see if it is touching the ring
+ // Check to see if it is touching the ring
int containerCenterX = mContainerLayoutParams.x + mContainer.getWidth() / 2;
int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2;
int distanceFromCenterX = rawX - containerCenterX;
@@ -832,7 +938,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
zoomRingRadius * zoomRingRadius) {
return mZoomRing;
}
-
+
// Check to see if it is touching any other clickable View.
// Reverse order so the child drawn on top gets first dibs.
int containerCoordsX = rawX - mContainerLayoutParams.x;
@@ -844,13 +950,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
!child.isClickable()) {
continue;
}
-
+
child.getHitRect(frame);
if (frame.contains(containerCoordsX, containerCoordsY)) {
return child;
}
}
-
+
return null;
}
@@ -861,34 +967,34 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
case KeyEvent.KEYCODE_DPAD_RIGHT:
// Eat these
return true;
-
+
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
// Keep the zoom alive a little longer
dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
// They started zooming, hide the thumb arrows
mZoomRing.setThumbArrowsVisible(false);
-
+
if (mCallback != null && event.getAction() == KeyEvent.ACTION_DOWN) {
mCallback.onSimpleZoom(keyCode == KeyEvent.KEYCODE_DPAD_UP);
}
-
+
return true;
}
-
+
return false;
}
private void onScrollerTick() {
if (!mScroller.computeScrollOffset() || !mIsZoomRingVisible) return;
-
+
mContainerLayoutParams.x = mScroller.getCurrX();
mContainerLayoutParams.y = mScroller.getCurrY();
mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
}
-
+
private void onPostConfigurationChanged() {
dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
refreshPositioningVariables();
@@ -908,7 +1014,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* before. Furthermore, if the application does not have privilege to write
* to the system settings, it will store this bit locally in a shared
* preference.
- *
+ *
* @hide This should only be used by our main apps--browser, maps, and
* gallery
*/
@@ -917,53 +1023,65 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) {
return;
}
-
+
SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) {
return;
}
-
+
if (sTutorialDialog != null && sTutorialDialog.isShowing()) {
sTutorialDialog.dismiss();
}
+
+ LayoutInflater layoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ TextView textView = (TextView) layoutInflater.inflate(
+ com.android.internal.R.layout.alert_dialog_simple_text, null)
+ .findViewById(android.R.id.text1);
+ textView.setText(com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short);
sTutorialDialog = new AlertDialog.Builder(context)
- .setMessage(
- com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short)
+ .setView(textView)
.setIcon(0)
.create();
-
+
Window window = sTutorialDialog.getWindow();
window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
-
+
sTutorialDialog.show();
sTutorialShowTime = SystemClock.elapsedRealtime();
}
-
- public void finishZoomTutorial() {
+
+ public static void finishZoomTutorial(Context context, boolean userNotified) {
if (sTutorialDialog == null) return;
-
+
sTutorialDialog.dismiss();
sTutorialDialog = null;
-
+
// Record that they have seen the tutorial
- try {
- Settings.System.putInt(mContext.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
- } catch (SecurityException e) {
- /*
- * The app does not have permission to clear this global flag, make
- * sure the user does not see the message when he comes back to this
- * same app at least.
- */
- SharedPreferences sp = mContext.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
- sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit();
+ if (userNotified) {
+ try {
+ Settings.System.putInt(context.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
+ } catch (SecurityException e) {
+ /*
+ * The app does not have permission to clear this global flag, make
+ * sure the user does not see the message when he comes back to this
+ * same app at least.
+ */
+ SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
+ sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit();
+ }
}
}
-
+
+ public void finishZoomTutorial() {
+ finishZoomTutorial(mContext, true);
+ }
+
public void setPannerStartVelocity(float startVelocity) {
mPanner.mStartVelocity = startVelocity;
}
@@ -983,27 +1101,27 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private class Panner implements Runnable {
private static final int RUN_DELAY = 15;
private static final float STOP_SLOWDOWN = 0.8f;
-
+
private final Handler mUiHandler = new Handler();
-
+
private int mVerticalStrength;
private int mHorizontalStrength;
private boolean mStopping;
-
+
/** The time this current pan started. */
private long mStartTime;
-
+
/** The time of the last callback to pan the map/browser/etc. */
private long mPreviousCallbackTime;
-
+
// TODO Adjust to be DPI safe
private float mStartVelocity = 135;
private float mAcceleration = 160;
private float mMaxVelocity = 1000;
private int mStartAcceleratingDuration = 700;
private float mVelocity;
-
+
/** -100 (full left) to 0 (none) to 100 (full right) */
public void setHorizontalStrength(int horizontalStrength) {
if (mHorizontalStrength == 0 && mVerticalStrength == 0 && horizontalStrength != 0) {
@@ -1011,7 +1129,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
} else if (mVerticalStrength == 0 && horizontalStrength == 0) {
stop();
}
-
+
mHorizontalStrength = horizontalStrength;
mStopping = false;
}
@@ -1023,11 +1141,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
} else if (mHorizontalStrength == 0 && verticalStrength == 0) {
stop();
}
-
- mVerticalStrength = verticalStrength;
+
+ mVerticalStrength = verticalStrength;
mStopping = false;
}
-
+
private void start() {
mUiHandler.post(this);
mPreviousCallbackTime = 0;
@@ -1037,37 +1155,37 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void stop() {
mStopping = true;
}
-
+
public void run() {
if (mStopping) {
mHorizontalStrength *= STOP_SLOWDOWN;
mVerticalStrength *= STOP_SLOWDOWN;
}
-
+
if (mHorizontalStrength == 0 && mVerticalStrength == 0) {
return;
}
-
+
boolean firstRun = mPreviousCallbackTime == 0;
long curTime = SystemClock.elapsedRealtime();
int panAmount = getPanAmount(mPreviousCallbackTime, curTime);
mPreviousCallbackTime = curTime;
-
+
if (firstRun) {
mStartTime = curTime;
mVelocity = mStartVelocity;
} else {
int panX = panAmount * mHorizontalStrength / 100;
int panY = panAmount * mVerticalStrength / 100;
-
+
if (mCallback != null) {
mCallback.onPan(panX, panY);
}
}
-
+
mUiHandler.postDelayed(this, RUN_DELAY);
}
-
+
private int getPanAmount(long previousTime, long currentTime) {
if (mVelocity > mMaxVelocity) {
mVelocity = mMaxVelocity;
@@ -1077,14 +1195,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mVelocity += (currentTime - previousTime) * mAcceleration / 1000;
}
}
-
+
return (int) ((currentTime - previousTime) * mVelocity) / 1000;
}
}
-
-
public interface OnZoomListener {
void onBeginDrag();
boolean onDragZoom(int deltaZoomLevel, int centerX, int centerY, float startAngle,
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index b502a6c..edda1d9 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -28,6 +28,8 @@ interface IBatteryStats {
void noteStopGps(int uid);
void noteScreenOn();
void noteScreenOff();
+ void notePhoneOn();
+ void notePhoneOff();
void setOnBattery(boolean onBattery);
long getAwakeTimeBattery();
long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/app/UsbStorageStopActivity.java b/core/java/com/android/internal/app/UsbStorageStopActivity.java
index 30523c4..557a523 100644
--- a/core/java/com/android/internal/app/UsbStorageStopActivity.java
+++ b/core/java/com/android/internal/app/UsbStorageStopActivity.java
@@ -54,7 +54,7 @@ public class UsbStorageStopActivity extends AlertActivity implements DialogInter
// Set up the "dialog"
final AlertController.AlertParams p = mAlertParams;
- p.mIconId = com.android.internal.R.drawable.stat_sys_warning;
+ p.mIconId = com.android.internal.R.drawable.ic_dialog_alert;
p.mTitle = getString(com.android.internal.R.string.usb_storage_stop_title);
p.mMessage = getString(com.android.internal.R.string.usb_storage_stop_message);
p.mPositiveButtonText = getString(com.android.internal.R.string.usb_storage_stop_button_mount);
diff --git a/core/java/com/android/internal/gadget/IGadgetService.aidl b/core/java/com/android/internal/gadget/IGadgetService.aidl
index a22f3f3..9c66b95 100644
--- a/core/java/com/android/internal/gadget/IGadgetService.aidl
+++ b/core/java/com/android/internal/gadget/IGadgetService.aidl
@@ -44,6 +44,7 @@ interface IGadgetService {
List<GadgetProviderInfo> getInstalledProviders();
GadgetProviderInfo getGadgetInfo(int gadgetId);
void bindGadgetId(int gadgetId, in ComponentName provider);
+ int[] getGadgetIds(in ComponentName provider);
}
diff --git a/core/java/com/android/internal/logging/AndroidConfig.java b/core/java/com/android/internal/logging/AndroidConfig.java
index d8be889..f8002c6 100644
--- a/core/java/com/android/internal/logging/AndroidConfig.java
+++ b/core/java/com/android/internal/logging/AndroidConfig.java
@@ -34,11 +34,14 @@ public class AndroidConfig {
super();
try {
- Logger.global.addHandler(new AndroidHandler());
- Logger.global.setLevel(Level.ALL);
+ Logger rootLogger = Logger.getLogger("");
+ rootLogger.addHandler(new AndroidHandler());
+ rootLogger.setLevel(Level.INFO);
+
+ // Turn down logging in Apache libraries.
+ Logger.getLogger("org.apache").setLevel(Level.WARNING);
} catch (Exception ex) {
ex.printStackTrace();
}
- }
-
+ }
}
diff --git a/core/java/com/android/internal/logging/AndroidHandler.java b/core/java/com/android/internal/logging/AndroidHandler.java
index a6a4c64..d9fcf60 100644
--- a/core/java/com/android/internal/logging/AndroidHandler.java
+++ b/core/java/com/android/internal/logging/AndroidHandler.java
@@ -18,14 +18,14 @@ package com.android.internal.logging;
import android.util.Log;
-import java.util.logging.Formatter;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.SimpleFormatter;
+import java.util.logging.*;
+import java.util.Date;
+import java.text.MessageFormat;
+import java.io.PrintWriter;
+import java.io.StringWriter;
/**
- * Implements a {@link java.util.Logger} handler that writes to the Android log. The
+ * Implements a {@link java.util.logging.Logger} handler that writes to the Android log. The
* implementation is rather straightforward. The name of the logger serves as
* the log tag. Only the log levels need to be converted appropriately. For
* this purpose, the following mapping is being used:
@@ -81,8 +81,24 @@ public class AndroidHandler extends Handler {
/**
* Holds the formatter for all Android log handlers.
*/
- private static final Formatter THE_FORMATTER = new SimpleFormatter();
-
+ private static final Formatter THE_FORMATTER = new Formatter() {
+ @Override
+ public String format(LogRecord r) {
+ Throwable thrown = r.getThrown();
+ if (thrown != null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ sw.write(r.getMessage());
+ sw.write("\n");
+ thrown.printStackTrace(pw);
+ pw.flush();
+ return sw.toString();
+ } else {
+ return r.getMessage();
+ }
+ }
+ };
+
/**
* Constructs a new instance of the Android log handler.
*/
@@ -106,27 +122,40 @@ public class AndroidHandler extends Handler {
int level = getAndroidLevel(record.getLevel());
String tag = record.getLoggerName();
+ if (tag == null) {
+ // Anonymous logger.
+ tag = "null";
+ } else {
+ // Tags must be <= 23 characters.
+ int length = tag.length();
+ if (length > 23) {
+ // Most loggers use the full class name. Try dropping the
+ // package.
+ int lastPeriod = tag.lastIndexOf(".");
+ if (length - lastPeriod - 1 <= 23) {
+ tag = tag.substring(lastPeriod + 1);
+ } else {
+ // Use last 23 chars.
+ tag = tag.substring(tag.length() - 23);
+ }
+ }
+ }
+
if (!Log.isLoggable(tag, level)) {
return;
}
-
- String msg;
- try {
- msg = getFormatter().format(record);
- } catch (RuntimeException e) {
- Log.e("AndroidHandler", "Error formatting log record", e);
- msg = record.getMessage();
- }
- Log.println(level, tag, msg);
+
+ String message = getFormatter().format(record);
+ Log.println(level, tag, message);
} catch (RuntimeException e) {
- Log.e("AndroidHandler", "Error publishing log record", e);
+ Log.e("AndroidHandler", "Error logging message.", e);
}
}
/**
- * Converts a {@link java.util.Logger} logging level into an Android one.
+ * Converts a {@link java.util.logging.Logger} logging level into an Android one.
*
- * @param level The {@link java.util.Logger} logging level.
+ * @param level The {@link java.util.logging.Logger} logging level.
*
* @return The resulting Android logging level.
*/
diff --git a/core/java/com/android/internal/net/DbSSLSessionCache.java b/core/java/com/android/internal/net/DbSSLSessionCache.java
new file mode 100644
index 0000000..06e4ca8
--- /dev/null
+++ b/core/java/com/android/internal/net/DbSSLSessionCache.java
@@ -0,0 +1,269 @@
+// Copyright 2009 The Android Open Source Project
+
+package com.android.internal.net;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * Hook into harmony SSL cache to persist the SSL sessions.
+ *
+ * Current implementation is suitable for saving a small number of hosts -
+ * like google services. It can be extended with expiration and more features
+ * to support more hosts.
+ *
+ * {@hide}
+ */
+public class DbSSLSessionCache implements SSLClientSessionCache {
+ private static final String TAG = "DbSSLSessionCache";
+
+ /**
+ * Table where sessions are stored.
+ */
+ public static final String SSL_CACHE_TABLE = "ssl_sessions";
+
+ private static final String SSL_CACHE_ID = "_id";
+
+ /**
+ * Key is host:port - port is not optional.
+ */
+ private static final String SSL_CACHE_HOSTPORT = "hostport";
+
+ /**
+ * Base64-encoded DER value of the session.
+ */
+ private static final String SSL_CACHE_SESSION = "session";
+
+ /**
+ * Time when the record was added - should be close to the time
+ * of the initial session negotiation.
+ */
+ private static final String SSL_CACHE_TIME_SEC = "time_sec";
+
+ public static final String DATABASE_NAME = "ssl_sessions.db";
+
+ public static final int DATABASE_VERSION = 2;
+
+ /** public for testing
+ */
+ public static final int SSL_CACHE_ID_COL = 0;
+ public static final int SSL_CACHE_HOSTPORT_COL = 1;
+ public static final int SSL_CACHE_SESSION_COL = 2;
+ public static final int SSL_CACHE_TIME_SEC_COL = 3;
+
+ public static final int MAX_CACHE_SIZE = 256;
+
+ private final Map<String, byte[]> mExternalCache =
+ new HashMap<String, byte[]>();
+
+
+ private DatabaseHelper mDatabaseHelper;
+
+ private boolean mNeedsCacheLoad = true;
+
+ public static final String[] PROJECTION = new String[] {
+ SSL_CACHE_ID,
+ SSL_CACHE_HOSTPORT,
+ SSL_CACHE_SESSION,
+ SSL_CACHE_TIME_SEC
+ };
+
+ /**
+ * Create a SslSessionCache instance, using the specified context to
+ * initialize the database.
+ *
+ * This constructor will use the default database - created for the application
+ * context.
+ *
+ * @param activityContext
+ */
+ public DbSSLSessionCache(Context activityContext) {
+ Context appContext = activityContext.getApplicationContext();
+ mDatabaseHelper = new DatabaseHelper(appContext);
+ }
+
+ /**
+ * Create a SslSessionCache that uses a specific database.
+ *
+ *
+ * @param database
+ */
+ public DbSSLSessionCache(DatabaseHelper database) {
+ this.mDatabaseHelper = database;
+ }
+
+ public void putSessionData(SSLSession session, byte[] der) {
+ if (mDatabaseHelper == null) {
+ return;
+ }
+ synchronized (this.getClass()) {
+ SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+ if (mExternalCache.size() == MAX_CACHE_SIZE) {
+ // remove oldest.
+ // TODO: check if the new one is in cached already ( i.e. update ).
+ Cursor byTime = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
+ PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
+ if (byTime.moveToFirst()) {
+ // TODO: can I do byTime.deleteRow() ?
+ String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);
+ db.delete(SSL_CACHE_TABLE,
+ SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
+ mExternalCache.remove(hostPort);
+ } else {
+ Log.w(TAG, "No rows found");
+ // something is wrong, clear it
+ clear();
+ }
+ }
+ // Serialize native session to standard DER encoding
+ long t0 = System.currentTimeMillis();
+
+ String b64 = new String(Base64.encodeBase64(der));
+ String key = session.getPeerHost() + ":" + session.getPeerPort();
+
+ ContentValues values = new ContentValues();
+ values.put(SSL_CACHE_HOSTPORT, key);
+ values.put(SSL_CACHE_SESSION, b64);
+ values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);
+
+ mExternalCache.put(key, der);
+
+ try {
+ db.insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
+ } catch(SQLException ex) {
+ // Ignore - nothing we can do to recover, and caller shouldn't
+ // be affected.
+ Log.w(TAG, "Ignoring SQL exception when caching session", ex);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ long t1 = System.currentTimeMillis();
+ Log.d(TAG, "New SSL session " + session.getPeerHost() +
+ " DER len: " + der.length + " " + (t1 - t0));
+ }
+ }
+
+ }
+
+ public byte[] getSessionData(String host, int port) {
+ // Current (simple) implementation does a single lookup to DB, then saves
+ // all entries to the cache.
+
+ // This works for google services - i.e. small number of certs.
+ // If we extend this to all processes - we should hold a separate cache
+ // or do lookups to DB each time.
+ if (mDatabaseHelper == null) {
+ return null;
+ }
+ synchronized(this.getClass()) {
+ if (mNeedsCacheLoad) {
+ // Don't try to load again, if something is wrong on the first
+ // request it'll likely be wrong each time.
+ mNeedsCacheLoad = false;
+ long t0 = System.currentTimeMillis();
+
+ Cursor cur = null;
+ try {
+ cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
+ PROJECTION, null, null, null, null, null);
+ if (cur.moveToFirst()) {
+ do {
+ String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
+ String value = cur.getString(SSL_CACHE_SESSION_COL);
+
+ if (hostPort == null || value == null) {
+ continue;
+ }
+ // TODO: blob support ?
+ byte[] der = Base64.decodeBase64(value.getBytes());
+ mExternalCache.put(hostPort, der);
+ } while (cur.moveToNext());
+
+ }
+ } catch (SQLException ex) {
+ Log.d(TAG, "Error loading SSL cached entries ", ex);
+ } finally {
+ if (cur != null) {
+ cur.close();
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ long t1 = System.currentTimeMillis();
+ Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
+ }
+ }
+ }
+
+ String key = host + ":" + port;
+
+ return mExternalCache.get(key);
+ }
+ }
+
+ /**
+ * Reset the database and internal state.
+ * Used for testing or to free space.
+ */
+ public void clear() {
+ synchronized(this) {
+ try {
+ mExternalCache.clear();
+ mNeedsCacheLoad = true;
+ mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE,
+ null, null);
+ } catch (SQLException ex) {
+ Log.d(TAG, "Error removing SSL cached entries ", ex);
+ // ignore - nothing we can do about it
+ }
+ }
+ }
+
+ public byte[] getSessionData(byte[] id) {
+ // We support client side only - the cache will do nothing for
+ // server-side sessions.
+ return null;
+ }
+
+ /** Visible for testing.
+ */
+ public static class DatabaseHelper extends SQLiteOpenHelper {
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" +
+ SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," +
+ SSL_CACHE_SESSION + " TEXT," +
+ SSL_CACHE_TIME_SEC + " INTEGER" +
+ ");");
+
+ // No index - we load on startup, index would slow down inserts.
+ // If we want to scale this to lots of rows - we could use
+ // index, but then we'll hit DB a bit too often ( including
+ // negative hits )
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE );
+ onCreate(db);
+ }
+
+ }
+
+}
diff --git a/core/java/com/android/internal/net/SSLSessionCache.java b/core/java/com/android/internal/net/SSLSessionCache.java
new file mode 100644
index 0000000..ec02fe5
--- /dev/null
+++ b/core/java/com/android/internal/net/SSLSessionCache.java
@@ -0,0 +1,101 @@
+// Copyright 2009 The Android Open Source Project
+package com.android.internal.net;
+
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+/**
+ * Utility class to configure SSL session caching.
+ *
+ *
+ *
+ * {@hide}
+ */
+public class SSLSessionCache {
+ private static final String TAG = "SSLSessionCache";
+
+ private static final String CACHE_TYPE_DB = "db";
+
+ private static boolean sInitializationDone = false;
+
+ // One per process
+ private static DbSSLSessionCache sDbCache;
+
+ /**
+ * Check settings for ssl session caching.
+ *
+ * @return false if disabled.
+ */
+ public static boolean isEnabled(ContentResolver resolver) {
+ String sslCache = Settings.Gservices.getString(resolver,
+ Settings.Gservices.SSL_SESSION_CACHE);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "enabled " + sslCache);
+ }
+
+ return CACHE_TYPE_DB.equals(sslCache);
+ }
+
+ /**
+ * Return the configured session cache, or null if not enabled.
+ */
+ public static SSLClientSessionCache getSessionCache(Context context) {
+ if (context == null) {
+ return null;
+ }
+ if (!sInitializationDone) {
+ if (isEnabled(context.getContentResolver())) {
+ sDbCache = new DbSSLSessionCache(context);
+ return sDbCache;
+ }
+ // Don't check again.
+ sInitializationDone = true;
+ }
+ return sDbCache;
+ }
+
+ /**
+ * Construct the factory, using default constructor if caching is disabled.
+ * Refactored here to avoid duplication, used in tests.
+ */
+ public static SSLSocketFactory getSocketFactory(Context androidContext,
+ TrustManager[] trustManager) {
+ try {
+ if (androidContext != null) {
+ SSLClientSessionCache sessionCache = getSessionCache(androidContext);
+
+ if (sessionCache != null) {
+ SSLContextImpl sslContext = new SSLContextImpl();
+ sslContext.engineInit(null /* kms */,
+ trustManager, new java.security.SecureRandom(),
+ sessionCache, null /* serverCache */);
+ return sslContext.engineGetSocketFactory();
+ }
+ }
+ // default
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, trustManager, new java.security.SecureRandom());
+ return context.getSocketFactory();
+
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ } catch (KeyManagementException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 558a122..7eea8b7 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -23,6 +23,7 @@ import android.os.ParcelFormatException;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.Log;
+import android.util.Printer;
import android.util.SparseArray;
import java.io.File;
@@ -39,14 +40,14 @@ import java.util.Map;
* otherwise.
*/
public final class BatteryStatsImpl extends BatteryStats {
-
private static final String TAG = "BatteryStatsImpl";
+ private static final boolean DEBUG = false;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 23;
+ private static final int VERSION = 25;
private final File mFile;
private final File mBackupFile;
@@ -86,9 +87,11 @@ public final class BatteryStatsImpl extends BatteryStats {
long mLastRealtime;
boolean mScreenOn;
- long mLastScreenOnTimeMillis;
- long mBatteryScreenOnTimeMillis;
- long mPluggedScreenOnTimeMillis;
+ Timer mScreenOnTimer;
+
+ boolean mPhoneOn;
+ Timer mPhoneOnTimer;
+
/**
* These provide time bases that discount the time the device is plugged
* in to power.
@@ -132,16 +135,45 @@ public final class BatteryStatsImpl extends BatteryStats {
// Times are in microseconds for better accuracy when dividing by the
// lock count, and are in "battery realtime" units.
+ /**
+ * The total time we have accumulated since the start of the original
+ * boot, to the last time something interesting happened in the
+ * current run.
+ */
long mTotalTime;
- long mLoadedTotalTime;
- long mLastTotalTime;
- long mUpdateTime;
/**
- * The value of mTotalTime when unplug() was last called, initially 0.
+ * The total time we loaded for the previous runs. Subtract this from
+ * mTotalTime to find the time for the current run of the system.
*/
- long mUnpluggedTotalTime;
+ long mLoadedTime;
+
+ /**
+ * The run time of the last run of the system, as loaded from the
+ * saved data.
+ */
+ long mLastTime;
+
+ /**
+ * The value of mTotalTime when unplug() was last called. Subtract
+ * this from mTotalTime to find the time since the last unplug from
+ * power.
+ */
+ long mUnpluggedTime;
+ /**
+ * The last time at which we updated the timer. If mNesting is > 0,
+ * subtract this from the current battery time to find the amount of
+ * time we have been running since we last computed an update.
+ */
+ long mUpdateTime;
+
+ /**
+ * The total time at which the timer was acquired, to determine if
+ * was actually held for an interesting duration.
+ */
+ long mAcquireTime;
+
Timer(int type, ArrayList<Timer> timerPool,
ArrayList<Unpluggable> unpluggables, Parcel in) {
mType = type;
@@ -151,10 +183,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mLastCount = in.readInt();
mUnpluggedCount = in.readInt();
mTotalTime = in.readLong();
- mLoadedTotalTime = in.readLong();
- mLastTotalTime = in.readLong();
+ mLoadedTime = in.readLong();
+ mLastTime = in.readLong();
mUpdateTime = in.readLong();
- mUnpluggedTotalTime = in.readLong();
+ mUnpluggedTime = in.readLong();
unpluggables.add(this);
}
@@ -171,21 +203,41 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mLastCount);
out.writeInt(mUnpluggedCount);
out.writeLong(computeRunTimeLocked(batteryRealtime));
- out.writeLong(mLoadedTotalTime);
- out.writeLong(mLastTotalTime);
+ out.writeLong(mLoadedTime);
+ out.writeLong(mLastTime);
out.writeLong(mUpdateTime);
- out.writeLong(mUnpluggedTotalTime);
+ out.writeLong(mUnpluggedTime);
}
public void unplug(long batteryUptime, long batteryRealtime) {
- mUnpluggedTotalTime = computeRunTimeLocked(batteryRealtime);
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
+ + " old mUnpluggedTime=" + mUnpluggedTime
+ + " old mUnpluggedCount=" + mUnpluggedCount);
+ }
+ mUnpluggedTime = computeRunTimeLocked(batteryRealtime);
mUnpluggedCount = mCount;
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "unplug #" + mType
+ + ": new mUnpluggedTime=" + mUnpluggedTime
+ + " new mUnpluggedCount=" + mUnpluggedCount);
+ }
}
public void plug(long batteryUptime, long batteryRealtime) {
if (mNesting > 0) {
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
+ + " old mTotalTime=" + mTotalTime
+ + " old mUpdateTime=" + mUpdateTime);
+ }
mTotalTime = computeRunTimeLocked(batteryRealtime);
mUpdateTime = batteryRealtime;
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "plug #" + mType
+ + ": new mTotalTime=" + mTotalTime
+ + " old mUpdateTime=" + mUpdateTime);
+ }
}
}
@@ -207,16 +259,16 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public long getTotalTime(long now, int which) {
+ public long getTotalTime(long batteryRealtime, int which) {
long val;
if (which == STATS_LAST) {
- val = mLastTotalTime;
+ val = mLastTime;
} else {
- val = computeRunTimeLocked(now);
+ val = computeRunTimeLocked(batteryRealtime);
if (which == STATS_UNPLUGGED) {
- val -= mUnpluggedTotalTime;
+ val -= mUnpluggedTime;
} else if (which != STATS_TOTAL) {
- val -= mLoadedTotalTime;
+ val -= mLoadedTime;
}
}
@@ -245,22 +297,32 @@ public final class BatteryStatsImpl extends BatteryStats {
+ " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
+ " mUnpluggedCount=" + mUnpluggedCount);
Log.i("foo", "mTotalTime=" + mTotalTime
- + " mLoadedTotalTime=" + mLoadedTotalTime);
- Log.i("foo", "mLastTotalTime=" + mLastTotalTime
- + " mUpdateTime=" + mUpdateTime);
+ + " mLoadedTime=" + mLoadedTime);
+ Log.i("foo", "mLastTime=" + mLastTime
+ + " mUnpluggedTime=" + mUnpluggedTime);
+ Log.i("foo", "mUpdateTime=" + mUpdateTime
+ + " mAcquireTime=" + mAcquireTime);
}
void startRunningLocked(BatteryStatsImpl stats) {
if (mNesting++ == 0) {
mUpdateTime = stats.getBatteryRealtimeLocked(
SystemClock.elapsedRealtime() * 1000);
- // Accumulate time to all currently active timers before adding
- // this new one to the pool.
- refreshTimersLocked(stats, mTimerPool);
- // Add this timer to the active pool
- mTimerPool.add(this);
+ if (mTimerPool != null) {
+ // Accumulate time to all currently active timers before adding
+ // this new one to the pool.
+ refreshTimersLocked(stats, mTimerPool);
+ // Add this timer to the active pool
+ mTimerPool.add(this);
+ }
// Increment the count
mCount++;
+ mAcquireTime = mTotalTime;
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "start #" + mType + ": mUpdateTime=" + mUpdateTime
+ + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+ + " mAcquireTime=" + mAcquireTime);
+ }
}
}
@@ -270,11 +332,31 @@ public final class BatteryStatsImpl extends BatteryStats {
return;
}
if (--mNesting == 0) {
- // Accumulate time to all active counters, scaled by the total
- // active in the pool, before taking this one out of the pool.
- refreshTimersLocked(stats, mTimerPool);
- // Remove this timer from the active pool
- mTimerPool.remove(this);
+ if (mTimerPool != null) {
+ // Accumulate time to all active counters, scaled by the total
+ // active in the pool, before taking this one out of the pool.
+ refreshTimersLocked(stats, mTimerPool);
+ // Remove this timer from the active pool
+ mTimerPool.remove(this);
+ } else {
+ final long realtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
+ mNesting = 1;
+ mTotalTime = computeRunTimeLocked(batteryRealtime);
+ mNesting = 0;
+ }
+
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "stop #" + mType + ": mUpdateTime=" + mUpdateTime
+ + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+ + " mAcquireTime=" + mAcquireTime);
+ }
+
+ if (mTotalTime == mAcquireTime) {
+ // If there was no change in the time, then discard this
+ // count. A somewhat cheezy strategy, but hey.
+ mCount--;
+ }
}
}
@@ -297,24 +379,28 @@ public final class BatteryStatsImpl extends BatteryStats {
private long computeRunTimeLocked(long curBatteryRealtime) {
return mTotalTime + (mNesting > 0
- ? (curBatteryRealtime - mUpdateTime) / mTimerPool.size() : 0);
+ ? (curBatteryRealtime - mUpdateTime)
+ / (mTimerPool != null ? mTimerPool.size() : 1)
+ : 0);
}
- void writeSummaryFromParcelLocked(Parcel out, long curBatteryUptime) {
- long runTime = computeRunTimeLocked(curBatteryUptime);
+ void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
+ long runTime = computeRunTimeLocked(batteryRealtime);
// Divide by 1000 for backwards compatibility
out.writeLong((runTime + 500) / 1000);
- out.writeLong(((runTime - mLoadedTotalTime) + 500) / 1000);
+ out.writeLong(((runTime - mLoadedTime) + 500) / 1000);
out.writeInt(mCount);
out.writeInt(mCount - mLoadedCount);
}
void readSummaryFromParcelLocked(Parcel in) {
// Multiply by 1000 for backwards compatibility
- mTotalTime = mLoadedTotalTime = in.readLong() * 1000;
- mLastTotalTime = in.readLong();
+ mTotalTime = mLoadedTime = in.readLong() * 1000;
+ mLastTime = in.readLong() * 1000;
+ mUnpluggedTime = mTotalTime;
mCount = mLoadedCount = in.readInt();
mLastCount = in.readInt();
+ mUnpluggedCount = mCount;
mNesting = 0;
}
}
@@ -357,47 +443,40 @@ public final class BatteryStatsImpl extends BatteryStats {
mUidStats.get(uid).noteStopGps();
}
- /**
- * When the device screen or battery state changes, update the appropriate "screen on time"
- * counter.
- */
- private void updateScreenOnTimeLocked(boolean screenOn) {
+ public void noteScreenOnLocked() {
if (!mScreenOn) {
- Log.w(TAG, "updateScreenOnTime without mScreenOn, ignored");
- return;
- }
- long now = SystemClock.elapsedRealtime();
- long elapsed = now - mLastScreenOnTimeMillis;
- if (mOnBattery) {
- mBatteryScreenOnTimeMillis += elapsed;
- } else {
- mPluggedScreenOnTimeMillis += elapsed;
+ mScreenOn = true;
+ mScreenOnTimer.startRunningLocked(this);
}
- if (screenOn) {
- mLastScreenOnTimeMillis = now;
+ }
+
+ public void noteScreenOffLocked() {
+ if (mScreenOn) {
+ mScreenOn = false;
+ mScreenOnTimer.stopRunningLocked(this);
}
}
- public void noteScreenOnLocked() {
- mScreenOn = true;
- mLastScreenOnTimeMillis = SystemClock.elapsedRealtime();
+ public void notePhoneOnLocked() {
+ if (!mPhoneOn) {
+ mPhoneOn = true;
+ mPhoneOnTimer.startRunningLocked(this);
+ }
}
- public void noteScreenOffLocked() {
- if (!mScreenOn) {
- Log.w(TAG, "noteScreenOff without mScreenOn, ignored");
- return;
+ public void notePhoneOffLocked() {
+ if (mPhoneOn) {
+ mPhoneOn = false;
+ mPhoneOnTimer.stopRunningLocked(this);
}
- updateScreenOnTimeLocked(false);
- mScreenOn = false;
}
- @Override public long getBatteryScreenOnTime() {
- return mBatteryScreenOnTimeMillis;
+ @Override public long getScreenOnTime(long batteryRealtime, int which) {
+ return mScreenOnTimer.getTotalTime(batteryRealtime, which);
}
- @Override public long getPluggedScreenOnTime() {
- return mPluggedScreenOnTimeMillis;
+ @Override public long getPhoneOnTime(long batteryRealtime, int which) {
+ return mPhoneOnTimer.getTotalTime(batteryRealtime, which);
}
@Override public boolean getIsOnBattery() {
@@ -1381,6 +1460,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mFile = new File(filename);
mBackupFile = new File(filename + ".bak");
mStartCount++;
+ mScreenOnTimer = new Timer(-1, null, mUnpluggables);
+ mPhoneOnTimer = new Timer(-2, null, mUnpluggables);
mOnBattery = mOnBatteryInternal = false;
mTrackBatteryPastUptime = 0;
mTrackBatteryPastRealtime = 0;
@@ -1407,10 +1488,6 @@ public final class BatteryStatsImpl extends BatteryStats {
public void setOnBattery(boolean onBattery) {
synchronized(this) {
if (mOnBattery != onBattery) {
- if (mScreenOn) {
- updateScreenOnTimeLocked(true);
- }
-
mOnBattery = mOnBatteryInternal = onBattery;
long uptime = SystemClock.uptimeMillis() * 1000;
@@ -1425,7 +1502,7 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
- doPlug(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+ doPlug(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
}
if ((mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
@@ -1447,10 +1524,10 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public long computeUptime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL: return mUptime + (curTime-mUptimeStart);
- case STATS_LAST: return mLastUptime;
- case STATS_CURRENT: return (curTime-mUptimeStart);
- case STATS_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
+ case STATS_TOTAL: return mUptime + (curTime-mUptimeStart);
+ case STATS_LAST: return mLastUptime;
+ case STATS_CURRENT: return (curTime-mUptimeStart);
+ case STATS_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
}
return 0;
}
@@ -1458,26 +1535,25 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public long computeRealtime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL: return mRealtime + (curTime-mRealtimeStart);
- case STATS_LAST: return mLastRealtime;
- case STATS_CURRENT: return (curTime-mRealtimeStart);
- case STATS_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
+ case STATS_TOTAL: return mRealtime + (curTime-mRealtimeStart);
+ case STATS_LAST: return mLastRealtime;
+ case STATS_CURRENT: return (curTime-mRealtimeStart);
+ case STATS_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
}
return 0;
}
@Override
public long computeBatteryUptime(long curTime, int which) {
- long uptime = getBatteryUptime(curTime);
switch (which) {
- case STATS_TOTAL:
- return mBatteryUptime + uptime;
- case STATS_LAST:
- return mBatteryLastUptime;
- case STATS_CURRENT:
- return uptime;
- case STATS_UNPLUGGED:
- return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime;
+ case STATS_TOTAL:
+ return mBatteryUptime + getBatteryUptime(curTime);
+ case STATS_LAST:
+ return mBatteryLastUptime;
+ case STATS_CURRENT:
+ return getBatteryUptime(curTime);
+ case STATS_UNPLUGGED:
+ return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime;
}
return 0;
}
@@ -1485,14 +1561,14 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public long computeBatteryRealtime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL:
- return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
- case STATS_LAST:
- return mBatteryLastRealtime;
- case STATS_CURRENT:
- return getBatteryRealtimeLocked(curTime);
- case STATS_UNPLUGGED:
- return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime;
+ case STATS_TOTAL:
+ return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
+ case STATS_LAST:
+ return mBatteryLastRealtime;
+ case STATS_CURRENT:
+ return getBatteryRealtimeLocked(curTime);
+ case STATS_UNPLUGGED:
+ return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime;
}
return 0;
}
@@ -1688,9 +1764,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mLastRealtime = in.readLong();
mStartCount++;
- mBatteryScreenOnTimeMillis = in.readLong();
- mPluggedScreenOnTimeMillis = in.readLong();
mScreenOn = false;
+ mScreenOnTimer.readSummaryFromParcelLocked(in);
+ mPhoneOn = false;
+ mPhoneOnTimer.readSummaryFromParcelLocked(in);
final int NU = in.readInt();
for (int iu = 0; iu < NU; iu++) {
@@ -1764,9 +1841,10 @@ public final class BatteryStatsImpl extends BatteryStats {
* @param out the Parcel to be written to.
*/
public void writeSummaryToParcel(Parcel out) {
- final long NOW = getBatteryUptimeLocked();
final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
+ final long NOW = getBatteryUptimeLocked(NOW_SYS);
+ final long NOWREAL = getBatteryRealtimeLocked(NOWREAL_SYS);
out.writeInt(VERSION);
@@ -1780,8 +1858,8 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(computeRealtime(NOWREAL_SYS, STATS_TOTAL));
out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT));
- out.writeLong(mBatteryScreenOnTimeMillis);
- out.writeLong(mPluggedScreenOnTimeMillis);
+ mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
final int NU = mUidStats.size();
out.writeInt(NU);
@@ -1798,19 +1876,19 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Wakelock wl = ent.getValue();
if (wl.mTimerFull != null) {
out.writeInt(1);
- wl.mTimerFull.writeSummaryFromParcelLocked(out, NOW);
+ wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
if (wl.mTimerPartial != null) {
out.writeInt(1);
- wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOW);
+ wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
if (wl.mTimerWindow != null) {
out.writeInt(1);
- wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOW);
+ wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
@@ -1826,7 +1904,7 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Sensor se = ent.getValue();
if (se.mTimer != null) {
out.writeInt(1);
- se.mTimer.writeSummaryFromParcelLocked(out, NOW);
+ se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
@@ -1897,9 +1975,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mBatteryLastUptime = in.readLong();
mBatteryRealtime = in.readLong();
mBatteryLastRealtime = in.readLong();
- mBatteryScreenOnTimeMillis = in.readLong();
- mPluggedScreenOnTimeMillis = in.readLong();
mScreenOn = false;
+ mScreenOnTimer = new Timer(-1, null, mUnpluggables, in);
+ mPhoneOn = false;
+ mPhoneOnTimer = new Timer(-2, null, mUnpluggables, in);
mUptime = in.readLong();
mUptimeStart = in.readLong();
mLastUptime = in.readLong();
@@ -1912,6 +1991,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mTrackBatteryUptimeStart = in.readLong();
mTrackBatteryPastRealtime = in.readLong();
mTrackBatteryRealtimeStart = in.readLong();
+ mUnpluggedBatteryUptime = in.readLong();
+ mUnpluggedBatteryRealtime = in.readLong();
mLastWriteTime = in.readLong();
mPartialTimers.clear();
@@ -1945,8 +2026,8 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mBatteryLastUptime);
out.writeLong(mBatteryRealtime);
out.writeLong(mBatteryLastRealtime);
- out.writeLong(mBatteryScreenOnTimeMillis);
- out.writeLong(mPluggedScreenOnTimeMillis);
+ mScreenOnTimer.writeToParcel(out, batteryRealtime);
+ mPhoneOnTimer.writeToParcel(out, batteryRealtime);
out.writeLong(mUptime);
out.writeLong(mUptimeStart);
out.writeLong(mLastUptime);
@@ -1954,10 +2035,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mRealtimeStart);
out.writeLong(mLastRealtime);
out.writeInt(mOnBattery ? 1 : 0);
- out.writeLong(mTrackBatteryPastUptime);
+ out.writeLong(batteryUptime);
out.writeLong(mTrackBatteryUptimeStart);
- out.writeLong(mTrackBatteryPastRealtime);
+ out.writeLong(batteryRealtime);
out.writeLong(mTrackBatteryRealtimeStart);
+ out.writeLong(mUnpluggedBatteryUptime);
+ out.writeLong(mUnpluggedBatteryRealtime);
out.writeLong(mLastWriteTime);
int size = mUidStats.size();
@@ -1980,4 +2063,14 @@ public final class BatteryStatsImpl extends BatteryStats {
return new BatteryStatsImpl[size];
}
};
+
+ public void dumpLocked(Printer pw) {
+ if (DEBUG) {
+ Log.i(TAG, "*** Screen timer:");
+ mScreenOnTimer.logState();
+ Log.i(TAG, "*** Phone timer:");
+ mPhoneOnTimer.logState();
+ }
+ super.dumpLocked(pw);
+ }
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f21b62f..ac8b589 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,6 +19,7 @@ package com.android.internal.os;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
@@ -335,32 +336,18 @@ public class ZygoteInit {
mResources.startPreloading();
if (PRELOAD_RESOURCES) {
Log.i(TAG, "Preloading resources...");
+
long startTime = SystemClock.uptimeMillis();
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
- int N = ar.length();
- for (int i=0; i<N; i++) {
- if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
- Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
- }
- runtime.gcSoftReferences();
- runtime.runFinalizationSync();
- Debug.resetGlobalAllocSize();
- }
- int id = ar.getResourceId(i, 0);
- if (Config.LOGV) {
- Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
- }
- if (id != 0) {
- Drawable dr = mResources.getDrawable(id);
- if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
- Log.w(TAG, "Preloaded drawable resource #0x"
- + Integer.toHexString(id)
- + " that varies with configuration!!");
- }
- }
- }
+ int N = preloadDrawables(runtime, ar);
+ Log.i(TAG, "...preloaded " + N + " resources in "
+ + (SystemClock.uptimeMillis()-startTime) + "ms.");
+
+ startTime = SystemClock.uptimeMillis();
+ ar = mResources.obtainTypedArray(
+ com.android.internal.R.array.preloaded_color_state_lists);
+ N = preloadColorStateLists(runtime, ar);
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
}
@@ -372,6 +359,56 @@ public class ZygoteInit {
}
}
+ private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
+ int N = ar.length();
+ for (int i=0; i<N; i++) {
+ if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
+ if (Config.LOGV) {
+ Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
+ }
+ runtime.gcSoftReferences();
+ runtime.runFinalizationSync();
+ Debug.resetGlobalAllocSize();
+ }
+ int id = ar.getResourceId(i, 0);
+ if (Config.LOGV) {
+ Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+ }
+ if (id != 0) {
+ mResources.getColorStateList(id);
+ }
+ }
+ return N;
+ }
+
+
+ private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
+ int N = ar.length();
+ for (int i=0; i<N; i++) {
+ if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
+ if (Config.LOGV) {
+ Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
+ }
+ runtime.gcSoftReferences();
+ runtime.runFinalizationSync();
+ Debug.resetGlobalAllocSize();
+ }
+ int id = ar.getResourceId(i, 0);
+ if (Config.LOGV) {
+ Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+ }
+ if (id != 0) {
+ Drawable dr = mResources.getDrawable(id);
+ if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
+ Log.w(TAG, "Preloaded drawable resource #0x"
+ + Integer.toHexString(id)
+ + " (" + ar.getString(i) + ") that varies with configuration!!");
+ }
+ }
+ }
+ return N;
+ }
+
/**
* Runs several special GCs to try to clean up a few generations of
* softly- and final-reachable objects, along with any other garbage.
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 4f98cee..8e65177 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -23,7 +23,8 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
private static final int DO_COMMIT_TEXT = 50;
private static final int DO_COMMIT_COMPLETION = 55;
private static final int DO_SET_SELECTION = 57;
- private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 58;
+ private static final int DO_PERFORM_EDITOR_ACTION = 58;
+ private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;
private static final int DO_SET_COMPOSING_TEXT = 60;
private static final int DO_FINISH_COMPOSING_TEXT = 65;
private static final int DO_SEND_KEY_EVENT = 70;
@@ -97,6 +98,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end));
}
+ public void performEditorAction(int id) {
+ dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0));
+ }
+
public void performContextMenuAction(int id) {
dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0));
}
@@ -235,6 +240,15 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
ic.setSelection(msg.arg1, msg.arg2);
return;
}
+ case DO_PERFORM_EDITOR_ACTION: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "performEditorAction on inactive InputConnection");
+ return;
+ }
+ ic.performEditorAction(msg.arg1);
+ return;
+ }
case DO_PERFORM_CONTEXT_MENU_ACTION: {
InputConnection ic = mInputConnection.get();
if (ic == null || !isActive()) {
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index 02b6044..02cb9e4 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -50,6 +50,8 @@ import com.android.internal.view.IInputContextCallback;
void setSelection(int start, int end);
+ void performEditorAction(int actionCode);
+
void performContextMenuAction(int id);
void beginBatchEdit();
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 32d9f3d..b92cb45 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -250,6 +250,15 @@ public class InputConnectionWrapper implements InputConnection {
}
}
+ public boolean performEditorAction(int actionCode) {
+ try {
+ mIInputContext.performEditorAction(actionCode);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
public boolean performContextMenuAction(int id) {
try {
mIInputContext.performContextMenuAction(id);
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index c16c165..9e4b4ce 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -80,6 +80,11 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men
setChildrenDrawingCacheEnabled(false);
}
+ @Override
+ protected boolean recycleOnMeasure() {
+ return false;
+ }
+
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 44cf0ed..f2ec064 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -59,7 +59,14 @@ public class EditableInputConnection extends BaseInputConnection {
final Editable content = getEditable();
if (content == null) return false;
KeyListener kl = mTextView.getKeyListener();
- if (kl != null) kl.clearMetaKeyState(mTextView, content, states);
+ if (kl != null) {
+ try {
+ kl.clearMetaKeyState(mTextView, content, states);
+ } catch (AbstractMethodError e) {
+ // This is an old listener that doesn't implement the
+ // new method.
+ }
+ }
return true;
}
@@ -71,6 +78,12 @@ public class EditableInputConnection extends BaseInputConnection {
return true;
}
+ public boolean performEditorAction(int actionCode) {
+ if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
+ mTextView.onEditorAction(actionCode);
+ return true;
+ }
+
public boolean performContextMenuAction(int id) {
if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
mTextView.beginBatchEdit();
diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java
index 5bf4601..aee7b76 100644
--- a/core/java/com/android/internal/widget/TextProgressBar.java
+++ b/core/java/com/android/internal/widget/TextProgressBar.java
@@ -115,8 +115,10 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick
// Update the ProgressBar maximum relative to Chronometer base
mDuration = (int) (durationBase - mChronometer.getBase());
+ if (mDuration <= 0) {
+ mDuration = 1;
+ }
mProgressBar.setMax(mDuration);
-
}
/**
diff --git a/core/java/com/google/android/gdata/client/AndroidGDataClient.java b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
index 1d8e9c5..fe7d860 100644
--- a/core/java/com/google/android/gdata/client/AndroidGDataClient.java
+++ b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
@@ -21,6 +21,7 @@ import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.AbstractHttpEntity;
import android.content.ContentResolver;
+import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.text.TextUtils;
import android.util.Config;
@@ -117,10 +118,7 @@ public class AndroidGDataClient implements GDataClient {
}
/**
- * Creates a new AndroidGDataClient.
- *
- * @param resolver The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
+ * @deprecated Use AndroidGDAtaClient(Context) instead.
*/
public AndroidGDataClient(ContentResolver resolver) {
mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_APP_VERSION,
@@ -129,6 +127,21 @@ public class AndroidGDataClient implements GDataClient {
mResolver = resolver;
}
+ /**
+ * Creates a new AndroidGDataClient.
+ *
+ * @param context The ContentResolver to get URL rewriting rules from
+ * through the Android proxy server, using null to indicate not using proxy.
+ * The context will also be used by GoogleHttpClient for configuration of
+ * SSL session persistence.
+ */
+ public AndroidGDataClient(Context context) {
+ mHttpClient = new GoogleHttpClient(context, USER_AGENT_APP_VERSION,
+ true /* gzip capable */);
+ mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
+ mResolver = context.getContentResolver();
+ }
+
public void close() {
mHttpClient.close();
}
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
index 2fcb0c3..ae78ba8 100644
--- a/core/java/com/google/android/net/GoogleHttpClient.java
+++ b/core/java/com/google/android/net/GoogleHttpClient.java
@@ -16,8 +16,12 @@
package com.google.android.net;
+import com.android.internal.net.DbSSLSessionCache;
+import com.android.internal.net.SSLSessionCache;
+
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.os.Build;
import android.os.NetStat;
@@ -79,6 +83,24 @@ public class GoogleHttpClient implements HttpClient {
}
/**
+ * GoogleHttpClient(Context, String, boolean) - without SSL session
+ * persistence.
+ *
+ * @deprecated use Context instead of ContentResolver.
+ */
+ public GoogleHttpClient(ContentResolver resolver, String appAndVersion,
+ boolean gzipCapable) {
+ String userAgent = appAndVersion
+ + " (" + Build.DEVICE + " " + Build.ID + ")";
+ if (gzipCapable) {
+ userAgent = userAgent + "; gzip";
+ }
+ mClient = AndroidHttpClient.newInstance(userAgent);
+ mResolver = resolver;
+ mUserAgent = userAgent;
+ }
+
+ /**
* Create an HTTP client. Normaly this client is shared throughout an app.
* The HTTP client will construct its User-Agent as follows:
*
@@ -87,7 +109,10 @@ public class GoogleHttpClient implements HttpClient {
* <appAndVersion> (<build device> <build id>); gzip
* (if gzip capable)
*
- * @param resolver to use for acccessing URL rewriting rules.
+ * The context has settings for URL rewriting rules and is used to enable
+ * SSL session persistence.
+ *
+ * @param context application context.
* @param appAndVersion Base app and version to use in the User-Agent.
* e.g., "MyApp/1.0"
* @param gzipCapable Whether or not this client is able to consume gzip'd
@@ -95,15 +120,18 @@ public class GoogleHttpClient implements HttpClient {
* headers. Needed because Google servers require gzip in the User-Agent
* in order to return gzip'd content.
*/
- public GoogleHttpClient(ContentResolver resolver, String appAndVersion,
- boolean gzipCapable) {
+ public GoogleHttpClient(Context context, String appAndVersion,
+ boolean gzipCapable) {
+
String userAgent = appAndVersion
+ " (" + Build.DEVICE + " " + Build.ID + ")";
if (gzipCapable) {
userAgent = userAgent + "; gzip";
}
- mClient = AndroidHttpClient.newInstance(userAgent);
- mResolver = resolver;
+ mClient = AndroidHttpClient.newInstance(userAgent,
+ SSLSessionCache.getSessionCache(context));
+
+ mResolver = context.getContentResolver();
mUserAgent = userAgent;
}