Merge "Change the OpenGL viewport when new layout params are used. Bug #3303158"
diff --git a/api/current.xml b/api/current.xml
index af6d2cd..0f461a2 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -138970,7 +138970,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139014,7 +139014,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139025,7 +139025,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139036,7 +139036,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139047,7 +139047,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139058,7 +139058,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139069,7 +139069,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139080,7 +139080,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139091,7 +139091,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139102,7 +139102,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139113,7 +139113,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -139124,7 +139124,7 @@
synchronized="false"
static="true"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -256580,7 +256580,7 @@
return="void"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
@@ -256595,7 +256595,7 @@
return="void"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
@@ -256729,7 +256729,7 @@
return="java.beans.PropertyChangeListener[]"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
@@ -256742,7 +256742,7 @@
return="java.beans.PropertyChangeListener[]"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
@@ -256753,7 +256753,7 @@
return="boolean"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
@@ -256766,7 +256766,7 @@
return="void"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
@@ -256781,7 +256781,7 @@
return="void"
abstract="false"
native="false"
- synchronized="true"
+ synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 9a8f2d2..d15fb88 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -18,14 +18,20 @@
import android.content.res.Resources;
import android.database.sqlite.SQLiteClosable;
+import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
+import android.util.Log;
+import android.util.SparseIntArray;
/**
* A buffer containing multiple cursor rows.
*/
public class CursorWindow extends SQLiteClosable implements Parcelable {
+ private static final String STATS_TAG = "CursorWindowStats";
+
/** The cursor window size. resource xml file specifies the value in kB.
* convert it to bytes here by multiplying with 1024.
*/
@@ -33,8 +39,9 @@
Resources.getSystem().getInteger(
com.android.internal.R.integer.config_cursorWindowSize) * 1024;
- /** The pointer to the native window class */
- @SuppressWarnings("unused")
+ /** The pointer to the native window class. set by the native methods in
+ * android_database_CursorWindow.cpp
+ */
private int nWindow;
private int mStartPos;
@@ -46,7 +53,18 @@
*/
public CursorWindow(boolean localWindow) {
mStartPos = 0;
- native_init(sCursorWindowSize, localWindow);
+ int rslt = native_init(sCursorWindowSize, localWindow);
+ printDebugMsgIfError(rslt);
+ recordNewWindow(Binder.getCallingPid(), nWindow);
+ }
+
+ private void printDebugMsgIfError(int rslt) {
+ if (rslt > 0) {
+ // cursor window allocation failed. either low memory or too many cursors being open.
+ // print info to help in debugging this.
+ throw new CursorWindowAllocationException("Cursor Window allocation of " +
+ sCursorWindowSize/1024 + " kb failed. " + printStats());
+ }
}
/**
@@ -574,21 +592,78 @@
private CursorWindow(Parcel source) {
IBinder nativeBinder = source.readStrongBinder();
mStartPos = source.readInt();
-
- native_init(nativeBinder);
+ int rslt = native_init(nativeBinder);
+ printDebugMsgIfError(rslt);
}
/** Get the binder for the native side of the window */
private native IBinder native_getBinder();
/** Does the native side initialization for an empty window */
- private native void native_init(int cursorWindowSize, boolean localOnly);
+ private native int native_init(int cursorWindowSize, boolean localOnly);
/** Does the native side initialization with an existing binder from another process */
- private native void native_init(IBinder nativeBinder);
+ private native int native_init(IBinder nativeBinder);
@Override
protected void onAllReferencesReleased() {
- close_native();
+ int windowId = nWindow;
+ recordClosingOfWindow(Binder.getCallingPid(), nWindow);
+ close_native();
+ }
+
+ private static final SparseIntArray sWindowToPidMap = new SparseIntArray();
+
+ private void recordNewWindow(int pid, int window) {
+ synchronized (sWindowToPidMap) {
+ sWindowToPidMap.put(window, pid);
+ if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) {
+ Log.i(STATS_TAG, "Created a new Cursor. " + printStats());
+ }
+ }
+ }
+
+ private void recordClosingOfWindow(int pid, int window) {
+ synchronized (sWindowToPidMap) {
+ if (sWindowToPidMap.size() == 0) {
+ // this means we are not in the ContentProvider.
+ return;
+ }
+ sWindowToPidMap.delete(window);
+ }
+ }
+ private String printStats() {
+ StringBuilder buff = new StringBuilder();
+ int myPid = Process.myPid();
+ int total = 0;
+ SparseIntArray pidCounts = new SparseIntArray();
+ synchronized (sWindowToPidMap) {
+ int size = sWindowToPidMap.size();
+ if (size == 0) {
+ // this means we are not in the ContentProvider.
+ return "";
+ }
+ for (int indx = 0; indx < size; indx++) {
+ int pid = sWindowToPidMap.valueAt(indx);
+ int value = pidCounts.get(pid);
+ pidCounts.put(pid, ++value);
+ }
+ }
+ int numPids = pidCounts.size();
+ for (int i = 0; i < numPids;i++) {
+ buff.append(" (# cursors opened by ");
+ int pid = pidCounts.keyAt(i);
+ if (pid == myPid) {
+ buff.append("this proc=");
+ } else {
+ buff.append("pid " + pid + "=");
+ }
+ int num = pidCounts.get(pid);
+ buff.append(num + ")");
+ total += num;
+ }
+ // limit the returned string size to 1000
+ String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
+ return "# Open Cursors=" + total + s;
}
}
diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java
new file mode 100644
index 0000000..ba7df68
--- /dev/null
+++ b/core/java/android/database/CursorWindowAllocationException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+/**
+ * This exception is thrown when a CursorWindow couldn't be allocated,
+ * most probably due to memory not being available
+ */
+class CursorWindowAllocationException extends java.lang.RuntimeException
+{
+ public CursorWindowAllocationException()
+ {
+ super();
+ }
+
+ public CursorWindowAllocationException(String description)
+ {
+ super(description);
+ }
+}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 93f9a71e..01e9fb3 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -38,20 +38,22 @@
}
public void releaseReference() {
+ boolean refCountIsZero = false;
synchronized(this) {
- mReferenceCount--;
- if (mReferenceCount == 0) {
- onAllReferencesReleased();
- }
+ refCountIsZero = --mReferenceCount == 0;
+ }
+ if (refCountIsZero) {
+ onAllReferencesReleased();
}
}
public void releaseReferenceFromContainer() {
+ boolean refCountIsZero = false;
synchronized(this) {
- mReferenceCount--;
- if (mReferenceCount == 0) {
- onAllReferencesReleasedFromContainer();
- }
+ refCountIsZero = --mReferenceCount == 0;
+ }
+ if (refCountIsZero) {
+ onAllReferencesReleasedFromContainer();
}
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 6f59dc9..d8bc266 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -991,8 +991,10 @@
}
return db;
} catch (SQLiteDatabaseCorruptException e) {
- db.mErrorHandler.onCorruption(db);
- return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
+ return handleCorruptedDatabase(db, path, factory, flags, errorHandler);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.e(TAG, "database file can't be opened. possibly due to database corruption.");
+ return handleCorruptedDatabase(db, path, factory, flags, errorHandler);
} catch (SQLiteException e) {
Log.e(TAG, "Failed to open the database. closing it.", e);
db.close();
@@ -1000,6 +1002,12 @@
}
}
+ private static SQLiteDatabase handleCorruptedDatabase(SQLiteDatabase db, String path,
+ CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {
+ db.mErrorHandler.onCorruption(db);
+ return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
+ }
+
/**
* Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
*/
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 8d9867e7..40d9cff 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -559,18 +559,55 @@
/* cumulative elapsed time for class initialization, in usec */
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
}
+
+ /**
+ * Returns the global count of external allocation requests. Now
+ * that external allocation tracking no longer exists this method
+ * always returns 0.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
public static int getGlobalExternalAllocCount() {
- return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
+ return 0;
}
+
+ /**
+ * Returns the global count of bytes externally allocated. Now
+ * that external allocation tracking no longer exists this method
+ * always returns 0.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
public static int getGlobalExternalAllocSize() {
- return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_BYTES);
+ return 0;
}
+
+ /**
+ * Returns the global count of freed external allocation requests.
+ * Now that external allocation tracking no longer exists this
+ * method always returns 0.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
public static int getGlobalExternalFreedCount() {
- return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_OBJECTS);
+ return 0;
}
+
+ /**
+ * Returns the global count of freed bytes from external
+ * allocation requests. Now that external allocation tracking no
+ * longer exists this method always returns 0.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
public static int getGlobalExternalFreedSize() {
- return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_BYTES);
+ return 0;
}
+
public static int getGlobalGcInvocationCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
}
@@ -580,12 +617,31 @@
public static int getThreadAllocSize() {
return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
}
+
+ /**
+ * Returns the count of external allocation requests made by the
+ * current thread. Now that external allocation tracking no
+ * longer exists this method always returns 0.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
public static int getThreadExternalAllocCount() {
- return VMDebug.getAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_OBJECTS);
+ return 0;
}
+
+ /**
+ * Returns the global count of bytes externally allocated. Now
+ * that external allocation tracking no longer exists this method
+ * always returns 0.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
public static int getThreadExternalAllocSize() {
- return VMDebug.getAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_BYTES);
+ return 0;
}
+
public static int getThreadGcInvocationCount() {
return VMDebug.getAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
}
@@ -608,18 +664,47 @@
public static void resetGlobalClassInitTime() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
}
- public static void resetGlobalExternalAllocCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
- }
- public static void resetGlobalExternalAllocSize() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_BYTES);
- }
- public static void resetGlobalExternalFreedCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_OBJECTS);
- }
- public static void resetGlobalExternalFreedSize() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_FREED_BYTES);
- }
+
+ /**
+ * Resets the global count of external allocation requests. Now
+ * that external allocation tracking has been removed this method
+ * has no effect.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalAllocCount() {}
+
+ /**
+ * Resets the global count of bytes externally allocated. Now
+ * that external allocation tracking has been removed this method
+ * has no effect.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalAllocSize() {}
+
+ /**
+ * Resets the global count of freed external allocations. Now
+ * that external allocation tracking has been removed this method
+ * has no effect.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalFreedCount() {}
+
+ /**
+ * Resets the global count counter of freed bytes from external
+ * allocations. Now that external allocation tracking has been
+ * removed this method has no effect.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalFreedSize() {}
+
public static void resetGlobalGcInvocationCount() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
}
@@ -629,12 +714,27 @@
public static void resetThreadAllocSize() {
VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
}
- public static void resetThreadExternalAllocCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_OBJECTS);
- }
- public static void resetThreadExternalAllocSize() {
- VMDebug.resetAllocCount(VMDebug.KIND_THREAD_EXT_ALLOCATED_BYTES);
- }
+
+ /**
+ * Resets the count of external allocation requests made by the
+ * current thread. Now that external allocation tracking has been
+ * removed this method has no effect.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetThreadExternalAllocCount() {}
+
+ /**
+ * Resets the count of bytes externally allocated by the current
+ * thread. Now that external allocation tracking has been removed
+ * this method has no effect.
+ *
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetThreadExternalAllocSize() {}
+
public static void resetThreadGcInvocationCount() {
VMDebug.resetAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index d8d63206..3dd1ecd 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -361,103 +361,101 @@
if (fitbottom > okbottom)
okbottom = fitbottom;
}
- } else {
- if (breakOnlyAtSpaces) {
- if (ok != here) {
- // Log.e("text", "output ok " + here + " to " +ok);
+ } else if (breakOnlyAtSpaces) {
+ if (ok != here) {
+ // Log.e("text", "output ok " + here + " to " +ok);
- while (ok < spanEnd && chs[ok - paraStart] == ' ') {
- ok++;
- }
-
- v = out(source,
- here, ok,
- okascent, okdescent, oktop, okbottom,
- v,
- spacingmult, spacingadd, chooseht,
- choosehtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- ok == bufend, includepad, trackpad,
- chs, widths, here - paraStart,
- where, ellipsizedWidth, okwidth,
- paint);
-
- here = ok;
- } else {
- // Act like it fit even though it didn't.
-
- fitwidth = w;
- fit = j + 1;
-
- if (fmtop < fittop)
- fittop = fmtop;
- if (fmascent < fitascent)
- fitascent = fmascent;
- if (fmdescent > fitdescent)
- fitdescent = fmdescent;
- if (fmbottom > fitbottom)
- fitbottom = fmbottom;
+ while (ok < spanEnd && chs[ok - paraStart] == ' ') {
+ ok++;
}
+
+ v = out(source,
+ here, ok,
+ okascent, okdescent, oktop, okbottom,
+ v,
+ spacingmult, spacingadd, chooseht,
+ choosehtv, fm, hasTabOrEmoji,
+ needMultiply, paraStart, chdirs, dir, easy,
+ ok == bufend, includepad, trackpad,
+ chs, widths, here - paraStart,
+ where, ellipsizedWidth, okwidth,
+ paint);
+
+ here = ok;
} else {
- if (ok != here) {
- // Log.e("text", "output ok " + here + " to " +ok);
+ // Act like it fit even though it didn't.
- while (ok < spanEnd && chs[ok - paraStart] == ' ') {
- ok++;
- }
+ fitwidth = w;
+ fit = j + 1;
- v = out(source,
- here, ok,
- okascent, okdescent, oktop, okbottom,
- v,
- spacingmult, spacingadd, chooseht,
- choosehtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- ok == bufend, includepad, trackpad,
- chs, widths, here - paraStart,
- where, ellipsizedWidth, okwidth,
- paint);
+ if (fmtop < fittop)
+ fittop = fmtop;
+ if (fmascent < fitascent)
+ fitascent = fmascent;
+ if (fmdescent > fitdescent)
+ fitdescent = fmdescent;
+ if (fmbottom > fitbottom)
+ fitbottom = fmbottom;
+ }
+ } else {
+ if (ok != here) {
+ // Log.e("text", "output ok " + here + " to " +ok);
- here = ok;
- } else if (fit != here) {
- // Log.e("text", "output fit " + here + " to " +fit);
- v = out(source,
- here, fit,
- fitascent, fitdescent,
- fittop, fitbottom,
- v,
- spacingmult, spacingadd, chooseht,
- choosehtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- fit == bufend, includepad, trackpad,
- chs, widths, here - paraStart,
- where, ellipsizedWidth, fitwidth,
- paint);
-
- here = fit;
- } else {
- // Log.e("text", "output one " + here + " to " +(here + 1));
- // XXX not sure why the existing fm wasn't ok.
- // measureText(paint, mWorkPaint,
- // source, here, here + 1, fm, tab,
- // null);
-
- v = out(source,
- here, here+1,
- fm.ascent, fm.descent,
- fm.top, fm.bottom,
- v,
- spacingmult, spacingadd, chooseht,
- choosehtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- here + 1 == bufend, includepad,
- trackpad,
- chs, widths, here - paraStart,
- where, ellipsizedWidth,
- widths[here - paraStart], paint);
-
- here = here + 1;
+ while (ok < spanEnd && chs[ok - paraStart] == ' ') {
+ ok++;
}
+
+ v = out(source,
+ here, ok,
+ okascent, okdescent, oktop, okbottom,
+ v,
+ spacingmult, spacingadd, chooseht,
+ choosehtv, fm, hasTabOrEmoji,
+ needMultiply, paraStart, chdirs, dir, easy,
+ ok == bufend, includepad, trackpad,
+ chs, widths, here - paraStart,
+ where, ellipsizedWidth, okwidth,
+ paint);
+
+ here = ok;
+ } else if (fit != here) {
+ // Log.e("text", "output fit " + here + " to " +fit);
+ v = out(source,
+ here, fit,
+ fitascent, fitdescent,
+ fittop, fitbottom,
+ v,
+ spacingmult, spacingadd, chooseht,
+ choosehtv, fm, hasTabOrEmoji,
+ needMultiply, paraStart, chdirs, dir, easy,
+ fit == bufend, includepad, trackpad,
+ chs, widths, here - paraStart,
+ where, ellipsizedWidth, fitwidth,
+ paint);
+
+ here = fit;
+ } else {
+ // Log.e("text", "output one " + here + " to " +(here + 1));
+ // XXX not sure why the existing fm wasn't ok.
+ // measureText(paint, mWorkPaint,
+ // source, here, here + 1, fm, tab,
+ // null);
+
+ v = out(source,
+ here, here+1,
+ fm.ascent, fm.descent,
+ fm.top, fm.bottom,
+ v,
+ spacingmult, spacingadd, chooseht,
+ choosehtv, fm, hasTabOrEmoji,
+ needMultiply, paraStart, chdirs, dir, easy,
+ here + 1 == bufend, includepad,
+ trackpad,
+ chs, widths, here - paraStart,
+ where, ellipsizedWidth,
+ widths[here - paraStart], paint);
+
+ here = here + 1;
}
if (here < spanStart) {
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 80c8169..9e73371 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -16,10 +16,13 @@
package android.text.method;
+import android.text.Layout;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.style.ClickableSpan;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.text.*;
-import android.text.style.*;
import android.view.View;
import android.widget.TextView;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 45927f9..43ec378 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2577,10 +2577,7 @@
.sendToTarget();
}
- // called by JNI. PluginWidget functions for creating an embedded View for
- // the surface drawing model.
- private ViewManager.ChildView addSurface(View pluginView, int x, int y,
- int width, int height) {
+ private ViewManager.ChildView createSurface(View pluginView) {
if (mWebView == null) {
return null;
}
@@ -2598,6 +2595,14 @@
ViewManager.ChildView view = mWebView.mViewManager.createView();
view.mView = pluginView;
+ return view;
+ }
+
+ // called by JNI. PluginWidget functions for creating an embedded View for
+ // the surface drawing model.
+ private ViewManager.ChildView addSurface(View pluginView, int x, int y,
+ int width, int height) {
+ ViewManager.ChildView view = createSurface(pluginView);
view.attachView(x, y, width, height);
return view;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ae4b193..76d4835 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7639,6 +7639,7 @@
type == Character.LOWERCASE_LETTER ||
type == Character.TITLECASE_LETTER ||
type == Character.MODIFIER_LETTER ||
+ type == Character.OTHER_LETTER || // Should handle asian characters
type == Character.DECIMAL_DIGIT_NUMBER);
}
@@ -7647,15 +7648,15 @@
*
* @param offset An offset in the text.
* @return The offsets for the start and end of the word located at <code>offset</code>.
- * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
- * Returns a negative value if no valid word was found.
+ * The two ints offsets are packed in a long using {@link #packRangeInLong(int, int)}.
+ * Returns -1 if no valid word was found.
*/
private long getWordLimitsAt(int offset) {
int klass = mInputType & InputType.TYPE_MASK_CLASS;
int variation = mInputType & InputType.TYPE_MASK_VARIATION;
// Text selection is not permitted in password fields
- if (isPasswordInputType(mInputType) || isVisiblePasswordInputType(mInputType)) {
+ if (hasPasswordTransformationMethod()) {
return -1;
}
@@ -7739,17 +7740,16 @@
}
if (hasPasswordTransformationMethod()) {
- // selectCurrentWord is not available on a password field and would return an
- // arbitrary 10-charater selection around pressed position. Select all instead.
+ // Always select all on a password field.
// Cut/copy menu entries are not available for passwords, but being able to select all
// is however useful to delete or paste to replace the entire content.
selectAll();
return;
}
- long lastTouchOffset = getLastTouchOffsets();
- final int minOffset = extractRangeStartFromLong(lastTouchOffset);
- final int maxOffset = extractRangeEndFromLong(lastTouchOffset);
+ long lastTouchOffsets = getLastTouchOffsets();
+ final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
+ final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
int selectionStart, selectionEnd;
@@ -7979,43 +7979,48 @@
/**
* Prepare text so that there are not zero or two spaces at beginning and end of region defined
* by [min, max] when replacing this region by paste.
- * Note that if there was two spaces (or more) at that position before, they are kept. We just
+ * Note that if there were two spaces (or more) at that position before, they are kept. We just
* make sure we do not add an extra one from the paste content.
*/
private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
- // Paste adds/removes spaces before or after insertion as needed.
- if (paste.length() > 0 && Character.isSpaceChar(paste.charAt(0))) {
- if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
- // Two spaces at beginning of paste: remove one
- final int originalLength = mText.length();
- ((Editable) mText).delete(min - 1, min);
- // Due to filters, there is no guarantee that exactly one character was
- // removed. Count instead.
- final int delta = mText.length() - originalLength;
- min += delta;
- max += delta;
- }
- } else {
- if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
- // No space at beginning of paste: add one
- final int originalLength = mText.length();
- ((Editable) mText).replace(min, min, " ");
- // Taking possible filters into account as above.
- final int delta = mText.length() - originalLength;
- min += delta;
- max += delta;
- }
- }
+ if (paste.length() > 0) {
+ if (min > 0) {
+ final char charBefore = mTransformed.charAt(min - 1);
+ final char charAfter = paste.charAt(0);
- if (paste.length() > 0 && Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
- if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
- // Two spaces at end of paste: remove one
- ((Editable) mText).delete(max, max + 1);
+ if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
+ // Two spaces at beginning of paste: remove one
+ final int originalLength = mText.length();
+ ((Editable) mText).delete(min - 1, min);
+ // Due to filters, there is no guarantee that exactly one character was
+ // removed: count instead.
+ final int delta = mText.length() - originalLength;
+ min += delta;
+ max += delta;
+ } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
+ !Character.isSpaceChar(charAfter) && charAfter != '\n') {
+ // No space at beginning of paste: add one
+ final int originalLength = mText.length();
+ ((Editable) mText).replace(min, min, " ");
+ // Taking possible filters into account as above.
+ final int delta = mText.length() - originalLength;
+ min += delta;
+ max += delta;
+ }
}
- } else {
- if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
- // No space at end of paste: add one
- ((Editable) mText).replace(max, max, " ");
+
+ if (max < mText.length()) {
+ final char charBefore = paste.charAt(paste.length() - 1);
+ final char charAfter = mTransformed.charAt(max);
+
+ if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
+ // Two spaces at end of paste: remove one
+ ((Editable) mText).delete(max, max + 1);
+ } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
+ !Character.isSpaceChar(charAfter) && charAfter != '\n') {
+ // No space at end of paste: add one
+ ((Editable) mText).replace(max, max, " ");
+ }
}
}
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index c4cd2a6..a428151 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -50,7 +50,7 @@
return GET_WINDOW(env, javaWindow);
}
-static void native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize,
+static jint native_init_empty(JNIEnv * env, jobject object, jint cursorWindowSize,
jboolean localOnly)
{
uint8_t * data;
@@ -59,44 +59,38 @@
window = new CursorWindow(cursorWindowSize);
if (!window) {
- jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
- return;
+ return 1;
}
-
if (!window->initBuffer(localOnly)) {
- jniThrowException(env, "java/lang/RuntimeException",
- "Memory couldn't be allocated for 1MB CursorWindow object.");
delete window;
- return;
+ return 1;
}
-LOG_WINDOW("native_init_empty: window = %p", window);
+ LOG_WINDOW("native_init_empty: window = %p", window);
SET_WINDOW(env, object, window);
+ return 0;
}
-static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
+static jint native_init_memory(JNIEnv * env, jobject object, jobject memObj)
{
sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));
if (memory == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
- return;
+ return 1;
}
CursorWindow * window = new CursorWindow();
if (!window) {
- jniThrowException(env, "java/lang/RuntimeException",
- "CursorWindow of size 1MB couldn't be created. No memory?");
- return;
+ return 1;
}
if (!window->setMemory(memory)) {
- jniThrowException(env, "java/lang/RuntimeException",
- "Memory couldn't be initialized for 1MB CursorWindow object.");
delete window;
- return;
+ return 1;
}
-LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
+ LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
SET_WINDOW(env, object, window);
+ return 0;
}
static jobject native_getBinder(JNIEnv * env, jobject object)
@@ -615,8 +609,8 @@
static JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
- {"native_init", "(IZ)V", (void *)native_init_empty},
- {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},
+ {"native_init", "(IZ)I", (void *)native_init_empty},
+ {"native_init", "(Landroid/os/IBinder;)I", (void *)native_init_memory},
{"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder},
{"native_clear", "()V", (void *)native_clear},
{"close_native", "()V", (void *)native_close},
diff --git a/core/tests/coretests/src/android/database/DatabaseCursorTest.java b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
index 0733229..d5b9ee6 100644
--- a/core/tests/coretests/src/android/database/DatabaseCursorTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
@@ -37,6 +37,7 @@
import android.util.Log;
import java.io.File;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
@@ -581,4 +582,35 @@
c.deactivate();
c.requery();
}
+ /**
+ * sometimes CursorWindow creation fails due to non-availability of memory create
+ * another CursorWindow object. One of the scenarios of its occurrence is when
+ * there are too many CursorWindow objects already opened by the process.
+ * This test is for that scenario.
+ */
+ @LargeTest
+ public void testCursorWindowFailureWhenTooManyCursorWindowsLeftOpen() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+ mDatabase.execSQL("INSERT INTO test values(1, 'test');");
+ int N = 1024;
+ ArrayList<Cursor> cursorList = new ArrayList<Cursor>();
+ // open many cursors until a failure occurs
+ for (int i = 0; i < N; i++) {
+ try {
+ Cursor cursor = mDatabase.rawQuery("select * from test", null);
+ cursor.getCount();
+ cursorList.add(cursor);
+ } catch (CursorWindowAllocationException e) {
+ // got the exception we wanted
+ break;
+ } catch (Exception e) {
+ fail("unexpected exception: " + e.getMessage());
+ e.printStackTrace();
+ break;
+ }
+ }
+ for (Cursor c : cursorList) {
+ c.close();
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java b/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java
new file mode 100644
index 0000000..fbc9e1f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.widget;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.text.InputType;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableString;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * TextViewPatchTest tests {@link TextView}'s definition of word. Finds and
+ * verifies word limits to be in strings containing different kinds of
+ * characters.
+ */
+public class TextViewWordLimitsTest extends AndroidTestCase {
+
+ TextView mTv = null;
+ Method mGetWordLimits, mSelectCurrentWord;
+ Field mContextMenuTriggeredByKey, mSelectionControllerEnabled;
+
+
+ /**
+ * Sets up common fields used in all test cases.
+ * @throws NoSuchFieldException
+ * @throws SecurityException
+ */
+ @Override
+ protected void setUp() throws NoSuchMethodException, SecurityException, NoSuchFieldException {
+ mTv = new TextView(getContext());
+ mTv.setInputType(InputType.TYPE_CLASS_TEXT);
+
+ mGetWordLimits = mTv.getClass().getDeclaredMethod("getWordLimitsAt",
+ new Class[] {int.class});
+ mGetWordLimits.setAccessible(true);
+
+ mSelectCurrentWord = mTv.getClass().getDeclaredMethod("selectCurrentWord", new Class[] {});
+ mSelectCurrentWord.setAccessible(true);
+
+ mContextMenuTriggeredByKey = mTv.getClass().getDeclaredField("mContextMenuTriggeredByKey");
+ mContextMenuTriggeredByKey.setAccessible(true);
+ mSelectionControllerEnabled = mTv.getClass().getDeclaredField("mSelectionControllerEnabled");
+ mSelectionControllerEnabled.setAccessible(true);
+ }
+
+ /**
+ * Calculate and verify word limits. Depends on the TextView implementation.
+ * Uses a private method and internal data representation.
+ *
+ * @param text Text to select a word from
+ * @param pos Position to expand word around
+ * @param correctStart Correct start position for the word
+ * @param correctEnd Correct end position for the word
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ */
+ private void verifyWordLimits(String text, int pos, int correctStart, int correctEnd)
+ throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ mTv.setText(text, TextView.BufferType.SPANNABLE);
+
+ long limits = (Long)mGetWordLimits.invoke(mTv, new Object[] {new Integer(pos)});
+ int actualStart = (int)(limits >>> 32);
+ int actualEnd = (int)(limits & 0x00000000FFFFFFFFL);
+ assertEquals(correctStart, actualStart);
+ assertEquals(correctEnd, actualEnd);
+ }
+
+
+ private void verifySelectCurrentWord(Spannable text, int selectionStart, int selectionEnd, int correctStart,
+ int correctEnd) throws InvocationTargetException, IllegalAccessException {
+ mTv.setText(text, TextView.BufferType.SPANNABLE);
+
+ Selection.setSelection((Spannable)mTv.getText(), selectionStart, selectionEnd);
+ mContextMenuTriggeredByKey.setBoolean(mTv, true);
+ mSelectionControllerEnabled.setBoolean(mTv, true);
+ mSelectCurrentWord.invoke(mTv);
+
+ assertEquals(correctStart, mTv.getSelectionStart());
+ assertEquals(correctEnd, mTv.getSelectionEnd());
+ }
+
+
+ /**
+ * Corner cases for string length.
+ */
+ @LargeTest
+ public void testLengths() throws Exception {
+ final String ONE_TWO = "one two";
+ final String EMPTY = "";
+ final String TOOLONG = "ThisWordIsTooLongToBeDefinedAsAWordInTheSenseUsedInTextView";
+
+ // Select first word
+ verifyWordLimits(ONE_TWO, 0, 0, 3);
+ verifyWordLimits(ONE_TWO, 3, 0, 3);
+
+ // Select last word
+ verifyWordLimits(ONE_TWO, 4, 4, 7);
+ verifyWordLimits(ONE_TWO, 7, 4, 7);
+
+ // Empty string
+ verifyWordLimits(EMPTY, 0, -1, -1);
+
+ // Too long word
+ verifyWordLimits(TOOLONG, 0, -1, -1);
+ }
+
+ /**
+ * Unicode classes included.
+ */
+ @LargeTest
+ public void testIncludedClasses() throws Exception {
+ final String LOWER = "njlj";
+ final String UPPER = "NJLJ";
+ final String TITLECASE = "\u01C8\u01CB\u01F2"; // Lj Nj Dz
+ final String OTHER = "\u3042\u3044\u3046"; // Hiragana AIU
+ final String MODIFIER = "\u02C6\u02CA\u02CB"; // Circumflex Acute Grave
+
+ // Each string contains a single valid word
+ verifyWordLimits(LOWER, 1, 0, 4);
+ verifyWordLimits(UPPER, 1, 0, 4);
+ verifyWordLimits(TITLECASE, 1, 0, 3);
+ verifyWordLimits(OTHER, 1, 0, 3);
+ verifyWordLimits(MODIFIER, 1, 0, 3);
+ }
+
+ /**
+ * Unicode classes included if combined with a letter.
+ */
+ @LargeTest
+ public void testPartlyIncluded() throws Exception {
+ final String NUMBER = "123";
+ final String NUMBER_LOWER = "1st";
+ final String APOSTROPHE = "''";
+ final String APOSTROPHE_LOWER = "'Android's'";
+
+ // Pure decimal number is ignored
+ verifyWordLimits(NUMBER, 1, -1, -1);
+
+ // Number with letter is valid
+ verifyWordLimits(NUMBER_LOWER, 1, 0, 3);
+
+ // Stand apostrophes are ignore
+ verifyWordLimits(APOSTROPHE, 1, -1, -1);
+
+ // Apostrophes are accepted if they are a part of a word
+ verifyWordLimits(APOSTROPHE_LOWER, 1, 0, 11);
+ }
+
+ /**
+ * Unicode classes included if combined with a letter.
+ */
+ @LargeTest
+ public void testFinalSeparator() throws Exception {
+ final String PUNCTUATION = "abc, def.";
+
+ // Starting from the comma
+ verifyWordLimits(PUNCTUATION, 3, 0, 3);
+ verifyWordLimits(PUNCTUATION, 4, 0, 4);
+
+ // Starting from the final period
+ verifyWordLimits(PUNCTUATION, 8, 5, 8);
+ verifyWordLimits(PUNCTUATION, 9, 5, 9);
+ }
+
+ /**
+ * Unicode classes other than listed in testIncludedClasses and
+ * testPartlyIncluded act as word separators.
+ */
+ @LargeTest
+ public void testNotIncluded() throws Exception {
+ // Selection of character classes excluded
+ final String MARK_NONSPACING = "a\u030A"; // a Combining ring above
+ final String PUNCTUATION_OPEN_CLOSE = "(c)"; // Parenthesis
+ final String PUNCTUATION_DASH = "non-fiction"; // Hyphen
+ final String PUNCTUATION_OTHER = "b&b"; // Ampersand
+ final String SYMBOL_OTHER = "Android\u00AE"; // Registered
+ final String SEPARATOR_SPACE = "one two"; // Space
+
+ // "a"
+ verifyWordLimits(MARK_NONSPACING, 1, 0, 1);
+
+ // "c"
+ verifyWordLimits(PUNCTUATION_OPEN_CLOSE, 1, 1, 2);
+
+ // "non-"
+ verifyWordLimits(PUNCTUATION_DASH, 3, 0, 3);
+ verifyWordLimits(PUNCTUATION_DASH, 4, 4, 11);
+
+ // "b"
+ verifyWordLimits(PUNCTUATION_OTHER, 0, 0, 1);
+ verifyWordLimits(PUNCTUATION_OTHER, 1, 0, 1);
+ verifyWordLimits(PUNCTUATION_OTHER, 2, 0, 3); // & is considered a punctuation sign.
+ verifyWordLimits(PUNCTUATION_OTHER, 3, 2, 3);
+
+ // "Android"
+ verifyWordLimits(SYMBOL_OTHER, 7, 0, 7);
+ verifyWordLimits(SYMBOL_OTHER, 8, -1, -1);
+
+ // "one"
+ verifyWordLimits(SEPARATOR_SPACE, 1, 0, 3);
+ }
+
+ /**
+ * Surrogate characters are treated as their code points.
+ */
+ @LargeTest
+ public void testSurrogate() throws Exception {
+ final String SURROGATE_LETTER = "\uD800\uDC00\uD800\uDC01\uD800\uDC02"; // Linear B AEI
+ final String SURROGATE_SYMBOL = "\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03"; // Three smileys
+
+ // Letter Other is included even when coded as surrogate pairs
+ verifyWordLimits(SURROGATE_LETTER, 1, -1, -1);
+ verifyWordLimits(SURROGATE_LETTER, 2, -1, -1);
+
+ // Not included classes are ignored even when coded as surrogate pairs
+ verifyWordLimits(SURROGATE_SYMBOL, 1, -1, -1);
+ verifyWordLimits(SURROGATE_SYMBOL, 2, -1, -1);
+ }
+
+ /**
+ * Selection is used if present and valid word.
+ */
+ @LargeTest
+ public void testSelectCurrentWord() throws Exception {
+ SpannableString textLower = new SpannableString("first second");
+ SpannableString textOther = new SpannableString("\u3042\3044\3046"); // Hiragana AIU
+ SpannableString textDash = new SpannableString("non-fiction"); // Hyphen
+ SpannableString textPunctOther = new SpannableString("b&b"); // Ampersand
+ SpannableString textSymbolOther = new SpannableString("Android\u00AE"); // Registered
+
+ // Valid selection - Letter, Lower
+ verifySelectCurrentWord(textLower, 2, 5, 0, 5);
+
+ // Adding the space spreads to the second word
+ verifySelectCurrentWord(textLower, 2, 6, 0, 12);
+
+ // Valid selection -- Letter, Other
+ verifySelectCurrentWord(textOther, 1, 2, 0, 5);
+
+ // Zero-width selection is interpreted as a cursor and the selection is ignored
+ verifySelectCurrentWord(textLower, 2, 2, 0, 5);
+
+ // Hyphen is part of selection
+ verifySelectCurrentWord(textDash, 2, 5, 0, 11);
+
+ // Ampersand part of selection or not
+ verifySelectCurrentWord(textPunctOther, 1, 2, 0, 3);
+ verifySelectCurrentWord(textPunctOther, 1, 3, 0, 3);
+
+ // (R) part of the selection
+ verifySelectCurrentWord(textSymbolOther, 2, 7, 0, 7);
+ verifySelectCurrentWord(textSymbolOther, 2, 8, 0, 8);
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java
index 4470356..9a3ca40 100644
--- a/graphics/java/android/graphics/drawable/TransitionDrawable.java
+++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java
@@ -137,7 +137,7 @@
final long time = SystemClock.uptimeMillis();
// Animation is over
if (time - mStartTimeMillis > mDuration) {
- if (mAlpha == 0) {
+ if (mTo == 0) {
mFrom = 0;
mTo = 255;
mAlpha = 0;
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 88b0c3e..178f0db 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -36,6 +36,7 @@
MEDIA_BUFFERING_UPDATE = 3,
MEDIA_SEEK_COMPLETE = 4,
MEDIA_SET_VIDEO_SIZE = 5,
+ MEDIA_RESET_COMPLETE = 6, // not visible on java side
MEDIA_ERROR = 100,
MEDIA_INFO = 200,
};
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index c92fc23..a3c3f09 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -262,14 +262,6 @@
return getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
}
- /**
- * FIXME
- * To be removed and replaced by getFrameAt().
- */
- public Bitmap captureFrame() {
- return _getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
- }
-
private native Bitmap _getFrameAtTime(long timeUs, int option);
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index c656e69..29e4971 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -87,7 +87,7 @@
break;
} else if (n < 0) {
LOGI("input data EOS reached.");
- mTSParser->signalEOS(ERROR_END_OF_STREAM);
+ mTSParser->signalEOS(n);
mEOS = true;
break;
} else {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 24efa35..9a8cff3 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -45,8 +45,11 @@
: mAudioEOS(false),
mVideoEOS(false),
mScanSourcesPending(false),
+ mScanSourcesGeneration(0),
mFlushingAudio(NONE),
- mFlushingVideo(NONE) {
+ mFlushingVideo(NONE),
+ mResetInProgress(false),
+ mResetPostponed(false) {
}
NuPlayer::~NuPlayer() {
@@ -87,18 +90,22 @@
(new AMessage(kWhatStart, id()))->post();
}
+void NuPlayer::resetAsync() {
+ (new AMessage(kWhatReset, id()))->post();
+}
+
// static
-bool NuPlayer::IsFlushingState(FlushStatus state, bool *formatChange) {
+bool NuPlayer::IsFlushingState(FlushStatus state, bool *needShutdown) {
switch (state) {
case FLUSHING_DECODER:
- if (formatChange != NULL) {
- *formatChange = false;
+ if (needShutdown != NULL) {
+ *needShutdown = false;
}
return true;
- case FLUSHING_DECODER_FORMATCHANGE:
- if (formatChange != NULL) {
- *formatChange = true;
+ case FLUSHING_DECODER_SHUTDOWN:
+ if (needShutdown != NULL) {
+ *needShutdown = true;
}
return true;
@@ -111,7 +118,7 @@
switch (msg->what()) {
case kWhatSetDataSource:
{
- LOGI("kWhatSetDataSource");
+ LOGV("kWhatSetDataSource");
CHECK(mSource == NULL);
@@ -124,7 +131,7 @@
case kWhatSetVideoSurface:
{
- LOGI("kWhatSetVideoSurface");
+ LOGV("kWhatSetVideoSurface");
sp<RefBase> obj;
CHECK(msg->findObject("surface", &obj));
@@ -135,7 +142,7 @@
case kWhatSetAudioSink:
{
- LOGI("kWhatSetAudioSink");
+ LOGV("kWhatSetAudioSink");
sp<RefBase> obj;
CHECK(msg->findObject("sink", &obj));
@@ -146,6 +153,9 @@
case kWhatStart:
{
+ mAudioEOS = false;
+ mVideoEOS = false;
+
mSource->start();
mRenderer = new Renderer(
@@ -154,13 +164,19 @@
looper()->registerHandler(mRenderer);
- (new AMessage(kWhatScanSources, id()))->post();
- mScanSourcesPending = true;
+ postScanSources();
break;
}
case kWhatScanSources:
{
+ int32_t generation;
+ CHECK(msg->findInt32("generation", &generation));
+ if (generation != mScanSourcesGeneration) {
+ // Drop obsolete msg.
+ break;
+ }
+
mScanSourcesPending = false;
instantiateDecoder(false, &mVideoDecoder);
@@ -170,6 +186,11 @@
}
if (!mSource->feedMoreTSData()) {
+ if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
+ // We're not currently decoding anything (no audio or
+ // video tracks found) and we just ran out of input data.
+ notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0);
+ }
break;
}
@@ -203,20 +224,20 @@
} else if (what == ACodec::kWhatEOS) {
mRenderer->queueEOS(audio, ERROR_END_OF_STREAM);
} else if (what == ACodec::kWhatFlushCompleted) {
- bool formatChange;
+ bool needShutdown;
if (audio) {
- CHECK(IsFlushingState(mFlushingAudio, &formatChange));
+ CHECK(IsFlushingState(mFlushingAudio, &needShutdown));
mFlushingAudio = FLUSHED;
} else {
- CHECK(IsFlushingState(mFlushingVideo, &formatChange));
+ CHECK(IsFlushingState(mFlushingVideo, &needShutdown));
mFlushingVideo = FLUSHED;
}
- LOGI("decoder %s flush completed", audio ? "audio" : "video");
+ LOGV("decoder %s flush completed", audio ? "audio" : "video");
- if (formatChange) {
- LOGI("initiating %s decoder shutdown",
+ if (needShutdown) {
+ LOGV("initiating %s decoder shutdown",
audio ? "audio" : "video");
(audio ? mAudioDecoder : mVideoDecoder)->initiateShutdown();
@@ -238,7 +259,7 @@
int32_t sampleRate;
CHECK(codecRequest->findInt32("sample-rate", &sampleRate));
- LOGI("Audio output format changed to %d Hz, %d channels",
+ LOGV("Audio output format changed to %d Hz, %d channels",
sampleRate, numChannels);
mAudioSink->close();
@@ -247,7 +268,7 @@
mRenderer->signalAudioSinkChanged();
} else if (what == ACodec::kWhatShutdownCompleted) {
- LOGI("%s shutdown completed", audio ? "audio" : "video");
+ LOGV("%s shutdown completed", audio ? "audio" : "video");
if (audio) {
mAudioDecoder.clear();
@@ -285,7 +306,7 @@
mVideoEOS = true;
}
- LOGI("reached %s EOS", audio ? "audio" : "video");
+ LOGV("reached %s EOS", audio ? "audio" : "video");
if ((mAudioEOS || mAudioDecoder == NULL)
&& (mVideoEOS || mVideoDecoder == NULL)) {
@@ -297,7 +318,7 @@
int32_t audio;
CHECK(msg->findInt32("audio", &audio));
- LOGI("renderer %s flush completed.", audio ? "audio" : "video");
+ LOGV("renderer %s flush completed.", audio ? "audio" : "video");
}
break;
}
@@ -307,6 +328,37 @@
break;
}
+ case kWhatReset:
+ {
+ LOGV("kWhatReset");
+
+ if (mFlushingAudio != NONE || mFlushingVideo != NONE) {
+ // We're currently flushing, postpone the reset until that's
+ // completed.
+
+ LOGV("postponing reset");
+
+ mResetPostponed = true;
+ break;
+ }
+
+ if (mAudioDecoder == NULL && mVideoDecoder == NULL) {
+ finishReset();
+ break;
+ }
+
+ if (mAudioDecoder != NULL) {
+ flushDecoder(true /* audio */, true /* needShutdown */);
+ }
+
+ if (mVideoDecoder != NULL) {
+ flushDecoder(false /* audio */, true /* needShutdown */);
+ }
+
+ mResetInProgress = true;
+ break;
+ }
+
default:
TRESPASS();
break;
@@ -322,7 +374,7 @@
return;
}
- LOGI("both audio and video are flushed now.");
+ LOGV("both audio and video are flushed now.");
mRenderer->signalTimeDiscontinuity();
@@ -343,12 +395,41 @@
mFlushingAudio = NONE;
mFlushingVideo = NONE;
- if (scanSourcesAgain && !mScanSourcesPending) {
- mScanSourcesPending = true;
- (new AMessage(kWhatScanSources, id()))->post();
+ if (mResetInProgress) {
+ LOGV("reset completed");
+
+ mResetInProgress = false;
+ finishReset();
+ } else if (mResetPostponed) {
+ (new AMessage(kWhatReset, id()))->post();
+ mResetPostponed = false;
+ } else if (scanSourcesAgain) {
+ postScanSources();
}
}
+void NuPlayer::finishReset() {
+ CHECK(mAudioDecoder == NULL);
+ CHECK(mVideoDecoder == NULL);
+
+ mRenderer.clear();
+ mSource.clear();
+
+ notifyListener(MEDIA_RESET_COMPLETE, 0, 0);
+}
+
+void NuPlayer::postScanSources() {
+ if (mScanSourcesPending) {
+ return;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatScanSources, id());
+ msg->setInt32("generation", mScanSourcesGeneration);
+ msg->post();
+
+ mScanSourcesPending = true;
+}
+
status_t NuPlayer::instantiateDecoder(bool audio, sp<Decoder> *decoder) {
if (*decoder != NULL) {
return OK;
@@ -396,37 +477,10 @@
bool formatChange =
type == ATSParser::DISCONTINUITY_FORMATCHANGE;
- LOGI("%s discontinuity (formatChange=%d)",
+ LOGV("%s discontinuity (formatChange=%d)",
audio ? "audio" : "video", formatChange);
- (audio ? mAudioDecoder : mVideoDecoder)->signalFlush();
- mRenderer->flush(audio);
-
- if (audio) {
- CHECK(mFlushingAudio == NONE
- || mFlushingAudio == AWAITING_DISCONTINUITY);
-
- mFlushingAudio = formatChange
- ? FLUSHING_DECODER_FORMATCHANGE : FLUSHING_DECODER;
-
- if (mFlushingVideo == NONE) {
- mFlushingVideo = (mVideoDecoder != NULL)
- ? AWAITING_DISCONTINUITY
- : FLUSHED;
- }
- } else {
- CHECK(mFlushingVideo == NONE
- || mFlushingVideo == AWAITING_DISCONTINUITY);
-
- mFlushingVideo = formatChange
- ? FLUSHING_DECODER_FORMATCHANGE : FLUSHING_DECODER;
-
- if (mFlushingAudio == NONE) {
- mFlushingAudio = (mAudioDecoder != NULL)
- ? AWAITING_DISCONTINUITY
- : FLUSHED;
- }
- }
+ flushDecoder(audio, formatChange);
}
reply->setInt32("err", err);
@@ -439,7 +493,7 @@
#if 0
int64_t mediaTimeUs;
CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs));
- LOGI("feeding %s input buffer at media time %.2f secs",
+ LOGV("feeding %s input buffer at media time %.2f secs",
audio ? "audio" : "video",
mediaTimeUs / 1E6);
#endif
@@ -478,4 +532,39 @@
listener->sendEvent(msg, ext1, ext2);
}
+void NuPlayer::flushDecoder(bool audio, bool needShutdown) {
+ // Make sure we don't continue to scan sources until we finish flushing.
+ ++mScanSourcesGeneration;
+
+ (audio ? mAudioDecoder : mVideoDecoder)->signalFlush();
+ mRenderer->flush(audio);
+
+ FlushStatus newStatus =
+ needShutdown ? FLUSHING_DECODER_SHUTDOWN : FLUSHING_DECODER;
+
+ if (audio) {
+ CHECK(mFlushingAudio == NONE
+ || mFlushingAudio == AWAITING_DISCONTINUITY);
+
+ mFlushingAudio = newStatus;
+
+ if (mFlushingVideo == NONE) {
+ mFlushingVideo = (mVideoDecoder != NULL)
+ ? AWAITING_DISCONTINUITY
+ : FLUSHED;
+ }
+ } else {
+ CHECK(mFlushingVideo == NONE
+ || mFlushingVideo == AWAITING_DISCONTINUITY);
+
+ mFlushingVideo = newStatus;
+
+ if (mFlushingAudio == NONE) {
+ mFlushingAudio = (mAudioDecoder != NULL)
+ ? AWAITING_DISCONTINUITY
+ : FLUSHED;
+ }
+ }
+}
+
} // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 172a962..1cf2f60 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -40,6 +40,10 @@
void setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink);
void start();
+ // Will notify the listener that reset() has completed
+ // with code MEDIA_RESET_COMPLETE.
+ void resetAsync();
+
protected:
virtual ~NuPlayer();
@@ -63,6 +67,7 @@
kWhatVideoNotify,
kWhatAudioNotify,
kWhatRendererNotify,
+ kWhatReset,
};
wp<MediaPlayerBase> mListener;
@@ -73,17 +78,17 @@
sp<Decoder> mAudioDecoder;
sp<Renderer> mRenderer;
- bool mEOS;
bool mAudioEOS;
bool mVideoEOS;
bool mScanSourcesPending;
+ int32_t mScanSourcesGeneration;
enum FlushStatus {
NONE,
AWAITING_DISCONTINUITY,
FLUSHING_DECODER,
- FLUSHING_DECODER_FORMATCHANGE,
+ FLUSHING_DECODER_SHUTDOWN,
SHUTTING_DOWN_DECODER,
FLUSHED,
SHUT_DOWN,
@@ -91,6 +96,8 @@
FlushStatus mFlushingAudio;
FlushStatus mFlushingVideo;
+ bool mResetInProgress;
+ bool mResetPostponed;
status_t instantiateDecoder(bool audio, sp<Decoder> *decoder);
@@ -101,7 +108,12 @@
void finishFlushIfPossible();
- static bool IsFlushingState(FlushStatus state, bool *formatChange = NULL);
+ void flushDecoder(bool audio, bool needShutdown);
+
+ static bool IsFlushingState(FlushStatus state, bool *needShutdown = NULL);
+
+ void finishReset();
+ void postScanSources();
DISALLOW_EVIL_CONSTRUCTORS(NuPlayer);
};
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index d21d4ff..4988d24 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -22,12 +22,14 @@
#include "NuPlayer.h"
+#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
namespace android {
NuPlayerDriver::NuPlayerDriver()
- : mLooper(new ALooper),
+ : mResetInProgress(false),
+ mLooper(new ALooper),
mPlayer(false) {
mLooper->setName("NuPlayerDriver Looper");
@@ -121,6 +123,15 @@
}
status_t NuPlayerDriver::reset() {
+ Mutex::Autolock autoLock(mLock);
+ mResetInProgress = true;
+
+ mPlayer->resetAsync();
+
+ while (mResetInProgress) {
+ mCondition.wait(mLock);
+ }
+
return OK;
}
@@ -145,4 +156,16 @@
return INVALID_OPERATION;
}
+void NuPlayerDriver::sendEvent(int msg, int ext1, int ext2) {
+ if (msg != MEDIA_RESET_COMPLETE) {
+ MediaPlayerInterface::sendEvent(msg, ext1, ext2);
+ return;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ CHECK(mResetInProgress);
+ mResetInProgress = false;
+ mCondition.broadcast();
+}
+
} // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index 44ae3bf..f153af4 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -54,10 +54,16 @@
virtual status_t getMetadata(
const media::Metadata::Filter& ids, Parcel *records);
+ virtual void sendEvent(int msg, int ext1 = 0, int ext2 = 0);
+
protected:
virtual ~NuPlayerDriver();
private:
+ Mutex mLock;
+ Condition mCondition;
+ bool mResetInProgress;
+
sp<ALooper> mLooper;
sp<NuPlayer> mPlayer;
bool mPlaying;
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index ee3eefc..6331a63 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -260,7 +260,7 @@
LOGV("getFrameAtTime: %lld us option: %d", timeUs, option);
if (0 == (mMode & METADATA_MODE_FRAME_CAPTURE_ONLY)) {
- LOGV("captureFrame disabled by mode (0x%08x)", mMode);
+ LOGV("getFrameAtTime disabled by mode (0x%08x)", mMode);
return NULL;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
index bd9bdb2..612427b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
@@ -257,20 +257,23 @@
private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) {
Bitmap outBitmap = background.copy(background.getConfig(), true);
- Canvas canvas = new Canvas(outBitmap);
- Paint paint = new Paint();
- paint.setAntiAlias(true);
- paint.setFilterBitmap(true);
- paint.setAlpha(255);
- final int srcWidth = thumbnail.getWidth();
- final int height = thumbnail.getHeight();
- final int srcHeight = srcWidth > height ? height : (height - height * srcWidth / height);
- canvas.drawBitmap(thumbnail,
- new Rect(0, 0, srcWidth-1, srcHeight-1),
- new RectF(GLOW_PADDING,
- GLOW_PADDING - 4.0f,
- outBitmap.getWidth() - GLOW_PADDING + 2.0f,
- outBitmap.getHeight() - GLOW_PADDING + 3.0f), paint);
+ if (thumbnail != null) {
+ Canvas canvas = new Canvas(outBitmap);
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setFilterBitmap(true);
+ paint.setAlpha(255);
+ final int srcWidth = thumbnail.getWidth();
+ final int height = thumbnail.getHeight();
+ final int srcHeight = srcWidth > height ? height
+ : (height - height * srcWidth / height);
+ canvas.drawBitmap(thumbnail,
+ new Rect(0, 0, srcWidth-1, srcHeight-1),
+ new RectF(GLOW_PADDING,
+ GLOW_PADDING - 4.0f,
+ outBitmap.getWidth() - GLOW_PADDING + 2.0f,
+ outBitmap.getHeight() - GLOW_PADDING + 3.0f), paint);
+ }
return outBitmap;
}