diff options
39 files changed, 1076 insertions, 689 deletions
diff --git a/api/current.txt b/api/current.txt index 6bac58af167a..93d9d44f8cf8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -26377,6 +26377,7 @@ package android.widget { ctor public EdgeEffect(android.content.Context); method public boolean draw(android.graphics.Canvas); method public void finish(); + method public android.graphics.Rect getBounds(); method public boolean isFinished(); method public void onAbsorb(int); method public void onPull(float); diff --git a/cmds/system_server/library/Android.mk b/cmds/system_server/library/Android.mk index 7d08a8c8a2e5..9f92330aec84 100644 --- a/cmds/system_server/library/Android.mk +++ b/cmds/system_server/library/Android.mk @@ -8,10 +8,7 @@ base = $(LOCAL_PATH)/../../.. native = $(LOCAL_PATH)/../../../../native LOCAL_C_INCLUDES := \ - $(base)/services/camera/libcameraservice \ - $(base)/services/audioflinger \ $(base)/services/sensorservice \ - $(base)/media/libmediaplayerservice \ $(native)/services/surfaceflinger \ $(JNI_H_INCLUDE) @@ -19,9 +16,6 @@ LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ libsensorservice \ libsurfaceflinger \ - libaudioflinger \ - libcameraservice \ - libmediaplayerservice \ libinput \ libutils \ libbinder \ diff --git a/cmds/system_server/library/system_init.cpp b/cmds/system_server/library/system_init.cpp index bfbc13870717..745c34a0478c 100644 --- a/cmds/system_server/library/system_init.cpp +++ b/cmds/system_server/library/system_init.cpp @@ -15,10 +15,6 @@ #include <utils/Log.h> #include <SurfaceFlinger.h> -#include <AudioFlinger.h> -#include <CameraService.h> -#include <AudioPolicyService.h> -#include <MediaPlayerService.h> #include <SensorService.h> #include <android_runtime/AndroidRuntime.h> diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index eb83dbc6e549..4b31552ef06c 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -17,6 +17,7 @@ package android.content; import android.content.res.AssetFileDescriptor; +import android.database.BulkCursorDescriptor; import android.database.BulkCursorNative; import android.database.BulkCursorToCursorAdaptor; import android.database.Cursor; @@ -113,20 +114,14 @@ abstract public class ContentProviderNative extends Binder implements IContentPr if (cursor != null) { CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( cursor, observer, getProviderName()); - final IBinder binder = adaptor.asBinder(); - final int count = adaptor.count(); - final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( - adaptor.getColumnNames()); - final boolean wantsAllOnMoveCalls = adaptor.getWantsAllOnMoveCalls(); + BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor(); reply.writeNoException(); - reply.writeStrongBinder(binder); - reply.writeInt(count); - reply.writeInt(index); - reply.writeInt(wantsAllOnMoveCalls ? 1 : 0); + reply.writeInt(1); + d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeNoException(); - reply.writeStrongBinder(null); + reply.writeInt(0); } return true; @@ -369,12 +364,9 @@ final class ContentProviderProxy implements IContentProvider DatabaseUtils.readExceptionFromParcel(reply); - IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder()); - if (bulkCursor != null) { - int rowCount = reply.readInt(); - int idColumnPosition = reply.readInt(); - boolean wantsAllOnMoveCalls = reply.readInt() != 0; - adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls); + if (reply.readInt() != 0) { + BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply); + adaptor.initialize(d); } else { adaptor.close(); adaptor = null; diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index b28ed8daf25f..dd6692cbc058 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -33,10 +33,39 @@ import java.util.Map; public abstract class AbstractCursor implements CrossProcessCursor { private static final String TAG = "Cursor"; - DataSetObservable mDataSetObservable = new DataSetObservable(); - ContentObservable mContentObservable = new ContentObservable(); + /** + * @deprecated This is never updated by this class and should not be used + */ + @Deprecated + protected HashMap<Long, Map<String, Object>> mUpdatedRows; + + protected int mPos; + + /** + * This must be set to the index of the row ID column by any + * subclass that wishes to support updates. + */ + protected int mRowIdColumnIndex; + + /** + * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of + * the column at {@link #mRowIdColumnIndex} for the current row this cursor is + * pointing at. + */ + protected Long mCurrentRowID; + + protected boolean mClosed; + protected ContentResolver mContentResolver; + private Uri mNotifyUri; + + private final Object mSelfObserverLock = new Object(); + private ContentObserver mSelfObserver; + private boolean mSelfObserverRegistered; - Bundle mExtras = Bundle.EMPTY; + private DataSetObservable mDataSetObservable = new DataSetObservable(); + private ContentObservable mContentObservable = new ContentObservable(); + + private Bundle mExtras = Bundle.EMPTY; /* -------------------------------------------------------- */ /* These need to be implemented by subclasses */ @@ -415,30 +444,4 @@ public abstract class AbstractCursor implements CrossProcessCursor { } } } - - /** - * @deprecated This is never updated by this class and should not be used - */ - @Deprecated - protected HashMap<Long, Map<String, Object>> mUpdatedRows; - - /** - * This must be set to the index of the row ID column by any - * subclass that wishes to support updates. - */ - protected int mRowIdColumnIndex; - - protected int mPos; - /** - * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of - * the column at {@link #mRowIdColumnIndex} for the current row this cursor is - * pointing at. - */ - protected Long mCurrentRowID; - protected ContentResolver mContentResolver; - protected boolean mClosed = false; - private Uri mNotifyUri; - private ContentObserver mSelfObserver; - final private Object mSelfObserverLock = new Object(); - private boolean mSelfObserverRegistered; } diff --git a/core/java/android/database/BulkCursorDescriptor.java b/core/java/android/database/BulkCursorDescriptor.java new file mode 100644 index 000000000000..c1e5e63b72a0 --- /dev/null +++ b/core/java/android/database/BulkCursorDescriptor.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.database; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Describes the properties of a {@link CursorToBulkCursorAdaptor} that are + * needed to initialize its {@link BulkCursorToCursorAdaptor} counterpart on the client's end. + * + * {@hide} + */ +public final class BulkCursorDescriptor implements Parcelable { + public static final Parcelable.Creator<BulkCursorDescriptor> CREATOR = + new Parcelable.Creator<BulkCursorDescriptor>() { + @Override + public BulkCursorDescriptor createFromParcel(Parcel in) { + BulkCursorDescriptor d = new BulkCursorDescriptor(); + d.readFromParcel(in); + return d; + } + + @Override + public BulkCursorDescriptor[] newArray(int size) { + return new BulkCursorDescriptor[size]; + } + }; + + public IBulkCursor cursor; + public String[] columnNames; + public boolean wantsAllOnMoveCalls; + public int count; + public CursorWindow window; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeStrongBinder(cursor.asBinder()); + out.writeStringArray(columnNames); + out.writeInt(wantsAllOnMoveCalls ? 1 : 0); + out.writeInt(count); + if (window != null) { + out.writeInt(1); + window.writeToParcel(out, flags); + } else { + out.writeInt(0); + } + } + + public void readFromParcel(Parcel in) { + cursor = BulkCursorNative.asInterface(in.readStrongBinder()); + columnNames = in.readStringArray(); + wantsAllOnMoveCalls = in.readInt() != 0; + count = in.readInt(); + if (in.readInt() != 0) { + window = CursorWindow.CREATOR.createFromParcel(in); + } + } +} diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java index 67cf0f82700a..d3c11e785d7f 100644 --- a/core/java/android/database/BulkCursorNative.java +++ b/core/java/android/database/BulkCursorNative.java @@ -72,26 +72,6 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor return true; } - case COUNT_TRANSACTION: { - data.enforceInterface(IBulkCursor.descriptor); - int count = count(); - reply.writeNoException(); - reply.writeInt(count); - return true; - } - - case GET_COLUMN_NAMES_TRANSACTION: { - data.enforceInterface(IBulkCursor.descriptor); - String[] columnNames = getColumnNames(); - reply.writeNoException(); - reply.writeInt(columnNames.length); - int length = columnNames.length; - for (int i = 0; i < length; i++) { - reply.writeString(columnNames[i]); - } - return true; - } - case DEACTIVATE_TRANSACTION: { data.enforceInterface(IBulkCursor.descriptor); deactivate(); @@ -125,14 +105,6 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor return true; } - case WANTS_ON_MOVE_TRANSACTION: { - data.enforceInterface(IBulkCursor.descriptor); - boolean result = getWantsAllOnMoveCalls(); - reply.writeNoException(); - reply.writeInt(result ? 1 : 0); - return true; - } - case GET_EXTRAS_TRANSACTION: { data.enforceInterface(IBulkCursor.descriptor); Bundle extras = getExtras(); @@ -217,52 +189,6 @@ final class BulkCursorProxy implements IBulkCursor { } } - public int count() throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - try { - data.writeInterfaceToken(IBulkCursor.descriptor); - - boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0); - DatabaseUtils.readExceptionFromParcel(reply); - - int count; - if (result == false) { - count = -1; - } else { - count = reply.readInt(); - } - return count; - } finally { - data.recycle(); - reply.recycle(); - } - } - - public String[] getColumnNames() throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - try { - data.writeInterfaceToken(IBulkCursor.descriptor); - - mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0); - DatabaseUtils.readExceptionFromParcel(reply); - - String[] columnNames = null; - int numColumns = reply.readInt(); - columnNames = new String[numColumns]; - for (int i = 0; i < numColumns; i++) { - columnNames[i] = reply.readString(); - } - return columnNames; - } finally { - data.recycle(); - reply.recycle(); - } - } - public void deactivate() throws RemoteException { Parcel data = Parcel.obtain(); @@ -317,23 +243,6 @@ final class BulkCursorProxy implements IBulkCursor { } } - public boolean getWantsAllOnMoveCalls() throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - try { - data.writeInterfaceToken(IBulkCursor.descriptor); - - mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0); - DatabaseUtils.readExceptionFromParcel(reply); - - int result = reply.readInt(); - return result != 0; - } finally { - data.recycle(); - reply.recycle(); - } - } - public Bundle getExtras() throws RemoteException { if (mExtras == null) { Parcel data = Parcel.obtain(); diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java index 885046bf1995..98c70436c34b 100644 --- a/core/java/android/database/BulkCursorToCursorAdaptor.java +++ b/core/java/android/database/BulkCursorToCursorAdaptor.java @@ -30,34 +30,23 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { private SelfContentObserver mObserverBridge = new SelfContentObserver(this); private IBulkCursor mBulkCursor; - private int mCount; private String[] mColumns; private boolean mWantsAllOnMoveCalls; + private int mCount; /** * Initializes the adaptor. * Must be called before first use. */ - public void initialize(IBulkCursor bulkCursor, int count, int idIndex, - boolean wantsAllOnMoveCalls) { - mBulkCursor = bulkCursor; - mColumns = null; // lazily retrieved - mCount = count; - mRowIdColumnIndex = idIndex; - mWantsAllOnMoveCalls = wantsAllOnMoveCalls; - } - - /** - * Returns column index of "_id" column, or -1 if not found. - */ - public static int findRowIdColumnIndex(String[] columnNames) { - int length = columnNames.length; - for (int i = 0; i < length; i++) { - if (columnNames[i].equals("_id")) { - return i; - } + public void initialize(BulkCursorDescriptor d) { + mBulkCursor = d.cursor; + mColumns = d.columnNames; + mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns); + mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls; + mCount = d.count; + if (d.window != null) { + setWindow(d.window); } - return -1; } /** @@ -169,14 +158,6 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { public String[] getColumnNames() { throwIfCursorIsClosed(); - if (mColumns == null) { - try { - mColumns = mBulkCursor.getColumnNames(); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to fetch column names because the remote process is dead"); - return null; - } - } return mColumns; } diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java index 167278a10b4c..525be96c1498 100644 --- a/core/java/android/database/CursorToBulkCursorAdaptor.java +++ b/core/java/android/database/CursorToBulkCursorAdaptor.java @@ -132,6 +132,25 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } + public BulkCursorDescriptor getBulkCursorDescriptor() { + synchronized (mLock) { + throwIfCursorIsClosed(); + + BulkCursorDescriptor d = new BulkCursorDescriptor(); + d.cursor = this; + d.columnNames = mCursor.getColumnNames(); + d.wantsAllOnMoveCalls = mCursor.getWantsAllOnMoveCalls(); + d.count = mCursor.getCount(); + d.window = mCursor.getWindow(); + if (d.window != null) { + // Acquire a reference to the window because its reference count will be + // decremented when it is returned as part of the binder call reply parcel. + d.window.acquireReference(); + } + return d; + } + } + @Override public CursorWindow getWindow(int position) { synchronized (mLock) { @@ -157,10 +176,9 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative mCursor.fillWindow(position, window); } - // Acquire a reference before returning from this RPC. - // The Binder proxy will decrement the reference count again as part of writing - // the CursorWindow to the reply parcel as a return value. if (window != null) { + // Acquire a reference to the window because its reference count will be + // decremented when it is returned as part of the binder call reply parcel. window.acquireReference(); } return window; @@ -177,24 +195,6 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } @Override - public int count() { - synchronized (mLock) { - throwIfCursorIsClosed(); - - return mCursor.getCount(); - } - } - - @Override - public String[] getColumnNames() { - synchronized (mLock) { - throwIfCursorIsClosed(); - - return mCursor.getColumnNames(); - } - } - - @Override public void deactivate() { synchronized (mLock) { if (mCursor != null) { @@ -237,15 +237,6 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } - @Override - public boolean getWantsAllOnMoveCalls() { - synchronized (mLock) { - throwIfCursorIsClosed(); - - return mCursor.getWantsAllOnMoveCalls(); - } - } - /** * Create a ContentObserver from the observer and register it as an observer on the * underlying cursor. diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 99d260e0f6ee..40a54cfacee0 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -1386,4 +1386,18 @@ public class DatabaseUtils { System.arraycopy(newValues, 0, result, originalValues.length, newValues.length); return result; } + + /** + * Returns column index of "_id" column, or -1 if not found. + * @hide + */ + public static int findRowIdColumnIndex(String[] columnNames) { + int length = columnNames.length; + for (int i = 0; i < length; i++) { + if (columnNames[i].equals("_id")) { + return i; + } + } + return -1; + } } diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java index 0f4500a7aec8..b551116a0122 100644 --- a/core/java/android/database/IBulkCursor.java +++ b/core/java/android/database/IBulkCursor.java @@ -43,29 +43,12 @@ public interface IBulkCursor extends IInterface { */ public void onMove(int position) throws RemoteException; - /** - * Returns the number of rows in the cursor. - * - * @return the number of rows in the cursor. - */ - public int count() throws RemoteException; - - /** - * Returns a string array holding the names of all of the columns in the - * cursor in the order in which they were listed in the result. - * - * @return the names of the columns returned in this query. - */ - public String[] getColumnNames() throws RemoteException; - public void deactivate() throws RemoteException; public void close() throws RemoteException; public int requery(IContentObserver observer) throws RemoteException; - boolean getWantsAllOnMoveCalls() throws RemoteException; - Bundle getExtras() throws RemoteException; Bundle respond(Bundle extras) throws RemoteException; @@ -74,13 +57,10 @@ public interface IBulkCursor extends IInterface { static final String descriptor = "android.content.IBulkCursor"; static final int GET_CURSOR_WINDOW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; - static final int COUNT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1; - static final int GET_COLUMN_NAMES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2; - static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5; - static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6; - static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 7; - static final int WANTS_ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 8; - static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9; - static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10; - static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 11; + static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1; + static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2; + static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3; + static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4; + static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5; + static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6; } diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 82bb23ecb159..b29897eed97b 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -105,12 +105,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { mQuery = query; mColumns = query.getColumnNames(); - for (int i = 0; i < mColumns.length; i++) { - // Make note of the row ID column index for quick access to it - if ("_id".equals(mColumns[i])) { - mRowIdColumnIndex = i; - } - } + mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns); } /** diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index f09e29d857df..dbcea71ef180 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -742,7 +742,8 @@ class BrowserFrame extends Handler { url = url.replaceFirst(ANDROID_ASSET, ""); try { AssetManager assets = mContext.getAssets(); - return assets.open(url, AssetManager.ACCESS_STREAMING); + Uri uri = Uri.parse(url); + return assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING); } catch (IOException e) { return null; } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 577444068bd7..9e07151536ae 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -88,6 +88,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback { + @SuppressWarnings("UnusedDeclaration") private static final String TAG = "AbsListView"; /** @@ -2429,7 +2430,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final ViewTreeObserver treeObserver = getViewTreeObserver(); treeObserver.removeOnTouchModeChangeListener(this); if (mTextFilterEnabled && mPopup != null) { - treeObserver.removeGlobalOnLayoutListener(this); + treeObserver.removeOnGlobalLayoutListener(this); mGlobalLayoutListenerAddedFilter = false; } @@ -2943,11 +2944,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mDirection = 0; // Reset when entering overscroll. mTouchMode = TOUCH_MODE_OVERSCROLL; if (rawDeltaY > 0) { + if (!mEdgeGlowTop.isIdle()) { + invalidate(mEdgeGlowTop.getBounds()); + } else { + invalidate(); + } + mEdgeGlowTop.onPull((float) overscroll / getHeight()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else if (rawDeltaY < 0) { + if (!mEdgeGlowBottom.isIdle()) { + invalidate(mEdgeGlowBottom.getBounds()); + } else { + invalidate(); + } + mEdgeGlowBottom.onPull((float) overscroll / getHeight()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); @@ -2956,7 +2969,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } mMotionY = y; - invalidate(); } mLastY = y; } @@ -2990,26 +3002,26 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } + invalidate(mEdgeGlowTop.getBounds()); } else if (rawDeltaY < 0) { mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } + invalidate(mEdgeGlowBottom.getBounds()); } - invalidate(); } } if (incrementalDeltaY != 0) { // Coming back to 'real' list scrolling - mScrollY = 0; - invalidateParentIfNeeded(); - - // No need to do all this work if we're not going to move anyway - if (incrementalDeltaY != 0) { - trackMotionScroll(incrementalDeltaY, incrementalDeltaY); + if (mScrollY != 0) { + mScrollY = 0; + invalidateParentIfNeeded(); } + trackMotionScroll(incrementalDeltaY, incrementalDeltaY); + mTouchMode = TOUCH_MODE_SCROLL; // We did not scroll the full amount. Treat this essentially like the @@ -3468,11 +3480,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int rightPadding = mListPadding.right + mGlowPaddingRight; final int width = getWidth() - leftPadding - rightPadding; - canvas.translate(leftPadding, - Math.min(0, scrollY + mFirstPositionDistanceGuess)); + int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess); + canvas.translate(leftPadding, edgeY); mEdgeGlowTop.setSize(width, getHeight()); if (mEdgeGlowTop.draw(canvas)) { - invalidate(); + mEdgeGlowTop.setPosition(leftPadding, edgeY); + invalidate(mEdgeGlowTop.getBounds()); } canvas.restoreToCount(restoreCount); } @@ -3483,12 +3496,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int width = getWidth() - leftPadding - rightPadding; final int height = getHeight(); - canvas.translate(-width + leftPadding, - Math.max(height, scrollY + mLastPositionDistanceGuess)); + int edgeX = -width + leftPadding; + int edgeY = Math.max(height, scrollY + mLastPositionDistanceGuess); + canvas.translate(edgeX, edgeY); canvas.rotate(180, width, 0); mEdgeGlowBottom.setSize(width, height); if (mEdgeGlowBottom.draw(canvas)) { - invalidate(); + // Account for the rotation + mEdgeGlowBottom.setPosition(edgeX + width, edgeY - mEdgeGlowBottom.getHeight()); + invalidate(mEdgeGlowBottom.getBounds()); } canvas.restoreToCount(restoreCount); } @@ -3874,7 +3890,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } // Don't stop just because delta is zero (it could have been rounded) - final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0); + final boolean atEdge = trackMotionScroll(delta, delta); + final boolean atEnd = atEdge && (delta != 0); if (atEnd) { if (motionView != null) { // Tweak the scroll for how far we overshot @@ -3889,7 +3906,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } if (more && !atEnd) { - invalidate(); + if (atEdge) invalidate(); mLastFlingY = y; post(this); } else { @@ -4431,7 +4448,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private void createScrollingCache() { - if (mScrollingCacheEnabled && !mCachingStarted) { + if (mScrollingCacheEnabled && !mCachingStarted && !isHardwareAccelerated()) { setChildrenDrawnWithCacheEnabled(true); setChildrenDrawingCacheEnabled(true); mCachingStarted = mCachingActive = true; @@ -4439,23 +4456,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private void clearScrollingCache() { - if (mClearScrollingCache == null) { - mClearScrollingCache = new Runnable() { - public void run() { - if (mCachingStarted) { - mCachingStarted = mCachingActive = false; - setChildrenDrawnWithCacheEnabled(false); - if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { - setChildrenDrawingCacheEnabled(false); - } - if (!isAlwaysDrawnWithCacheEnabled()) { - invalidate(); + if (!isHardwareAccelerated()) { + if (mClearScrollingCache == null) { + mClearScrollingCache = new Runnable() { + public void run() { + if (mCachingStarted) { + mCachingStarted = mCachingActive = false; + setChildrenDrawnWithCacheEnabled(false); + if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { + setChildrenDrawingCacheEnabled(false); + } + if (!isAlwaysDrawnWithCacheEnabled()) { + invalidate(); + } } } - } - }; + }; + } + post(mClearScrollingCache); } - post(mClearScrollingCache); } /** @@ -4599,14 +4618,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mRecycler.removeSkippedScrap(); } + // invalidate before moving the children to avoid unnecessary invalidate + // calls to bubble up from the children all the way to the top + if (!awakenScrollBars()) { + invalidate(); + } + offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { mFirstPosition += count; } - invalidate(); - final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); @@ -4629,7 +4652,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mBlockLayoutRequests = false; invokeOnItemScrollListener(); - awakenScrollBars(); return false; } diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 83aa8ba3a908..c1f31bb982a6 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -16,6 +16,7 @@ package android.widget; +import android.graphics.Rect; import com.android.internal.R; import android.content.Context; @@ -45,6 +46,7 @@ import android.view.animation.Interpolator; * {@link #draw(Canvas)} method.</p> */ public class EdgeEffect { + @SuppressWarnings("UnusedDeclaration") private static final String TAG = "EdgeEffect"; // Time it will take the effect to fully recede in ms @@ -57,10 +59,7 @@ public class EdgeEffect { private static final int PULL_DECAY_TIME = 1000; private static final float MAX_ALPHA = 1.f; - private static final float HELD_EDGE_ALPHA = 0.7f; private static final float HELD_EDGE_SCALE_Y = 0.5f; - private static final float HELD_GLOW_ALPHA = 0.5f; - private static final float HELD_GLOW_SCALE_Y = 0.5f; private static final float MAX_GLOW_HEIGHT = 4.f; @@ -76,7 +75,9 @@ public class EdgeEffect { private final Drawable mGlow; private int mWidth; private int mHeight; - private final int MIN_WIDTH = 300; + private int mX; + private int mY; + private static final int MIN_WIDTH = 300; private final int mMinWidth; private float mEdgeAlpha; @@ -119,6 +120,8 @@ public class EdgeEffect { private int mState = STATE_IDLE; private float mPullDistance; + + private final Rect mBounds = new Rect(); /** * Construct a new EdgeEffect with a theme appropriate for the provided context. @@ -145,6 +148,29 @@ public class EdgeEffect { } /** + * Set the position of this edge effect in pixels. This position is + * only used by {@link #getBounds()}. + * + * @param x The position of the edge effect on the X axis + * @param y The position of the edge effect on the Y axis + */ + void setPosition(int x, int y) { + mX = x; + mY = y; + } + + boolean isIdle() { + return mState == STATE_IDLE; + } + + /** + * Returns the height of the effect itself. + */ + int getHeight() { + return Math.max(mGlow.getBounds().height(), mEdge.getBounds().height()); + } + + /** * Reports if this EdgeEffect's animation is finished. If this method returns false * after a call to {@link #draw(Canvas)} the host widget should schedule another * drawing pass to continue the animation. @@ -301,7 +327,6 @@ public class EdgeEffect { update(); final int edgeHeight = mEdge.getIntrinsicHeight(); - final int edgeWidth = mEdge.getIntrinsicWidth(); final int glowHeight = mGlow.getIntrinsicHeight(); final int glowWidth = mGlow.getIntrinsicWidth(); @@ -334,9 +359,23 @@ public class EdgeEffect { } mEdge.draw(canvas); + if (mState == STATE_RECEDE && glowBottom == 0 && edgeBottom == 0) { + mState = STATE_IDLE; + } + return mState != STATE_IDLE; } + /** + * Returns the bounds of the edge effect. + */ + public Rect getBounds() { + mBounds.set(mGlow.getBounds()); + mBounds.union(mEdge.getBounds()); + mBounds.offset(mX, mY); + return mBounds; + } + private void update() { final long time = AnimationUtils.currentAnimationTimeMillis(); final float t = Math.min((time - mStartTime) / mDuration, 1.f); diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index fc08cc5bfeab..60dd55cec36e 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -29,8 +29,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; - import com.android.internal.R; +import android.widget.RemoteViews.RemoteView; import java.lang.reflect.Array; import java.util.ArrayList; @@ -146,6 +146,7 @@ import static java.lang.Math.min; * @attr ref android.R.styleable#GridLayout_rowOrderPreserved * @attr ref android.R.styleable#GridLayout_columnOrderPreserved */ +@RemoteView public class GridLayout extends ViewGroup { // Public constants @@ -234,7 +235,6 @@ public class GridLayout extends ViewGroup { final Axis horizontalAxis = new Axis(true); final Axis verticalAxis = new Axis(false); - boolean layoutParamsValid = false; int orientation = DEFAULT_ORIENTATION; boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS; int alignmentMode = DEFAULT_ALIGNMENT_MODE; @@ -713,12 +713,10 @@ public class GridLayout extends ViewGroup { minor = minor + minorSpan; } - lastLayoutParamsHashCode = computeLayoutParamsHashCode(); - invalidateStructure(); } private void invalidateStructure() { - layoutParamsValid = false; + lastLayoutParamsHashCode = UNINITIALIZED_HASH; horizontalAxis.invalidateStructure(); verticalAxis.invalidateStructure(); // This can end up being done twice. Better twice than not at all. @@ -742,10 +740,6 @@ public class GridLayout extends ViewGroup { } final LayoutParams getLayoutParams(View c) { - if (!layoutParamsValid) { - validateLayoutParams(); - layoutParamsValid = true; - } return (LayoutParams) c.getLayoutParams(); } @@ -874,20 +868,22 @@ public class GridLayout extends ViewGroup { return result; } - private void checkForLayoutParamsModification() { - int layoutParamsHashCode = computeLayoutParamsHashCode(); - if (lastLayoutParamsHashCode != UNINITIALIZED_HASH && - lastLayoutParamsHashCode != layoutParamsHashCode) { - invalidateStructure(); + private void consistencyCheck() { + if (lastLayoutParamsHashCode == UNINITIALIZED_HASH) { + validateLayoutParams(); + lastLayoutParamsHashCode = computeLayoutParamsHashCode(); + } else if (lastLayoutParamsHashCode != computeLayoutParamsHashCode()) { Log.w(TAG, "The fields of some layout parameters were modified in between layout " + "operations. Check the javadoc for GridLayout.LayoutParams#rowSpec."); + invalidateStructure(); + consistencyCheck(); } } // Measurement private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec, - int childWidth, int childHeight) { + int childWidth, int childHeight) { int childWidthSpec = getChildMeasureSpec(parentWidthSpec, mPaddingLeft + mPaddingRight + getTotalMargin(child, true), childWidth); int childHeightSpec = getChildMeasureSpec(parentHeightSpec, @@ -923,7 +919,7 @@ public class GridLayout extends ViewGroup { @Override protected void onMeasure(int widthSpec, int heightSpec) { - checkForLayoutParamsModification(); + consistencyCheck(); /** If we have been called by {@link View#measure(int, int)}, one of width or height * is likely to have changed. We must invalidate if so. */ @@ -993,7 +989,7 @@ public class GridLayout extends ViewGroup { */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - checkForLayoutParamsModification(); + consistencyCheck(); int targetWidth = right - left; int targetHeight = bottom - top; @@ -1250,7 +1246,7 @@ public class GridLayout extends ViewGroup { } private void include(List<Arc> arcs, Interval key, MutableInt size, - boolean ignoreIfAlreadyPresent) { + boolean ignoreIfAlreadyPresent) { /* Remove self referential links. These appear: @@ -1429,8 +1425,8 @@ public class GridLayout extends ViewGroup { int dst = arc.span.max; int value = arc.value.value; result.append((src < dst) ? - var + dst + " - " + var + src + " > " + value : - var + src + " - " + var + dst + " < " + -value); + var + dst + "-" + var + src + ">=" + value : + var + src + "-" + var + dst + "<=" + -value); } return result.toString(); diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h index 662dd13afca5..a68ab4e8edbd 100644 --- a/include/media/mediaplayer.h +++ b/include/media/mediaplayer.h @@ -120,6 +120,9 @@ enum media_info_type { MEDIA_INFO_NOT_SEEKABLE = 801, // New media metadata is available. MEDIA_INFO_METADATA_UPDATE = 802, + + //9xx + MEDIA_INFO_TIMED_TEXT_ERROR = 900, }; @@ -140,9 +143,6 @@ enum media_player_states { // The same enum space is used for both set and get, in case there are future keys that // can be both set and get. But as of now, all parameters are either set only or get only. enum media_parameter_keys { - KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000, // set only - KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001, // set only - // Streaming/buffering parameters KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS = 1100, // set only @@ -155,6 +155,23 @@ enum media_parameter_keys { KEY_PARAMETER_PLAYBACK_RATE_PERMILLE = 1300, // set only }; +// Keep INVOKE_ID_* in sync with MediaPlayer.java. +enum media_player_invoke_ids { + INVOKE_ID_GET_TRACK_INFO = 1, + INVOKE_ID_ADD_EXTERNAL_SOURCE = 2, + INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3, + INVOKE_ID_SELECT_TRACK = 4, + INVOKE_ID_UNSELECT_TRACK = 5, +}; + +// Keep MEDIA_TRACK_TYPE_* in sync with MediaPlayer.java. +enum media_track_type { + MEDIA_TRACK_TYPE_UNKNOWN = 0, + MEDIA_TRACK_TYPE_VIDEO = 1, + MEDIA_TRACK_TYPE_AUDIO = 2, + MEDIA_TRACK_TYPE_TIMEDTEXT = 3, +}; + // ---------------------------------------------------------------------------- // ref-counted object for callbacks class MediaPlayerListener: virtual public RefBase diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h index 2eb259e8b590..457d5d72e98c 100644 --- a/include/media/stagefright/MediaDefs.h +++ b/include/media/stagefright/MediaDefs.h @@ -54,6 +54,7 @@ extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG2PS; extern const char *MEDIA_MIMETYPE_CONTAINER_WVM; extern const char *MEDIA_MIMETYPE_TEXT_3GPP; +extern const char *MEDIA_MIMETYPE_TEXT_SUBRIP; } // namespace android diff --git a/include/media/stagefright/timedtext/TimedTextDriver.h b/include/media/stagefright/timedtext/TimedTextDriver.h index efedb6e9028e..b9752df5a7e6 100644 --- a/include/media/stagefright/timedtext/TimedTextDriver.h +++ b/include/media/stagefright/timedtext/TimedTextDriver.h @@ -37,26 +37,26 @@ public: ~TimedTextDriver(); - // TODO: pause-resume pair seems equivalent to stop-start pair. - // Check if it is replaceable with stop-start. status_t start(); - status_t stop(); status_t pause(); - status_t resume(); + status_t selectTrack(int32_t index); + status_t unselectTrack(int32_t index); status_t seekToAsync(int64_t timeUs); status_t addInBandTextSource(const sp<MediaSource>& source); - status_t addOutOfBandTextSource(const Parcel &request); + status_t addOutOfBandTextSource(const char *uri, const char *mimeType); + // Caller owns the file desriptor and caller is responsible for closing it. + status_t addOutOfBandTextSource( + int fd, off64_t offset, size_t length, const char *mimeType); - status_t setTimedTextTrackIndex(int32_t index); + void getTrackInfo(Parcel *parcel); private: Mutex mLock; enum State { UNINITIALIZED, - STOPPED, PLAYING, PAUSED, }; @@ -67,11 +67,11 @@ private: // Variables to be guarded by mLock. State mState; - Vector<sp<TimedTextSource> > mTextInBandVector; - Vector<sp<TimedTextSource> > mTextOutOfBandVector; + int32_t mCurrentTrackIndex; + Vector<sp<TimedTextSource> > mTextSourceVector; // -- End of variables to be guarded by mLock - status_t setTimedTextTrackIndex_l(int32_t index); + status_t selectTrack_l(int32_t index); DISALLOW_EVIL_CONSTRUCTORS(TimedTextDriver); }; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index f5fa877c97d3..d92180db62d5 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -24,6 +24,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.util.Log; @@ -455,6 +456,22 @@ import java.lang.ref.WeakReference; * <td>Successful invoke of this method in a valid state transfers the * object to the <em>Stopped</em> state. Calling this method in an * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * <tr><td>getTrackInfo </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>addExternalSource </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>selectTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> + * <tr><td>disableTrack </p></td> + * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td> + * <td>{Idle, Initialized, Error}</p></td> + * <td>Successful invoke of this method does not change the state.</p></td></tr> * * </table> * @@ -572,6 +589,15 @@ public class MediaPlayer */ private native void _setVideoSurface(Surface surface); + /* Do not change these values (starting with INVOKE_ID) without updating + * their counterparts in include/media/mediaplayer.h! + */ + private static final int INVOKE_ID_GET_TRACK_INFO = 1; + private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2; + private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3; + private static final int INVOKE_ID_SELECT_TRACK = 4; + private static final int INVOKE_ID_UNSELECT_TRACK = 5; + /** * Create a request parcel which can be routed to the native media * player using {@link #invoke(Parcel, Parcel)}. The Parcel @@ -1312,23 +1338,6 @@ public class MediaPlayer /* Do not change these values (starting with KEY_PARAMETER) without updating * their counterparts in include/media/mediaplayer.h! */ - /* - * Key used in setParameter method. - * Indicates the index of the timed text track to be enabled/disabled. - * The index includes both the in-band and out-of-band timed text. - * The index should start from in-band text if any. Application can retrieve the number - * of in-band text tracks by using MediaMetadataRetriever::extractMetadata(). - * Note it might take a few hundred ms to scan an out-of-band text file - * before displaying it. - */ - private static final int KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX = 1000; - /* - * Key used in setParameter method. - * Used to add out-of-band timed text source path. - * Application can add multiple text sources by calling setParameter() with - * KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE multiple times. - */ - private static final int KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE = 1001; // There are currently no defined keys usable from Java with get*Parameter. // But if any keys are defined, the order must be kept in sync with include/media/mediaplayer.h. @@ -1373,7 +1382,7 @@ public class MediaPlayer return ret; } - /** + /* * Gets the value of the parameter indicated by key. * @param key key indicates the parameter to get. * @param reply value of the parameter to get. @@ -1435,7 +1444,7 @@ public class MediaPlayer */ public native void setAuxEffectSendLevel(float level); - /** + /* * @param request Parcel destinated to the media player. The * Interface token must be set to the IMediaPlayer * one to be routed correctly through the system. @@ -1445,7 +1454,7 @@ public class MediaPlayer private native final int native_invoke(Parcel request, Parcel reply); - /** + /* * @param update_only If true fetch only the set of metadata that have * changed since the last invocation of getMetadata. * The set is built using the unfiltered @@ -1462,7 +1471,7 @@ public class MediaPlayer boolean apply_filter, Parcel reply); - /** + /* * @param request Parcel with the 2 serialized lists of allowed * metadata types followed by the one to be * dropped. Each list starts with an integer @@ -1476,33 +1485,289 @@ public class MediaPlayer private native final void native_finalize(); /** - * @param index The index of the text track to be turned on. - * @return true if the text track is enabled successfully. + * Class for MediaPlayer to return each audio/video/subtitle track's metadata. + * + * {@see #getTrackInfo()}. + * {@hide} + */ + static public class TrackInfo implements Parcelable { + /** + * Gets the track type. + * @return TrackType which indicates if the track is video, audio, timed text. + */ + public int getTrackType() { + return mTrackType; + } + + /** + * Gets the language code of the track. + * @return a language code in either way of ISO-639-1 or ISO-639-2. + * When the language is unknown or could not be determined, + * ISO-639-2 language code, "und", is returned. + */ + public String getLanguage() { + return mLanguage; + } + + public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; + public static final int MEDIA_TRACK_TYPE_VIDEO = 1; + public static final int MEDIA_TRACK_TYPE_AUDIO = 2; + public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; + + final int mTrackType; + final String mLanguage; + + TrackInfo(Parcel in) { + mTrackType = in.readInt(); + mLanguage = in.readString(); + } + + /* + * No special parcel contents. Keep it as hide. + * {@hide} + */ + @Override + public int describeContents() { + return 0; + } + + /* + * {@hide} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mTrackType); + dest.writeString(mLanguage); + } + + /** + * Used to read a TrackInfo from a Parcel. + */ + static final Parcelable.Creator<TrackInfo> CREATOR + = new Parcelable.Creator<TrackInfo>() { + @Override + public TrackInfo createFromParcel(Parcel in) { + return new TrackInfo(in); + } + + @Override + public TrackInfo[] newArray(int size) { + return new TrackInfo[size]; + } + }; + + }; + + /** + * Returns an array of track information. + * + * @return Array of track info. null if an error occured. + * {@hide} + */ + // FIXME: It returns timed text tracks' information for now. Other types of tracks will be + // supported in future. + public TrackInfo[] getTrackInfo() { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_GET_TRACK_INFO); + invoke(request, reply); + TrackInfo trackInfo[] = reply.createTypedArray(TrackInfo.CREATOR); + return trackInfo; + } + + /* + * A helper function to check if the mime type is supported by media framework. + */ + private boolean availableMimeTypeForExternalSource(String mimeType) { + if (mimeType == MEDIA_MIMETYPE_TEXT_SUBRIP) { + return true; + } + return false; + } + + /* TODO: Limit the total number of external timed text source to a reasonable number. + */ + /** + * Adds an external source file. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param path The file path of external source file. + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(String path, String mimeType) + throws IllegalArgumentException { + if (!availableMimeTypeForExternalSource(mimeType)) { + throw new IllegalArgumentException("Illegal mimeType for external source: " + mimeType); + } + + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE); + request.writeString(path); + request.writeString(mimeType); + invoke(request, reply); + } + + /** + * Adds an external source file (Uri). + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play * {@hide} */ - public boolean enableTimedTextTrackIndex(int index) { - if (index < 0) { - return false; + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(Context context, Uri uri, String mimeType) + throws IOException, IllegalArgumentException { + String scheme = uri.getScheme(); + if(scheme == null || scheme.equals("file")) { + addExternalSource(uri.getPath(), mimeType); + return; } - return setParameter(KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX, index); + + AssetFileDescriptor fd = null; + try { + ContentResolver resolver = context.getContentResolver(); + fd = resolver.openAssetFileDescriptor(uri, "r"); + if (fd == null) { + return; + } + addExternalSource(fd.getFileDescriptor(), mimeType); + return; + } catch (SecurityException ex) { + } catch (IOException ex) { + } finally { + if (fd != null) { + fd.close(); + } + } + + // TODO: try server side. } + /* Do not change these values without updating their counterparts + * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp! + */ /** - * Enables the first timed text track if any. - * @return true if the text track is enabled successfully + * MIME type for SubRip (SRT) container. Used in {@link #addExternalSource()} APIs. * {@hide} */ - public boolean enableTimedText() { - return enableTimedTextTrackIndex(0); + public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; + + /** + * Adds an external source file (FileDescriptor). + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param mimeType A MIME type for the content. It can be null. + * <ul> + * <li>{@link #MEDIA_MIMETYPE_TEXT_SUBRIP} + * </ul> + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(FileDescriptor fd, String mimeType) + throws IllegalArgumentException { + // intentionally less than LONG_MAX + addExternalSource(fd, 0, 0x7ffffffffffffffL, mimeType); + } + + /** + * Adds an external timed text file (FileDescriptor). + * It is the caller's responsibility to close the file descriptor. + * It is safe to do so as soon as this call returns. + * + * Currently supported format is SubRip with the file extension .srt, case insensitive. + * Note that a single external source may contain multiple tracks in it. + * One can find the total number of available tracks using {@link #getTrackInfo()} to see what + * additional tracks become available after this method call. + * + * @param fd the FileDescriptor for the file you want to play + * @param offset the offset into the file where the data to be played starts, in bytes + * @param length the length in bytes of the data to be played + * @param mimeType A MIME type for the content. It can be null. + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException). + public void addExternalSource(FileDescriptor fd, long offset, long length, String mimeType) + throws IllegalArgumentException { + if (!availableMimeTypeForExternalSource(mimeType)) { + throw new IllegalArgumentException("Illegal mimeType for external source: " + mimeType); + } + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE_FD); + request.writeFileDescriptor(fd); + request.writeLong(offset); + request.writeLong(length); + request.writeString(mimeType); + invoke(request, reply); } /** - * Disables timed text display. - * @return true if the text track is disabled successfully. + * Selects a track. + * <p> + * If a MediaPlayer is in invalid state, it throws exception. + * If a MediaPlayer is in Started state, the selected track will be presented immediately. + * If a MediaPlayer is not in Started state, it just marks the track to be played. + * </p> + * <p> + * In any valid state, if it is called multiple times on the same type of track (ie. Video, + * Audio, Timed Text), the most recent one will be chosen. + * </p> + * <p> + * The first audio and video tracks will be selected by default, even though this function is not + * called. However, no timed text track will be selected until this function is called. + * </p> * {@hide} */ - public boolean disableTimedText() { - return setParameter(KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX, -1); + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException, IllegalArgumentException). + public void selectTrack(int index) { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_SELECT_TRACK); + request.writeInt(index); + invoke(request, reply); + } + + /** + * Unselect a track. + * If the track identified by index has not been selected before, it throws an exception. + * {@hide} + */ + // FIXME: define error codes and throws exceptions according to the error codes. + // (IllegalStateException, IOException, IllegalArgumentException). + public void unselectTrack(int index) { + Parcel request = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + request.writeInterfaceToken(IMEDIA_PLAYER); + request.writeInt(INVOKE_ID_UNSELECT_TRACK); + request.writeInt(index); + invoke(request, reply); } /** @@ -1641,14 +1906,14 @@ public class MediaPlayer // No real default action so far. return; case MEDIA_TIMED_TEXT: - if (mOnTimedTextListener != null) { - if (msg.obj == null) { - mOnTimedTextListener.onTimedText(mMediaPlayer, null); - } else { - if (msg.obj instanceof byte[]) { - TimedText text = new TimedText((byte[])(msg.obj)); - mOnTimedTextListener.onTimedText(mMediaPlayer, text); - } + if (mOnTimedTextListener == null) + return; + if (msg.obj == null) { + mOnTimedTextListener.onTimedText(mMediaPlayer, null); + } else { + if (msg.obj instanceof byte[]) { + TimedText text = new TimedText((byte[])(msg.obj)); + mOnTimedTextListener.onTimedText(mMediaPlayer, text); } } return; @@ -1663,7 +1928,7 @@ public class MediaPlayer } } - /** + /* * Called from native code when an interesting event happens. This method * just uses the EventHandler system to post the event back to the main app thread. * We use a weak reference to the original MediaPlayer object so that the native @@ -1977,6 +2242,13 @@ public class MediaPlayer */ public static final int MEDIA_INFO_METADATA_UPDATE = 802; + /** Failed to handle timed text track properly. + * @see android.media.MediaPlayer.OnInfoListener + * + * {@hide} + */ + public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900; + /** * Interface definition of a callback to be invoked to communicate some * info and/or warning about the media or its playback. diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp index 052ebf04df2c..619c14908962 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.cpp +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -166,7 +166,8 @@ player_type StagefrightPlayer::playerType() { } status_t StagefrightPlayer::invoke(const Parcel &request, Parcel *reply) { - return INVALID_OPERATION; + ALOGV("invoke()"); + return mPlayer->invoke(request, reply); } void StagefrightPlayer::setAudioSink(const sp<AudioSink> &audioSink) { diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 9e00bb349144..b4cb1abff94f 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -1114,7 +1114,7 @@ status_t AwesomePlayer::pause_l(bool at_eos) { modifyFlags(AUDIO_RUNNING, CLEAR); } - if (mFlags & TEXTPLAYER_STARTED) { + if (mFlags & TEXTPLAYER_INITIALIZED) { mTextDriver->pause(); modifyFlags(TEXT_RUNNING, CLEAR); } @@ -1268,32 +1268,6 @@ status_t AwesomePlayer::seekTo(int64_t timeUs) { return OK; } -status_t AwesomePlayer::setTimedTextTrackIndex(int32_t index) { - if (mTextDriver != NULL) { - if (index >= 0) { // to turn on a text track - status_t err = mTextDriver->setTimedTextTrackIndex(index); - if (err != OK) { - return err; - } - - modifyFlags(TEXT_RUNNING, SET); - modifyFlags(TEXTPLAYER_STARTED, SET); - return OK; - } else { // to turn off the text track display - if (mFlags & TEXT_RUNNING) { - modifyFlags(TEXT_RUNNING, CLEAR); - } - if (mFlags & TEXTPLAYER_STARTED) { - modifyFlags(TEXTPLAYER_STARTED, CLEAR); - } - - return mTextDriver->setTimedTextTrackIndex(index); - } - } else { - return INVALID_OPERATION; - } -} - status_t AwesomePlayer::seekTo_l(int64_t timeUs) { if (mFlags & CACHE_UNDERRUN) { modifyFlags(CACHE_UNDERRUN, CLEAR); @@ -1315,7 +1289,7 @@ status_t AwesomePlayer::seekTo_l(int64_t timeUs) { seekAudioIfNecessary_l(); - if (mFlags & TEXTPLAYER_STARTED) { + if (mFlags & TEXTPLAYER_INITIALIZED) { mTextDriver->seekToAsync(mSeekTimeUs); } @@ -1691,8 +1665,8 @@ void AwesomePlayer::onVideoEvent() { } } - if ((mFlags & TEXTPLAYER_STARTED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) { - mTextDriver->resume(); + if ((mFlags & TEXTPLAYER_INITIALIZED) && !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) { + mTextDriver->start(); modifyFlags(TEXT_RUNNING, SET); } @@ -2232,20 +2206,6 @@ void AwesomePlayer::postAudioSeekComplete() { status_t AwesomePlayer::setParameter(int key, const Parcel &request) { switch (key) { - case KEY_PARAMETER_TIMED_TEXT_TRACK_INDEX: - { - Mutex::Autolock autoLock(mTimedTextLock); - return setTimedTextTrackIndex(request.readInt32()); - } - case KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE: - { - Mutex::Autolock autoLock(mTimedTextLock); - if (mTextDriver == NULL) { - mTextDriver = new TimedTextDriver(mListener); - } - - return mTextDriver->addOutOfBandTextSource(request); - } case KEY_PARAMETER_CACHE_STAT_COLLECT_FREQ_MS: { return setCacheStatCollectFreq(request); @@ -2294,6 +2254,103 @@ status_t AwesomePlayer::getParameter(int key, Parcel *reply) { } } +status_t AwesomePlayer::invoke(const Parcel &request, Parcel *reply) { + if (NULL == reply) { + return android::BAD_VALUE; + } + int32_t methodId; + status_t ret = request.readInt32(&methodId); + if (ret != android::OK) { + return ret; + } + switch(methodId) { + case INVOKE_ID_GET_TRACK_INFO: + { + Mutex::Autolock autoLock(mTimedTextLock); + if (mTextDriver == NULL) { + return INVALID_OPERATION; + } + mTextDriver->getTrackInfo(reply); + return OK; + } + case INVOKE_ID_ADD_EXTERNAL_SOURCE: + { + Mutex::Autolock autoLock(mTimedTextLock); + if (mTextDriver == NULL) { + mTextDriver = new TimedTextDriver(mListener); + } + // String values written in Parcel are UTF-16 values. + String16 uri16 = request.readString16(); + const char *uri = NULL; + if (uri16 != NULL) { + uri = String8(uri16).string(); + } + String16 mimeType16 = request.readString16(); + const char *mimeType = NULL; + if (mimeType16 != NULL) { + mimeType = String8(mimeType16).string(); + } + return mTextDriver->addOutOfBandTextSource(uri, mimeType); + } + case INVOKE_ID_ADD_EXTERNAL_SOURCE_FD: + { + Mutex::Autolock autoLock(mTimedTextLock); + if (mTextDriver == NULL) { + mTextDriver = new TimedTextDriver(mListener); + } + int fd = request.readFileDescriptor(); + off64_t offset = request.readInt64(); + size_t length = request.readInt64(); + String16 mimeType16 = request.readString16(); + const char *mimeType = NULL; + if (mimeType16 != NULL) { + mimeType = String8(mimeType16).string(); + } + + return mTextDriver->addOutOfBandTextSource( + fd, offset, length, mimeType); + } + case INVOKE_ID_SELECT_TRACK: + { + Mutex::Autolock autoLock(mTimedTextLock); + if (mTextDriver == NULL) { + return INVALID_OPERATION; + } + + status_t err = mTextDriver->selectTrack( + request.readInt32()); + if (err == OK) { + modifyFlags(TEXTPLAYER_INITIALIZED, SET); + if (mFlags & PLAYING && !(mFlags & TEXT_RUNNING)) { + mTextDriver->start(); + modifyFlags(TEXT_RUNNING, SET); + } + } + return err; + } + case INVOKE_ID_UNSELECT_TRACK: + { + Mutex::Autolock autoLock(mTimedTextLock); + if (mTextDriver == NULL) { + return INVALID_OPERATION; + } + status_t err = mTextDriver->unselectTrack( + request.readInt32()); + if (err == OK) { + modifyFlags(TEXTPLAYER_INITIALIZED, CLEAR); + modifyFlags(TEXT_RUNNING, CLEAR); + } + return err; + } + default: + { + return ERROR_UNSUPPORTED; + } + } + // It will not reach here. + return OK; +} + bool AwesomePlayer::isStreamingHTTP() const { return mCachedSource != NULL || mWVMExtractor != NULL; } diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp index 444e823295a3..2549de68afb6 100644 --- a/media/libstagefright/MediaDefs.cpp +++ b/media/libstagefright/MediaDefs.cpp @@ -52,5 +52,6 @@ const char *MEDIA_MIMETYPE_CONTAINER_MPEG2PS = "video/mp2p"; const char *MEDIA_MIMETYPE_CONTAINER_WVM = "video/wvm"; const char *MEDIA_MIMETYPE_TEXT_3GPP = "text/3gpp-tt"; +const char *MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip"; } // namespace android diff --git a/media/libstagefright/XINGSeeker.cpp b/media/libstagefright/XINGSeeker.cpp index 20913816b362..e36d61978cbb 100644 --- a/media/libstagefright/XINGSeeker.cpp +++ b/media/libstagefright/XINGSeeker.cpp @@ -24,7 +24,7 @@ namespace android { static bool parse_xing_header( const sp<DataSource> &source, off64_t first_frame_pos, int32_t *frame_number = NULL, int32_t *byte_number = NULL, - unsigned char *table_of_contents = NULL, + unsigned char *table_of_contents = NULL, bool *toc_is_valid = NULL, int32_t *quality_indicator = NULL, int64_t *duration = NULL); // static @@ -36,7 +36,7 @@ sp<XINGSeeker> XINGSeeker::CreateFromSource( if (!parse_xing_header( source, first_frame_pos, - NULL, &seeker->mSizeBytes, seeker->mTableOfContents, + NULL, &seeker->mSizeBytes, seeker->mTOC, &seeker->mTOCValid, NULL, &seeker->mDurationUs)) { return NULL; } @@ -60,7 +60,7 @@ bool XINGSeeker::getDuration(int64_t *durationUs) { } bool XINGSeeker::getOffsetForTime(int64_t *timeUs, off64_t *pos) { - if (mSizeBytes == 0 || mTableOfContents[0] <= 0 || mDurationUs < 0) { + if (mSizeBytes == 0 || !mTOCValid || mDurationUs < 0) { return false; } @@ -76,10 +76,10 @@ bool XINGSeeker::getOffsetForTime(int64_t *timeUs, off64_t *pos) { if ( a == 0 ) { fa = 0.0f; } else { - fa = (float)mTableOfContents[a-1]; + fa = (float)mTOC[a-1]; } if ( a < 99 ) { - fb = (float)mTableOfContents[a]; + fb = (float)mTOC[a]; } else { fb = 256.0f; } @@ -94,7 +94,8 @@ bool XINGSeeker::getOffsetForTime(int64_t *timeUs, off64_t *pos) { static bool parse_xing_header( const sp<DataSource> &source, off64_t first_frame_pos, int32_t *frame_number, int32_t *byte_number, - unsigned char *table_of_contents, int32_t *quality_indicator, + unsigned char *table_of_contents, bool *toc_valid, + int32_t *quality_indicator, int64_t *duration) { if (frame_number) { *frame_number = 0; @@ -102,8 +103,8 @@ static bool parse_xing_header( if (byte_number) { *byte_number = 0; } - if (table_of_contents) { - table_of_contents[0] = 0; + if (toc_valid) { + *toc_valid = false; } if (quality_indicator) { *quality_indicator = 0; @@ -199,10 +200,13 @@ static bool parse_xing_header( offset += 4; } if (flags & 0x0004) { // TOC field is present - if (table_of_contents) { + if (table_of_contents) { if (source->readAt(offset + 1, table_of_contents, 99) < 99) { return false; } + if (toc_valid) { + *toc_valid = true; + } } offset += 100; } diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 4c7bfa684a13..06e946887f18 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -90,6 +90,7 @@ struct AwesomePlayer { status_t setParameter(int key, const Parcel &request); status_t getParameter(int key, Parcel *reply); + status_t invoke(const Parcel &request, Parcel *reply); status_t setCacheStatCollectFreq(const Parcel &request); status_t seekTo(int64_t timeUs); @@ -100,8 +101,6 @@ struct AwesomePlayer { void postAudioEOS(int64_t delayUs = 0ll); void postAudioSeekComplete(); - status_t setTimedTextTrackIndex(int32_t index); - status_t dump(int fd, const Vector<String16> &args) const; private: @@ -136,7 +135,7 @@ private: INCOGNITO = 0x8000, TEXT_RUNNING = 0x10000, - TEXTPLAYER_STARTED = 0x20000, + TEXTPLAYER_INITIALIZED = 0x20000, SLOW_DECODER_HACK = 0x40000, }; diff --git a/media/libstagefright/include/XINGSeeker.h b/media/libstagefright/include/XINGSeeker.h index ec5bd9b0ac94..85109791d9b8 100644 --- a/media/libstagefright/include/XINGSeeker.h +++ b/media/libstagefright/include/XINGSeeker.h @@ -37,7 +37,8 @@ private: int32_t mSizeBytes; // TOC entries in XING header. Skip the first one since it's always 0. - unsigned char mTableOfContents[99]; + unsigned char mTOC[99]; + bool mTOCValid; XINGSeeker(); diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.cpp b/media/libstagefright/timedtext/TimedText3GPPSource.cpp index 4a3bfd323e74..c423ef063f30 100644 --- a/media/libstagefright/timedtext/TimedText3GPPSource.cpp +++ b/media/libstagefright/timedtext/TimedText3GPPSource.cpp @@ -110,4 +110,8 @@ status_t TimedText3GPPSource::extractGlobalDescriptions(Parcel *parcel) { return OK; } +sp<MetaData> TimedText3GPPSource::getFormat() { + return mSource->getFormat(); +} + } // namespace android diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.h b/media/libstagefright/timedtext/TimedText3GPPSource.h index dfc6418764a4..4ec3d8a13dfb 100644 --- a/media/libstagefright/timedtext/TimedText3GPPSource.h +++ b/media/libstagefright/timedtext/TimedText3GPPSource.h @@ -37,6 +37,7 @@ public: Parcel *parcel, const MediaSource::ReadOptions *options = NULL); virtual status_t extractGlobalDescriptions(Parcel *parcel); + virtual sp<MetaData> getFormat(); protected: virtual ~TimedText3GPPSource(); diff --git a/media/libstagefright/timedtext/TimedTextDriver.cpp b/media/libstagefright/timedtext/TimedTextDriver.cpp index c70870ea690f..ed8389401926 100644 --- a/media/libstagefright/timedtext/TimedTextDriver.cpp +++ b/media/libstagefright/timedtext/TimedTextDriver.cpp @@ -20,10 +20,13 @@ #include <binder/IPCThreadState.h> +#include <media/mediaplayer.h> #include <media/MediaPlayerInterface.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaSource.h> -#include <media/stagefright/DataSource.h> +#include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/ALooper.h> @@ -47,24 +50,22 @@ TimedTextDriver::TimedTextDriver( } TimedTextDriver::~TimedTextDriver() { - mTextInBandVector.clear(); - mTextOutOfBandVector.clear(); + mTextSourceVector.clear(); mLooper->stop(); } -status_t TimedTextDriver::setTimedTextTrackIndex_l(int32_t index) { - if (index >= - (int)(mTextInBandVector.size() + mTextOutOfBandVector.size())) { +status_t TimedTextDriver::selectTrack_l(int32_t index) { + if (index >= (int)(mTextSourceVector.size())) { return BAD_VALUE; } sp<TimedTextSource> source; - if (index < mTextInBandVector.size()) { - source = mTextInBandVector.itemAt(index); - } else { - source = mTextOutOfBandVector.itemAt(index - mTextInBandVector.size()); - } + source = mTextSourceVector.itemAt(index); mPlayer->setDataSource(source); + if (mState == UNINITIALIZED) { + mState = PAUSED; + } + mCurrentTrackIndex = index; return OK; } @@ -73,13 +74,10 @@ status_t TimedTextDriver::start() { switch (mState) { case UNINITIALIZED: return INVALID_OPERATION; - case STOPPED: - mPlayer->start(); - break; case PLAYING: return OK; case PAUSED: - mPlayer->resume(); + mPlayer->start(); break; default: TRESPASS(); @@ -88,10 +86,6 @@ status_t TimedTextDriver::start() { return OK; } -status_t TimedTextDriver::stop() { - return pause(); -} - // TODO: Test if pause() works properly. // Scenario 1: start - pause - resume // Scenario 2: start - seek @@ -101,8 +95,6 @@ status_t TimedTextDriver::pause() { switch (mState) { case UNINITIALIZED: return INVALID_OPERATION; - case STOPPED: - return OK; case PLAYING: mPlayer->pause(); break; @@ -115,45 +107,17 @@ status_t TimedTextDriver::pause() { return OK; } -status_t TimedTextDriver::resume() { - return start(); -} - -status_t TimedTextDriver::seekToAsync(int64_t timeUs) { - mPlayer->seekToAsync(timeUs); - return OK; -} - -status_t TimedTextDriver::setTimedTextTrackIndex(int32_t index) { - // TODO: This is current implementation for MediaPlayer::disableTimedText(). - // Find better way for readability. - if (index < 0) { - mPlayer->pause(); - return OK; - } - +status_t TimedTextDriver::selectTrack(int32_t index) { status_t ret = OK; Mutex::Autolock autoLock(mLock); switch (mState) { case UNINITIALIZED: - ret = INVALID_OPERATION; - break; case PAUSED: - ret = setTimedTextTrackIndex_l(index); + ret = selectTrack_l(index); break; case PLAYING: mPlayer->pause(); - ret = setTimedTextTrackIndex_l(index); - if (ret != OK) { - break; - } - mPlayer->start(); - break; - case STOPPED: - // TODO: The only difference between STOPPED and PAUSED is this - // part. Revise the flow from "MediaPlayer::enableTimedText()" and - // remove one of the status, PAUSED and STOPPED, if possible. - ret = setTimedTextTrackIndex_l(index); + ret = selectTrack_l(index); if (ret != OK) { break; } @@ -165,6 +129,24 @@ status_t TimedTextDriver::setTimedTextTrackIndex(int32_t index) { return ret; } +status_t TimedTextDriver::unselectTrack(int32_t index) { + if (mCurrentTrackIndex != index) { + return INVALID_OPERATION; + } + status_t err = pause(); + if (err != OK) { + return err; + } + Mutex::Autolock autoLock(mLock); + mState = UNINITIALIZED; + return OK; +} + +status_t TimedTextDriver::seekToAsync(int64_t timeUs) { + mPlayer->seekToAsync(timeUs); + return OK; +} + status_t TimedTextDriver::addInBandTextSource( const sp<MediaSource>& mediaSource) { sp<TimedTextSource> source = @@ -173,25 +155,17 @@ status_t TimedTextDriver::addInBandTextSource( return ERROR_UNSUPPORTED; } Mutex::Autolock autoLock(mLock); - mTextInBandVector.add(source); - if (mState == UNINITIALIZED) { - mState = STOPPED; - } + mTextSourceVector.add(source); return OK; } status_t TimedTextDriver::addOutOfBandTextSource( - const Parcel &request) { + const char *uri, const char *mimeType) { // TODO: Define "TimedTextSource::CreateFromURI(uri)" // and move below lines there..? - // String values written in Parcel are UTF-16 values. - const String16 uri16 = request.readString16(); - String8 uri = String8(request.readString16()); - - uri.toLower(); // To support local subtitle file only for now - if (strncasecmp("file://", uri.string(), 7)) { + if (strncasecmp("file://", uri, 7)) { return ERROR_UNSUPPORTED; } sp<DataSource> dataSource = @@ -201,7 +175,7 @@ status_t TimedTextDriver::addOutOfBandTextSource( } sp<TimedTextSource> source; - if (uri.getPathExtension() == String8(".srt")) { + if (strcasecmp(mimeType, MEDIA_MIMETYPE_TEXT_SUBRIP)) { source = TimedTextSource::CreateTimedTextSource( dataSource, TimedTextSource::OUT_OF_BAND_FILE_SRT); } @@ -211,12 +185,38 @@ status_t TimedTextDriver::addOutOfBandTextSource( } Mutex::Autolock autoLock(mLock); + mTextSourceVector.add(source); + return OK; +} - mTextOutOfBandVector.add(source); - if (mState == UNINITIALIZED) { - mState = STOPPED; +status_t TimedTextDriver::addOutOfBandTextSource( + int fd, off64_t offset, size_t length, const char *mimeType) { + // Not supported yet. This requires DataSource::sniff to detect various text + // formats such as srt/smi/ttml. + return ERROR_UNSUPPORTED; +} + +void TimedTextDriver::getTrackInfo(Parcel *parcel) { + Mutex::Autolock autoLock(mLock); + Vector<sp<TimedTextSource> >::const_iterator iter; + parcel->writeInt32(mTextSourceVector.size()); + for (iter = mTextSourceVector.begin(); + iter != mTextSourceVector.end(); ++iter) { + sp<MetaData> meta = (*iter)->getFormat(); + if (meta != NULL) { + // There are two fields. + parcel->writeInt32(2); + + // track type. + parcel->writeInt32(MEDIA_TRACK_TYPE_TIMEDTEXT); + + const char *lang = "und"; + meta->findCString(kKeyMediaLanguage, &lang); + parcel->writeString16(String16(lang)); + } else { + parcel->writeInt32(0); + } } - return OK; } } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index bda7b4639d4f..8717914f984c 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -56,10 +56,6 @@ void TimedTextPlayer::pause() { (new AMessage(kWhatPause, id()))->post(); } -void TimedTextPlayer::resume() { - start(); -} - void TimedTextPlayer::seekToAsync(int64_t timeUs) { sp<AMessage> msg = new AMessage(kWhatSeek, id()); msg->setInt64("seekTimeUs", timeUs); @@ -104,9 +100,9 @@ void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) { if (obj != NULL) { sp<ParcelEvent> parcelEvent; parcelEvent = static_cast<ParcelEvent*>(obj.get()); - notifyListener(MEDIA_TIMED_TEXT, &(parcelEvent->parcel)); + notifyListener(&(parcelEvent->parcel)); } else { - notifyListener(MEDIA_TIMED_TEXT); + notifyListener(); } doRead(); break; @@ -119,14 +115,18 @@ void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) { mSource->stop(); } mSource = static_cast<TimedTextSource*>(obj.get()); - mSource->start(); + status_t err = mSource->start(); + if (err != OK) { + notifyError(err); + break; + } Parcel parcel; - if (mSource->extractGlobalDescriptions(&parcel) == OK && - parcel.dataSize() > 0) { - notifyListener(MEDIA_TIMED_TEXT, &parcel); - } else { - notifyListener(MEDIA_TIMED_TEXT); + err = mSource->extractGlobalDescriptions(&parcel); + if (err != OK) { + notifyError(err); + break; } + notifyListener(&parcel); break; } } @@ -141,8 +141,12 @@ void TimedTextPlayer::doSeekAndRead(int64_t seekTimeUs) { void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) { int64_t timeUs = 0; sp<ParcelEvent> parcelEvent = new ParcelEvent(); - mSource->read(&timeUs, &(parcelEvent->parcel), options); - postTextEvent(parcelEvent, timeUs); + status_t err = mSource->read(&timeUs, &(parcelEvent->parcel), options); + if (err != OK) { + notifyError(err); + } else { + postTextEvent(parcelEvent, timeUs); + } } void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) { @@ -151,7 +155,7 @@ void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeU int64_t positionUs, delayUs; int32_t positionMs = 0; listener->getCurrentPosition(&positionMs); - positionUs = positionMs * 1000; + positionUs = positionMs * 1000ll; if (timeUs <= positionUs + kAdjustmentProcessingTimeUs) { delayUs = 0; @@ -167,13 +171,20 @@ void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeU } } -void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) { +void TimedTextPlayer::notifyError(int error) { + sp<MediaPlayerBase> listener = mListener.promote(); + if (listener != NULL) { + listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error); + } +} + +void TimedTextPlayer::notifyListener(const Parcel *parcel) { sp<MediaPlayerBase> listener = mListener.promote(); if (listener != NULL) { if (parcel != NULL && (parcel->dataSize() > 0)) { - listener->sendEvent(msg, 0, 0, parcel); + listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel); } else { // send an empty timed text to clear the screen - listener->sendEvent(msg); + listener->sendEvent(MEDIA_TIMED_TEXT); } } } diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h index 837beeb0ee3e..b869f187d819 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.h +++ b/media/libstagefright/timedtext/TimedTextPlayer.h @@ -40,7 +40,6 @@ public: void start(); void pause(); - void resume(); void seekToAsync(int64_t timeUs); void setDataSource(sp<TimedTextSource> source); @@ -68,7 +67,8 @@ private: void doRead(MediaSource::ReadOptions* options = NULL); void onTextEvent(); void postTextEvent(const sp<ParcelEvent>& parcel = NULL, int64_t timeUs = -1); - void notifyListener(int msg, const Parcel *parcel = NULL); + void notifyError(int error = 0); + void notifyListener(const Parcel *parcel = NULL); DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer); }; diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.cpp b/media/libstagefright/timedtext/TimedTextSRTSource.cpp index 3752d34469a5..c44a99b400df 100644 --- a/media/libstagefright/timedtext/TimedTextSRTSource.cpp +++ b/media/libstagefright/timedtext/TimedTextSRTSource.cpp @@ -21,8 +21,10 @@ #include <binder/Parcel.h> #include <media/stagefright/foundation/AString.h> #include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> #include "TimedTextSRTSource.h" #include "TextDescriptions.h" @@ -31,6 +33,7 @@ namespace android { TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource) : mSource(dataSource), + mMetaData(new MetaData), mIndex(0) { } @@ -42,10 +45,14 @@ status_t TimedTextSRTSource::start() { if (err != OK) { reset(); } + // TODO: Need to detect the language, because SRT doesn't give language + // information explicitly. + mMetaData->setCString(kKeyMediaLanguage, ""); return err; } void TimedTextSRTSource::reset() { + mMetaData->clear(); mTextVector.clear(); mIndex = 0; } @@ -272,4 +279,8 @@ status_t TimedTextSRTSource::extractAndAppendLocalDescriptions( return OK; } +sp<MetaData> TimedTextSRTSource::getFormat() { + return mMetaData; +} + } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.h b/media/libstagefright/timedtext/TimedTextSRTSource.h index acc01f94d9f5..62710a01e735 100644 --- a/media/libstagefright/timedtext/TimedTextSRTSource.h +++ b/media/libstagefright/timedtext/TimedTextSRTSource.h @@ -39,12 +39,14 @@ public: int64_t *timeUs, Parcel *parcel, const MediaSource::ReadOptions *options = NULL); + virtual sp<MetaData> getFormat(); protected: virtual ~TimedTextSRTSource(); private: sp<DataSource> mSource; + sp<MetaData> mMetaData; struct TextInfo { int64_t endTimeUs; diff --git a/media/libstagefright/timedtext/TimedTextSource.cpp b/media/libstagefright/timedtext/TimedTextSource.cpp index ffbe1c3bca41..953f7b5f45d2 100644 --- a/media/libstagefright/timedtext/TimedTextSource.cpp +++ b/media/libstagefright/timedtext/TimedTextSource.cpp @@ -59,4 +59,8 @@ sp<TimedTextSource> TimedTextSource::CreateTimedTextSource( return NULL; } +sp<MetaData> TimedTextSource::getFormat() { + return NULL; +} + } // namespace android diff --git a/media/libstagefright/timedtext/TimedTextSource.h b/media/libstagefright/timedtext/TimedTextSource.h index 06bae7150db4..93493426267c 100644 --- a/media/libstagefright/timedtext/TimedTextSource.h +++ b/media/libstagefright/timedtext/TimedTextSource.h @@ -25,6 +25,7 @@ namespace android { class DataSource; +class MetaData; class Parcel; class TimedTextSource : public RefBase { @@ -48,6 +49,7 @@ class TimedTextSource : public RefBase { virtual status_t extractGlobalDescriptions(Parcel *parcel) { return INVALID_OPERATION; } + virtual sp<MetaData> getFormat(); protected: virtual ~TimedTextSource() { } diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index b84fbdbd6629..5ca09e78bb8a 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -27,6 +27,7 @@ import android.util.Slog; import android.view.IApplicationToken; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerPolicy; import android.view.animation.Animation; import android.view.animation.Transformation; @@ -37,7 +38,7 @@ import java.util.ArrayList; * Version of WindowToken that is specifically for a particular application (or * really activity) that is displaying windows. */ -class AppWindowToken extends WindowToken implements WindowManagerService.StepAnimator { +class AppWindowToken extends WindowToken { // Non-null only for application tokens. final IApplicationToken appToken; @@ -195,8 +196,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.StepAni } } - @Override - public boolean stepAnimation(long currentTime) { + + private boolean stepAnimation(long currentTime) { if (animation == null) { return false; } @@ -216,7 +217,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.StepAni } // This must be called while inside a transaction. - boolean startAndFinishAnimationLocked(long currentTime, int dw, int dh) { + boolean stepAnimationLocked(long currentTime, int dw, int dh) { if (!service.mDisplayFrozen && service.mPolicy.isScreenOnFully()) { // We will run animations as long as the display isn't frozen. @@ -240,7 +241,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.StepAni animating = true; } // we're done! - return true; + return stepAnimation(currentTime); } } else if (animation != null) { // If the display is frozen, and there is a pending animation, @@ -255,6 +256,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.StepAni return false; } + service.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; clearAnimation(); animating = false; if (animLayerAdjustment != 0) { diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java index 7b5bf08165cb..58187b699e53 100644 --- a/services/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -29,7 +29,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -class ScreenRotationAnimation implements WindowManagerService.StepAnimator { +class ScreenRotationAnimation { static final String TAG = "ScreenRotationAnimation"; static final boolean DEBUG_STATE = false; static final boolean DEBUG_TRANSFORMS = false; @@ -540,8 +540,7 @@ class ScreenRotationAnimation implements WindowManagerService.StepAnimator { || mRotateFrameAnimation != null; } - @Override - public boolean stepAnimation(long now) { + private boolean stepAnimation(long now) { if (mFinishAnimReady && mFinishAnimStartTime < 0) { if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready"); @@ -725,7 +724,7 @@ class ScreenRotationAnimation implements WindowManagerService.StepAnimator { setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); } - public boolean startAndFinishAnimationLocked(long now) { + public boolean stepAnimationLocked(long now) { if (!isAnimating()) { if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running"); mFinishAnimReady = false; @@ -763,8 +762,8 @@ class ScreenRotationAnimation implements WindowManagerService.StepAnimator { } mAnimRunning = true; } - - return true; + + return stepAnimation(now); } public Transformation getEnterTransformation() { diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 22949f397462..4f5521787cbc 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -461,6 +461,7 @@ public class WindowManagerService extends IWindowManager.Stub = new ArrayList<IRotationWatcher>(); int mDeferredRotationPauseCount; + int mPendingLayoutChanges = 0; boolean mLayoutNeeded = true; boolean mTraversalScheduled = false; boolean mDisplayFrozen = false; @@ -617,18 +618,6 @@ public class WindowManagerService extends IWindowManager.Stub final AnimationRunnable mAnimationRunnable = new AnimationRunnable(); boolean mAnimationScheduled; - interface StepAnimator { - /** - * Continue the stepping of an ongoing animation. When the animation completes this method - * must disable the animation on the StepAnimator. - * @param currentTime Animation time in milliseconds. Use SystemClock.uptimeMillis(). - * @return True if the animation is still going on, false if the animation has completed - * and stepAnimation has cleared the animation locally. - */ - boolean stepAnimation(long currentTime); - } - final ArrayList<StepAnimator> mStepAnimators = new ArrayList<StepAnimator>(); - final class DragInputEventReceiver extends InputEventReceiver { public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); @@ -2995,15 +2984,27 @@ public class WindowManagerService extends IWindowManager.Stub } void applyEnterAnimationLocked(WindowState win) { - int transit = WindowManagerPolicy.TRANSIT_SHOW; + final int transit; if (win.mEnterAnimationPending) { win.mEnterAnimationPending = false; transit = WindowManagerPolicy.TRANSIT_ENTER; + } else { + transit = WindowManagerPolicy.TRANSIT_SHOW; } applyAnimationLocked(win, transit, true); } + /** + * Choose the correct animation and set it to the passed WindowState. + * @param win The window to add the animation to. + * @param transit If WindowManagerPolicy.TRANSIT_PREVIEW_DONE and the app window has been drawn + * then the animation will be app_starting_exit. Any other value loads the animation from + * the switch statement below. + * @param isEntrance The animation type the last time this was called. Used to keep from + * loading the same animation twice. + * @return true if an animation has been loaded. + */ boolean applyAnimationLocked(WindowState win, int transit, boolean isEntrance) { if (win.mLocalAnimating && win.mAnimationIsEntrance == isEntrance) { @@ -7643,20 +7644,6 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Run through each of the animating objects saved in mStepAnimators. - */ - private void stepAnimations() { - final long currentTime = SystemClock.uptimeMillis(); - for (final StepAnimator stepAnimator : mStepAnimators) { - final boolean more = stepAnimator.stepAnimation(currentTime); - if (DEBUG_ANIM) { - Slog.v(TAG, "stepAnimations: " + currentTime + ": Stepped " + stepAnimator - + (more ? " more" : " done")); - } - } - } - - /** * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. * Update animations of all applications, including those associated with exiting/removed apps. * @@ -7670,16 +7657,14 @@ public class WindowManagerService extends IWindowManager.Stub final int NAT = mAppTokens.size(); for (i=0; i<NAT; i++) { final AppWindowToken appToken = mAppTokens.get(i); - if (appToken.startAndFinishAnimationLocked(currentTime, innerDw, innerDh)) { - mStepAnimators.add(appToken); + if (appToken.stepAnimationLocked(currentTime, innerDw, innerDh)) { mInnerFields.mAnimating = true; } } final int NEAT = mExitingAppTokens.size(); for (i=0; i<NEAT; i++) { final AppWindowToken appToken = mExitingAppTokens.get(i); - if (appToken.startAndFinishAnimationLocked(currentTime, innerDw, innerDh)) { - mStepAnimators.add(appToken); + if (appToken.stepAnimationLocked(currentTime, innerDw, innerDh)) { mInnerFields.mAnimating = true; } } @@ -7687,10 +7672,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mScreenRotationAnimation != null) { if (mScreenRotationAnimation.isAnimating() || mScreenRotationAnimation.mFinishAnimReady) { - if (mScreenRotationAnimation.startAndFinishAnimationLocked(currentTime)) { + if (mScreenRotationAnimation.stepAnimationLocked(currentTime)) { mInnerFields.mUpdateRotation = false; mInnerFields.mAnimating = true; - mStepAnimators.add(mScreenRotationAnimation); } else { mInnerFields.mUpdateRotation = true; mScreenRotationAnimation.kill(); @@ -7711,9 +7695,7 @@ public class WindowManagerService extends IWindowManager.Stub */ private int updateWindowsAndWallpaperLocked(final long currentTime, final int dw, final int dh, final int innerDw, final int innerDh) { - - mPolicy.beginAnimationLw(dw, dh); - + int changes = 0; for (int i = mWindows.size() - 1; i >= 0; i--) { WindowState w = mWindows.get(i); @@ -7727,6 +7709,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WALLPAPER) Slog.v(TAG, "First draw done in potential wallpaper target " + w); mInnerFields.mWallpaperMayChange = true; + changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } } @@ -7749,13 +7732,7 @@ public class WindowManagerService extends IWindowManager.Stub } final boolean wasAnimating = w.mWasAnimating; - - - final boolean nowAnimating = w.startAndFinishAnimationLocked(currentTime); - if (nowAnimating) { - mStepAnimators.add(w); - mInnerFields.mAnimating = true; - } + final boolean nowAnimating = w.stepAnimationLocked(currentTime); if (DEBUG_WALLPAPER) { Slog.v(TAG, w + ": wasAnimating=" + wasAnimating + @@ -7806,6 +7783,7 @@ public class WindowManagerService extends IWindowManager.Stub if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) { mInnerFields.mWallpaperMayChange = true; + changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } if (mPolicy.doesForceHide(w, attrs)) { @@ -7814,6 +7792,7 @@ public class WindowManagerService extends IWindowManager.Stub "Animation started that could impact force hide: " + w); mInnerFields.mWallpaperForceHidingChanged = true; + changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; mFocusMayChange = true; } else if (w.isReadyForDisplay() && w.mAnimation == null) { mInnerFields.mForceHiding = true; @@ -7852,10 +7831,9 @@ public class WindowManagerService extends IWindowManager.Stub if (changed && (attrs.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { mInnerFields.mWallpaperMayChange = true; + changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; } } - - mPolicy.animatingWindowLw(w, attrs); } final AppWindowToken atoken = w.mAppToken; @@ -7900,10 +7878,11 @@ public class WindowManagerService extends IWindowManager.Stub } } else if (w.mReadyToShow) { w.performShowLocked(); + changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; } } // end forall windows - return mPolicy.finishAnimationLw(); + return changes; } /** @@ -8322,6 +8301,72 @@ public class WindowManagerService extends IWindowManager.Stub return changes; } + private void updateResizingWindows(final WindowState w) { + if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { + w.mContentInsetsChanged |= + !w.mLastContentInsets.equals(w.mContentInsets); + w.mVisibleInsetsChanged |= + !w.mLastVisibleInsets.equals(w.mVisibleInsets); + boolean configChanged = + w.mConfiguration != mCurConfiguration + && (w.mConfiguration == null + || mCurConfiguration.diff(w.mConfiguration) != 0); + if (DEBUG_CONFIGURATION && configChanged) { + Slog.v(TAG, "Win " + w + " config changed: " + + mCurConfiguration); + } + if (localLOGV) Slog.v(TAG, "Resizing " + w + + ": configChanged=" + configChanged + + " last=" + w.mLastFrame + " frame=" + w.mFrame); + w.mLastFrame.set(w.mFrame); + if (w.mContentInsetsChanged + || w.mVisibleInsetsChanged + || w.mSurfaceResized + || configChanged) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) { + Slog.v(TAG, "Resize reasons: " + + " contentInsetsChanged=" + w.mContentInsetsChanged + + " visibleInsetsChanged=" + w.mVisibleInsetsChanged + + " surfaceResized=" + w.mSurfaceResized + + " configChanged=" + configChanged); + } + + w.mLastContentInsets.set(w.mContentInsets); + w.mLastVisibleInsets.set(w.mVisibleInsets); + makeWindowFreezingScreenIfNeededLocked(w); + // If the orientation is changing, then we need to + // hold off on unfreezing the display until this + // window has been redrawn; to do that, we need + // to go through the process of getting informed + // by the application when it has finished drawing. + if (w.mOrientationChanging) { + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation start waiting for draw in " + + w + ", surface " + w.mSurface); + w.mDrawPending = true; + w.mCommitDrawPending = false; + w.mReadyToShow = false; + if (w.mAppToken != null) { + w.mAppToken.allDrawn = false; + } + } + if (!mResizingWindows.contains(w)) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, + "Resizing window " + w + " to " + w.mSurfaceW + + "x" + w.mSurfaceH); + mResizingWindows.add(w); + } + } else if (w.mOrientationChanging) { + if (!w.mDrawPending && !w.mCommitDrawPending) { + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation not waiting for draw in " + + w + ", surface " + w.mSurface); + w.mOrientationChanging = false; + } + } + } + } + /** * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. * @@ -8407,69 +8452,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { - w.mContentInsetsChanged |= - !w.mLastContentInsets.equals(w.mContentInsets); - w.mVisibleInsetsChanged |= - !w.mLastVisibleInsets.equals(w.mVisibleInsets); - boolean configChanged = - w.mConfiguration != mCurConfiguration - && (w.mConfiguration == null - || mCurConfiguration.diff(w.mConfiguration) != 0); - if (DEBUG_CONFIGURATION && configChanged) { - Slog.v(TAG, "Win " + w + " config changed: " - + mCurConfiguration); - } - if (localLOGV) Slog.v(TAG, "Resizing " + w - + ": configChanged=" + configChanged - + " last=" + w.mLastFrame + " frame=" + w.mFrame); - w.mLastFrame.set(w.mFrame); - if (w.mContentInsetsChanged - || w.mVisibleInsetsChanged - || w.mSurfaceResized - || configChanged) { - if (DEBUG_RESIZE || DEBUG_ORIENTATION) { - Slog.v(TAG, "Resize reasons: " - + " contentInsetsChanged=" + w.mContentInsetsChanged - + " visibleInsetsChanged=" + w.mVisibleInsetsChanged - + " surfaceResized=" + w.mSurfaceResized - + " configChanged=" + configChanged); - } - - w.mLastContentInsets.set(w.mContentInsets); - w.mLastVisibleInsets.set(w.mVisibleInsets); - makeWindowFreezingScreenIfNeededLocked(w); - // If the orientation is changing, then we need to - // hold off on unfreezing the display until this - // window has been redrawn; to do that, we need - // to go through the process of getting informed - // by the application when it has finished drawing. - if (w.mOrientationChanging) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation start waiting for draw in " - + w + ", surface " + w.mSurface); - w.mDrawPending = true; - w.mCommitDrawPending = false; - w.mReadyToShow = false; - if (w.mAppToken != null) { - w.mAppToken.allDrawn = false; - } - } - if (!mResizingWindows.contains(w)) { - if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, - "Resizing window " + w + " to " + w.mSurfaceW - + "x" + w.mSurfaceH); - mResizingWindows.add(w); - } - } else if (w.mOrientationChanging) { - if (!w.mDrawPending && !w.mCommitDrawPending) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation not waiting for draw in " - + w + ", surface " + w.mSurface); - w.mOrientationChanging = false; - } - } - } + updateResizingWindows(w); if (w.mAttachedHidden || !w.isReadyForDisplay()) { if (!w.mLastHidden) { @@ -8678,6 +8661,67 @@ public class WindowManagerService extends IWindowManager.Stub } } + private final int performAnimationsLocked(long currentTime, int dw, int dh, + int innerDw, int innerDh) { + ++mTransactionSequence; + + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq=" + + mTransactionSequence + " mAnimating=" + + mInnerFields.mAnimating); + + mInnerFields.mTokenMayBeDrawn = false; + mInnerFields.mWallpaperMayChange = false; + mInnerFields.mForceHiding = false; + mInnerFields.mDetachedWallpaper = null; + mInnerFields.mWindowAnimationBackground = null; + mInnerFields.mWindowAnimationBackgroundColor = 0; + + int changes = updateWindowsAndWallpaperLocked(currentTime, dw, dh, innerDw, innerDh); + + if (mInnerFields.mTokenMayBeDrawn) { + changes |= testTokenMayBeDrawnLocked(); + } + + // If we are ready to perform an app transition, check through + // all of the app tokens to be shown and see if they are ready + // to go. + if (mAppTransitionReady) { + changes |= handleAppTransitionReadyLocked(); + } + + mInnerFields.mAdjResult = 0; + + if (!mInnerFields.mAnimating && mAppTransitionRunning) { + // We have finished the animation of an app transition. To do + // this, we have delayed a lot of operations like showing and + // hiding apps, moving apps in Z-order, etc. The app token list + // reflects the correct Z-order, but the window list may now + // be out of sync with it. So here we will just rebuild the + // entire app window list. Fun! + changes |= handleAnimatingStoppedAndTransitionLocked(); + } + + if (mInnerFields.mWallpaperForceHidingChanged && changes == 0 && !mAppTransitionReady) { + // At this point, there was a window with a wallpaper that + // was force hiding other windows behind it, but now it + // is going away. This may be simple -- just animate + // away the wallpaper and its window -- or it may be + // hard -- the wallpaper now needs to be shown behind + // something that was hidden. + changes |= animateAwayWallpaperLocked(); + } + + changes |= testWallpaperAndBackgroundLocked(); + + if (mLayoutNeeded) { + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + } + + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x" + + Integer.toHexString(changes)); + return changes; + } + // "Something has changed! Let's make it correct now." private final void performLayoutAndPlaceSurfacesLockedInner( boolean recoveringMemory) { @@ -8741,7 +8785,6 @@ public class WindowManagerService extends IWindowManager.Stub try { mInnerFields.mWallpaperForceHidingChanged = false; int repeats = 0; - int changes = 0; do { repeats++; @@ -8751,20 +8794,20 @@ public class WindowManagerService extends IWindowManager.Stub break; } - if ((changes & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { + if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { assignLayersLocked(); mLayoutNeeded = true; } } - if ((changes & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { + if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); if (updateOrientationFromAppTokensLocked(true)) { mLayoutNeeded = true; mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } } - if ((changes & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { + if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { mLayoutNeeded = true; } @@ -8775,71 +8818,26 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Layout repeat skipped after too many iterations"); } - ++mTransactionSequence; - - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq=" - + mTransactionSequence + " mAnimating=" - + mInnerFields.mAnimating); - - mInnerFields.mTokenMayBeDrawn = false; - mInnerFields.mWallpaperMayChange = false; - mInnerFields.mForceHiding = false; - mInnerFields.mDetachedWallpaper = null; - mInnerFields.mWindowAnimationBackground = null; - mInnerFields.mWindowAnimationBackgroundColor = 0; - - mStepAnimators.clear(); - changes = updateWindowsAndWallpaperLocked(currentTime, dw, dh, innerDw, innerDh); - - if (mInnerFields.mTokenMayBeDrawn) { - changes |= testTokenMayBeDrawnLocked(); - } - - // If we are ready to perform an app transition, check through - // all of the app tokens to be shown and see if they are ready - // to go. - if (mAppTransitionReady) { - changes |= handleAppTransitionReadyLocked(); - } - - mInnerFields.mAdjResult = 0; - - if (!mInnerFields.mAnimating && mAppTransitionRunning) { - // We have finished the animation of an app transition. To do - // this, we have delayed a lot of operations like showing and - // hiding apps, moving apps in Z-order, etc. The app token list - // reflects the correct Z-order, but the window list may now - // be out of sync with it. So here we will just rebuild the - // entire app window list. Fun! - changes |= handleAnimatingStoppedAndTransitionLocked(); - } - - if (mInnerFields.mWallpaperForceHidingChanged && changes == 0 && !mAppTransitionReady) { - // At this point, there was a window with a wallpaper that - // was force hiding other windows behind it, but now it - // is going away. This may be simple -- just animate - // away the wallpaper and its window -- or it may be - // hard -- the wallpaper now needs to be shown behind - // something that was hidden. - changes |= animateAwayWallpaperLocked(); - } - - changes |= testWallpaperAndBackgroundLocked(); - - if (mLayoutNeeded) { - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think + // it is animating. + mPendingLayoutChanges = 0; + mPolicy.beginAnimationLw(dw, dh); + for (i = mWindows.size() - 1; i >= 0; i--) { + WindowState w = mWindows.get(i); + if (w.mSurface != null) { + mPolicy.animatingWindowLw(w, w.mAttrs); + } } - - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x" - + Integer.toHexString(changes)); - } while (changes != 0); + mPendingLayoutChanges |= mPolicy.finishAnimationLw(); + + } while (mPendingLayoutChanges != 0); // Update animations of all applications, including those // associated with exiting/removed apps + mPendingLayoutChanges = performAnimationsLocked(currentTime, dw, dh, + innerDw, innerDh); updateWindowsAppsAndRotationAnimationsLocked(currentTime, innerDw, innerDh); - - stepAnimations(); // THIRD LOOP: Update the surfaces of all windows. @@ -9049,6 +9047,13 @@ public class WindowManagerService extends IWindowManager.Stub if (wallpaperDestroyed) { needRelayout = adjustWallpaperWindowsLocked() != 0; } + if ((mPendingLayoutChanges & ( + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER | + ADJUST_WALLPAPER_LAYERS_CHANGED | + WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG | + WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT)) != 0) { + needRelayout = true; + } if (needRelayout) { requestTraversalLocked(); } else if (mInnerFields.mAnimating) { diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index e11c87a7b90f..48788e7fc6f8 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -54,8 +54,7 @@ import java.util.ArrayList; /** * A window in the window manager. */ -final class WindowState implements WindowManagerPolicy.WindowState, - WindowManagerService.StepAnimator { +final class WindowState implements WindowManagerPolicy.WindowState { static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY; static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS; static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; @@ -995,8 +994,7 @@ final class WindowState implements WindowManagerPolicy.WindowState, return true; } - @Override - public boolean stepAnimation(long currentTime) { + private boolean stepAnimation(long currentTime) { if ((mAnimation == null) || !mLocalAnimating || (mAnimState != ANIM_STATE_RUNNING)) { return false; } @@ -1013,7 +1011,7 @@ final class WindowState implements WindowManagerPolicy.WindowState, // This must be called while inside a transaction. Returns true if // there is more animation to run. - boolean startAndFinishAnimationLocked(long currentTime) { + boolean stepAnimationLocked(long currentTime) { // Save the animation state as it was before this step so WindowManagerService can tell if // we just started or just stopped animating by comparing mWasAnimating with isAnimating(). mWasAnimating = mAnimating; @@ -1038,7 +1036,7 @@ final class WindowState implements WindowManagerPolicy.WindowState, } if ((mAnimation != null) && mLocalAnimating && (mAnimState != ANIM_STATE_STOPPING)) { - return true; + return stepAnimation(currentTime); } if (WindowManagerService.DEBUG_ANIM) Slog.v( WindowManagerService.TAG, "Finished animation in " + this + @@ -1133,6 +1131,7 @@ final class WindowState implements WindowManagerPolicy.WindowState, } finishExit(); + mService.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; if (mAppToken != null) { mAppToken.updateReportedVisibilityLocked(); @@ -1608,6 +1607,7 @@ final class WindowState implements WindowManagerPolicy.WindowState, boolean showLw(boolean doAnimation, boolean requestAnim) { if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { + // Already showing. return false; } if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility true: " + this); @@ -1647,6 +1647,7 @@ final class WindowState implements WindowManagerPolicy.WindowState, boolean current = doAnimation ? mPolicyVisibilityAfterAnim : mPolicyVisibility; if (!current) { + // Already hiding. return false; } if (doAnimation) { |