Merge "Add the full suite of RpcData types." into ics-aah
diff --git a/Android.mk b/Android.mk
index a8c4612..4f62d78 100644
--- a/Android.mk
+++ b/Android.mk
@@ -220,7 +220,11 @@
LOCAL_NO_EMMA_COMPILE := true
# List of classes and interfaces which should be loaded by the Zygote.
+ifneq ($(TARGET_PRELOADED_CLASSES),)
+LOCAL_JAVA_RESOURCE_FILES += $(TARGET_PRELOADED_CLASSES)
+else
LOCAL_JAVA_RESOURCE_FILES += $(LOCAL_PATH)/preloaded-classes
+endif
#LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
diff --git a/api/current.txt b/api/current.txt
index ebfec94..95fa139 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -710,6 +710,7 @@
field public static final int overScrollFooter = 16843459; // 0x10102c3
field public static final int overScrollHeader = 16843458; // 0x10102c2
field public static final int overScrollMode = 16843457; // 0x10102c1
+ field public static final int overridesImplicitlyEnabledSubtype = 16843696; // 0x10103b0
field public static final int packageNames = 16843649; // 0x1010381
field public static final int padding = 16842965; // 0x10100d5
field public static final int paddingBottom = 16842969; // 0x10100d9
@@ -8650,6 +8651,7 @@
method public void setEmpty();
method public boolean setIntersect(android.graphics.RectF, android.graphics.RectF);
method public void sort();
+ method public java.lang.String toShortString();
method public void union(float, float, float, float);
method public void union(android.graphics.RectF);
method public void union(float, float);
@@ -10681,7 +10683,7 @@
method public void setAuxEffectSendLevel(float);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
- method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
+ method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDisplay(android.view.SurfaceHolder);
@@ -10694,8 +10696,8 @@
method public void setOnSeekCompleteListener(android.media.MediaPlayer.OnSeekCompleteListener);
method public void setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener);
method public void setScreenOnWhilePlaying(boolean);
- method public void setTexture(android.graphics.SurfaceTexture);
method public void setSurface(android.view.Surface);
+ method public void setTexture(android.graphics.SurfaceTexture);
method public void setVolume(float, float);
method public void setWakeMode(android.content.Context, int);
method public void start() throws java.lang.IllegalStateException;
@@ -12411,6 +12413,7 @@
public final class NdefRecord implements android.os.Parcelable {
ctor public NdefRecord(short, byte[], byte[], byte[]);
ctor public NdefRecord(byte[]) throws android.nfc.FormatException;
+ method public static android.nfc.NdefRecord createApplicationRecord(java.lang.String);
method public static android.nfc.NdefRecord createUri(android.net.Uri);
method public static android.nfc.NdefRecord createUri(java.lang.String);
method public int describeContents();
@@ -12445,9 +12448,9 @@
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method public static deprecated android.nfc.NfcAdapter getDefaultAdapter();
method public boolean isEnabled();
- method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity...);
- method public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity...);
- method public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity...);
+ method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+ method public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+ method public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
field public static final java.lang.String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field public static final java.lang.String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
field public static final java.lang.String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
@@ -12501,6 +12504,7 @@
method public byte[] getHiLayerResponse();
method public byte[] getHistoricalBytes();
method public int getMaxTransceiveLength();
+ method public int getTimeout();
method public void setTimeout(int);
method public byte[] transceive(byte[]) throws java.io.IOException;
}
@@ -12516,11 +12520,13 @@
method public int getMaxTransceiveLength();
method public int getSectorCount();
method public int getSize();
+ method public int getTimeout();
method public int getType();
method public void increment(int, int) throws java.io.IOException;
method public byte[] readBlock(int) throws java.io.IOException;
method public void restore(int) throws java.io.IOException;
method public int sectorToBlock(int);
+ method public void setTimeout(int);
method public byte[] transceive(byte[]) throws java.io.IOException;
method public void transfer(int) throws java.io.IOException;
method public void writeBlock(int, byte[]) throws java.io.IOException;
@@ -12541,8 +12547,10 @@
public final class MifareUltralight extends android.nfc.tech.BasicTagTechnology {
method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
method public int getMaxTransceiveLength();
+ method public int getTimeout();
method public int getType();
method public byte[] readPages(int) throws java.io.IOException;
+ method public void setTimeout(int);
method public byte[] transceive(byte[]) throws java.io.IOException;
method public void writePage(int, byte[]) throws java.io.IOException;
field public static final int PAGE_SIZE = 4; // 0x4
@@ -12579,6 +12587,8 @@
method public byte[] getAtqa();
method public int getMaxTransceiveLength();
method public short getSak();
+ method public int getTimeout();
+ method public void setTimeout(int);
method public byte[] transceive(byte[]) throws java.io.IOException;
}
@@ -12595,6 +12605,8 @@
method public byte[] getManufacturer();
method public int getMaxTransceiveLength();
method public byte[] getSystemCode();
+ method public int getTimeout();
+ method public void setTimeout(int);
method public byte[] transceive(byte[]) throws java.io.IOException;
}
@@ -15613,7 +15625,7 @@
public static final class CalendarContract.Calendars implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.SyncColumns {
field public static final java.lang.String CALENDAR_LOCATION = "calendar_location";
field public static final android.net.Uri CONTENT_URI;
- field public static final java.lang.String DEFAULT_SORT_ORDER = "displayName";
+ field public static final java.lang.String DEFAULT_SORT_ORDER = "calendar_displayName";
field public static final java.lang.String NAME = "name";
}
@@ -16777,7 +16789,7 @@
field public static final android.net.Uri CONTENT_URI;
}
- public final class LiveFolders implements android.provider.BaseColumns {
+ public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";
field public static final int DISPLAY_MODE_GRID = 1; // 0x1
@@ -18466,7 +18478,7 @@
field public static final java.lang.String SERVICE_INTERFACE = "android.service.textservice.SpellCheckerService";
}
- public abstract class SpellCheckerService.Session {
+ public static abstract class SpellCheckerService.Session {
ctor public SpellCheckerService.Session();
method public android.os.Bundle getBundle();
method public java.lang.String getLocale();
@@ -20705,7 +20717,6 @@
public class EasyEditSpan implements android.text.ParcelableSpan {
ctor public EasyEditSpan();
- ctor public EasyEditSpan(android.os.Parcel);
method public int describeContents();
method public int getSpanTypeId();
method public void writeToParcel(android.os.Parcel, int);
@@ -23049,7 +23060,6 @@
method public boolean willNotDraw();
field public static android.util.Property ALPHA;
field protected static int DEFAULT_TEXT_DIRECTION;
- field protected static float DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD;
field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -24491,7 +24501,7 @@
method public boolean isWatchingCursor(android.view.View);
method public void restartInput(android.view.View);
method public void sendAppPrivateCommand(android.view.View, java.lang.String, android.os.Bundle);
- method public boolean setAdditionalInputMethodSubtypes(java.lang.String, android.view.inputmethod.InputMethodSubtype[]);
+ method public void setAdditionalInputMethodSubtypes(java.lang.String, android.view.inputmethod.InputMethodSubtype[]);
method public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
method public void setInputMethod(android.os.IBinder, java.lang.String);
method public void setInputMethodAndSubtype(android.os.IBinder, java.lang.String, android.view.inputmethod.InputMethodSubtype);
@@ -24536,8 +24546,7 @@
}
public final class InputMethodSubtype implements android.os.Parcelable {
- ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String);
- ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean);
+ ctor public InputMethodSubtype(int, int, java.lang.String, java.lang.String, java.lang.String, boolean, boolean);
method public boolean containsExtraValueKey(java.lang.String);
method public int describeContents();
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
@@ -24548,6 +24557,7 @@
method public java.lang.String getMode();
method public int getNameResId();
method public boolean isAuxiliary();
+ method public boolean overridesImplicitlyEnabledSubtype();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
@@ -25790,6 +25800,17 @@
ctor public DigitalClock(android.content.Context, android.util.AttributeSet);
}
+ public class EdgeEffect {
+ ctor public EdgeEffect(android.content.Context);
+ method public boolean draw(android.graphics.Canvas);
+ method public void finish();
+ method public boolean isFinished();
+ method public void onAbsorb(int);
+ method public void onPull(float);
+ method public void onRelease();
+ method public void setSize(int, int);
+ }
+
public class EditText extends android.widget.TextView {
ctor public EditText(android.content.Context);
ctor public EditText(android.content.Context, android.util.AttributeSet);
@@ -26375,6 +26396,7 @@
method public void fling(int, int, int, int, int, int, int, int);
method public void fling(int, int, int, int, int, int, int, int, int, int);
method public final void forceFinished(boolean);
+ method public float getCurrVelocity();
method public final int getCurrX();
method public final int getCurrY();
method public final int getFinalX();
@@ -26717,6 +26739,7 @@
method public void extendDuration(int);
method public void fling(int, int, int, int, int, int, int, int);
method public final void forceFinished(boolean);
+ method public float getCurrVelocity();
method public final int getCurrX();
method public final int getCurrY();
method public final int getDuration();
diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp
index aa3bc03..b13236a 100644
--- a/cmds/stagefright/stream.cpp
+++ b/cmds/stagefright/stream.cpp
@@ -357,9 +357,9 @@
}
sp<IMediaPlayer> player =
- service->create(getpid(), client, source, 0);
+ service->create(getpid(), client, 0);
- if (player != NULL) {
+ if (player != NULL && player->setDataSource(source) == NO_ERROR) {
player->setVideoSurface(surface);
player->start();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0e3eaaa..41e3fdf 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4471,9 +4471,12 @@
ManagedCursor mc = mManagedCursors.get(i);
if (mc.mReleased || mc.mUpdated) {
if (!mc.mCursor.requery()) {
- throw new IllegalStateException(
- "trying to requery an already closed cursor "
- + mc.mCursor);
+ if (getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ throw new IllegalStateException(
+ "trying to requery an already closed cursor "
+ + mc.mCursor);
+ }
}
mc.mReleased = false;
mc.mUpdated = false;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e3075d7..8275cbd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1291,7 +1291,8 @@
public final boolean queueIdle() {
ActivityClientRecord a = mNewActivities;
boolean stopProfiling = false;
- if (mBoundApplication.profileFd != null && mBoundApplication.autoStopProfiler) {
+ if (mBoundApplication != null && mBoundApplication.profileFd != null
+ && mBoundApplication.autoStopProfiler) {
stopProfiling = true;
}
if (a != null) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8d6cee1..c7698bf 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5627,6 +5627,10 @@
if (scheme != null) {
if (scheme.equalsIgnoreCase("tel")) {
b.append("tel:xxx-xxx-xxxx");
+ } else if (scheme.equalsIgnoreCase("sip")) {
+ b.append("sip:xxxxxxxxxx");
+ } else if (scheme.equalsIgnoreCase("sms")) {
+ b.append("sms:xxx-xxx-xxxx");
} else if (scheme.equalsIgnoreCase("smsto")) {
b.append("smsto:xxx-xxx-xxxx");
} else {
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 496da41..ef6e131 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -191,6 +191,12 @@
private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
+ /**
+ * The amount of time to wait after attempting a bind before canceling a sync and disabling
+ * the sync adapter
+ */
+ public static final long BIND_TIMEOUT_MS = 30 * 1000;
+
public void onAccountsUpdated(Account[] accounts) {
// remember if this was the first time this was called after an update
final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
@@ -1068,6 +1074,9 @@
pw.print(" - ");
pw.print(activeSyncContext.mSyncOperation.dump(false));
pw.println();
+ if (activeSyncContext.mSyncAdapter == null) {
+ pw.println(" **** Waiting for onServiceConnected ****");
+ }
}
synchronized (mSyncQueue) {
@@ -1424,6 +1433,7 @@
public void handleMessage(Message msg) {
long earliestFuturePollTime = Long.MAX_VALUE;
long nextPendingSyncTime = Long.MAX_VALUE;
+ long nextBindTimeoutTime = Long.MAX_VALUE;
// Setting the value here instead of a method because we want the dumpsys logs
// to have the most recent value used.
@@ -1431,6 +1441,7 @@
waitUntilReadyToRun();
mDataConnectionIsConnected = readDataConnectionState();
mSyncManagerWakeLock.acquire();
+ nextBindTimeoutTime = auditRunningSyncsForStuckBindsLocked();
// Always do this first so that we be sure that any periodic syncs that
// are ready to run have been converted into pending syncs. This allows the
// logic that considers the next steps to take based on the set of pending syncs
@@ -1532,6 +1543,7 @@
break;
}
} finally {
+ nextPendingSyncTime = Math.min(nextBindTimeoutTime, nextPendingSyncTime);
manageSyncNotificationLocked();
manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
mSyncTimeTracker.update();
@@ -1540,6 +1552,36 @@
}
/**
+ * Looks to see if any of the active syncs have been waiting for a bind for too long,
+ * and if so the sync is canceled and the sync adapter is disabled for that account.
+ * @return the earliest time that an active sync can have waited too long to bind,
+ * relative to {@link android.os.SystemClock#elapsedRealtime()}.
+ */
+ private long auditRunningSyncsForStuckBindsLocked() {
+ final long now = SystemClock.elapsedRealtime();
+ long oldest = Long.MAX_VALUE;
+ for (ActiveSyncContext active : mActiveSyncContexts) {
+ if (active.mSyncAdapter == null) {
+ final long timeoutTime = active.mStartTime + BIND_TIMEOUT_MS;
+ if (timeoutTime < now) {
+ Log.w(TAG, "canceling long-running bind and disabling sync for "
+ + active.mSyncOperation.account + ", authority "
+ + active.mSyncOperation.authority);
+ runSyncFinishedOrCanceledLocked(null, active);
+ ContentResolver.setIsSyncable(active.mSyncOperation.account,
+ active.mSyncOperation.authority, 0);
+ } else {
+ if (oldest > timeoutTime) {
+ oldest = timeoutTime;
+ }
+ }
+ }
+ }
+
+ return oldest;
+ }
+
+ /**
* Turn any periodic sync operations that are ready to run into pending sync operations.
* @return the desired start time of the earliest future periodic sync operation,
* in milliseconds since boot
@@ -1819,13 +1861,17 @@
synchronized (mSyncQueue){
mSyncQueue.remove(candidate);
}
- dispatchSyncOperation(candidate);
+ ActiveSyncContext newSyncContext = dispatchSyncOperation(candidate);
+ if (newSyncContext != null) {
+ nextReadyToRunTime = Math.min(nextReadyToRunTime,
+ newSyncContext.mStartTime + BIND_TIMEOUT_MS);
+ }
}
return nextReadyToRunTime;
}
- private boolean dispatchSyncOperation(SyncOperation op) {
+ private ActiveSyncContext dispatchSyncOperation(SyncOperation op) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
@@ -1842,7 +1888,7 @@
Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+ ", removing settings for it");
mSyncStorageEngine.removeAuthority(op.account, op.authority);
- return false;
+ return null;
}
ActiveSyncContext activeSyncContext =
@@ -1855,10 +1901,10 @@
if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
closeActiveSyncContext(activeSyncContext);
- return false;
+ return null;
}
- return true;
+ return activeSyncContext;
}
private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index b6487bd..5fe42db 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -36,6 +36,8 @@
DataSetObservable mDataSetObservable = new DataSetObservable();
ContentObservable mContentObservable = new ContentObservable();
+ Bundle mExtras = Bundle.EMPTY;
+
/* -------------------------------------------------------- */
/* These need to be implemented by subclasses */
abstract public int getCount();
@@ -71,11 +73,11 @@
public int getColumnCount() {
return getColumnNames().length;
}
-
+
public void deactivate() {
deactivateInternal();
}
-
+
/**
* @hide
*/
@@ -99,7 +101,7 @@
public boolean isClosed() {
return mClosed;
}
-
+
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
@@ -120,7 +122,7 @@
return true;
}
-
+
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
// Default implementation, uses getString
String result = getString(columnIndex);
@@ -136,7 +138,7 @@
buffer.sizeCopied = 0;
}
}
-
+
/* -------------------------------------------------------- */
/* Implementation */
public AbstractCursor() {
@@ -181,7 +183,7 @@
return result;
}
-
+
/**
* Copy data from cursor to CursorWindow
* @param position start position of data
@@ -199,7 +201,7 @@
window.setStartPosition(position);
int columnNum = getColumnCount();
window.setNumColumns(columnNum);
- while (moveToNext() && window.allocRow()) {
+ while (moveToNext() && window.allocRow()) {
for (int i = 0; i < columnNum; i++) {
String field = getString(i);
if (field != null) {
@@ -215,7 +217,7 @@
}
}
}
-
+
mPos = oldpos;
} catch (IllegalStateException e){
// simply ignore it
@@ -314,7 +316,7 @@
mContentObservable.unregisterObserver(observer);
}
}
-
+
/**
* This is hidden until the data set change model has been re-evaluated.
* @hide
@@ -322,14 +324,14 @@
protected void notifyDataSetChange() {
mDataSetObservable.notifyChanged();
}
-
+
/**
* This is hidden until the data set change model has been re-evaluated.
* @hide
*/
protected DataSetObservable getDataSetObservable() {
return mDataSetObservable;
-
+
}
public void registerDataSetObserver(DataSetObserver observer) {
@@ -383,8 +385,19 @@
return false;
}
+ /**
+ * Sets a {@link Bundle} that will be returned by {@link #getExtras()}. <code>null</code> will
+ * be converted into {@link Bundle#EMPTY}.
+ *
+ * @param extras {@link Bundle} to set.
+ * @hide
+ */
+ public void setExtras(Bundle extras) {
+ mExtras = (extras == null) ? Bundle.EMPTY : extras;
+ }
+
public Bundle getExtras() {
- return Bundle.EMPTY;
+ return mExtras;
}
public Bundle respond(Bundle extras) {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index bc45945..63f2244 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1468,6 +1468,7 @@
private static final String KEY_MAX_NUM_DETECTED_FACES_HW = "max-num-detected-faces-hw";
private static final String KEY_MAX_NUM_DETECTED_FACES_SW = "max-num-detected-faces-sw";
private static final String KEY_RECORDING_HINT = "recording-hint";
+ private static final String KEY_VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
// Parameter key suffix for supported values.
private static final String SUPPORTED_VALUES_SUFFIX = "-values";
@@ -3210,6 +3211,35 @@
set(KEY_RECORDING_HINT, hint ? TRUE : FALSE);
}
+ /**
+ * Returns true if video snapshot is supported. That is, applications
+ * can call {@link #takePicture(Camera.ShutterCallback,
+ * Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)}
+ * during recording. Applications do not need to call {@link
+ * #startPreview()} after taking a picture. The preview will be still
+ * active. Other than that, taking a picture during recording is
+ * identical to taking a picture normally. All settings and methods
+ * related to takePicture work identically. Ex: {@link
+ * #getPictureSize()}, {@link #getSupportedPictureSizes()}, {@link
+ * #setJpegQuality(int)}, {@link #setRotation(int)}, and etc. The
+ * picture will have an EXIF header. {@link #FLASH_MODE_AUTO} and {@link
+ * #FLASH_MODE_ON} also still work, but the video will record the flash.
+ *
+ * Applications can set shutter callback as null to avoid the shutter
+ * sound. It is also recommended to set raw picture and post view
+ * callbacks to null to avoid the interrupt of preview display.
+ *
+ * Field-of-view of the recorded video may be different from that of the
+ * captured pictures.
+ *
+ * @return true if video snapshot is supported.
+ * @hide
+ */
+ public boolean isVideoSnapshotSupported() {
+ String str = get(KEY_VIDEO_SNAPSHOT_SUPPORTED);
+ return TRUE.equals(str);
+ }
+
// Splits a comma delimited string to an ArrayList of String.
// Return null if the passing string is null or the size is 0.
private ArrayList<String> split(String str) {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 3441217..530122c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -814,4 +814,22 @@
} catch (RemoteException e) {
}
}
+
+ /**
+ * Returns true if the hardware supports the given network type
+ * else it returns false. This doesn't indicate we have coverage
+ * or are authorized onto a network, just whether or not the
+ * hardware supports it. For example a gsm phone without a sim
+ * should still return true for mobile data, but a wifi only tablet
+ * would return false.
+ * @param networkType The nework type we'd like to check
+ * @return true if supported, else false
+ * @hide
+ */
+ public boolean isNetworkSupported(int networkType) {
+ try {
+ return mService.isNetworkSupported(networkType);
+ } catch (RemoteException e) {}
+ return false;
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index c9553c0..eef658e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -43,6 +43,8 @@
NetworkInfo getNetworkInfo(int networkType);
NetworkInfo[] getAllNetworkInfo();
+ boolean isNetworkSupported(int networkType);
+
LinkProperties getActiveLinkProperties();
LinkProperties getLinkProperties(int networkType);
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e5f3273..3918cfd 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -320,15 +320,36 @@
* checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
*/
public long getTotalBytes() {
- long totalBytes = 0;
+ final Entry entry = getTotal(null);
+ return entry.rxBytes + entry.txBytes;
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object.
+ */
+ public Entry getTotal(Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+
+ entry.iface = IFACE_ALL;
+ entry.uid = UID_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.rxBytes = 0;
+ entry.rxPackets = 0;
+ entry.txBytes = 0;
+ entry.txPackets = 0;
+
for (int i = 0; i < size; i++) {
// skip specific tags, since already counted in TAG_NONE
if (tag[i] != TAG_NONE) continue;
- totalBytes += rxBytes[i];
- totalBytes += txBytes[i];
+ entry.rxBytes += rxBytes[i];
+ entry.rxPackets += rxPackets[i];
+ entry.txBytes += txBytes[i];
+ entry.txPackets += txPackets[i];
+ entry.operations += operations[i];
}
- return totalBytes;
+ return entry;
}
/**
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 6ba3451..26571ff 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -152,8 +152,6 @@
* RTD_ANDROID_APP records.
* @hide
*/
- // TODO unhide for ICS
- // TODO recheck docs
public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
private static final byte FLAG_MB = (byte) 0x80;
@@ -352,21 +350,29 @@
/**
* Creates an Android application NDEF record.
* <p>
+ * This record indicates to other Android devices the package
+ * that should be used to handle the rest of the NDEF message.
+ * You can embed this record anywhere into your NDEF message
+ * to ensure that the intended package receives the message.
+ * <p>
* When an Android device dispatches an {@link NdefMessage}
* containing one or more Android application records,
* the applications contained in those records will be the
* preferred target for the NDEF_DISCOVERED intent, in
* the order in which they appear in the {@link NdefMessage}.
+ * This dispatch behavior was first added to Android in
+ * Ice Cream Sandwich.
* <p>
* If none of the applications are installed on the device,
* a Market link will be opened to the first application.
* <p>
* Note that Android application records do not overrule
- * applications that have called {@link NfcAdapter#enableForegroundDispatch}.
- * @hide
+ * applications that have called
+ * {@link NfcAdapter#enableForegroundDispatch}.
+ *
+ * @param packageName Android package name
+ * @return Android application NDEF record
*/
- // TODO unhide for ICS
- // TODO recheck javadoc - should mention this works from ICS only
public static NdefRecord createApplicationRecord(String packageName) {
return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, new byte[] {},
packageName.getBytes(Charsets.US_ASCII));
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 3cc6820..da878d4 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -20,7 +20,7 @@
import android.os.RemoteException;
import android.util.Log;
-import java.util.HashMap;
+import java.util.WeakHashMap;
/**
* Manages NFC API's that are coupled to the life-cycle of an Activity.
@@ -38,7 +38,7 @@
static final Boolean DBG = false;
final NfcAdapter mAdapter;
- final HashMap<Activity, NfcActivityState> mNfcState; // contents protected by this
+ final WeakHashMap<Activity, NfcActivityState> mNfcState; // contents protected by this
final NfcEvent mDefaultEvent; // can re-use one NfcEvent because it just contains adapter
/**
@@ -60,7 +60,7 @@
public NfcActivityManager(NfcAdapter adapter) {
mAdapter = adapter;
- mNfcState = new HashMap<Activity, NfcActivityState>();
+ mNfcState = new WeakHashMap<Activity, NfcActivityState>();
mDefaultEvent = new NfcEvent(mAdapter);
}
@@ -88,6 +88,13 @@
}
}
+ /**
+ * onDestroy hook from fragment attached to activity
+ */
+ public void onDestroy(Activity activity) {
+ mNfcState.remove(activity);
+ }
+
public synchronized void setNdefPushMessage(Activity activity, NdefMessage message) {
NfcActivityState state = getOrCreateState(activity, message != null);
if (state == null || state.ndefMessage == message) {
@@ -214,4 +221,5 @@
callback.onNdefPushComplete(mDefaultEvent);
}
}
+
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index d58b249..e392bca 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -507,16 +507,24 @@
* <p>Pass a null NDEF message to disable foreground NDEF push in the
* specified activities.
*
+ * <p>One or more activities must be specified.
+ *
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param message NDEF message to push over NFC, or null to disable
- * @param activities one or more {@link Activity} to enable for NDEF push
+ * @param activity an activity to enable for NDEF push (at least one is required)
+ * @param activities zero or more additional activities to enable for NDEF Push
*/
- public void setNdefPushMessage(NdefMessage message, Activity ... activities) {
- if (activities.length == 0) {
- throw new NullPointerException("Must specificy one or more activities");
+ public void setNdefPushMessage(NdefMessage message, Activity activity,
+ Activity ... activities) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
}
+ mNfcActivityManager.setNdefPushMessage(activity, message);
for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
mNfcActivityManager.setNdefPushMessage(a, message);
}
}
@@ -536,17 +544,24 @@
* <p>Pass a null callback to disable the callback in the
* specified activities.
*
+ * <p>One or more activities must be specified.
+ *
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param callback callback, or null to disable
- * @param activities one or more {@link Activity} to enable for NDEF push
+ * @param activity an activity to enable for NDEF push (at least one is required)
+ * @param activities zero or more additional activities to enable for NDEF Push
*/
- public void setNdefPushMessageCallback(CreateNdefMessageCallback callback,
+ public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
Activity ... activities) {
- if (activities.length == 0) {
- throw new NullPointerException("Must specificy one or more activities");
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
}
+ mNfcActivityManager.setNdefPushMessageCallback(activity, callback);
for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
mNfcActivityManager.setNdefPushMessageCallback(a, callback);
}
}
@@ -558,17 +573,24 @@
* can only occur when one of the specified activities is in resumed
* (foreground) state.
*
+ * <p>One or more activities must be specified.
+ *
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param callback callback, or null to disable
- * @param activities one or more {@link Activity} to enable the callback
+ * @param activity an activity to enable the callback (at least one is required)
+ * @param activities zero or more additional activities to enable to callback
*/
public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
- Activity ... activities) {
- if (activities.length == 0) {
- throw new NullPointerException("Must specificy one or more activities");
+ Activity activity, Activity ... activities) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
}
+ mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callback);
for (Activity a : activities) {
+ if (a == null) {
+ throw new NullPointerException("activities cannot contain null");
+ }
mNfcActivityManager.setOnNdefPushCompleteCallback(a, callback);
}
}
diff --git a/core/java/android/nfc/NfcFragment.java b/core/java/android/nfc/NfcFragment.java
index c48859b..17278dc 100644
--- a/core/java/android/nfc/NfcFragment.java
+++ b/core/java/android/nfc/NfcFragment.java
@@ -80,4 +80,14 @@
sNfcActivityManager.onPause(getActivity());
}
}
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (sNfcActivityManager != null) {
+ sNfcActivityManager.onDestroy(getActivity());
+ }
+ }
+
+
}
diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java
index 6054fe8..1859877 100644
--- a/core/java/android/nfc/tech/IsoDep.java
+++ b/core/java/android/nfc/tech/IsoDep.java
@@ -101,14 +101,12 @@
}
/**
- * Gets the currently set timeout of {@link #transceive} in milliseconds.
+ * Get the current timeout for {@link #transceive} in milliseconds.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public int getTimeout() {
try {
return mTag.getTagService().getTimeout(TagTechnology.ISO_DEP);
diff --git a/core/java/android/nfc/tech/MifareClassic.java b/core/java/android/nfc/tech/MifareClassic.java
index ce923ae..9d1e6a1 100644
--- a/core/java/android/nfc/tech/MifareClassic.java
+++ b/core/java/android/nfc/tech/MifareClassic.java
@@ -584,9 +584,11 @@
}
/**
- * Set the timeout of {@link #transceive} in milliseconds.
- * <p>The timeout only applies to MifareUltralight {@link #transceive},
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
* and is reset to a default value when {@link #close} is called.
+ *
* <p>Setting a longer timeout may be useful when performing
* transactions that require a long processing time on the tag
* such as key generation.
@@ -594,9 +596,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public void setTimeout(int timeout) {
try {
int err = mTag.getTagService().setTimeout(TagTechnology.MIFARE_CLASSIC, timeout);
@@ -609,14 +609,12 @@
}
/**
- * Gets the currently set timeout of {@link #transceive} in milliseconds.
+ * Get the current {@link #transceive} timeout in milliseconds.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public int getTimeout() {
try {
return mTag.getTagService().getTimeout(TagTechnology.MIFARE_CLASSIC);
diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/core/java/android/nfc/tech/MifareUltralight.java
index 890b735..dec2c65 100644
--- a/core/java/android/nfc/tech/MifareUltralight.java
+++ b/core/java/android/nfc/tech/MifareUltralight.java
@@ -224,9 +224,11 @@
}
/**
- * Set the timeout of {@link #transceive} in milliseconds.
- * <p>The timeout only applies to MifareUltralight {@link #transceive},
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
* and is reset to a default value when {@link #close} is called.
+ *
* <p>Setting a longer timeout may be useful when performing
* transactions that require a long processing time on the tag
* such as key generation.
@@ -234,9 +236,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public void setTimeout(int timeout) {
try {
int err = mTag.getTagService().setTimeout(
@@ -250,14 +250,12 @@
}
/**
- * Gets the currently set timeout of {@link #transceive} in milliseconds.
+ * Get the current {@link #transceive} timeout in milliseconds.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public int getTimeout() {
try {
return mTag.getTagService().getTimeout(TagTechnology.MIFARE_ULTRALIGHT);
diff --git a/core/java/android/nfc/tech/NfcA.java b/core/java/android/nfc/tech/NfcA.java
index bb8aec9..88730f9 100644
--- a/core/java/android/nfc/tech/NfcA.java
+++ b/core/java/android/nfc/tech/NfcA.java
@@ -129,9 +129,11 @@
}
/**
- * Set the timeout of {@link #transceive} in milliseconds.
- * <p>The timeout only applies to NfcA {@link #transceive}, and is
- * reset to a default value when {@link #close} is called.
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
+ * and is reset to a default value when {@link #close} is called.
+ *
* <p>Setting a longer timeout may be useful when performing
* transactions that require a long processing time on the tag
* such as key generation.
@@ -139,9 +141,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public void setTimeout(int timeout) {
try {
int err = mTag.getTagService().setTimeout(TagTechnology.NFC_A, timeout);
@@ -154,14 +154,12 @@
}
/**
- * Gets the currently set timeout of {@link #transceive} in milliseconds.
+ * Get the current {@link #transceive} timeout in milliseconds.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public int getTimeout() {
try {
return mTag.getTagService().getTimeout(TagTechnology.NFC_A);
diff --git a/core/java/android/nfc/tech/NfcF.java b/core/java/android/nfc/tech/NfcF.java
index 0938fb4..b3e3ab6 100644
--- a/core/java/android/nfc/tech/NfcF.java
+++ b/core/java/android/nfc/tech/NfcF.java
@@ -128,9 +128,11 @@
}
/**
- * Set the timeout of {@link #transceive} in milliseconds.
- * <p>The timeout only applies to NfcF {@link #transceive}, and is
- * reset to a default value when {@link #close} is called.
+ * Set the {@link #transceive} timeout in milliseconds.
+ *
+ * <p>The timeout only applies to {@link #transceive} on this object,
+ * and is reset to a default value when {@link #close} is called.
+ *
* <p>Setting a longer timeout may be useful when performing
* transactions that require a long processing time on the tag
* such as key generation.
@@ -138,9 +140,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public void setTimeout(int timeout) {
try {
int err = mTag.getTagService().setTimeout(TagTechnology.NFC_F, timeout);
@@ -153,14 +153,12 @@
}
/**
- * Gets the currently set timeout of {@link #transceive} in milliseconds.
+ * Get the current {@link #transceive} timeout in milliseconds.
*
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
- * @hide
*/
- // TODO Unhide for ICS
public int getTimeout() {
try {
return mTag.getTagService().getTimeout(TagTechnology.NFC_F);
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 6fe5124..9ba1fdb 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -599,7 +599,7 @@
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = "displayName";
+ public static final String DEFAULT_SORT_ORDER = CALENDAR_DISPLAY_NAME;
/**
* The name of the calendar. Column name.
diff --git a/core/java/android/provider/LiveFolders.java b/core/java/android/provider/LiveFolders.java
index 7856bab..cf8ad46 100644
--- a/core/java/android/provider/LiveFolders.java
+++ b/core/java/android/provider/LiveFolders.java
@@ -162,7 +162,17 @@
* </tr>
* </tbody>
* </table>
+ *
+ * @deprecated Live folders are no longer supported by Android. These have been
+ * replaced by the new
+ * <a href="{@docRoot}guide/topics/appwidgets/index.html#collections">AppWidget Collection</a>
+ * APIs introduced in {@link android.os.Build.VERSION_CODES#HONEYCOMB}. These provide
+ * all of the features of live folders plus many more. The use of live folders is greatly
+ * discouraged because of security issues they introduce -- publishing a live folder requires
+ * making all data show for the live folder available to all applications with no
+ * permissions protecting it.
*/
+@Deprecated
public final class LiveFolders implements BaseColumns {
/**
* <p>Content provider column.</p>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index efdb79e..bc5994e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -42,6 +42,7 @@
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
+import android.view.WindowOrientationListener;
import java.net.URISyntaxException;
import java.util.HashMap;
@@ -59,7 +60,7 @@
* <p>
* Input: Nothing.
* <p>
- * Output: nothing.
+ * Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
@@ -69,7 +70,7 @@
* <p>
* Input: Nothing.
* <p>
- * Output: nothing.
+ * Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS";
@@ -328,6 +329,23 @@
"android.settings.USER_DICTIONARY_SETTINGS";
/**
+ * Activity Action: Adds a word to the user dictionary.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: An extra with key <code>word</code> that contains the word
+ * that should be added to the dictionary.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_USER_DICTIONARY_INSERT =
+ "com.android.settings.USER_DICTIONARY_INSERT";
+
+ /**
* Activity Action: Show settings to allow configuration of application-related settings.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -1462,6 +1480,7 @@
* @hide
* @deprecated
*/
+ @Deprecated
public static final String NOTIFICATIONS_USE_RING_VOLUME =
"notifications_use_ring_volume";
diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java
index 3e2e38e..b96099e 100644
--- a/core/java/android/service/textservice/SpellCheckerService.java
+++ b/core/java/android/service/textservice/SpellCheckerService.java
@@ -66,7 +66,7 @@
/**
* This abstract class should be overridden by a concrete implementation of a spell checker.
*/
- public abstract class Session {
+ public static abstract class Session {
private InternalISpellCheckerSession mInternalSession;
/**
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index f8d0142..75dcd6e 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -415,6 +415,9 @@
}
if (params.mBytesWritten < params.mAudioBufferSize) {
+ if (DBG) Log.d(TAG, "Stopping audio track to flush audio, state was : " +
+ audioTrack.getPlayState());
+ params.mIsShortUtterance = true;
audioTrack.stop();
}
@@ -452,10 +455,40 @@
return;
}
+ if (params.mIsShortUtterance) {
+ // In this case we would have called AudioTrack#stop() to flush
+ // buffers to the mixer. This makes the playback head position
+ // unobservable and notification markers do not work reliably. We
+ // have no option but to wait until we think the track would finish
+ // playing and release it after.
+ //
+ // This isn't as bad as it looks because (a) We won't end up waiting
+ // for much longer than we should because even at 4khz mono, a short
+ // utterance weighs in at about 2 seconds, and (b) such short utterances
+ // are expected to be relatively infrequent and in a stream of utterances
+ // this shows up as a slightly longer pause.
+ blockUntilEstimatedCompletion(params);
+ } else {
+ blockUntilCompletion(params);
+ }
+ }
+
+ private static void blockUntilEstimatedCompletion(SynthesisMessageParams params) {
+ final int lengthInFrames = params.mBytesWritten / params.mBytesPerFrame;
+ final long estimatedTimeMs = (lengthInFrames * 1000 / params.mSampleRateInHz);
+
+ if (DBG) Log.d(TAG, "About to sleep for: " + estimatedTimeMs + "ms for a short utterance");
+
+ try {
+ Thread.sleep(estimatedTimeMs);
+ } catch (InterruptedException ie) {
+ // Do nothing.
+ }
+ }
+
+ private static void blockUntilCompletion(SynthesisMessageParams params) {
final AudioTrack audioTrack = params.mAudioTrack;
- final int bytesPerFrame = params.mBytesPerFrame;
- final int lengthInBytes = params.mBytesWritten;
- final int lengthInFrames = lengthInBytes / bytesPerFrame;
+ final int lengthInFrames = params.mBytesWritten / params.mBytesPerFrame;
int currentPosition = 0;
while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames) {
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
index 3e905d6..779721e 100644
--- a/core/java/android/speech/tts/SynthesisMessageParams.java
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -41,7 +41,13 @@
// Written by the synthesis thread, but read on the audio playback
// thread.
volatile int mBytesWritten;
+ // A "short utterance" is one that uses less bytes than the audio
+ // track buffer size (mAudioBufferSize). In this case, we need to call
+ // AudioTrack#stop() to send pending buffers to the mixer, and slightly
+ // different logic is required to wait for the track to finish.
+ //
// Not volatile, accessed only from the audio playback thread.
+ boolean mIsShortUtterance;
int mAudioBufferSize;
// Always synchronized on "this".
int mUnconsumedBytes;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 421e995..768071f 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1296,7 +1296,10 @@
float h1 = getHorizontal(st, false, line);
float h2 = getHorizontal(en, true, line);
- dest.addRect(h1, top, h2, bottom, Path.Direction.CW);
+ float left = Math.min(h1, h2);
+ float right = Math.max(h1, h2);
+
+ dest.addRect(left, top, right, bottom, Path.Direction.CW);
}
}
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index e914316..894afbd 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -24,8 +24,8 @@
import android.text.style.BackgroundColorSpan;
import android.text.style.BulletSpan;
import android.text.style.CharacterStyle;
-import android.text.style.ForegroundColorSpan;
import android.text.style.EasyEditSpan;
+import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.QuoteSpan;
@@ -748,11 +748,11 @@
break;
case SUGGESTION_RANGE_SPAN:
- readSpan(p, sp, new SuggestionRangeSpan());
+ readSpan(p, sp, new SuggestionRangeSpan(p));
break;
-
+
case EASY_EDIT_SPAN:
- readSpan(p, sp, new EasyEditSpan(p));
+ readSpan(p, sp, new EasyEditSpan());
break;
default:
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index fe96565..b8728ee 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -232,8 +232,7 @@
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (action == MotionEvent.ACTION_DOWN) {
- boolean cap = isSelecting(buffer);
- if (cap) {
+ if (isSelecting(buffer)) {
int offset = widget.getOffsetForPosition(event.getX(), event.getY());
buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
@@ -245,9 +244,7 @@
widget.getParent().requestDisallowInterceptTouchEvent(true);
}
} else if (action == MotionEvent.ACTION_MOVE) {
- boolean cap = isSelecting(buffer);
-
- if (cap && handled) {
+ if (isSelecting(buffer) && handled) {
// Before selecting, make sure we've moved out of the "slop".
// handled will be true, if we're in select mode AND we're
// OUT of the slop
@@ -279,7 +276,7 @@
if (isSelecting(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
- } else {
+ } else if (!widget.shouldIgnoreActionUpEvent()) {
Selection.setSelection(buffer, offset);
}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index a528044..3f9b945 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -147,12 +147,10 @@
int nx = widget.getScrollX() + (int) dx;
int ny = widget.getScrollY() + (int) dy;
- int padding = widget.getTotalPaddingTop() +
- widget.getTotalPaddingBottom();
+ int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
Layout layout = widget.getLayout();
- ny = Math.min(ny, layout.getHeight() - (widget.getHeight() -
- padding));
+ ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding));
ny = Math.max(ny, 0);
int oldX = widget.getScrollX();
@@ -161,8 +159,7 @@
scrollTo(widget, layout, nx, ny);
// If we actually scrolled, then cancel the up action.
- if (oldX != widget.getScrollX()
- || oldY != widget.getScrollY()) {
+ if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
widget.cancelLongPress();
}
diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index e6e4d2c..2feb719 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -33,10 +33,6 @@
// Empty
}
- public EasyEditSpan(Parcel src) {
- this();
- }
-
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java
index fc91697..a637b1c 100644
--- a/core/java/android/text/style/SuggestionRangeSpan.java
+++ b/core/java/android/text/style/SuggestionRangeSpan.java
@@ -16,7 +16,6 @@
package android.text.style;
-import android.graphics.Color;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;
@@ -29,12 +28,20 @@
* @hide
*/
public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan {
+ private final int mBackgroundColor;
+
@Override
public void updateDrawState(TextPaint tp) {
- tp.setColor(Color.GREEN);
+ tp.bgColor = mBackgroundColor;
}
- public SuggestionRangeSpan() { /* Nothing to do*/ }
+ public SuggestionRangeSpan(int color) {
+ mBackgroundColor = color;
+ }
+
+ public SuggestionRangeSpan(Parcel src) {
+ mBackgroundColor = src.readInt();
+ }
@Override
public int describeContents() {
@@ -42,7 +49,9 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) { /* Nothing to do*/ }
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mBackgroundColor);
+ }
@Override
public int getSpanTypeId() {
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 8625257..693a7a9 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -76,8 +76,11 @@
private final String mNotificationTargetClassName;
private final int mHashCode;
- private float mUnderlineThickness;
- private int mUnderlineColor;
+ private float mEasyCorrectUnderlineThickness;
+ private int mEasyCorrectUnderlineColor;
+
+ private float mMisspelledUnderlineThickness;
+ private int mMisspelledUnderlineColor;
/*
* TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo
@@ -132,25 +135,22 @@
}
private void initStyle(Context context) {
- int defStyle = 0;
- if ((getFlags() & FLAG_MISSPELLED) != 0) {
- defStyle = com.android.internal.R.attr.textAppearanceMisspelledSuggestion;
- } else if ((getFlags() & FLAG_EASY_CORRECT) != 0) {
- defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
- } else {
- // No style is applied.
- mUnderlineThickness = 0;
- mUnderlineColor = 0;
- return;
- }
-
- TypedArray typedArray = context.obtainStyledAttributes(null,
- com.android.internal.R.styleable.SuggestionSpan,
- defStyle, 0);
-
- mUnderlineThickness = typedArray.getDimension(
+ int defStyle = com.android.internal.R.attr.textAppearanceMisspelledSuggestion;
+ TypedArray typedArray = context.obtainStyledAttributes(
+ null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0);
+ mMisspelledUnderlineThickness = typedArray.getDimension(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
- mUnderlineColor = typedArray.getColor(
+ mMisspelledUnderlineColor = typedArray.getColor(
+ com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+
+ defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
+
+ typedArray = context.obtainStyledAttributes(
+ null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0);
+
+ mEasyCorrectUnderlineThickness = typedArray.getDimension(
+ com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
+ mEasyCorrectUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
}
@@ -160,8 +160,10 @@
mLocaleString = src.readString();
mNotificationTargetClassName = src.readString();
mHashCode = src.readInt();
- mUnderlineColor = src.readInt();
- mUnderlineThickness = src.readFloat();
+ mEasyCorrectUnderlineColor = src.readInt();
+ mEasyCorrectUnderlineThickness = src.readFloat();
+ mMisspelledUnderlineColor = src.readInt();
+ mMisspelledUnderlineThickness = src.readFloat();
}
/**
@@ -211,8 +213,10 @@
dest.writeString(mLocaleString);
dest.writeString(mNotificationTargetClassName);
dest.writeInt(mHashCode);
- dest.writeInt(mUnderlineColor);
- dest.writeFloat(mUnderlineThickness);
+ dest.writeInt(mEasyCorrectUnderlineColor);
+ dest.writeFloat(mEasyCorrectUnderlineThickness);
+ dest.writeInt(mMisspelledUnderlineColor);
+ dest.writeFloat(mMisspelledUnderlineThickness);
}
@Override
@@ -254,6 +258,10 @@
@Override
public void updateDrawState(TextPaint tp) {
- tp.setUnderlineText(mUnderlineColor, mUnderlineThickness);
+ if ((mFlags & FLAG_MISSPELLED) != 0) {
+ tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness);
+ } else if ((mFlags & FLAG_EASY_CORRECT) != 0) {
+ tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness);
+ }
}
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 2bf16d8..1fcde3d 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -344,9 +344,9 @@
static final int EGL_SURFACE_TYPE = 0x3033;
static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
- private static final int SURFACE_STATE_ERROR = 0;
- private static final int SURFACE_STATE_SUCCESS = 1;
- private static final int SURFACE_STATE_UPDATED = 2;
+ static final int SURFACE_STATE_ERROR = 0;
+ static final int SURFACE_STATE_SUCCESS = 1;
+ static final int SURFACE_STATE_UPDATED = 2;
static EGL10 sEgl;
static EGLDisplay sEglDisplay;
@@ -913,8 +913,7 @@
@Override
void destroyLayers(View view) {
- if (view != null && isEnabled()) {
- checkCurrent();
+ if (view != null && isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) {
destroyHardwareLayer(view);
GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ae81537..3880bc4 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -446,7 +446,9 @@
/** @hide */
public native void setLayer(int zorder);
/** @hide */
- public native void setPosition(int x, int y);
+ public void setPosition(int x, int y) { setPosition((float)x, (float)y); }
+ /** @hide */
+ public native void setPosition(float x, float y);
/** @hide */
public native void setSize(int w, int h);
/** @hide */
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 9d959fb..c735d6b 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -286,6 +286,11 @@
}
@Override
+ boolean destroyLayer() {
+ return false;
+ }
+
+ @Override
HardwareLayer getHardwareLayer() {
if (mLayer == null) {
if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 14677e1..fa1d249 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2595,11 +2595,6 @@
protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT;
/**
- * Default threshold for "char count" heuristic.
- */
- protected static float DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD = 0.6f;
-
- /**
* The text direction that has been defined by {@link #setTextDirection(int)}.
*
* {@hide}
@@ -6052,23 +6047,29 @@
* @see #onHoverChanged
*/
public boolean onHoverEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- if (!hasHoveredChild() && !mSendingHoverAccessibilityEvents) {
- mSendingHoverAccessibilityEvents = true;
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
- }
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- if (mSendingHoverAccessibilityEvents) {
- mSendingHoverAccessibilityEvents = false;
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
- }
- break;
+ // The root view may receive hover (or touch) events that are outside the bounds of
+ // the window. This code ensures that we only send accessibility events for
+ // hovers that are actually within the bounds of the root view.
+ final int action = event.getAction();
+ if (!mSendingHoverAccessibilityEvents) {
+ if ((action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE)
+ && !hasHoveredChild()
+ && pointInView(event.getX(), event.getY())) {
+ mSendingHoverAccessibilityEvents = true;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ }
+ } else {
+ if (action == MotionEvent.ACTION_HOVER_EXIT
+ || (action == MotionEvent.ACTION_HOVER_MOVE
+ && !pointInView(event.getX(), event.getY()))) {
+ mSendingHoverAccessibilityEvents = false;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ }
}
if (isHoverable()) {
- switch (event.getAction()) {
+ switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
setHovered(true);
break;
@@ -9361,7 +9362,6 @@
if (mAttachInfo != null) {
mAttachInfo.mHandler.removeMessages(AttachInfo.INVALIDATE_MSG, this);
- mAttachInfo.mHandler.removeMessages(AttachInfo.INVALIDATE_RECT_MSG, this);
}
mCurrentAnimation = null;
@@ -13909,6 +13909,7 @@
}
public void onReleased(InvalidateInfo element) {
+ element.target = null;
}
}, POOL_LIMIT)
);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a0cc287..7db1b32 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -454,7 +454,10 @@
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
- mInputChannel = new InputChannel();
+ if ((mWindowAttributes.inputFeatures
+ & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
+ mInputChannel = new InputChannel();
+ }
try {
res = sWindowSession.add(mWindow, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets,
@@ -524,12 +527,14 @@
mInputQueueCallback =
((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
}
- if (mInputQueueCallback != null) {
- mInputQueue = new InputQueue(mInputChannel);
- mInputQueueCallback.onInputQueueCreated(mInputQueue);
- } else {
- InputQueue.registerInputChannel(mInputChannel, mInputHandler,
- Looper.myQueue());
+ if (mInputChannel != null) {
+ if (mInputQueueCallback != null) {
+ mInputQueue = new InputQueue(mInputChannel);
+ mInputQueueCallback.onInputQueueCreated(mInputQueue);
+ } else {
+ InputQueue.registerInputChannel(mInputChannel, mInputHandler,
+ Looper.myQueue());
+ }
}
view.assignParent(this);
@@ -2136,6 +2141,10 @@
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
+ if (mAttachInfo.mHardwareRenderer != null &&
+ mAttachInfo.mHardwareRenderer.isEnabled()) {
+ mAttachInfo.mHardwareRenderer.validate();
+ }
mView.dispatchDetachedFromWindow();
}
@@ -2152,13 +2161,12 @@
mSurface.release();
- if (mInputChannel != null) {
- if (mInputQueueCallback != null) {
- mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
- mInputQueueCallback = null;
- } else {
- InputQueue.unregisterInputChannel(mInputChannel);
- }
+ if (mInputQueueCallback != null && mInputQueue != null) {
+ mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
+ mInputQueueCallback = null;
+ mInputQueue = null;
+ } else if (mInputChannel != null) {
+ InputQueue.unregisterInputChannel(mInputChannel);
}
try {
sWindowSession.remove(mWindow);
@@ -3564,6 +3572,11 @@
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
synchronized (this) {
+ if (mAdded) {
+ mAdded = false;
+ dispatchDetachedFromWindow();
+ }
+
if (mAdded && !mFirst) {
destroyHardwareRenderer();
@@ -3584,10 +3597,6 @@
mSurface.release();
}
- if (mAdded) {
- mAdded = false;
- dispatchDetachedFromWindow();
- }
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fdd9b2c..52d25d9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1029,9 +1029,18 @@
public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;
/**
+ * Does not construct an input channel for this window. The channel will therefore
+ * be incapable of receiving input.
+ *
+ * @hide
+ */
+ public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;
+
+ /**
* Control special features of the input subsystem.
*
* @see #INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES
+ * @see #INPUT_FEATURE_NO_INPUT_CHANNEL
* @hide
*/
public int inputFeatures;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 1b4edf1..dc1bbd7 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -20,6 +20,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.IBinder;
import android.os.LocalPowerManager;
import android.view.animation.Animation;
@@ -165,7 +166,7 @@
*
* @return Rect The rectangle holding the shown window frame.
*/
- public Rect getShownFrameLw();
+ public RectF getShownFrameLw();
/**
* Retrieve the frame of the display that this window was last
@@ -871,6 +872,12 @@
public void systemReady();
/**
+ * Called when the system is done booting to the point where the
+ * user can start interacting with it.
+ */
+ public void systemBooted();
+
+ /**
* Show boot time message to the user.
*/
public void showBootMessage(final CharSequence msg, final boolean always);
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 5d4fbbe..76b47ca 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -332,7 +332,7 @@
// we perform an orientation change under ideal conditions. It will take
// proportionally longer than this to effect an orientation change when
// the proposed orientation confidence is low.
- private static final float ORIENTATION_SETTLE_TIME_MS = 100;
+ private static final float ORIENTATION_SETTLE_TIME_MS = 250;
// The confidence that we have abount effecting each orientation change.
// When one of these values exceeds 1.0, we have determined our new orientation!
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 4ec4ff9..0119d03 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -164,7 +164,9 @@
a.getString(com.android.internal.R.styleable
.InputMethod_Subtype_imeSubtypeExtraValue),
a.getBoolean(com.android.internal.R.styleable
- .InputMethod_Subtype_isAuxiliary, false));
+ .InputMethod_Subtype_isAuxiliary, false),
+ a.getBoolean(com.android.internal.R.styleable
+ .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false));
mSubtypes.add(subtype);
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index da5baf8..3ead9df 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1583,17 +1583,23 @@
/**
* Set additional input method subtypes. Only a process which shares the same uid with the IME
* can add additional input method subtypes to the IME.
+ * Please note that a subtype's status is stored in the system.
+ * For example, enabled subtypes are remembered by the framework even after they are removed
+ * by using this method. If you re-add the same subtypes again,
+ * they will just get enabled. If you want to avoid such conflicts, for instance, you may
+ * want to create a "different" new subtype even with the same locale and mode,
+ * by changing its extra value. The different subtype won't get affected by the stored past
+ * status. (You may want to take a look at {@link InputMethodSubtype#hashCode()} to refer
+ * to the current implementation.)
* @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
* @param subtypes subtypes will be added as additional subtypes of the current input method.
- * @return true if the additional input method subtypes are successfully added.
*/
- public boolean setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
synchronized (mH) {
try {
- return mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
+ mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
- return false;
}
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 4a98336..5670432 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -42,6 +42,7 @@
private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
private final boolean mIsAuxiliary;
+ private final boolean mOverridesImplicitlyEnabledSubtype;
private final int mSubtypeHashCode;
private final int mSubtypeIconResId;
private final int mSubtypeNameResId;
@@ -57,10 +58,12 @@
* @param locale The locale supported by the subtype
* @param mode The mode supported by the subtype
* @param extraValue The extra value of the subtype
+ * @param isAuxiliary true when this subtype is one shot subtype.
+ * @hide
*/
- public InputMethodSubtype(
- int nameId, int iconId, String locale, String mode, String extraValue) {
- this(nameId, iconId, locale, mode, extraValue, false);
+ public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
+ boolean isAuxiliary) {
+ this(nameId, iconId, locale, mode, extraValue, false, false);
}
/**
@@ -71,17 +74,21 @@
* @param mode The mode supported by the subtype
* @param extraValue The extra value of the subtype
* @param isAuxiliary true when this subtype is one shot subtype.
+ * @param overridesImplicitlyEnabledSubtype true when this subtype should be selected by default
+ * if no other subtypes are selected explicitly. Note that a subtype with this parameter being
+ * true will not be shown in the subtypes list.
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
- boolean isAuxiliary) {
+ boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
mSubtypeNameResId = nameId;
mSubtypeIconResId = iconId;
mSubtypeLocale = locale != null ? locale : "";
mSubtypeMode = mode != null ? mode : "";
mSubtypeExtraValue = extraValue != null ? extraValue : "";
mIsAuxiliary = isAuxiliary;
+ mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
- mIsAuxiliary);
+ mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
}
InputMethodSubtype(Parcel source) {
@@ -95,8 +102,9 @@
s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
mIsAuxiliary = (source.readInt() == 1);
+ mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
- mIsAuxiliary);
+ mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
}
/**
@@ -144,6 +152,14 @@
}
/**
+ * @return true when this subtype is selected by default if no other subtypes are selected
+ * explicitly. Note that a subtype that returns true will not be shown in the subtypes list.
+ */
+ public boolean overridesImplicitlyEnabledSubtype() {
+ return mOverridesImplicitlyEnabledSubtype;
+ }
+
+ /**
* @param context Context will be used for getting Locale and PackageManager.
* @param packageName The package name of the IME
* @param appInfo The application info of the IME
@@ -242,6 +258,7 @@
dest.writeString(mSubtypeMode);
dest.writeString(mSubtypeExtraValue);
dest.writeInt(mIsAuxiliary ? 1 : 0);
+ dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
}
public static final Parcelable.Creator<InputMethodSubtype> CREATOR
@@ -274,8 +291,9 @@
}
private static int hashCodeInternal(String locale, String mode, String extraValue,
- boolean isAuxiliary) {
- return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary});
+ boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
+ return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype});
}
/**
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index c85b2d9..01587aa 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -66,7 +66,13 @@
/**
* Get a spell checker session for the specified spell checker
- * @param locale the locale for the spell checker
+ * @param locale the locale for the spell checker. If {@param locale} is null and
+ * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+ * returned. If {@param locale} is not null and referToSpellCheckerLanguageSettings is true,
+ * the locale specified in Settings will be returned only when it is same as {@param locale}.
+ * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@param locale} is
+ * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+ * selected.
* @param listener a spell checker session lister for getting results from a spell checker.
* @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
* languages in settings will be returned.
@@ -81,6 +87,11 @@
throw new IllegalArgumentException("Locale should not be null if you don't refer"
+ " settings.");
}
+
+ if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+ return null;
+ }
+
final SpellCheckerInfo sci;
try {
sci = sService.getCurrentSpellChecker(null);
@@ -108,7 +119,12 @@
final String localeStr = locale.toString();
for (int i = 0; i < sci.getSubtypeCount(); ++i) {
final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
- if (subtype.getLocale().equals(localeStr)) {
+ final String tempSubtypeLocale = subtype.getLocale();
+ if (tempSubtypeLocale.equals(localeStr)) {
+ subtypeInUse = subtype;
+ break;
+ } else if (localeStr.length() >= 2 && tempSubtypeLocale.length() >= 2
+ && localeStr.startsWith(tempSubtypeLocale)) {
subtypeInUse = subtype;
}
}
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
index 4264e9d..ef1641d 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -28,6 +28,7 @@
static {
System.loadLibrary("webcore");
+ System.loadLibrary("chromium_net");
}
private static final String LOGTAG = "webkit";
private JniUtil() {} // Utility class, do not instantiate.
diff --git a/core/java/android/webkit/OverScrollGlow.java b/core/java/android/webkit/OverScrollGlow.java
index ff5b30b..e906f7f 100644
--- a/core/java/android/webkit/OverScrollGlow.java
+++ b/core/java/android/webkit/OverScrollGlow.java
@@ -22,7 +22,7 @@
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.View;
-import android.widget.EdgeGlow;
+import android.widget.EdgeEffect;
/**
* This class manages the edge glow effect when a WebView is flung or pulled beyond the edges.
@@ -31,10 +31,10 @@
public class OverScrollGlow {
private WebView mHostView;
- private EdgeGlow mEdgeGlowTop;
- private EdgeGlow mEdgeGlowBottom;
- private EdgeGlow mEdgeGlowLeft;
- private EdgeGlow mEdgeGlowRight;
+ private EdgeEffect mEdgeGlowTop;
+ private EdgeEffect mEdgeGlowBottom;
+ private EdgeEffect mEdgeGlowLeft;
+ private EdgeEffect mEdgeGlowRight;
private int mOverScrollDeltaX;
private int mOverScrollDeltaY;
@@ -42,13 +42,10 @@
public OverScrollGlow(WebView host) {
mHostView = host;
Context context = host.getContext();
- final Resources res = context.getResources();
- final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
- final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
- mEdgeGlowTop = new EdgeGlow(context, edge, glow);
- mEdgeGlowBottom = new EdgeGlow(context, edge, glow);
- mEdgeGlowLeft = new EdgeGlow(context, edge, glow);
- mEdgeGlowRight = new EdgeGlow(context, edge, glow);
+ mEdgeGlowTop = new EdgeEffect(context);
+ mEdgeGlowBottom = new EdgeEffect(context);
+ mEdgeGlowLeft = new EdgeEffect(context);
+ mEdgeGlowRight = new EdgeEffect(context);
}
/**
diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java
index 0fc76fa..5f91ed3 100644
--- a/core/java/android/webkit/ViewStateSerializer.java
+++ b/core/java/android/webkit/ViewStateSerializer.java
@@ -36,11 +36,15 @@
static boolean serializeViewState(OutputStream stream, WebView web)
throws IOException {
+ int baseLayer = web.getBaseLayer();
+ if (baseLayer == 0) {
+ return false;
+ }
DataOutputStream dos = new DataOutputStream(stream);
dos.writeInt(VERSION);
dos.writeInt(web.getContentWidth());
dos.writeInt(web.getContentHeight());
- return nativeSerializeViewState(web.getBaseLayer(), dos,
+ return nativeSerializeViewState(baseLayer, dos,
new byte[WORKING_STREAM_STORAGE]);
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4748522..64fbae6 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -34,7 +34,6 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.CornerPathEffect;
import android.graphics.DrawFilter;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
@@ -58,8 +57,6 @@
import android.os.StrictMode;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
-import android.text.Selection;
-import android.text.Spannable;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
@@ -606,9 +603,15 @@
// know to handle Shift and arrows natively first
private boolean mAccessibilityScriptInjected;
+ static final boolean USE_JAVA_TEXT_SELECTION = true;
+ private Region mTextSelectionRegion = new Region();
+ private Paint mTextSelectionPaint;
+ private Drawable mSelectHandleLeft;
+ private Drawable mSelectHandleRight;
+
static final boolean USE_WEBKIT_RINGS = true;
// the color used to highlight the touch rectangles
- private static final int mHightlightColor = 0x6633b5e5;
+ private static final int HIGHLIGHT_COLOR = 0x6633b5e5;
// the round corner for the highlight path
private static final float TOUCH_HIGHLIGHT_ARC = 5.0f;
// the region indicating where the user touched on the screen
@@ -2343,6 +2346,14 @@
}
/**
+ * Return the reading level scale of the WebView
+ * @return The reading level scale.
+ */
+ /*package*/ float getReadingLevelScale() {
+ return mZoomManager.getReadingLevelScale();
+ }
+
+ /**
* Set the initial scale for the WebView. 0 means default. If
* {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
* way. Otherwise it starts with 100%. If initial scale is greater than 0,
@@ -4004,12 +4015,9 @@
// state.
// If mNativeClass is 0, we should not reach here, so we do not
// need to check it again.
- if (mDrawCursorRing && drawRings) {
- // Only update if we are actually going to use the result
- nativeRecordButtons(hasFocus() && hasWindowFocus(),
- mTouchMode == TOUCH_SHORTPRESS_START_MODE
- || mTrackballDown || mGotCenterDown, false);
- }
+ nativeRecordButtons(hasFocus() && hasWindowFocus(),
+ (mTouchMode == TOUCH_SHORTPRESS_START_MODE && !USE_WEBKIT_RINGS)
+ || mTrackballDown || mGotCenterDown, false);
drawCoreAndCursorRing(canvas, mBackgroundColor,
mDrawCursorRing && drawRings);
}
@@ -4050,8 +4058,10 @@
@Override
protected void onDraw(Canvas canvas) {
- // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
+ // if mNativeClass is 0, the WebView is either destroyed or not
+ // initialized. In either case, just draw the background color and return
if (mNativeClass == 0) {
+ canvas.drawColor(mBackgroundColor);
return;
}
@@ -4105,7 +4115,7 @@
} else {
if (mTouchHightlightPaint == null) {
mTouchHightlightPaint = new Paint();
- mTouchHightlightPaint.setColor(mHightlightColor);
+ mTouchHightlightPaint.setColor(HIGHLIGHT_COLOR);
}
RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
Rect r = new Rect();
@@ -4306,6 +4316,9 @@
}
int getBaseLayer() {
+ if (mNativeClass == 0) {
+ return 0;
+ }
return nativeGetBaseLayer();
}
@@ -4400,7 +4413,7 @@
int extras = DRAW_EXTRAS_NONE;
if (mFindIsUp) {
extras = DRAW_EXTRAS_FIND;
- } else if (mSelectingText) {
+ } else if (mSelectingText && !USE_JAVA_TEXT_SELECTION) {
extras = DRAW_EXTRAS_SELECTION;
nativeSetSelectionPointer(mDrawSelectionPointer,
mZoomManager.getInvScale(),
@@ -4427,6 +4440,10 @@
nativeUseHardwareAccelSkia(mHardwareAccelSkia);
}
+ if (mSelectingText && USE_JAVA_TEXT_SELECTION) {
+ drawTextSelectionHandles(canvas);
+ }
+
} else {
DrawFilter df = null;
if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) {
@@ -4461,6 +4478,56 @@
}
}
+ private void drawTextSelectionHandles(Canvas canvas) {
+ if (mTextSelectionPaint == null) {
+ mTextSelectionPaint = new Paint();
+ mTextSelectionPaint.setColor(HIGHLIGHT_COLOR);
+ }
+ mTextSelectionRegion.setEmpty();
+ nativeGetTextSelectionRegion(mTextSelectionRegion);
+ Rect r = new Rect();
+ RegionIterator iter = new RegionIterator(mTextSelectionRegion);
+ int start_x = -1;
+ int start_y = -1;
+ int end_x = -1;
+ int end_y = -1;
+ while (iter.next(r)) {
+ r = new Rect(
+ contentToViewDimension(r.left),
+ contentToViewDimension(r.top),
+ contentToViewDimension(r.right),
+ contentToViewDimension(r.bottom));
+ // Regions are in order. First one is where selection starts,
+ // last one is where it ends
+ if (start_x < 0 || start_y < 0) {
+ start_x = r.left;
+ start_y = r.bottom;
+ }
+ end_x = r.right;
+ end_y = r.bottom;
+ canvas.drawRect(r, mTextSelectionPaint);
+ }
+ if (mSelectHandleLeft == null) {
+ mSelectHandleLeft = mContext.getResources().getDrawable(
+ com.android.internal.R.drawable.text_select_handle_left);
+ }
+ // Magic formula copied from TextView
+ start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
+ mSelectHandleLeft.setBounds(start_x, start_y,
+ start_x + mSelectHandleLeft.getIntrinsicWidth(),
+ start_y + mSelectHandleLeft.getIntrinsicHeight());
+ if (mSelectHandleRight == null) {
+ mSelectHandleRight = mContext.getResources().getDrawable(
+ com.android.internal.R.drawable.text_select_handle_right);
+ }
+ end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
+ mSelectHandleRight.setBounds(end_x, end_y,
+ end_x + mSelectHandleRight.getIntrinsicWidth(),
+ end_y + mSelectHandleRight.getIntrinsicHeight());
+ mSelectHandleLeft.draw(canvas);
+ mSelectHandleRight.draw(canvas);
+ }
+
// draw history
private boolean mDrawHistory = false;
private Picture mHistoryPicture = null;
@@ -9329,4 +9396,5 @@
private native int nativeGetBackgroundColor();
native boolean nativeSetProperty(String key, String value);
native String nativeGetProperty(String key);
+ private native void nativeGetTextSelectionRegion(Region region);
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 3ca3eaa..c61bd48 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -57,13 +57,14 @@
private static final String LOGTAG = "webcore";
static {
- // Load libwebcore during static initialization. This happens in the
- // zygote process so it will be shared read-only across all app
- // processes.
+ // Load libwebcore and libchromium_net during static initialization.
+ // This happens in the zygote process so they will be shared read-only
+ // across all app processes.
try {
System.loadLibrary("webcore");
+ System.loadLibrary("chromium_net");
} catch (UnsatisfiedLinkError e) {
- Log.e(LOGTAG, "Unable to load webcore library");
+ Log.e(LOGTAG, "Unable to load native support libraries.");
}
}
@@ -537,6 +538,8 @@
private native void nativeSendListBoxChoice(int choice);
+ private native void nativeCloseIdleConnections();
+
/* Tell webkit what its width and height are, for the purposes
of layout/line-breaking. These coordinates are in document space,
which is the same as View coords unless we have zoomed the document
@@ -1040,6 +1043,7 @@
// Flag for blocking messages. This is used during DESTROY to avoid
// posting more messages to the EventHub or to WebView's event handler.
private boolean mBlockMessages;
+ private boolean mDestroying;
private int mTid;
private int mSavedPriority;
@@ -1071,12 +1075,20 @@
+ " arg1=" + msg.arg1 + " arg2=" + msg.arg2
+ " obj=" + msg.obj);
}
- if (mWebView == null
+ if (mWebView == null || mNativeClass == 0) {
+ if (DebugFlags.WEB_VIEW_CORE) {
+ Log.w(LOGTAG, "Rejecting message " + msg.what
+ + " because we are destroyed");
+ }
+ return;
+ }
+ if (mDestroying == true
&& msg.what != EventHub.RESUME_TIMERS
- && msg.what != EventHub.PAUSE_TIMERS) {
+ && msg.what != EventHub.PAUSE_TIMERS
+ && msg.what != EventHub.DESTROY) {
if (DebugFlags.WEB_VIEW_CORE) {
Log.v(LOGTAG, "Rejecting message " + msg.what
- + " because we are destroyed");
+ + " because we are being destroyed");
}
return;
}
@@ -1252,6 +1264,8 @@
if (!JniUtil.useChromiumHttpStack()) {
WebViewWorker.getHandler().sendEmptyMessage(
WebViewWorker.MSG_PAUSE_CACHE_TRANSACTION);
+ } else {
+ nativeCloseIdleConnections();
}
break;
@@ -1780,7 +1794,8 @@
// or RESUME_TIMERS messages, which we must still handle as they
// are per process. DESTROY will instead trigger a white list in
// mEventHub, skipping any remaining messages in the queue
- mEventHub.sendMessageAtFrontOfQueue(
+ mEventHub.mDestroying = true;
+ mEventHub.sendMessage(
Message.obtain(null, EventHub.DESTROY));
mEventHub.blockMessages();
}
@@ -2445,7 +2460,7 @@
if (mSettings.isNarrowColumnLayout()) {
// In case of automatic text reflow in fixed view port mode.
mInitialViewState.mTextWrapScale =
- ZoomManager.computeReadingLevelScale(data.mScale);
+ mWebView.getReadingLevelScale();
}
} else {
// Scale is given such as when page is restored, use it.
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index 2bcb020..0bfb668 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -58,13 +58,6 @@
private ZoomControlExternal mExternalZoomControl;
/*
- * For large screen devices, the defaultScale usually set to 1.0 and
- * equal to the overview scale, to differentiate the zoom level for double tapping,
- * a default reading level scale is used.
- */
- private static final float DEFAULT_READING_LEVEL_SCALE = 1.5f;
-
- /*
* The scale factors that determine the upper and lower bounds for the
* default zoom scale.
*/
@@ -151,6 +144,19 @@
private float mDefaultScale;
private float mInvDefaultScale;
+ /*
+ * The scale factor that is used to determine the zoom level for reading text.
+ * The value is initially set to equal the display density.
+ * TODO: Support changing this in WebSettings
+ */
+ private float mReadingLevelScale;
+
+ /*
+ * The scale factor that is used as the minimum increment when going from
+ * overview to reading level on a double tap.
+ */
+ private static float MIN_DOUBLE_TAP_SCALE_INCREMENT = 0.5f;
+
// the current computed zoom scale and its inverse.
private float mActualScale;
private float mInvActualScale;
@@ -230,6 +236,7 @@
setDefaultZoomScale(density);
mActualScale = density;
mInvActualScale = 1 / density;
+ mReadingLevelScale = density;
mTextWrapScale = density;
}
@@ -304,13 +311,7 @@
}
public final float getReadingLevelScale() {
- return computeScaleWithLimits(computeReadingLevelScale(getZoomOverviewScale()));
- }
-
- /* package */ final static float computeReadingLevelScale(float scale) {
- // The reading scale is at least 0.5f apart from the input scale.
- final float MIN_SCALE_DIFF = 0.5f;
- return Math.max(scale + MIN_SCALE_DIFF, DEFAULT_READING_LEVEL_SCALE);
+ return mReadingLevelScale;
}
public final float getInvDefaultScale() {
@@ -652,7 +653,7 @@
} else if (!mInZoomOverview && willScaleTriggerZoom(getZoomOverviewScale())) {
zoomToOverview();
} else {
- zoomToReadingLevel();
+ zoomToReadingLevelOrMore();
}
}
@@ -683,8 +684,10 @@
!mWebView.getSettings().getUseFixedViewport());
}
- private void zoomToReadingLevel() {
- final float readingScale = getReadingLevelScale();
+ private void zoomToReadingLevelOrMore() {
+ final float zoomScale = Math.max(getReadingLevelScale(),
+ mActualScale + MIN_DOUBLE_TAP_SCALE_INCREMENT);
+
int left = mWebView.nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
if (left != WebView.NO_LEFTEDGE) {
// add a 5pt padding to the left edge.
@@ -693,13 +696,13 @@
// Re-calculate the zoom center so that the new scroll x will be
// on the left edge.
if (viewLeft > 0) {
- mZoomCenterX = viewLeft * readingScale / (readingScale - mActualScale);
+ mZoomCenterX = viewLeft * zoomScale / (zoomScale - mActualScale);
} else {
mWebView.scrollBy(viewLeft, 0);
mZoomCenterX = 0;
}
}
- startZoomAnimation(readingScale,
+ startZoomAnimation(zoomScale,
!mWebView.getSettings().getUseFixedViewport());
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 80cbbfb..05d4f05 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -594,12 +594,12 @@
/**
* Tracks the state of the top edge glow.
*/
- private EdgeGlow mEdgeGlowTop;
+ private EdgeEffect mEdgeGlowTop;
/**
* Tracks the state of the bottom edge glow.
*/
- private EdgeGlow mEdgeGlowBottom;
+ private EdgeEffect mEdgeGlowBottom;
/**
* An estimate of how many pixels are between the top of the list and
@@ -788,11 +788,8 @@
if (mode != OVER_SCROLL_NEVER) {
if (mEdgeGlowTop == null) {
Context context = getContext();
- final Resources res = context.getResources();
- final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
- final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
- mEdgeGlowTop = new EdgeGlow(context, edge, glow);
- mEdgeGlowBottom = new EdgeGlow(context, edge, glow);
+ mEdgeGlowTop = new EdgeEffect(context);
+ mEdgeGlowBottom = new EdgeEffect(context);
}
} else {
mEdgeGlowTop = null;
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index f267458c..4ba604d 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -926,6 +926,8 @@
}
event.setItemCount(getCount());
event.setCurrentItemIndex(getSelectedItemPosition());
+ event.setFromIndex(mFirstPosition);
+ event.setToIndex(mFirstPosition + getChildCount());
}
@Override
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 4812283..3b67f44 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -30,6 +30,7 @@
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.NumberPicker.OnValueChangeListener;
import com.android.internal.R;
@@ -264,6 +265,11 @@
// re-order the number spinners to match the current date format
reorderSpinners();
+
+ // set content descriptions
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ setContentDescriptions();
+ }
}
/**
@@ -357,11 +363,16 @@
}
@Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return true;
+ }
+
+ @Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
- final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
- | DateUtils.FORMAT_SHOW_YEAR;
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mCurrentDate.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
@@ -709,5 +720,22 @@
}
};
}
-}
+ private void setContentDescriptions() {
+ // Day
+ String text = mContext.getString(R.string.date_picker_increment_day_button);
+ mDaySpinner.findViewById(R.id.increment).setContentDescription(text);
+ text = mContext.getString(R.string.date_picker_decrement_day_button);
+ mDaySpinner.findViewById(R.id.decrement).setContentDescription(text);
+ // Month
+ text = mContext.getString(R.string.date_picker_increment_month_button);
+ mMonthSpinner.findViewById(R.id.increment).setContentDescription(text);
+ text = mContext.getString(R.string.date_picker_decrement_month_button);
+ mMonthSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ // Year
+ text = mContext.getString(R.string.date_picker_increment_year_button);
+ mYearSpinner.findViewById(R.id.increment).setContentDescription(text);
+ text = mContext.getString(R.string.date_picker_decrement_year_button);
+ mYearSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ }
+}
diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeEffect.java
similarity index 77%
rename from core/java/android/widget/EdgeGlow.java
rename to core/java/android/widget/EdgeEffect.java
index 75cef38..fd2abc2 100644
--- a/core/java/android/widget/EdgeGlow.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -16,7 +16,10 @@
package android.widget;
+import com.android.internal.R;
+
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.animation.AnimationUtils;
@@ -24,19 +27,33 @@
import android.view.animation.Interpolator;
/**
- * This class performs the glow effect used at the edges of scrollable widgets.
- * @hide
+ * This class performs the graphical effect used at the edges of scrollable widgets
+ * when the user scrolls beyond the content bounds in 2D space.
+ *
+ * <p>EdgeEffect is stateful. Custom widgets using EdgeEffect should create an
+ * instance for each edge that should show the effect, feed it input data using
+ * the methods {@link #onAbsorb(int)}, {@link #onPull(float)}, and {@link #onRelease()},
+ * and draw the effect using {@link #draw(Canvas)} in the widget's overridden
+ * {@link android.view.View#draw(Canvas)} method. If {@link #isFinished()} returns
+ * false after drawing, the edge effect's animation is not yet complete and the widget
+ * should schedule another drawing pass to continue the animation.</p>
+ *
+ * <p>When drawing, widgets should draw their main content and child views first,
+ * usually by invoking <code>super.draw(canvas)</code> from an overridden <code>draw</code>
+ * method. (This will invoke onDraw and dispatch drawing to child views as needed.)
+ * The edge effect may then be drawn on top of the view's content using the
+ * {@link #draw(Canvas)} method.</p>
*/
-public class EdgeGlow {
- private static final String TAG = "EdgeGlow";
+public class EdgeEffect {
+ private static final String TAG = "EdgeEffect";
// Time it will take the effect to fully recede in ms
private static final int RECEDE_TIME = 1000;
- // Time it will take before a pulled glow begins receding
+ // Time it will take before a pulled glow begins receding in ms
private static final int PULL_TIME = 167;
- // Time it will take for a pulled glow to decay to partial strength before release
+ // Time it will take in ms for a pulled glow to decay to partial strength before release
private static final int PULL_DECAY_TIME = 1000;
private static final float MAX_ALPHA = 0.8f;
@@ -103,31 +120,58 @@
private float mPullDistance;
- public EdgeGlow(Context context, Drawable edge, Drawable glow) {
- mEdge = edge;
- mGlow = glow;
+ /**
+ * Construct a new EdgeEffect with a theme appropriate for the provided context.
+ * @param context Context used to provide theming and resource information for the EdgeEffect
+ */
+ public EdgeEffect(Context context) {
+ final Resources res = context.getResources();
+ mEdge = res.getDrawable(R.drawable.overscroll_edge);
+ mGlow = res.getDrawable(R.drawable.overscroll_glow);
mMinWidth = (int) (context.getResources().getDisplayMetrics().density * MIN_WIDTH + 0.5f);
mInterpolator = new DecelerateInterpolator();
}
+ /**
+ * Set the size of this edge effect in pixels.
+ *
+ * @param width Effect width in pixels
+ * @param height Effect height in pixels
+ */
public void setSize(int width, int height) {
mWidth = width;
mHeight = 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.
+ *
+ * @return true if animation is finished, false if drawing should continue on the next frame.
+ */
public boolean isFinished() {
return mState == STATE_IDLE;
}
+ /**
+ * Immediately finish the current animation.
+ * After this call {@link #isFinished()} will return true.
+ */
public void finish() {
mState = STATE_IDLE;
}
/**
- * Call when the object is pulled by the user.
+ * A view should call this when content is pulled away from an edge by the user.
+ * This will update the state of the current visual effect and its associated animation.
+ * The host view should always {@link android.view.View#invalidate()} after this
+ * and draw the results accordingly.
*
- * @param deltaDistance Change in distance since the last call
+ * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+ * 1.f (full length of the view) or negative values to express change
+ * back toward the edge reached to initiate the effect.
*/
public void onPull(float deltaDistance) {
final long now = AnimationUtils.currentAnimationTimeMillis();
@@ -173,6 +217,9 @@
/**
* Call when the object is released after being pulled.
+ * This will begin the "decay" phase of the effect. After calling this method
+ * the host view should {@link android.view.View#invalidate()} and thereby
+ * draw the results accordingly.
*/
public void onRelease() {
mPullDistance = 0;
@@ -198,6 +245,11 @@
/**
* Call when the effect absorbs an impact at the given velocity.
+ * Used when a fling reaches the scroll boundary.
+ *
+ * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
+ * the method <code>getCurrVelocity</code> will provide a reasonable approximation
+ * to use here.</p>
*
* @param velocity Velocity at impact in pixels per second.
*/
@@ -238,7 +290,7 @@
/**
* Draw into the provided canvas. Assumes that the canvas has been rotated
* accordingly and the size has been set. The effect will be drawn the full
- * width of X=0 to X=width, emitting from Y=0 and extending to some factor <
+ * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
* 1.f of height.
*
* @param canvas Canvas to draw into
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index d638732..a7a05be 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -75,8 +75,8 @@
private final Rect mTempRect = new Rect();
private OverScroller mScroller;
- private EdgeGlow mEdgeGlowLeft;
- private EdgeGlow mEdgeGlowRight;
+ private EdgeEffect mEdgeGlowLeft;
+ private EdgeEffect mEdgeGlowRight;
/**
* Position of the last motion event.
@@ -1477,11 +1477,8 @@
if (mode != OVER_SCROLL_NEVER) {
if (mEdgeGlowLeft == null) {
Context context = getContext();
- final Resources res = context.getResources();
- final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
- final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
- mEdgeGlowLeft = new EdgeGlow(context, edge, glow);
- mEdgeGlowRight = new EdgeGlow(context, edge, glow);
+ mEdgeGlowLeft = new EdgeEffect(context);
+ mEdgeGlowRight = new EdgeEffect(context);
}
} else {
mEdgeGlowLeft = null;
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 2a299bd..35e48f2 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -33,6 +33,7 @@
import android.graphics.Rect;
import android.graphics.Paint.Align;
import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
@@ -48,6 +49,8 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.LayoutInflater.Filter;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
@@ -471,7 +474,7 @@
// the fading edge effect implemented by View and we need our
// draw() method to be called. Therefore, we declare we will draw.
setWillNotDraw(false);
- setDrawSelectorWheel(false);
+ setDrawScrollWheel(false);
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
@@ -561,7 +564,7 @@
public void onAnimationEnd(Animator animation) {
if (!mCanceled) {
// if canceled => we still want the wheel drawn
- setDrawSelectorWheel(false);
+ setDrawScrollWheel(false);
}
mCanceled = false;
mSelectorPaint.setAlpha(255);
@@ -587,7 +590,7 @@
// Start with shown selector wheel and hidden controls. When made
// visible hide the selector and fade-in the controls to suggest
// fling interaction.
- setDrawSelectorWheel(true);
+ setDrawScrollWheel(true);
hideInputControls();
}
}
@@ -630,7 +633,7 @@
|| (!mDecrementButton.isShown()
&& isEventInViewHitRect(event, mDecrementButton))) {
mAdjustScrollerOnUpEvent = false;
- setDrawSelectorWheel(true);
+ setDrawScrollWheel(true);
hideInputControls();
return true;
}
@@ -641,7 +644,7 @@
if (deltaDownY > mTouchSlop) {
mBeginEditOnUpEvent = false;
onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
- setDrawSelectorWheel(true);
+ setDrawScrollWheel(true);
hideInputControls();
return true;
}
@@ -678,7 +681,7 @@
break;
case MotionEvent.ACTION_UP:
if (mBeginEditOnUpEvent) {
- setDrawSelectorWheel(false);
+ setDrawScrollWheel(false);
showInputControls(mShowInputControlsAnimimationDuration);
mInputText.requestFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
@@ -1135,6 +1138,12 @@
}
}
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ // Do not send accessibility events - we want the user to
+ // perceive this widget as several controls rather as a whole.
+ }
+
/**
* Resets the selector indices and clear the cached
* string representation of these indices.
@@ -1192,10 +1201,19 @@
/**
* Sets if to <code>drawSelectionWheel</code>.
*/
- private void setDrawSelectorWheel(boolean drawSelectorWheel) {
+ private void setDrawScrollWheel(boolean drawSelectorWheel) {
mDrawSelectorWheel = drawSelectorWheel;
// do not fade if the selector wheel not shown
setVerticalFadingEdgeEnabled(drawSelectorWheel);
+
+ if (mFlingable && mDrawSelectorWheel
+ && AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityManager.getInstance(mContext).interrupt();
+ String text = mContext.getString(R.string.number_picker_increment_scroll_action);
+ mInputText.setContentDescription(text);
+ mInputText.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ mInputText.setContentDescription(null);
+ }
}
private void initializeScrollWheel() {
@@ -1429,6 +1447,12 @@
mInputText.setText(mDisplayedValues[mValue - mMinValue]);
}
mInputText.setSelection(mInputText.getText().length());
+
+ if (mFlingable && AccessibilityManager.getInstance(mContext).isEnabled()) {
+ String text = mContext.getString(R.string.number_picker_increment_scroll_mode,
+ mInputText.getText());
+ mInputText.setContentDescription(text);
+ }
}
/**
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 0ba7889..542a1ef 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -166,7 +166,6 @@
}
/**
- * @hide
* Returns the absolute value of the current velocity.
*
* @return The original velocity less the deceleration, norm of the X and Y velocity vector.
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 09c875b..6a6bc23 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -69,8 +69,8 @@
private final Rect mTempRect = new Rect();
private OverScroller mScroller;
- private EdgeGlow mEdgeGlowTop;
- private EdgeGlow mEdgeGlowBottom;
+ private EdgeEffect mEdgeGlowTop;
+ private EdgeEffect mEdgeGlowBottom;
/**
* Position of the last motion event.
@@ -1511,11 +1511,8 @@
if (mode != OVER_SCROLL_NEVER) {
if (mEdgeGlowTop == null) {
Context context = getContext();
- final Resources res = context.getResources();
- final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
- final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
- mEdgeGlowTop = new EdgeGlow(context, edge, glow);
- mEdgeGlowBottom = new EdgeGlow(context, edge, glow);
+ mEdgeGlowTop = new EdgeEffect(context);
+ mEdgeGlowBottom = new EdgeEffect(context);
}
} else {
mEdgeGlowTop = null;
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index f00640e..a6e83f0 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -196,7 +196,6 @@
}
/**
- * @hide
* Returns the current velocity.
*
* @return The original velocity less the deceleration. Result may be
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index b89b8ec..14cbf6f 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -1,4 +1,18 @@
-// Copyright 2011 Google Inc. All Rights Reserved.
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package android.widget;
@@ -32,7 +46,7 @@
private final TextView mTextView;
- final SpellCheckerSession spellCheckerSession;
+ final SpellCheckerSession mSpellCheckerSession;
final int mCookie;
// Paired arrays for the (id, spellCheckSpan) pair. mIndex is the next available position
@@ -49,7 +63,7 @@
final TextServicesManager textServicesManager = (TextServicesManager) textView.getContext().
getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
- spellCheckerSession = textServicesManager.newSpellCheckerSession(
+ mSpellCheckerSession = textServicesManager.newSpellCheckerSession(
null /* not currently used by the textServicesManager */, Locale.getDefault(),
this, true /* means use the languages defined in Settings */);
mCookie = hashCode();
@@ -112,7 +126,7 @@
private void scheduleSpellCheck() {
if (mLength == 0) return;
- if (spellCheckerSession == null) return;
+ if (mSpellCheckerSession == null) return;
if (mChecker != null) {
mTextView.removeCallbacks(mChecker);
@@ -157,7 +171,7 @@
System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount);
textInfos = textInfosCopy;
}
- spellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
+ mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
false /* TODO Set sequentialWords to true for initial spell check */);
}
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 4fcb358..02c9d03 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -161,6 +161,7 @@
mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
// Refresh display with current params
+ refreshDrawableState();
setChecked(isChecked());
}
@@ -632,8 +633,9 @@
int[] myDrawableState = getDrawableState();
// Set the state of the Drawable
- mThumbDrawable.setState(myDrawableState);
- mTrackDrawable.setState(myDrawableState);
+ // Drawable may be null when checked state is set from XML, from super constructor
+ if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
+ if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
invalidate();
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 135797f..abf1281 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -21,6 +21,7 @@
import android.content.ClipData.Item;
import android.content.ClipboardManager;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -42,6 +43,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.provider.Settings;
import android.text.BoringLayout;
import android.text.DynamicLayout;
import android.text.Editable;
@@ -63,6 +65,7 @@
import android.text.TextDirectionHeuristics;
import android.text.TextPaint;
import android.text.TextUtils;
+import android.text.TextUtils.TruncateAt;
import android.text.TextWatcher;
import android.text.method.AllCapsTransformationMethod;
import android.text.method.ArrowKeyMovementMethod;
@@ -81,7 +84,6 @@
import android.text.method.TransformationMethod;
import android.text.method.TransformationMethod2;
import android.text.method.WordIterator;
-import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
import android.text.style.EasyEditSpan;
import android.text.style.ParagraphStyle;
@@ -130,6 +132,7 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.util.FastMath;
@@ -366,6 +369,35 @@
private boolean mResolvedDrawables = false;
+ /**
+ * On some devices the fading edges add a performance penalty if used
+ * extensively in the same layout. This mode indicates how the marquee
+ * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
+ */
+ private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
+
+ /**
+ * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
+ * the layout that should be used when the mode switches.
+ */
+ private Layout mSavedMarqueeModeLayout;
+
+ /**
+ * Draw marquee text with fading edges as usual
+ */
+ private static final int MARQUEE_FADE_NORMAL = 0;
+
+ /**
+ * Draw marquee text as ellipsize end while inactive instead of with the fade.
+ * (Useful for devices where the fade can be expensive if overdone)
+ */
+ private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
+
+ /**
+ * Draw marquee text with fading edges because it is currently active/animating.
+ */
+ private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
+
/*
* Kick-start the font cache for the zygote process (to pay the cost of
* initializing freetype for our default font only once).
@@ -997,8 +1029,13 @@
setEllipsize(TextUtils.TruncateAt.END);
break;
case 4:
- setHorizontalFadingEdgeEnabled(
- ViewConfiguration.get(context).isFadingMarqueeEnabled());
+ if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
+ setHorizontalFadingEdgeEnabled(true);
+ mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
+ } else {
+ setHorizontalFadingEdgeEnabled(false);
+ mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
+ }
setEllipsize(TextUtils.TruncateAt.MARQUEE);
break;
}
@@ -3069,8 +3106,13 @@
if (text instanceof Spanned &&
((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
- setHorizontalFadingEdgeEnabled(
- ViewConfiguration.get(mContext).isFadingMarqueeEnabled());
+ if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
+ setHorizontalFadingEdgeEnabled(true);
+ mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
+ } else {
+ setHorizontalFadingEdgeEnabled(false);
+ mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
+ }
setEllipsize(TextUtils.TruncateAt.MARQUEE);
}
@@ -4763,7 +4805,8 @@
final int layoutDirection = getResolvedLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
- if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
+ mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
(absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
@@ -5947,7 +5990,7 @@
mSavedHintLayout = (BoringLayout) mHintLayout;
}
- mLayout = mHintLayout = null;
+ mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
// Since it depends on the value of mLayout
prepareCursorControllers();
@@ -6067,73 +6110,25 @@
Layout.Alignment alignment = getLayoutAlignment();
boolean shouldEllipsize = mEllipsize != null && mInput == null;
+ final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
+ mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
+ TruncateAt effectiveEllipsize = mEllipsize;
+ if (mEllipsize == TruncateAt.MARQUEE &&
+ mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
+ effectiveEllipsize = TruncateAt.END;
+ }
if (mTextDir == null) {
resolveTextDirection();
}
- if (mText instanceof Spannable) {
- mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
- alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
- ellipsisWidth);
- } else {
- if (boring == UNKNOWN_BORING) {
- boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
- if (boring != null) {
- mBoring = boring;
- }
- }
- if (boring != null) {
- if (boring.width <= w &&
- (mEllipsize == null || boring.width <= ellipsisWidth)) {
- if (mSavedLayout != null) {
- mLayout = mSavedLayout.
- replaceOrMake(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
- boring, mIncludePad);
- } else {
- mLayout = BoringLayout.make(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
- boring, mIncludePad);
- }
-
- mSavedLayout = (BoringLayout) mLayout;
- } else if (shouldEllipsize && boring.width <= w) {
- if (mSavedLayout != null) {
- mLayout = mSavedLayout.
- replaceOrMake(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
- boring, mIncludePad, mEllipsize,
- ellipsisWidth);
- } else {
- mLayout = BoringLayout.make(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
- boring, mIncludePad, mEllipsize,
- ellipsisWidth);
- }
- } else if (shouldEllipsize) {
- mLayout = new StaticLayout(mTransformed,
- 0, mTransformed.length(),
- mTextPaint, w, alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, mEllipsize,
- ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
- } else {
- mLayout = new StaticLayout(mTransformed, mTextPaint,
- w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
- mIncludePad);
- }
- } else if (shouldEllipsize) {
- mLayout = new StaticLayout(mTransformed,
- 0, mTransformed.length(),
- mTextPaint, w, alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, mEllipsize,
- ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
- } else {
- mLayout = new StaticLayout(mTransformed, mTextPaint,
- w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
- mIncludePad);
- }
+ mLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment, shouldEllipsize,
+ effectiveEllipsize, effectiveEllipsize == mEllipsize);
+ if (switchEllipsize) {
+ TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
+ TruncateAt.END : TruncateAt.MARQUEE;
+ mSavedMarqueeModeLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment,
+ shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
}
shouldEllipsize = mEllipsize != null;
@@ -6224,6 +6219,77 @@
prepareCursorControllers();
}
+ private Layout makeSingleLayout(int w, BoringLayout.Metrics boring, int ellipsisWidth,
+ Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
+ boolean useSaved) {
+ Layout result = null;
+ if (mText instanceof Spannable) {
+ result = new DynamicLayout(mText, mTransformed, mTextPaint, w,
+ alignment, mTextDir, mSpacingMult,
+ mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
+ ellipsisWidth);
+ } else {
+ if (boring == UNKNOWN_BORING) {
+ boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
+ if (boring != null) {
+ mBoring = boring;
+ }
+ }
+
+ if (boring != null) {
+ if (boring.width <= w &&
+ (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
+ if (useSaved && mSavedLayout != null) {
+ result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
+ w, alignment, mSpacingMult, mSpacingAdd,
+ boring, mIncludePad);
+ } else {
+ result = BoringLayout.make(mTransformed, mTextPaint,
+ w, alignment, mSpacingMult, mSpacingAdd,
+ boring, mIncludePad);
+ }
+
+ if (useSaved) {
+ mSavedLayout = (BoringLayout) result;
+ }
+ } else if (shouldEllipsize && boring.width <= w) {
+ if (useSaved && mSavedLayout != null) {
+ result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
+ w, alignment, mSpacingMult, mSpacingAdd,
+ boring, mIncludePad, effectiveEllipsize,
+ ellipsisWidth);
+ } else {
+ result = BoringLayout.make(mTransformed, mTextPaint,
+ w, alignment, mSpacingMult, mSpacingAdd,
+ boring, mIncludePad, effectiveEllipsize,
+ ellipsisWidth);
+ }
+ } else if (shouldEllipsize) {
+ result = new StaticLayout(mTransformed,
+ 0, mTransformed.length(),
+ mTextPaint, w, alignment, mTextDir, mSpacingMult,
+ mSpacingAdd, mIncludePad, effectiveEllipsize,
+ ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
+ } else {
+ result = new StaticLayout(mTransformed, mTextPaint,
+ w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
+ mIncludePad);
+ }
+ } else if (shouldEllipsize) {
+ result = new StaticLayout(mTransformed,
+ 0, mTransformed.length(),
+ mTextPaint, w, alignment, mTextDir, mSpacingMult,
+ mSpacingAdd, mIncludePad, effectiveEllipsize,
+ ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
+ } else {
+ result = new StaticLayout(mTransformed, mTextPaint,
+ w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
+ mIncludePad);
+ }
+ }
+ return result;
+ }
+
private boolean compressText(float width) {
if (isHardwareAccelerated()) return false;
@@ -7179,7 +7245,9 @@
private boolean canMarquee() {
int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
- return width > 0 && mLayout.getLineWidth(0) > width;
+ return width > 0 && (mLayout.getLineWidth(0) > width ||
+ (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
+ mSavedMarqueeModeLayout.getLineWidth(0) > width));
}
private void startMarquee() {
@@ -7193,6 +7261,16 @@
if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
getLineCount() == 1 && canMarquee()) {
+ if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
+ mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
+ final Layout tmp = mLayout;
+ mLayout = mSavedMarqueeModeLayout;
+ mSavedMarqueeModeLayout = tmp;
+ setHorizontalFadingEdgeEnabled(true);
+ requestLayout();
+ invalidate();
+ }
+
if (mMarquee == null) mMarquee = new Marquee(this);
mMarquee.start(mMarqueeRepeatLimit);
}
@@ -7202,6 +7280,16 @@
if (mMarquee != null && !mMarquee.isStopped()) {
mMarquee.stop();
}
+
+ if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
+ mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
+ final Layout tmp = mSavedMarqueeModeLayout;
+ mSavedMarqueeModeLayout = mLayout;
+ mLayout = tmp;
+ setHorizontalFadingEdgeEnabled(false);
+ requestLayout();
+ invalidate();
+ }
}
private void startStopMarquee(boolean start) {
@@ -7782,7 +7870,9 @@
@Override
protected void initContentView() {
- mContentView.setOrientation(LinearLayout.HORIZONTAL);
+ LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
+ linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mContentView = linearLayout;
mContentView.setBackgroundResource(
com.android.internal.R.drawable.text_edit_side_paste_window);
@@ -8031,6 +8121,7 @@
Selection.setSelection((Spannable) mText, selStart, selEnd);
} else {
hideControllers();
+ downgradeEasyCorrectionSpans();
}
// No need to create the controller
@@ -8091,17 +8182,6 @@
startStopMarquee(hasWindowFocus);
}
- private void removeSpans(int start, int end, Class<?> type) {
- if (mText instanceof Editable) {
- Editable editable = ((Editable) mText);
- Object[] spans = editable.getSpans(start, end, type);
- final int length = spans.length;
- for (int i = 0; i < length; i++) {
- editable.removeSpan(spans[i]);
- }
- }
- }
-
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -8166,10 +8246,10 @@
return superResult;
}
- final boolean touchIsFinished = action == MotionEvent.ACTION_UP && !mIgnoreActionUpEvent &&
- isFocused();
+ final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
+ !shouldIgnoreActionUpEvent() && isFocused();
- if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
+ if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
@@ -8177,9 +8257,10 @@
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
- if (mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable && touchIsFinished) {
+ if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable) {
// The LinkMovementMethod which should handle taps on links has not been installed
- // to support text selection. We reproduce its behavior here to open links.
+ // on non editable text that support text selection.
+ // We reproduce its behavior here to open links for these.
ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
getSelectionEnd(), ClickableSpan.class);
@@ -8189,7 +8270,7 @@
}
}
- if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
+ if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
@@ -8200,16 +8281,12 @@
}
boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
- if (!selectAllGotFocus && hasSelection()) {
- startSelectionActionMode();
- } else {
- hideControllers();
- if (!selectAllGotFocus && mText.length() > 0) {
- if (isCursorInsideEasyCorrectionSpan()) {
- showSuggestions();
- } else if (hasInsertionController()) {
- getInsertionController().show();
- }
+ hideControllers();
+ if (!selectAllGotFocus && mText.length() > 0) {
+ if (isCursorInsideEasyCorrectionSpan()) {
+ showSuggestions();
+ } else if (hasInsertionController()) {
+ getInsertionController().show();
}
}
@@ -8251,6 +8328,26 @@
return false;
}
+ /**
+ * Downgrades to simple suggestions all the easy correction spans that are not a spell check
+ * span.
+ */
+ private void downgradeEasyCorrectionSpans() {
+ if (mText instanceof Spannable) {
+ Spannable spannable = (Spannable) mText;
+ SuggestionSpan[] suggestionSpans = spannable.getSpans(0,
+ spannable.length(), SuggestionSpan.class);
+ for (int i = 0; i < suggestionSpans.length; i++) {
+ int flags = suggestionSpans[i].getFlags();
+ if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
+ && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) {
+ flags = flags & ~SuggestionSpan.FLAG_EASY_CORRECT;
+ suggestionSpans[i].setFlags(flags);
+ }
+ }
+ }
+ }
+
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable && mLayout != null) {
@@ -8321,7 +8418,18 @@
super.cancelLongPress();
mIgnoreActionUpEvent = true;
}
-
+
+ /**
+ * This method is only valid during a touch event.
+ *
+ * @return true when the ACTION_UP event should be ignored, false otherwise.
+ *
+ * @hide
+ */
+ public boolean shouldIgnoreActionUpEvent() {
+ return mIgnoreActionUpEvent;
+ }
+
@Override
public boolean onTrackballEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable &&
@@ -8407,7 +8515,8 @@
@Override
protected float getLeftFadingEdgeStrength() {
if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
- if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
+ mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
if (mMarquee != null && !mMarquee.isStopped()) {
final Marquee marquee = mMarquee;
if (marquee.shouldDrawLeftFade()) {
@@ -8436,7 +8545,8 @@
@Override
protected float getRightFadingEdgeStrength() {
if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
- if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
+ mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
if (mMarquee != null && !mMarquee.isStopped()) {
final Marquee marquee = mMarquee;
return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
@@ -9073,6 +9183,7 @@
startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
+ getSelectionController().hide();
selectCurrentWord();
getSelectionController().show();
}
@@ -9120,7 +9231,8 @@
}
private interface TextViewPositionListener {
- public void updatePosition(int parentPositionX, int parentPositionY, boolean modified);
+ public void updatePosition(int parentPositionX, int parentPositionY,
+ boolean parentPositionChanged, boolean parentScrolled);
}
private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
@@ -9133,6 +9245,7 @@
// Absolute position of the TextView with respect to its parent window
private int mPositionX, mPositionY;
private int mNumberOfListeners;
+ private boolean mScrollHasChanged;
public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
if (mNumberOfListeners == 0) {
@@ -9184,15 +9297,16 @@
updatePosition();
for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
- if (mPositionHasChanged || mCanMove[i]) {
+ if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
TextViewPositionListener positionListener = mPositionListeners[i];
if (positionListener != null) {
positionListener.updatePosition(mPositionX, mPositionY,
- mPositionHasChanged);
+ mPositionHasChanged, mScrollHasChanged);
}
}
}
+ mScrollHasChanged = false;
return true;
}
@@ -9234,11 +9348,23 @@
final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
return isVisible(primaryHorizontal, lineBottom);
}
+
+ public void onScrollChanged() {
+ mScrollHasChanged = true;
+ }
+ }
+
+ @Override
+ protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
+ super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
+ if (mPositionListener != null) {
+ mPositionListener.onScrollChanged();
+ }
}
private abstract class PinnedPopupWindow implements TextViewPositionListener {
protected PopupWindow mPopupWindow;
- protected LinearLayout mContentView;
+ protected ViewGroup mContentView;
int mPositionX, mPositionY;
protected abstract void createPopupWindow();
@@ -9254,35 +9380,41 @@
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
- mContentView = new LinearLayout(TextView.this.getContext());
- LayoutParams wrapContent = new LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ initContentView();
+
+ LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
mContentView.setLayoutParams(wrapContent);
- initContentView();
mPopupWindow.setContentView(mContentView);
}
public void show() {
- final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
- mContentView.measure(
- View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
- View.MeasureSpec.AT_MOST),
- View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
- View.MeasureSpec.AT_MOST));
-
- TextView.this.getPositionListener().addSubscriber(this, false);
+ TextView.this.getPositionListener().addSubscriber(this, false /* offset is fixed */);
computeLocalPosition();
final PositionListener positionListener = TextView.this.getPositionListener();
updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
}
+
+ protected void measureContent() {
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+ mContentView.measure(
+ View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
+ View.MeasureSpec.AT_MOST),
+ View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
+ View.MeasureSpec.AT_MOST));
+ }
+ /* The popup window will be horizontally centered on the getTextOffset() and vertically
+ * positioned according to viewportToContentHorizontalOffset.
+ *
+ * This method assumes that mContentView has properly been measured from its content. */
private void computeLocalPosition() {
- final int offset = getTextOffset();
-
+ measureContent();
final int width = mContentView.getMeasuredWidth();
+ final int offset = getTextOffset();
mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f);
mPositionX += viewportToContentHorizontalOffset();
@@ -9317,8 +9449,11 @@
}
@Override
- public void updatePosition(int parentPositionX, int parentPositionY, boolean modified) {
+ public void updatePosition(int parentPositionX, int parentPositionY,
+ boolean parentPositionChanged, boolean parentScrolled) {
+ // Either parentPositionChanged or parentScrolled is true, check if still visible
if (isShowing() && getPositionListener().isOffsetVisible(getTextOffset())) {
+ if (parentScrolled) computeLocalPosition();
updatePosition(parentPositionX, parentPositionY);
} else {
hide();
@@ -9330,42 +9465,16 @@
}
}
- private static class SuggestionRangeSpan extends CharacterStyle {
-
- private final int mTextColor;
- private final int mBackgroundColor;
-
- public SuggestionRangeSpan(Context context) {
- TypedArray typedArray = context.obtainStyledAttributes(null,
- com.android.internal.R.styleable.SuggestionRangeSpan,
- com.android.internal.R.attr.textAppearanceSuggestionRange, 0);
-
- mTextColor = typedArray.getColor(
- com.android.internal.R.styleable.SuggestionRangeSpan_textColor, 0);
- mBackgroundColor = typedArray.getColor(
- com.android.internal.R.styleable.SuggestionRangeSpan_colorBackground, 0);
- }
-
- @Override
- public void updateDrawState(TextPaint tp) {
- if (mTextColor != 0) {
- tp.setColor(mTextColor);
- }
-
- if (mBackgroundColor != 0) {
- tp.bgColor = mBackgroundColor;
- }
- }
- }
-
- private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnClickListener {
+ private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener {
private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
- private static final int NO_SUGGESTIONS = -1;
private static final float AVERAGE_HIGHLIGHTS_PER_SUGGESTION = 1.4f;
private WordIterator mSuggestionWordIterator;
private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan
[(int) (AVERAGE_HIGHLIGHTS_PER_SUGGESTION * MAX_NUMBER_SUGGESTIONS)];
+ private SuggestionInfo[] mSuggestionInfos;
+ private int mNumberOfSuggestions;
private boolean mCursorWasVisibleBeforeSuggestions;
+ private SuggestionAdapter mSuggestionsAdapter;
private class CustomPopupWindow extends PopupWindow {
public CustomPopupWindow(Context context, int defStyle) {
@@ -9406,29 +9515,16 @@
@Override
protected void initContentView() {
- mContentView.setOrientation(LinearLayout.VERTICAL);
+ ListView listView = new ListView(TextView.this.getContext());
+ mSuggestionsAdapter = new SuggestionAdapter();
+ listView.setAdapter(mSuggestionsAdapter);
+ listView.setOnItemClickListener(this);
+ mContentView = listView;
- LayoutInflater inflater = (LayoutInflater) TextView.this.mContext.
- getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- if (inflater == null) {
- throw new IllegalArgumentException(
- "Unable to create inflater for TextEdit suggestions");
- }
-
- // Inflate the suggestion items once and for all.
- for (int i = 0; i < MAX_NUMBER_SUGGESTIONS; i++) {
- View childView = inflater.inflate(mTextEditSuggestionItemLayout,
- mContentView, false);
-
- if (! (childView instanceof TextView)) {
- throw new IllegalArgumentException(
- "Inflated TextEdit suggestion item is not a TextView: " + childView);
- }
-
- childView.setTag(new SuggestionInfo());
- mContentView.addView(childView);
- childView.setOnClickListener(this);
+ // Inflate the suggestion items once and for all. +1 for add to dictionary
+ mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 1];
+ for (int i = 0; i < MAX_NUMBER_SUGGESTIONS + 1; i++) {
+ mSuggestionInfos[i] = new SuggestionInfo();
}
}
@@ -9437,6 +9533,49 @@
int spanStart, spanEnd; // range in TextView where text should be inserted
SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
int suggestionIndex; // the index of the suggestion inside suggestionSpan
+ SpannableStringBuilder text = new SpannableStringBuilder();
+
+ void removeMisspelledFlag() {
+ int suggestionSpanFlags = suggestionSpan.getFlags();
+ if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
+ suggestionSpanFlags &= ~(SuggestionSpan.FLAG_MISSPELLED);
+ suggestionSpanFlags &= ~(SuggestionSpan.FLAG_EASY_CORRECT);
+ suggestionSpan.setFlags(suggestionSpanFlags);
+ }
+ }
+ }
+
+ private class SuggestionAdapter extends BaseAdapter {
+ private LayoutInflater mInflater = (LayoutInflater) TextView.this.mContext.
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ @Override
+ public int getCount() {
+ return mNumberOfSuggestions;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mSuggestionInfos[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView textView = (TextView) convertView;
+
+ if (textView == null) {
+ textView = (TextView) mInflater.inflate(mTextEditSuggestionItemLayout, parent,
+ false);
+ }
+
+ textView.setText(mSuggestionInfos[position].text);
+ return textView;
+ }
}
/**
@@ -9481,6 +9620,36 @@
}
@Override
+ protected void measureContent() {
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+ final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec(
+ displayMetrics.widthPixels, View.MeasureSpec.AT_MOST);
+ final int verticalMeasure = View.MeasureSpec.makeMeasureSpec(
+ displayMetrics.heightPixels, View.MeasureSpec.AT_MOST);
+
+ int width = 0;
+ View view = null;
+ for (int i = 0; i < mNumberOfSuggestions; i++) {
+ view = mSuggestionsAdapter.getView(i, view, mContentView);
+ view.measure(horizontalMeasure, verticalMeasure);
+ width = Math.max(width, view.getMeasuredWidth());
+ }
+
+ // Enforce the width based on actual text widths
+ mContentView.measure(
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+ verticalMeasure);
+
+ Drawable popupBackground = mPopupWindow.getBackground();
+ if (popupBackground != null) {
+ if (mTempRect == null) mTempRect = new Rect();
+ popupBackground.getPadding(mTempRect);
+ width += mTempRect.left + mTempRect.right;
+ }
+ mPopupWindow.setWidth(width);
+ }
+
+ @Override
protected int getTextOffset() {
return getSelectionStart();
}
@@ -9508,10 +9677,12 @@
final int nbSpans = suggestionSpans.length;
- int totalNbSuggestions = 0;
+ mNumberOfSuggestions = 0;
int spanUnionStart = mText.length();
int spanUnionEnd = 0;
+ SuggestionSpan misspelledSpan = null;
+
for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
final int spanStart = spannable.getSpanStart(suggestionSpan);
@@ -9519,20 +9690,23 @@
spanUnionStart = Math.min(spanStart, spanUnionStart);
spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
+ if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
+ misspelledSpan = suggestionSpan;
+ }
+
String[] suggestions = suggestionSpan.getSuggestions();
int nbSuggestions = suggestions.length;
for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
- TextView textView = (TextView) mContentView.getChildAt(
- totalNbSuggestions);
- textView.setText(suggestions[suggestionIndex]);
- SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
+ SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
suggestionInfo.spanStart = spanStart;
suggestionInfo.spanEnd = spanEnd;
suggestionInfo.suggestionSpan = suggestionSpan;
suggestionInfo.suggestionIndex = suggestionIndex;
+ suggestionInfo.text.replace(0, suggestionInfo.text.length(),
+ suggestions[suggestionIndex]);
- totalNbSuggestions++;
- if (totalNbSuggestions == MAX_NUMBER_SUGGESTIONS) {
+ mNumberOfSuggestions++;
+ if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
// Also end outer for loop
spanIndex = nbSpans;
break;
@@ -9540,54 +9714,38 @@
}
}
- if (totalNbSuggestions == 0) return false;
-
- if (mSuggestionRangeSpan == null) {
- mSuggestionRangeSpan = new SuggestionRangeSpan(getContext());
+ for (int i = 0; i < mNumberOfSuggestions; i++) {
+ highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd);
}
-
- ((Editable) mText).setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- if (mSuggestionRangeSpan == null) mSuggestionRangeSpan =
- new SuggestionRangeSpan(getContext());
+ if (misspelledSpan != null) {
+ final int misspelledStart = spannable.getSpanStart(misspelledSpan);
+ final int misspelledEnd = spannable.getSpanEnd(misspelledSpan);
+ if (misspelledStart >= 0 && misspelledEnd > misspelledStart) {
+ SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
+ suggestionInfo.spanStart = misspelledStart;
+ suggestionInfo.spanEnd = misspelledEnd;
+ suggestionInfo.suggestionSpan = misspelledSpan;
+ suggestionInfo.suggestionIndex = -1;
+ suggestionInfo.text.replace(0, suggestionInfo.text.length(),
+ getContext().getString(com.android.internal.R.string.addToDictionary));
+
+ mNumberOfSuggestions++;
+ }
+ }
+
+ if (mNumberOfSuggestions == 0) return false;
+
+ if (mSuggestionRangeSpan == null) mSuggestionRangeSpan =
+ new SuggestionRangeSpan(mHighlightColor);
((Editable) mText).setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- for (int i = 0; i < totalNbSuggestions; i++) {
- final TextView textView = (TextView) mContentView.getChildAt(i);
- highlightTextDifferences(textView, spanUnionStart, spanUnionEnd);
- }
-
- for (int i = 0; i < totalNbSuggestions; i++) {
- mContentView.getChildAt(i).setVisibility(VISIBLE);
- }
- for (int i = totalNbSuggestions; i < MAX_NUMBER_SUGGESTIONS; i++) {
- mContentView.getChildAt(i).setVisibility(GONE);
- }
+ mSuggestionsAdapter.notifyDataSetChanged();
return true;
}
- private void onDictionarySuggestionsReceived(String[] suggestions) {
- if (suggestions.length == 0) {
- // TODO Actual implementation of this feature
- suggestions = new String[] {"Add to dictionary"};
- }
-
- WordIterator wordIterator = getWordIterator();
- wordIterator.setCharSequence(mText);
-
- final int pos = getSelectionStart();
- int wordStart = wordIterator.getBeginning(pos);
- int wordEnd = wordIterator.getEnd(pos);
-
- SuggestionSpan suggestionSpan = new SuggestionSpan(getContext(), suggestions, 0);
- ((Editable) mText).setSpan(suggestionSpan, wordStart, wordEnd,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- show();
- }
-
private long[] getWordLimits(CharSequence text) {
// TODO locale for mSuggestionWordIterator
if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator();
@@ -9631,13 +9789,13 @@
return highlightSpan;
}
- private void highlightTextDifferences(TextView textView, int unionStart, int unionEnd) {
- SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
+ private void highlightTextDifferences(SuggestionInfo suggestionInfo,
+ int unionStart, int unionEnd) {
final int spanStart = suggestionInfo.spanStart;
final int spanEnd = suggestionInfo.spanEnd;
// Remove all text formating by converting to Strings
- final String text = textView.getText().toString();
+ final String text = suggestionInfo.text.toString();
final String sourceText = mText.subSequence(spanStart, spanEnd).toString();
long[] sourceWordLimits = getWordLimits(sourceText);
@@ -9712,7 +9870,7 @@
if (previousCommonWordIndex < words.length - 1) {
int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
- int lastDifferentPosition = textView.length();
+ int lastDifferentPosition = text.length();
ssb.setSpan(highlightSpan(nbHighlightSpans++),
shift + firstDifferentPosition, shift + lastDifferentPosition,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
@@ -9723,28 +9881,36 @@
int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 :
extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
- String textSpaces = text.substring(lastCommonTextWordEnd, textView.length());
+ String textSpaces = text.substring(lastCommonTextWordEnd, text.length());
if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) {
ssb.setSpan(highlightSpan(nbHighlightSpans++),
- shift + lastCommonTextWordEnd, shift + textView.length(),
+ shift + lastCommonTextWordEnd, shift + text.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// Final part, text after the current suggestion range.
ssb.append(mText.subSequence(spanEnd, unionEnd).toString());
- textView.setText(ssb);
}
@Override
- public void onClick(View view) {
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
- SuggestionInfo suggestionInfo = (SuggestionInfo) textView.getTag();
+
+ SuggestionInfo suggestionInfo = mSuggestionInfos[position];
final int spanStart = suggestionInfo.spanStart;
final int spanEnd = suggestionInfo.spanEnd;
- if (spanStart != NO_SUGGESTIONS) {
+ final String originalText = mText.subSequence(spanStart, spanEnd).toString();
+
+ if (suggestionInfo.suggestionIndex < 0) {
+ Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
+ intent.putExtra("word", originalText);
+ intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ suggestionInfo.removeMisspelledFlag();
+ } else {
// SuggestionSpans are removed by replace: save them before
Editable editable = (Editable) mText;
SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
@@ -9764,17 +9930,9 @@
final int suggestionEnd = suggestionInfo.suggestionEnd;
final String suggestion = textView.getText().subSequence(
suggestionStart, suggestionEnd).toString();
- final String originalText = mText.subSequence(spanStart, spanEnd).toString();
editable.replace(spanStart, spanEnd, suggestion);
- // A replacement on a misspelled text removes the misspelled flag.
- // TODO restore the flag if the misspelled word is selected back?
- int suggestionSpanFlags = suggestionInfo.suggestionSpan.getFlags();
- if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
- suggestionSpanFlags &= ~(SuggestionSpan.FLAG_MISSPELLED);
- suggestionSpanFlags &= ~(SuggestionSpan.FLAG_EASY_CORRECT);
- suggestionInfo.suggestionSpan.setFlags(suggestionSpanFlags);
- }
+ suggestionInfo.removeMisspelledFlag();
// Notify source IME of the suggestion pick. Do this before swaping texts.
if (!TextUtils.isEmpty(
@@ -9821,12 +9979,6 @@
return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
}
- void onDictionarySuggestionsReceived(String[] suggestions) {
- if (mSuggestionsPopupWindow != null) {
- mSuggestionsPopupWindow.onDictionarySuggestionsReceived(suggestions);
- }
- }
-
/**
* Return whether or not suggestions are enabled on this TextView. The suggestions are generated
* by the IME or by the spell checker as the user types. This is done by adding
@@ -10102,9 +10254,11 @@
@Override
protected void initContentView() {
- mContentView.setOrientation(LinearLayout.HORIZONTAL);
+ LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
+ linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mContentView = linearLayout;
mContentView.setBackgroundResource(
- com.android.internal.R.drawable.text_edit_side_paste_window);
+ com.android.internal.R.drawable.text_edit_paste_window);
LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -10252,7 +10406,7 @@
if (i > 0 && i < iMax &&
(now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
- positionAtCursorOffset(mPreviousOffsets[index]);
+ positionAtCursorOffset(mPreviousOffsets[index], false);
}
}
@@ -10268,11 +10422,11 @@
public void show() {
if (isShowing()) return;
- getPositionListener().addSubscriber(this, true);
+ getPositionListener().addSubscriber(this, true /* local position may change */);
// Make sure the offset is always considered new, even when focusing at same position
mPreviousOffset = -1;
- positionAtCursorOffset(getCurrentCursorOffset());
+ positionAtCursorOffset(getCurrentCursorOffset(), false);
hideActionPopupWindow();
}
@@ -10337,7 +10491,7 @@
public abstract void updatePosition(float x, float y);
- protected void positionAtCursorOffset(int offset) {
+ protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
// A HandleView relies on the layout, which may be nulled by external methods
if (mLayout == null) {
// Will update controllers' state, hiding them and stopping selection mode if needed
@@ -10345,7 +10499,7 @@
return;
}
- if (offset != mPreviousOffset) {
+ if (offset != mPreviousOffset || parentScrolled) {
updateSelection(offset);
addPositionToTouchUpFilter(offset);
final int line = mLayout.getLineForOffset(offset);
@@ -10362,9 +10516,10 @@
}
}
- public void updatePosition(int parentPositionX, int parentPositionY, boolean modified) {
- positionAtCursorOffset(getCurrentCursorOffset());
- if (modified || mPositionHasChanged) {
+ public void updatePosition(int parentPositionX, int parentPositionY,
+ boolean parentPositionChanged, boolean parentScrolled) {
+ positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
+ if (parentPositionChanged || mPositionHasChanged) {
if (mIsDragging) {
// Update touchToWindow offset in case of parent scrolling while dragging
if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
@@ -10569,7 +10724,7 @@
@Override
public void updatePosition(float x, float y) {
- positionAtCursorOffset(getOffsetForPosition(x, y));
+ positionAtCursorOffset(getOffsetForPosition(x, y), false);
}
@Override
@@ -10608,17 +10763,13 @@
@Override
public void updatePosition(float x, float y) {
- final int selectionStart = getSelectionStart();
- final int selectionEnd = getSelectionEnd();
-
int offset = getOffsetForPosition(x, y);
- // No need to redraw when the offset is unchanged
- if (offset == selectionStart) return;
// Handles can not cross and selection is at least one character
+ final int selectionEnd = getSelectionEnd();
if (offset >= selectionEnd) offset = selectionEnd - 1;
- positionAtCursorOffset(offset);
+ positionAtCursorOffset(offset, false);
}
public ActionPopupWindow getActionPopupWindow() {
@@ -10649,17 +10800,13 @@
@Override
public void updatePosition(float x, float y) {
- final int selectionStart = getSelectionStart();
- final int selectionEnd = getSelectionEnd();
-
int offset = getOffsetForPosition(x, y);
- // No need to redraw when the offset is unchanged
- if (offset == selectionEnd) return;
// Handles can not cross and selection is at least one character
+ final int selectionStart = getSelectionStart();
if (offset <= selectionStart) offset = selectionStart + 1;
- positionAtCursorOffset(offset);
+ positionAtCursorOffset(offset, false);
}
public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
@@ -11194,7 +11341,7 @@
private final TextPaint mTextPaint;
private boolean mUserSetTextScaleX;
private final Paint mHighlightPaint;
- private int mHighlightColor = 0xCC475925;
+ private int mHighlightColor = 0x4C33B5E5;
/**
* This is temporarily visible to fix bug 3085564 in webView. Do not rely on
* this field being protected. Will be restored as private when lineHeight
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 0547438..2350229 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -29,6 +29,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.NumberPicker.OnValueChangeListener;
import java.text.DateFormatSymbols;
@@ -227,6 +228,11 @@
if (!isEnabled()) {
setEnabled(false);
}
+
+ // set the content descriptions
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ setContentDescriptions();
+ }
}
@Override
@@ -433,6 +439,12 @@
}
@Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return true;
+ }
+
+ @Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
@@ -487,4 +499,22 @@
mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
}
}
+
+ private void setContentDescriptions() {
+ // Minute
+ String text = mContext.getString(R.string.time_picker_increment_minute_button);
+ mMinuteSpinner.findViewById(R.id.increment).setContentDescription(text);
+ text = mContext.getString(R.string.time_picker_decrement_minute_button);
+ mMinuteSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ // Hour
+ text = mContext.getString(R.string.time_picker_increment_hour_button);
+ mHourSpinner.findViewById(R.id.increment).setContentDescription(text);
+ text = mContext.getString(R.string.time_picker_decrement_hour_button);
+ mHourSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ // AM/PM
+ text = mContext.getString(R.string.time_picker_increment_set_pm_button);
+ mAmPmSpinner.findViewById(R.id.increment).setContentDescription(text);
+ text = mContext.getString(R.string.time_picker_decrement_set_am_button);
+ mAmPmSpinner.findViewById(R.id.decrement).setContentDescription(text);
+ }
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index ce0299c..683aca5 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -68,5 +68,5 @@
boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
boolean switchToLastInputMethod(in IBinder token);
boolean setInputMethodEnabled(String id, boolean enabled);
- boolean setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
+ oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index ff26d50..63b0274 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -266,6 +266,17 @@
if (mActionMenuPresenter != null) {
mActionMenuPresenter.onConfigurationChanged(newConfig);
}
+
+ mTitleView = null;
+ mSubtitleView = null;
+ mTitleUpView = null;
+ if (mTitleLayout != null && mTitleLayout.getParent() == this) {
+ removeView(mTitleLayout);
+ }
+ mTitleLayout = null;
+ if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+ initTitle();
+ }
}
@Override
diff --git a/core/java/com/android/internal/widget/LockScreenWidgetInterface.java b/core/java/com/android/internal/widget/LockScreenWidgetInterface.java
index 6dfcc75..8f80cfc 100644
--- a/core/java/com/android/internal/widget/LockScreenWidgetInterface.java
+++ b/core/java/com/android/internal/widget/LockScreenWidgetInterface.java
@@ -20,4 +20,6 @@
public void setCallback(LockScreenWidgetCallback callback);
+ public boolean providesClock();
+
}
diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java
index 1c47ca8..29ad15b 100644
--- a/core/java/com/android/internal/widget/TransportControlView.java
+++ b/core/java/com/android/internal/widget/TransportControlView.java
@@ -381,4 +381,8 @@
mWidgetCallbacks = callback;
}
+ public boolean providesClock() {
+ return false;
+ }
+
}
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
index 76bc535..cd1f8ba 100644
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
@@ -902,13 +902,13 @@
String directionDescription = getDirectionDescription(i);
if (!TextUtils.isEmpty(targetDescription)
&& !TextUtils.isEmpty(directionDescription)) {
- utterance.append(targetDescription);
- utterance.append(" ");
- utterance.append(directionDescription);
- utterance.append(".");
+ String text = String.format(directionDescription, targetDescription);
+ utterance.append(text);
+ }
+ if (utterance.length() > 0) {
+ announceText(utterance.toString());
}
}
- announceText(utterance.toString());
}
private void announceText(String text) {
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index ddac820..419e464 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -352,7 +352,6 @@
char buf[32];
int len;
snprintf(buf, sizeof(buf), "%lld", value);
- jchar* dst = env->GetCharArrayElements(buffer, NULL);
sizeCopied = charToJchar(buf, dst, bufferSize);
}
} else if (type == FIELD_TYPE_FLOAT) {
@@ -360,7 +359,6 @@
if (window->getDouble(row, column, &value)) {
char tempbuf[32];
snprintf(tempbuf, sizeof(tempbuf), "%g", value);
- jchar* dst = env->GetCharArrayElements(buffer, NULL);
sizeCopied = charToJchar(tempbuf, dst, bufferSize);
}
} else if (type == FIELD_TYPE_NULL) {
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 0c2801c..5fcf8fa 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -111,7 +111,9 @@
NativeInputChannel* nativeInputChannel) {
jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
gInputChannelClassInfo.ctor);
- android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel);
+ if (inputChannelObj) {
+ android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel);
+ }
return inputChannelObj;
}
@@ -126,18 +128,29 @@
status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
if (result) {
- LOGE("Could not open input channel pair. status=%d", result);
- jniThrowRuntimeException(env, "Could not open input channel pair.");
+ String8 message;
+ message.appendFormat("Could not open input channel pair. status=%d", result);
+ jniThrowRuntimeException(env, message.string());
return NULL;
}
- // TODO more robust error checking
+ jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
+
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(serverChannel));
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
+
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(clientChannel));
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
- jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;
@@ -161,7 +174,7 @@
static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
jobject otherObj) {
- if (android_view_InputChannel_getInputChannel(env, otherObj) != NULL) {
+ if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"Other object already has a native input channel.");
return;
@@ -175,7 +188,7 @@
static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
jobject parcelObj) {
- if (android_view_InputChannel_getInputChannel(env, obj) != NULL) {
+ if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"This object already has a native input channel.");
return;
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 80c4871..300c04a 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -455,8 +455,9 @@
env, inputChannelObj, inputHandlerObj, messageQueueObj);
if (status) {
- jniThrowRuntimeException(env, "Failed to register input channel. "
- "Check logs for details.");
+ String8 message;
+ message.appendFormat("Failed to register input channel. status=%d", status);
+ jniThrowRuntimeException(env, message.string());
}
}
@@ -465,8 +466,9 @@
status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj);
if (status) {
- jniThrowRuntimeException(env, "Failed to unregister input channel. "
- "Check logs for details.");
+ String8 message;
+ message.appendFormat("Failed to unregister input channel. status=%d", status);
+ jniThrowRuntimeException(env, message.string());
}
}
@@ -479,8 +481,9 @@
// was no longer registered (DEAD_OBJECT) since it is a common race that can occur
// during application shutdown. The input dispatcher recovers gracefully anyways.
if (status != OK && status != DEAD_OBJECT) {
- jniThrowRuntimeException(env, "Failed to finish input event. "
- "Check logs for details.");
+ String8 message;
+ message.appendFormat("Failed to finish input event. status=%d", status);
+ jniThrowRuntimeException(env, message.string());
}
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 4c1ca31..49441eb 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -589,7 +589,7 @@
}
static void Surface_setPosition(
- JNIEnv* env, jobject clazz, jint x, jint y)
+ JNIEnv* env, jobject clazz, jfloat x, jfloat y)
{
const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
if (surface == 0) return;
@@ -832,7 +832,7 @@
{"screenshot", "(II)Landroid/graphics/Bitmap;", (void*)Surface_screenshotAll },
{"screenshot", "(IIII)Landroid/graphics/Bitmap;", (void*)Surface_screenshot },
{"setLayer", "(I)V", (void*)Surface_setLayer },
- {"setPosition", "(II)V",(void*)Surface_setPosition },
+ {"setPosition", "(FF)V",(void*)Surface_setPosition },
{"setSize", "(II)V",(void*)Surface_setSize },
{"hide", "()V", (void*)Surface_hide },
{"show", "()V", (void*)Surface_show },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 74648a3..162cab6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -920,7 +920,7 @@
<!-- Allows applications to write the apn settings -->
<permission android:name="android.permission.WRITE_APN_SETTINGS"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
- android:protectionLevel="dangerous"
+ android:protectionLevel="signatureOrSystem"
android:description="@string/permdesc_writeApnSettings"
android:label="@string/permlab_writeApnSettings" />
diff --git a/core/res/res/anim/wallpaper_close_exit.xml b/core/res/res/anim/wallpaper_close_exit.xml
index 7e0e05f..987fd89 100644
--- a/core/res/res/anim/wallpaper_close_exit.xml
+++ b/core/res/res/anim/wallpaper_close_exit.xml
@@ -29,4 +29,7 @@
android:fillEnabled="true" android:fillAfter="true"
android:interpolator="@interpolator/decelerate_quint"
android:duration="300" />
+ <!-- This is needed to keep the animation running while wallpaper_close_enter completes -->
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:duration="600" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/wallpaper_open_exit.xml b/core/res/res/anim/wallpaper_open_exit.xml
index 075831b..1804fa8 100644
--- a/core/res/res/anim/wallpaper_open_exit.xml
+++ b/core/res/res/anim/wallpaper_open_exit.xml
@@ -29,5 +29,7 @@
android:interpolator="@interpolator/accelerate_quint"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:duration="200" />
-
+ <!-- This is needed to keep the animation running while wallpaper_open_enter completes -->
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:duration="500" />
</set>
\ No newline at end of file
diff --git a/core/res/res/drawable-hdpi/text_edit_paste_window.9.png b/core/res/res/drawable-hdpi/text_edit_paste_window.9.png
index 6654e4d..b74f37b 100644
--- a/core/res/res/drawable-hdpi/text_edit_paste_window.9.png
+++ b/core/res/res/drawable-hdpi/text_edit_paste_window.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_edit_suggestions_window.9.png b/core/res/res/drawable-hdpi/text_edit_suggestions_window.9.png
index c97514f..b74f37b 100644
--- a/core/res/res/drawable-hdpi/text_edit_suggestions_window.9.png
+++ b/core/res/res/drawable-hdpi/text_edit_suggestions_window.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_edit_paste_window.9.png b/core/res/res/drawable-mdpi/text_edit_paste_window.9.png
index 41886eb..16f623d 100644
--- a/core/res/res/drawable-mdpi/text_edit_paste_window.9.png
+++ b/core/res/res/drawable-mdpi/text_edit_paste_window.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_edit_suggestions_window.9.png b/core/res/res/drawable-mdpi/text_edit_suggestions_window.9.png
index 88be6e1..16f623d 100644
--- a/core/res/res/drawable-mdpi/text_edit_suggestions_window.9.png
+++ b/core/res/res/drawable-mdpi/text_edit_suggestions_window.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_edit_paste_window.9.png b/core/res/res/drawable-xhdpi/text_edit_paste_window.9.png
new file mode 100644
index 0000000..5c043b6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/text_edit_paste_window.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_edit_suggestions_window.9.png b/core/res/res/drawable-xhdpi/text_edit_suggestions_window.9.png
new file mode 100644
index 0000000..5c043b6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/text_edit_suggestions_window.9.png
Binary files differ
diff --git a/core/res/res/drawable/switch_inner_holo_dark.xml b/core/res/res/drawable/switch_inner_holo_dark.xml
index 3eb55ee..67584bc 100644
--- a/core/res/res/drawable/switch_inner_holo_dark.xml
+++ b/core/res/res/drawable/switch_inner_holo_dark.xml
@@ -17,5 +17,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:drawable="@drawable/switch_thumb_disabled_holo_dark" />
<item android:state_pressed="true" android:drawable="@drawable/switch_thumb_pressed_holo_dark" />
+ <item android:state_checked="true" android:drawable="@drawable/switch_thumb_activated_holo_dark" />
<item android:drawable="@drawable/switch_thumb_holo_dark" />
</selector>
diff --git a/core/res/res/drawable/switch_inner_holo_light.xml b/core/res/res/drawable/switch_inner_holo_light.xml
index 9b287cf..95df0e88 100644
--- a/core/res/res/drawable/switch_inner_holo_light.xml
+++ b/core/res/res/drawable/switch_inner_holo_light.xml
@@ -17,5 +17,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:drawable="@drawable/switch_thumb_disabled_holo_light" />
<item android:state_pressed="true" android:drawable="@drawable/switch_thumb_pressed_holo_light" />
+ <item android:state_checked="true" android:drawable="@drawable/switch_thumb_activated_holo_light" />
<item android:drawable="@drawable/switch_thumb_holo_light" />
</selector>
diff --git a/core/res/res/layout-sw600dp/status_bar_latest_event_ticker.xml b/core/res/res/layout-sw600dp/status_bar_latest_event_ticker.xml
index 7631781..269e086 100644
--- a/core/res/res/layout-sw600dp/status_bar_latest_event_ticker.xml
+++ b/core/res/res/layout-sw600dp/status_bar_latest_event_ticker.xml
@@ -38,8 +38,8 @@
android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Info"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginTop="-10dp"
+ android:layout_marginBottom="6dip"
+ android:gravity="bottom"
android:singleLine="true"
/>
</LinearLayout>
-
diff --git a/core/res/res/layout-sw600dp/status_bar_latest_event_ticker_large_icon.xml b/core/res/res/layout-sw600dp/status_bar_latest_event_ticker_large_icon.xml
index ff0f7d4..69eac92 100644
--- a/core/res/res/layout-sw600dp/status_bar_latest_event_ticker_large_icon.xml
+++ b/core/res/res/layout-sw600dp/status_bar_latest_event_ticker_large_icon.xml
@@ -33,8 +33,9 @@
android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Info"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:layout_marginBottom="6dip"
+ android:gravity="bottom"
android:singleLine="true"
- android:layout_marginTop="-10dp"
/>
<ImageView android:id="@+id/icon"
android:layout_width="wrap_content"
diff --git a/core/res/res/layout/action_bar_title_item.xml b/core/res/res/layout/action_bar_title_item.xml
index 0828402..4c74f6a 100644
--- a/core/res/res/layout/action_bar_title_item.xml
+++ b/core/res/res/layout/action_bar_title_item.xml
@@ -16,7 +16,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingRight="16dip"
android:background="?android:attr/actionBarItemBackground"
@@ -41,6 +41,8 @@
<TextView android:id="@+id/action_bar_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/action_bar_subtitle_top_margin"
+ android:layout_marginBottom="@dimen/action_bar_subtitle_bottom_margin"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone" />
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 4c8c0d1..0368530 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -131,7 +131,7 @@
android:targetDrawables="@array/lockscreen_targets_with_camera"
android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
- android:directionDescriptions="@array/lockscreen_direction_descriptions_with_camera"
+ android:directionDescriptions="@array/lockscreen_direction_descriptions"
android:handleDrawable="@drawable/ic_lockscreen_handle"
android:waveDrawable="@drawable/ic_lockscreen_outerring"
android:outerRadius="@dimen/multiwaveview_target_placement_radius"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index ba55f0c0..2849376 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -136,7 +136,7 @@
android:targetDrawables="@array/lockscreen_targets_with_camera"
android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
- android:directionDescriptions="@array/lockscreen_direction_descriptions_with_camera"
+ android:directionDescriptions="@array/lockscreen_direction_descriptions"
android:handleDrawable="@drawable/ic_lockscreen_handle"
android:waveDrawable="@drawable/ic_lockscreen_outerring"
android:outerRadius="@dimen/multiwaveview_target_placement_radius"
diff --git a/core/res/res/layout/text_edit_action_popup_text.xml b/core/res/res/layout/text_edit_action_popup_text.xml
index aa6a5e1..42e6f2e 100644
--- a/core/res/res/layout/text_edit_action_popup_text.xml
+++ b/core/res/res/layout/text_edit_action_popup_text.xml
@@ -25,4 +25,5 @@
android:textAppearance="?android:attr/textAppearanceSmallInverse"
android:textColor="@android:color/black"
android:textAllCaps="true"
+ android:textStyle="bold"
/>
diff --git a/core/res/res/values-land/arrays.xml b/core/res/res/values-land/arrays.xml
index 57aafc8..68e5cfd 100644
--- a/core/res/res/values-land/arrays.xml
+++ b/core/res/res/values-land/arrays.xml
@@ -34,7 +34,7 @@
<item>@string/description_target_soundon</item>
</array>
- <array name="lockscreen_direction_descriptions_when_silent">
+ <array name="lockscreen_direction_descriptions">
<item>@null</item>
<item>@string/description_direction_up</item>
<item>@null</item>
@@ -55,13 +55,6 @@
<item>@string/description_target_silent</item>
</array>
- <array name="lockscreen_direction_descriptions_when_soundon">
- <item>@null</item>
- <item>@string/description_direction_up</item>
- <item>@null</item>
- <item>@string/description_direction_down</item>
- </array>
-
<array name="lockscreen_targets_with_camera">
<item>@null</item>
<item>@drawable/ic_lockscreen_unlock</item>
@@ -76,11 +69,4 @@
<item>@string/description_target_camera</item>
</array>
- <array name="lockscreen_direction_descriptions_with_camera">
- <item>@null</item>
- <item>@string/description_direction_up</item>
- <item>@null</item>
- <item>@string/description_direction_down</item>
- </array>
-
</resources>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index 02bb3c8..6f96852 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -35,6 +35,10 @@
<dimen name="action_bar_title_text_size">16dp</dimen>
<!-- Text size for action bar subtitles -->
<dimen name="action_bar_subtitle_text_size">12dp</dimen>
+ <!-- Top margin for action bar subtitles -->
+ <dimen name="action_bar_subtitle_top_margin">-2dp</dimen>
+ <!-- Bottom margin for action bar subtitles -->
+ <dimen name="action_bar_subtitle_bottom_margin">4dip</dimen>
<!-- Size of clock font in LockScreen on Unsecure unlock screen. -->
<dimen name="keyguard_lockscreen_clock_font_size">70sp</dimen>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 792066e..2daaaa2 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -37,6 +37,10 @@
<dimen name="action_bar_title_text_size">18dp</dimen>
<!-- Text size for action bar subtitles -->
<dimen name="action_bar_subtitle_text_size">14dp</dimen>
+ <!-- Top margin for action bar subtitles -->
+ <dimen name="action_bar_subtitle_top_margin">-3dp</dimen>
+ <!-- Bottom margin for action bar subtitles -->
+ <dimen name="action_bar_subtitle_bottom_margin">9dip</dimen>
<!-- Size of clock font in LockScreen. -->
<dimen name="keyguard_pattern_unlock_clock_font_size">98sp</dimen>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index c9043ba..8d5bd0b 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -356,7 +356,7 @@
<item>@null</item>
</array>
- <array name="lockscreen_direction_descriptions_when_silent">
+ <array name="lockscreen_direction_descriptions">
<item>@string/description_direction_right</item>
<item>@null</item>
<item>@string/description_direction_left</item>
@@ -377,13 +377,6 @@
<item>@null</item>
</array>
- <array name="lockscreen_direction_descriptions_when_soundon">
- <item>@string/description_direction_right</item>
- <item>@null</item>
- <item>@string/description_direction_left</item>
- <item>@null</item>
- </array>
-
<array name="lockscreen_targets_with_camera">
<item>@drawable/ic_lockscreen_unlock</item>
<item>@null</item>
@@ -398,11 +391,4 @@
<item>@null</item>
</array>
- <array name="lockscreen_direction_descriptions_with_camera">
- <item>@string/description_direction_right</item>
- <item>@null</item>
- <item>@string/description_direction_left</item>
- <item>@null</item>
- </array>
-
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index fed5651..0bf5b0a 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -158,10 +158,6 @@
<!-- The underline color and thickness for misspelled suggestion -->
<attr name="textAppearanceMisspelledSuggestion" format="reference" />
- <!-- The text color and the background for suggestion range span. This span identifies the
- portion of text the suggestions refer to). -->
- <attr name="textAppearanceSuggestionRange" format="reference" />
-
<!-- The underline color -->
<attr name="textUnderlineColor" format="reference|color" />
<!-- The underline thickness -->
@@ -2241,6 +2237,10 @@
InputMethodManager#switchToLastInputMethod will ignore auxiliary subtypes when it
chooses a target subtype. -->
<attr name="isAuxiliary" format="boolean" />
+ <!-- Set true when this subtype should be selected by default if no other subtypes are
+ selected explicitly. Note that a subtype with this parameter being true will
+ not be shown in the subtypes list. -->
+ <attr name="overridesImplicitlyEnabledSubtype" format="boolean" />
<!-- The extra value of the subtype. This string can be any string and will be passed to
the IME when the framework calls the IME with the subtype. -->
<attr name="imeSubtypeExtraValue" format="string" />
@@ -3154,10 +3154,6 @@
<attr name="textUnderlineColor" />
<attr name="textUnderlineThickness" />
</declare-styleable>
- <declare-styleable name="SuggestionRangeSpan">
- <attr name="textColor" />
- <attr name="colorBackground" />
- </declare-styleable>
<!-- An <code>input-extras</code> is a container for extra data to supply to
an input method. Contains
one more more {@link #Extra <extra>} tags. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index aee7ea4..fd525f3 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -54,8 +54,6 @@
<color name="dim_foreground_light_inverse">#bebebe</color>
<color name="dim_foreground_light_inverse_disabled">#80bebebe</color>
<color name="hint_foreground_light">#808080</color>
- <color name="highlight_background">#cc475925</color>
- <color name="highlight_background_inverse">#ccd2e461</color>
<color name="highlighted_text_dark">#9983CC39</color>
<color name="highlighted_text_light">#9983CC39</color>
<color name="link_text_dark">#5c5cff</color>
@@ -135,10 +133,8 @@
<color name="dim_foreground_inverse_holo_light">#bebebe</color>
<color name="dim_foreground_inverse_disabled_holo_light">#80bebebe</color>
<color name="hint_foreground_holo_light">#808080</color>
- <color name="highlight_background_holo">#cc475925</color>
- <color name="highlight_background_inverse_holo">#ccd2e461</color>
- <color name="highlighted_text_holo_dark">#9983CC39</color>
- <color name="highlighted_text_holo_light">#9983CC39</color>
+ <color name="highlighted_text_holo_dark">#4c33b5e5</color>
+ <color name="highlighted_text_holo_light">#4c33b5e5</color>
<color name="link_text_holo_dark">#5c5cff</color>
<color name="link_text_holo_light">#0000ee</color>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 62a2187..2753eab 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -146,6 +146,10 @@
<dimen name="action_bar_title_text_size">18dp</dimen>
<!-- Text size for action bar subtitles -->
<dimen name="action_bar_subtitle_text_size">14dp</dimen>
+ <!-- Top margin for action bar subtitles -->
+ <dimen name="action_bar_subtitle_top_margin">-3dp</dimen>
+ <!-- Bottom margin for action bar subtitles -->
+ <dimen name="action_bar_subtitle_bottom_margin">5dip</dimen>
<!-- Size of clock font in LockScreen on Unsecure unlock screen. -->
<dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6988f6b..bc2b907 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2010,4 +2010,5 @@
<public type="attr" name="targetDescriptions" />
<public type="attr" name="directionDescriptions" />
+ <public type="attr" name="overridesImplicitlyEnabledSubtype" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1c04d06..606ad73 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2489,6 +2489,9 @@
<!-- Text selection contextual mode title, displayed in the CAB. [CHAR LIMIT=20] -->
<string name="textSelectionCABTitle">Text selection</string>
+ <!-- Option to add the current misspelled word to the user dictionary. [CHAR LIMIT=25] -->
+ <string name="addToDictionary">+ add to dictionary</string>
+
<!-- EditText context menu -->
<string name="inputMethod">Input method</string>
@@ -3096,6 +3099,38 @@
<string name="number_picker_increment_button">Increment</string>
<!-- Description of the button to decrement the NumberPicker value. [CHAR LIMIT=NONE] -->
<string name="number_picker_decrement_button">Decrement</string>
+ <!-- Description of the tap and hold action to get into scroll mode in NumberPicker. [CHAR LIMIT=NONE] -->
+ <string name="number_picker_increment_scroll_mode"><xliff:g id="value" example="3">%s</xliff:g> tap and hold.</string>
+ <!-- Description of the scrolling action in NumberPicker. [CHAR LIMIT=NONE] -->
+ <string name="number_picker_increment_scroll_action">Slide up to increment and down to decrement.</string>
+
+ <!-- TimePicker - accessibility support -->
+ <!-- Description of the button to increment the TimePicker's minute value. [CHAR LIMIT=NONE] -->
+ <string name="time_picker_increment_minute_button">Increment minute</string>
+ <!-- Description of the button to decrement the TimePicker's minute value. [CHAR LIMIT=NONE] -->
+ <string name="time_picker_decrement_minute_button">Decrement minute</string>
+ <!-- Description of the button to increment the TimePicker's hour value. [CHAR LIMIT=NONE] -->
+ <string name="time_picker_increment_hour_button">Increment hour</string>
+ <!-- Description of the button to decrement the TimePicker's hour value. [CHAR LIMIT=NONE] -->
+ <string name="time_picker_decrement_hour_button">Decrement hour</string>
+ <!-- Description of the button to increment the TimePicker's set PM value. [CHAR LIMIT=NONE] -->
+ <string name="time_picker_increment_set_pm_button">Set PM</string>
+ <!-- Description of the button to decrement the TimePicker's set AM value. [CHAR LIMIT=NONE] -->
+ <string name="time_picker_decrement_set_am_button">Set AM</string>
+
+ <!-- DatePicker - accessibility support -->
+ <!-- Description of the button to increment the DatePicker's month value. [CHAR LIMIT=NONE] -->
+ <string name="date_picker_increment_month_button">Increment month</string>
+ <!-- Description of the button to decrement the DatePicker's month value. [CHAR LIMIT=NONE] -->
+ <string name="date_picker_decrement_month_button">Decrement month</string>
+ <!-- Description of the button to increment the DatePicker's day value. [CHAR LIMIT=NONE] -->
+ <string name="date_picker_increment_day_button">Increment day</string>
+ <!-- Description of the button to decrement the DatePicker's day value. [CHAR LIMIT=NONE] -->
+ <string name="date_picker_decrement_day_button">Decrement day</string>
+ <!-- Description of the button to increment the DatePicker's year value. [CHAR LIMIT=NONE] -->
+ <string name="date_picker_increment_year_button">Increment year</string>
+ <!-- Description of the button to decrement the DatePicker's year value. [CHAR LIMIT=NONE] -->
+ <string name="date_picker_decrement_year_button">Decrement year</string>
<!-- CheckBox - accessibility support -->
<!-- Description of the checked state of a CheckBox. [CHAR LIMIT=NONE] -->
@@ -3143,13 +3178,13 @@
<string name="content_description_sliding_handle">"Sliding handle. Tap and hold."</string>
<!-- Description of the up direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
- <string name="description_direction_up">"Up</string>
+ <string name="description_direction_up">Up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
<!-- Description of the down direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
- <string name="description_direction_down">Down</string>
+ <string name="description_direction_down">Down for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
<!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
- <string name="description_direction_left">"Left</string>
+ <string name="description_direction_left">"Left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
<!-- Description of the right direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
- <string name="description_direction_right">Right</string>
+ <string name="description_direction_right">Right for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
<!-- Description of the unlock target in the Slide unlock screen. [CHAR LIMIT=NONE] -->
<string name="description_target_unlock">Unlock</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 1a2ad05..1a6a523 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -255,11 +255,6 @@
<item name="android:textUnderlineColor">@color/holo_red_light</item>
</style>
- <style name="TextAppearance.SuggestionRange">
- <item name="android:textColor">@color/white</item>
- <item name="android:colorBackground">@color/holo_blue_dark</item>
- </style>
-
<!-- Widget Styles -->
<style name="Widget">
@@ -1554,6 +1549,7 @@
<style name="Widget.Holo.TextView.ListSeparator" parent="Widget.TextView.ListSeparator">
<item name="android:background">@android:drawable/list_section_divider_holo_dark</item>
+ <item name="android:textAllCaps">true</item>
</style>
<style name="Widget.Holo.TextSelectHandle" parent="Widget.TextSelectHandle">
@@ -1997,6 +1993,7 @@
<style name="Widget.Holo.Light.TextView.ListSeparator" parent="Widget.TextView.ListSeparator">
<item name="android:background">@android:drawable/list_section_divider_holo_light</item>
+ <item name="android:textAllCaps">true</item>
</style>
<style name="Widget.Holo.Light.TextSelectHandle" parent="Widget.TextSelectHandle">
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 899c9d5..f434ce8 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -91,7 +91,6 @@
<item name="textAppearanceEasyCorrectSuggestion">@android:style/TextAppearance.EasyCorrectSuggestion</item>
<item name="textAppearanceMisspelledSuggestion">@android:style/TextAppearance.MisspelledSuggestion</item>
- <item name="textAppearanceSuggestionRange">@android:style/TextAppearance.SuggestionRange</item>
<item name="textAppearanceButton">@android:style/TextAppearance.Widget.Button</item>
diff --git a/data/fonts/DroidSansArmenian.ttf b/data/fonts/DroidSansArmenian.ttf
new file mode 100644
index 0000000..62f67e0
--- /dev/null
+++ b/data/fonts/DroidSansArmenian.ttf
Binary files differ
diff --git a/data/fonts/DroidSansFallback.ttf b/data/fonts/DroidSansFallback.ttf
index 03ceae5..ba9d76f 100644
--- a/data/fonts/DroidSansFallback.ttf
+++ b/data/fonts/DroidSansFallback.ttf
Binary files differ
diff --git a/data/fonts/DroidSansFallbackFull.ttf b/data/fonts/DroidSansFallbackFull.ttf
new file mode 100644
index 0000000..03ceae5
--- /dev/null
+++ b/data/fonts/DroidSansFallbackFull.ttf
Binary files differ
diff --git a/data/fonts/DroidSansGeorgian.ttf b/data/fonts/DroidSansGeorgian.ttf
new file mode 100644
index 0000000..743ae66
--- /dev/null
+++ b/data/fonts/DroidSansGeorgian.ttf
Binary files differ
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index 1bee47a..e468558 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -46,6 +46,16 @@
</family>
<family>
<fileset>
+ <file>DroidSansArmenian.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>DroidSansGeorgian.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
<file>Lohit_Hindi.ttf</file>
</fileset>
</family>
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
index d8c1fa2..5bac8f0 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -14,15 +14,14 @@
# Warning: this is actually a product definition, to be inherited from
+# On space-constrained devices, we include a subset of fonts:
+# First, core/required fonts
PRODUCT_COPY_FILES := \
- frameworks/base/data/fonts/Roboto-Regular.ttf:system/fonts/Roboto-Regular.ttf \
- frameworks/base/data/fonts/Roboto-Bold.ttf:system/fonts/Roboto-Bold.ttf \
- frameworks/base/data/fonts/Roboto-Italic.ttf:system/fonts/Roboto-Italic.ttf \
- frameworks/base/data/fonts/Roboto-BoldItalic.ttf:system/fonts/Roboto-BoldItalic.ttf \
- frameworks/base/data/fonts/DroidSans.ttf:system/fonts/DroidSans.ttf \
- frameworks/base/data/fonts/DroidSans-Bold.ttf:system/fonts/DroidSans-Bold.ttf \
+ frameworks/base/data/fonts/Roboto-Regular.ttf:system/fonts/Roboto-Regular.ttf \
+ frameworks/base/data/fonts/Roboto-Bold.ttf:system/fonts/Roboto-Bold.ttf \
+ frameworks/base/data/fonts/Roboto-Italic.ttf:system/fonts/Roboto-Italic.ttf \
+ frameworks/base/data/fonts/Roboto-BoldItalic.ttf:system/fonts/Roboto-BoldItalic.ttf \
frameworks/base/data/fonts/DroidNaskh-Regular.ttf:system/fonts/DroidNaskh-Regular.ttf \
- frameworks/base/data/fonts/DroidSansEthiopic-Regular.ttf:system/fonts/DroidSansEthiopic-Regular.ttf \
frameworks/base/data/fonts/DroidSansHebrew-Regular.ttf:system/fonts/DroidSansHebrew-Regular.ttf \
frameworks/base/data/fonts/DroidSansHebrew-Bold.ttf:system/fonts/DroidSansHebrew-Bold.ttf \
frameworks/base/data/fonts/DroidSansThai.ttf:system/fonts/DroidSansThai.ttf \
@@ -32,10 +31,25 @@
frameworks/base/data/fonts/DroidSerif-BoldItalic.ttf:system/fonts/DroidSerif-BoldItalic.ttf \
frameworks/base/data/fonts/DroidSansMono.ttf:system/fonts/DroidSansMono.ttf \
frameworks/base/data/fonts/Lohit_Hindi.ttf:system/fonts/Lohit_Hindi.ttf \
+ frameworks/base/data/fonts/DroidSansArmenian.ttf:system/fonts/DroidSansArmenian.ttf \
+ frameworks/base/data/fonts/DroidSansGeorgian.ttf:system/fonts/DroidSansGeorgian.ttf \
frameworks/base/data/fonts/Clockopia.ttf:system/fonts/Clockopia.ttf \
- frameworks/base/data/fonts/DroidSansFallback.ttf:system/fonts/DroidSansFallback.ttf \
frameworks/base/data/fonts/AndroidClock.ttf:system/fonts/AndroidClock.ttf \
frameworks/base/data/fonts/AndroidClock_Highlight.ttf:system/fonts/AndroidClock_Highlight.ttf \
frameworks/base/data/fonts/AndroidClock_Solid.ttf:system/fonts/AndroidClock_Solid.ttf \
frameworks/base/data/fonts/system_fonts.xml:system/etc/system_fonts.xml \
frameworks/base/data/fonts/fallback_fonts.xml:system/etc/fallback_fonts.xml
+
+# Next, include additional fonts, depending on how much space we have
+ifeq ($(SMALLER_FONT_FOOTPRINT),true)
+# Smaller fonts alternatives
+PRODUCT_COPY_FILES += \
+ frameworks/base/data/fonts/DroidSansFallback.ttf:system/fonts/DroidSansFallback.ttf
+else
+# Full font set alternatives
+PRODUCT_COPY_FILES += \
+ frameworks/base/data/fonts/DroidSansFallbackFull.ttf:system/fonts/DroidSansFallback.ttf \
+ frameworks/base/data/fonts/DroidSans.ttf:system/fonts/DroidSans.ttf \
+ frameworks/base/data/fonts/DroidSans-Bold.ttf:system/fonts/DroidSans-Bold.ttf \
+ frameworks/base/data/fonts/DroidSansEthiopic-Regular.ttf:system/fonts/DroidSansEthiopic-Regular.ttf
+endif
diff --git a/data/sounds/AudioPackage6.mk b/data/sounds/AudioPackage6.mk
index fd6bf17..610e821 100755
--- a/data/sounds/AudioPackage6.mk
+++ b/data/sounds/AudioPackage6.mk
@@ -41,29 +41,4 @@
$(LOCAL_PATH)/notifications/ogg/Thallium.ogg:system/media/audio/notifications/Thallium.ogg \
$(LOCAL_PATH)/notifications/ogg/Xenon.ogg:system/media/audio/notifications/Xenon.ogg \
$(LOCAL_PATH)/notifications/ogg/Zirconium.ogg:system/media/audio/notifications/Zirconium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Andromeda.ogg:system/media/audio/ringtones/Andromeda.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg \
- $(LOCAL_PATH)/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Bootes.ogg:system/media/audio/ringtones/Bootes.ogg \
- $(LOCAL_PATH)/ringtones/ogg/CanisMajor.ogg:system/media/audio/ringtones/CanisMajor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Carina.ogg:system/media/audio/ringtones/Carina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Cassiopeia.ogg:system/media/audio/ringtones/Cassiopeia.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Draco.ogg:system/media/audio/ringtones/Draco.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Eridani.ogg:system/media/audio/ringtones/Eridani.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Hydra.ogg:system/media/audio/ringtones/hydra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Iridium.ogg:system/media/audio/ringtones/Iridium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Lyra.ogg:system/media/audio/ringtones/Lyra.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Perseus.ogg:system/media/audio/ringtones/Perseus.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg \
$(LOCAL_PATH)/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Testudo.ogg:system/media/audio/ringtones/Testudo.ogg \
- $(LOCAL_PATH)/ringtones/ogg/UrsaMinor.ogg:system/media/audio/ringtones/UrsaMinor.ogg \
- $(LOCAL_PATH)/ringtones/ogg/Vespa.ogg:system/media/audio/ringtones/Vespa.ogg
diff --git a/data/sounds/alarms/ogg/Copernicium.ogg b/data/sounds/alarms/ogg/Copernicium.ogg
index c619e8b..65d2867 100644
--- a/data/sounds/alarms/ogg/Copernicium.ogg
+++ b/data/sounds/alarms/ogg/Copernicium.ogg
Binary files differ
diff --git a/data/sounds/alarms/ogg/Curium.ogg b/data/sounds/alarms/ogg/Curium.ogg
index ebce391..125a236 100644
--- a/data/sounds/alarms/ogg/Curium.ogg
+++ b/data/sounds/alarms/ogg/Curium.ogg
Binary files differ
diff --git a/data/sounds/alarms/ogg/Fermium.ogg b/data/sounds/alarms/ogg/Fermium.ogg
index 6132565..6940442 100644
--- a/data/sounds/alarms/ogg/Fermium.ogg
+++ b/data/sounds/alarms/ogg/Fermium.ogg
Binary files differ
diff --git a/data/sounds/alarms/ogg/Hassium.ogg b/data/sounds/alarms/ogg/Hassium.ogg
index 408b7c2..86b2b71 100644
--- a/data/sounds/alarms/ogg/Hassium.ogg
+++ b/data/sounds/alarms/ogg/Hassium.ogg
Binary files differ
diff --git a/data/sounds/alarms/ogg/Neptunium.ogg b/data/sounds/alarms/ogg/Neptunium.ogg
index 058e2db..1a99141 100644
--- a/data/sounds/alarms/ogg/Neptunium.ogg
+++ b/data/sounds/alarms/ogg/Neptunium.ogg
Binary files differ
diff --git a/data/sounds/alarms/ogg/Nobelium.ogg b/data/sounds/alarms/ogg/Nobelium.ogg
index 33878c9..4309bc6 100644
--- a/data/sounds/alarms/ogg/Nobelium.ogg
+++ b/data/sounds/alarms/ogg/Nobelium.ogg
Binary files differ
diff --git a/data/sounds/alarms/wav/Copernicium.wav b/data/sounds/alarms/wav/Copernicium.wav
new file mode 100644
index 0000000..935ea75
--- /dev/null
+++ b/data/sounds/alarms/wav/Copernicium.wav
Binary files differ
diff --git a/data/sounds/alarms/wav/Curium.wav b/data/sounds/alarms/wav/Curium.wav
new file mode 100644
index 0000000..a9942d3
--- /dev/null
+++ b/data/sounds/alarms/wav/Curium.wav
Binary files differ
diff --git a/data/sounds/alarms/wav/Fermium.wav b/data/sounds/alarms/wav/Fermium.wav
new file mode 100644
index 0000000..0174884
--- /dev/null
+++ b/data/sounds/alarms/wav/Fermium.wav
Binary files differ
diff --git a/data/sounds/alarms/wav/Hassium.wav b/data/sounds/alarms/wav/Hassium.wav
new file mode 100644
index 0000000..e3992cf
--- /dev/null
+++ b/data/sounds/alarms/wav/Hassium.wav
Binary files differ
diff --git a/data/sounds/alarms/wav/Neptunium.wav b/data/sounds/alarms/wav/Neptunium.wav
new file mode 100644
index 0000000..cf3684a
--- /dev/null
+++ b/data/sounds/alarms/wav/Neptunium.wav
Binary files differ
diff --git a/data/sounds/alarms/wav/Nobelium.wav b/data/sounds/alarms/wav/Nobelium.wav
new file mode 100644
index 0000000..0e9101a
--- /dev/null
+++ b/data/sounds/alarms/wav/Nobelium.wav
Binary files differ
diff --git a/data/sounds/effects/wav/CameraClick.wav b/data/sounds/effects/wav/CameraClick.wav
new file mode 100644
index 0000000..9fe75f2
--- /dev/null
+++ b/data/sounds/effects/wav/CameraClick.wav
Binary files differ
diff --git a/data/sounds/effects/wav/Dock.wav b/data/sounds/effects/wav/Dock.wav
index 7fa6a7e..7ec64a8 100644
--- a/data/sounds/effects/wav/Dock.wav
+++ b/data/sounds/effects/wav/Dock.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressDelete_14.wav b/data/sounds/effects/wav/KeypressDelete_14.wav
new file mode 100644
index 0000000..1d3498e
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressDelete_14.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressDelete_49.wav b/data/sounds/effects/wav/KeypressDelete_49.wav
new file mode 100644
index 0000000..2404759
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressDelete_49.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressReturn_14.wav b/data/sounds/effects/wav/KeypressReturn_14.wav
new file mode 100644
index 0000000..01573ff
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressReturn_14.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressReturn_49.wav b/data/sounds/effects/wav/KeypressReturn_49.wav
new file mode 100644
index 0000000..6bf216f
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressReturn_49.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressSpacebar_14.wav b/data/sounds/effects/wav/KeypressSpacebar_14.wav
new file mode 100644
index 0000000..77269a6
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressSpacebar_14.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressSpacebar_49.wav b/data/sounds/effects/wav/KeypressSpacebar_49.wav
new file mode 100644
index 0000000..8504a25
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressSpacebar_49.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressStandard_14.wav b/data/sounds/effects/wav/KeypressStandard_14.wav
new file mode 100644
index 0000000..93389ac
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressStandard_14.wav
Binary files differ
diff --git a/data/sounds/effects/wav/KeypressStandard_49.wav b/data/sounds/effects/wav/KeypressStandard_49.wav
new file mode 100644
index 0000000..9a660a1
--- /dev/null
+++ b/data/sounds/effects/wav/KeypressStandard_49.wav
Binary files differ
diff --git a/data/sounds/effects/wav/Lock.wav b/data/sounds/effects/wav/Lock.wav
index 88be052..fead37f 100644
--- a/data/sounds/effects/wav/Lock.wav
+++ b/data/sounds/effects/wav/Lock.wav
Binary files differ
diff --git a/data/sounds/effects/wav/LowBattery.wav b/data/sounds/effects/wav/LowBattery.wav
index 8905534..5d8b48d 100644
--- a/data/sounds/effects/wav/LowBattery.wav
+++ b/data/sounds/effects/wav/LowBattery.wav
Binary files differ
diff --git a/data/sounds/effects/wav/Undock.wav b/data/sounds/effects/wav/Undock.wav
index 358eb18..79abb4e 100644
--- a/data/sounds/effects/wav/Undock.wav
+++ b/data/sounds/effects/wav/Undock.wav
Binary files differ
diff --git a/data/sounds/effects/wav/Unlock.wav b/data/sounds/effects/wav/Unlock.wav
index 4b39c5c..33b80ff 100644
--- a/data/sounds/effects/wav/Unlock.wav
+++ b/data/sounds/effects/wav/Unlock.wav
Binary files differ
diff --git a/data/sounds/effects/wav/VideoRecord.wav b/data/sounds/effects/wav/VideoRecord.wav
index 6880b29..f431023 100644
--- a/data/sounds/effects/wav/VideoRecord.wav
+++ b/data/sounds/effects/wav/VideoRecord.wav
Binary files differ
diff --git a/data/sounds/effects/wav/camera_click.wav b/data/sounds/effects/wav/camera_click.wav
deleted file mode 100644
index 420da7c..0000000
--- a/data/sounds/effects/wav/camera_click.wav
+++ /dev/null
Binary files differ
diff --git a/data/sounds/notifications/ogg/Altair.ogg b/data/sounds/notifications/ogg/Altair.ogg
old mode 100755
new mode 100644
index d84b59e..407aeb9
--- a/data/sounds/notifications/ogg/Altair.ogg
+++ b/data/sounds/notifications/ogg/Altair.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Antares.ogg b/data/sounds/notifications/ogg/Antares.ogg
old mode 100755
new mode 100644
index 9d60917..409c684
--- a/data/sounds/notifications/ogg/Antares.ogg
+++ b/data/sounds/notifications/ogg/Antares.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Betelgeuse.ogg b/data/sounds/notifications/ogg/Betelgeuse.ogg
index 83c7722..488d1e8 100644
--- a/data/sounds/notifications/ogg/Betelgeuse.ogg
+++ b/data/sounds/notifications/ogg/Betelgeuse.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Deneb.ogg b/data/sounds/notifications/ogg/Deneb.ogg
old mode 100755
new mode 100644
index e58b3b6..b84eae3
--- a/data/sounds/notifications/ogg/Deneb.ogg
+++ b/data/sounds/notifications/ogg/Deneb.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Hojus.ogg b/data/sounds/notifications/ogg/Hojus.ogg
index fc8f73f..65b780c 100644
--- a/data/sounds/notifications/ogg/Hojus.ogg
+++ b/data/sounds/notifications/ogg/Hojus.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Lalande.ogg b/data/sounds/notifications/ogg/Lalande.ogg
old mode 100755
new mode 100644
index b6e253a..eda9c9d
--- a/data/sounds/notifications/ogg/Lalande.ogg
+++ b/data/sounds/notifications/ogg/Lalande.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Mira.ogg b/data/sounds/notifications/ogg/Mira.ogg
index f21e3c4..f5a6e94 100644
--- a/data/sounds/notifications/ogg/Mira.ogg
+++ b/data/sounds/notifications/ogg/Mira.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Proxima.ogg b/data/sounds/notifications/ogg/Proxima.ogg
index 235b5ca..53bf899 100644
--- a/data/sounds/notifications/ogg/Proxima.ogg
+++ b/data/sounds/notifications/ogg/Proxima.ogg
Binary files differ
diff --git a/data/sounds/notifications/ogg/Upsilon.ogg b/data/sounds/notifications/ogg/Upsilon.ogg
index 036dcad..e970422 100644
--- a/data/sounds/notifications/ogg/Upsilon.ogg
+++ b/data/sounds/notifications/ogg/Upsilon.ogg
Binary files differ
diff --git a/data/sounds/notifications/wav/Altair.wav b/data/sounds/notifications/wav/Altair.wav
new file mode 100644
index 0000000..0fb9788
--- /dev/null
+++ b/data/sounds/notifications/wav/Altair.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Antares.wav b/data/sounds/notifications/wav/Antares.wav
new file mode 100644
index 0000000..7c2dd23
--- /dev/null
+++ b/data/sounds/notifications/wav/Antares.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Betelgeuse.wav b/data/sounds/notifications/wav/Betelgeuse.wav
new file mode 100644
index 0000000..9ad799f
--- /dev/null
+++ b/data/sounds/notifications/wav/Betelgeuse.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Deneb.wav b/data/sounds/notifications/wav/Deneb.wav
new file mode 100644
index 0000000..ffe7c31
--- /dev/null
+++ b/data/sounds/notifications/wav/Deneb.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Hojus.wav b/data/sounds/notifications/wav/Hojus.wav
new file mode 100644
index 0000000..076a0c7
--- /dev/null
+++ b/data/sounds/notifications/wav/Hojus.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Lalande.wav b/data/sounds/notifications/wav/Lalande.wav
new file mode 100644
index 0000000..14c9fec
--- /dev/null
+++ b/data/sounds/notifications/wav/Lalande.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Mira.wav b/data/sounds/notifications/wav/Mira.wav
new file mode 100644
index 0000000..71be516
--- /dev/null
+++ b/data/sounds/notifications/wav/Mira.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Proxima.wav b/data/sounds/notifications/wav/Proxima.wav
new file mode 100644
index 0000000..109bfdd
--- /dev/null
+++ b/data/sounds/notifications/wav/Proxima.wav
Binary files differ
diff --git a/data/sounds/notifications/wav/Upsilon.wav b/data/sounds/notifications/wav/Upsilon.wav
new file mode 100644
index 0000000..ffc959b
--- /dev/null
+++ b/data/sounds/notifications/wav/Upsilon.wav
Binary files differ
diff --git a/data/sounds/ringtones/ogg/Cassiopeia.ogg b/data/sounds/ringtones/ogg/Cassiopeia.ogg
index 61c4d27..b871940 100644
--- a/data/sounds/ringtones/ogg/Cassiopeia.ogg
+++ b/data/sounds/ringtones/ogg/Cassiopeia.ogg
Binary files differ
diff --git a/data/sounds/ringtones/ogg/Lyra.ogg b/data/sounds/ringtones/ogg/Lyra.ogg
index b7f740d..d170bc0 100644
--- a/data/sounds/ringtones/ogg/Lyra.ogg
+++ b/data/sounds/ringtones/ogg/Lyra.ogg
Binary files differ
diff --git a/data/sounds/ringtones/ogg/Sceptrum.ogg b/data/sounds/ringtones/ogg/Sceptrum.ogg
index 89d64d70..e94abe0 100644
--- a/data/sounds/ringtones/ogg/Sceptrum.ogg
+++ b/data/sounds/ringtones/ogg/Sceptrum.ogg
Binary files differ
diff --git a/data/sounds/ringtones/ogg/Solarium.ogg b/data/sounds/ringtones/ogg/Solarium.ogg
index 361367a..8dac71e 100644
--- a/data/sounds/ringtones/ogg/Solarium.ogg
+++ b/data/sounds/ringtones/ogg/Solarium.ogg
Binary files differ
diff --git a/data/sounds/ringtones/ogg/UrsaMinor.ogg b/data/sounds/ringtones/ogg/UrsaMinor.ogg
index a80801d..a90d1de 100644
--- a/data/sounds/ringtones/ogg/UrsaMinor.ogg
+++ b/data/sounds/ringtones/ogg/UrsaMinor.ogg
Binary files differ
diff --git a/data/sounds/ringtones/ogg/Vespa.ogg b/data/sounds/ringtones/ogg/Vespa.ogg
index 1f75ec8..f637831 100644
--- a/data/sounds/ringtones/ogg/Vespa.ogg
+++ b/data/sounds/ringtones/ogg/Vespa.ogg
Binary files differ
diff --git a/data/sounds/ringtones/wav/Cassiopeia.wav b/data/sounds/ringtones/wav/Cassiopeia.wav
new file mode 100644
index 0000000..5c5c6e0
--- /dev/null
+++ b/data/sounds/ringtones/wav/Cassiopeia.wav
Binary files differ
diff --git a/data/sounds/ringtones/wav/Lyra.wav b/data/sounds/ringtones/wav/Lyra.wav
new file mode 100644
index 0000000..2943cf5
--- /dev/null
+++ b/data/sounds/ringtones/wav/Lyra.wav
Binary files differ
diff --git a/data/sounds/ringtones/wav/Sceptrum.wav b/data/sounds/ringtones/wav/Sceptrum.wav
new file mode 100644
index 0000000..3694373
--- /dev/null
+++ b/data/sounds/ringtones/wav/Sceptrum.wav
Binary files differ
diff --git a/data/sounds/ringtones/wav/Solarium.wav b/data/sounds/ringtones/wav/Solarium.wav
new file mode 100644
index 0000000..93f1e01
--- /dev/null
+++ b/data/sounds/ringtones/wav/Solarium.wav
Binary files differ
diff --git a/data/sounds/ringtones/wav/UrsaMinor.wav b/data/sounds/ringtones/wav/UrsaMinor.wav
new file mode 100644
index 0000000..5e16c94
--- /dev/null
+++ b/data/sounds/ringtones/wav/UrsaMinor.wav
Binary files differ
diff --git a/data/sounds/ringtones/wav/Vespa.wav b/data/sounds/ringtones/wav/Vespa.wav
new file mode 100644
index 0000000..7d696f8
--- /dev/null
+++ b/data/sounds/ringtones/wav/Vespa.wav
Binary files differ
diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd
index 362ee16..2b94b28 100644
--- a/docs/html/resources/dashboard/opengl.jd
+++ b/docs/html/resources/dashboard/opengl.jd
@@ -57,7 +57,7 @@
<div class="dashboard-panel">
<img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A8.9,91" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.4,90.6" />
<table>
<tr>
@@ -66,14 +66,14 @@
</tr>
<tr>
<td>1.1</th>
-<td>9%</td>
+<td>9.4%</td>
</tr>
<tr>
<td>2.0</th>
-<td>91%</td>
+<td>90.6%</td>
</tr>
</table>
-<p><em>Data collected during a 7-day period ending on July 1, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on September 2, 2011</em></p>
</div>
diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd
index d9adb36..51cbae3 100644
--- a/docs/html/resources/dashboard/platform-versions.jd
+++ b/docs/html/resources/dashboard/platform-versions.jd
@@ -52,27 +52,30 @@
<div class="dashboard-panel">
<img alt="" height="250" width="470"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:1.4,2.2,17.5,59.4,1.0,17.6,0.4,0.5&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3%20-%202.3.2|Android%202.3.3%20-%202.3.4|Android%203.0|Android%203.1&chco=c4df9b,6fad0c" />
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:1.0,1.8,13.3,51.2,0.6,30.7,0.2,0.7,0.5&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" />
<table>
<tr>
<th>Platform</th>
+ <th>Codename</th>
<th>API Level</th>
<th>Distribution</th>
</tr>
-<tr><td>Android 1.5</td><td>3</td><td>1.4%</td></tr>
-<tr><td>Android 1.6</td><td>4</td><td>2.2%</td></tr>
-<tr><td>Android 2.1</td><td>7</td><td>17.5%</td></tr>
-<tr><td>Android 2.2</td><td>8</td><td>59.4%</td></tr>
-<tr><td>Android 2.3 -<br/>
- Android 2.3.2</td><td>9</td><td>1%</td></tr>
-<tr><td>Android 2.3.3 -<br/>
- Android 2.3.4</td><td>10</td><td>17.6%</td></tr>
-<tr><td>Android 3.0</td><td>11</td><td>0.4%</td></tr>
-<tr><td>Android 3.1</td><td>12</td><td>0.5%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td> <td>3</td><td>1.0%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>1.8%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>13.3%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>51.2%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/>
+ Android 2.3.2</a></td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.6%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/>
+ Android 2.3.4</a></td><!-- Gingerbread --> <td>10</td><td>30.7%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td>
+ <td rowspan="3">Honeycomb</td> <td>11</td><td>0.2%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>0.7%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>0.5%</td></tr>
</table>
-<p><em>Data collected during a 14-day period ending on July 5, 2011</em></p>
+<p><em>Data collected during a 14-day period ending on September 2, 2011</em></p>
<!--
<p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p>
-->
@@ -101,9 +104,9 @@
<div class="dashboard-panel">
<img alt="" height="250" width="660" style="padding:5px;background:#fff"
-src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C01/01%7C01/15%7C02/01%7C02/15%7C03/01%7C03/15%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:100.0,99.9,99.9,99.9,100.0,99.8,99.7,99.6,99.6,99.5,99.4,99.3,99.2|95.2,95.6,96.0,96.3,96.7,96.8,97.0,97.1,97.3,97.5,97.5,97.5,97.7|87.2,88.3,89.7,90.5,91.5,92.0,93.5,93.9,94.3,94.8,95.0,95.2,95.5|51.8,54.3,58.3,59.7,61.5,63.0,66.4,68.0,69.8,71.5,73.9,75.4,77.6|0.4,0.6,0.7,0.8,1.1,1.7,2.5,3.1,4.0,6.1,9.5,13.6,17.8|0.0,0.0,0.0,0.0,0.0,1.0,1.7,2.2,3.0,5.1,8.4,12.6,16.8&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid2.3.3,131d02,5,11,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" />
+src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C03/01%7C03/15%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:100.0,99.8,99.7,99.6,99.6,99.5,99.4,99.3,99.2,99.0,98.8,98.7,98.6|96.7,96.8,97.0,97.1,97.3,97.5,97.5,97.5,97.7,97.6,97.5,97.5,97.5|91.5,92.0,93.5,93.9,94.3,94.8,95.0,95.2,95.5,95.5,95.5,95.6,95.8|61.5,63.0,66.4,68.0,69.8,71.5,73.9,75.4,77.6,79.0,80.2,81.1,82.4|1.1,1.7,2.5,3.1,4.0,6.1,9.5,13.6,17.8,20.6,24.3,27.5,31.1|0.0,1.0,1.7,2.2,3.0,5.1,8.4,12.6,16.8,20.0,23.7,26.9,30.5&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid 2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid 2.3.3,131d02,5,7,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" />
-<p><em>Last historical dataset collected during a 14-day period ending on July 5, 2011</em></p>
+<p><em>Last historical dataset collected during a 14-day period ending on September 2, 2011</em></p>
</div><!-- end dashboard-panel -->
diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd
index e61e799..77fd2d2 100644
--- a/docs/html/resources/dashboard/screens.jd
+++ b/docs/html/resources/dashboard/screens.jd
@@ -59,8 +59,7 @@
<div class="dashboard-panel">
-<img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi&chd=t%3A0.9,2.8,75,1.0,17,3.3" />
+<img alt="" width="400" height="250" src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi| Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi&chd=t%3A1.5, 3.2,74,0.9,16.9,3.5" />
<table>
<tr>
@@ -73,29 +72,29 @@
<tr><th scope="row">small</th>
<td></td> <!-- small/ldpi -->
<td></td> <!-- small/mdpi -->
-<td>3.3%</td> <!-- small/hdpi -->
+<td>3.5%</td> <!-- small/hdpi -->
<td></td> <!-- small/xhdpi -->
</tr>
<tr><th scope="row">normal</th>
-<td>1%</td> <!-- normal/ldpi -->
-<td>17%</td> <!-- normal/mdpi -->
-<td>75%</td> <!-- normal/hdpi -->
+<td>0.9%</td> <!-- normal/ldpi -->
+<td>16.9%</td> <!-- normal/mdpi -->
+<td>74%</td> <!-- normal/hdpi -->
<td></td> <!-- normal/xhdpi -->
</tr>
<tr><th scope="row">large</th>
<td></td> <!-- large/ldpi -->
-<td>2.8%</td> <!-- large/mdpi -->
+<td>3.2%</td> <!-- large/mdpi -->
<td></td> <!-- large/hdpi -->
<td></td> <!-- large/xhdpi -->
</tr>
<tr><th scope="row">xlarge</th>
<td></td> <!-- xlarge/ldpi -->
-<td>0.9%</td> <!-- xlarge/mdpi -->
+<td>1.5%</td> <!-- xlarge/mdpi -->
<td></td> <!-- xlarge/hdpi -->
<td></td> <!-- xlarge/xhdpi -->
</tr>
</table>
-<p><em>Data collected during a 7-day period ending on July 1, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on September 2, 2011</em></p>
</div>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b4d94f3..ee65223 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1349,7 +1349,7 @@
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
- if ((index | count) < 0 || index + count > text.length) {
+ if (index < 0 || text.length - index < Math.abs(count)) {
throw new ArrayIndexOutOfBoundsException();
}
@@ -1594,7 +1594,7 @@
}
/**
- * Return the glypth Ids for the characters in the string.
+ * Return the glyph Ids for the characters in the string.
*
* @param text The text to measure
* @param start The index of the first char to to measure
@@ -1613,7 +1613,7 @@
*
* Used only for BiDi / RTL Tests
*/
- public int getTextGlypths(String text, int start, int end, int contextStart, int contextEnd,
+ public int getTextGlyphs(String text, int start, int end, int contextStart, int contextEnd,
int flags, char[] glyphs) {
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index 2b3aa33..00e9609 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.io.PrintWriter;
+
import android.os.Parcel;
import android.os.Parcelable;
import android.util.FloatMath;
@@ -81,8 +83,37 @@
return "RectF(" + left + ", " + top + ", "
+ right + ", " + bottom + ")";
}
+
+ /**
+ * Return a string representation of the rectangle in a compact form.
+ */
+ public String toShortString() {
+ return toShortString(new StringBuilder(32));
+ }
/**
+ * Return a string representation of the rectangle in a compact form.
+ * @hide
+ */
+ public String toShortString(StringBuilder sb) {
+ sb.setLength(0);
+ sb.append('['); sb.append(left); sb.append(',');
+ sb.append(top); sb.append("]["); sb.append(right);
+ sb.append(','); sb.append(bottom); sb.append(']');
+ return sb.toString();
+ }
+
+ /**
+ * Print short representation to given writer.
+ * @hide
+ */
+ public void printShortString(PrintWriter pw) {
+ pw.print('['); pw.print(left); pw.print(',');
+ pw.print(top); pw.print("]["); pw.print(right);
+ pw.print(','); pw.print(bottom); pw.print(']');
+ }
+
+ /**
* Returns true if the rectangle is empty (left >= right or top >= bottom)
*/
public final boolean isEmpty() {
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index 94a8488..7c4e147 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -55,7 +55,7 @@
private void init() {
final AnimatedRotateState state = mState;
- mIncrement = 360.0f / (float) state.mFramesCount;
+ mIncrement = 360.0f / state.mFramesCount;
final Drawable drawable = state.mDrawable;
if (drawable != null) {
drawable.setFilterBitmap(true);
@@ -65,6 +65,7 @@
}
}
+ @Override
public void draw(Canvas canvas) {
int saveCount = canvas.save();
@@ -146,14 +147,17 @@
| mState.mDrawable.getChangingConfigurations();
}
+ @Override
public void setAlpha(int alpha) {
mState.mDrawable.setAlpha(alpha);
}
+ @Override
public void setColorFilter(ColorFilter cf) {
mState.mDrawable.setColorFilter(cf);
}
+ @Override
public int getOpacity() {
return mState.mDrawable.getOpacity();
}
@@ -228,10 +232,10 @@
tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY);
final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION;
final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
-
- final int framesCount = a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12);
- final int frameDuration = a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150);
-
+
+ setFramesCount(a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12));
+ setFramesDuration(a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150));
+
final int res = a.getResourceId(R.styleable.AnimatedRotateDrawable_drawable, 0);
Drawable drawable = null;
if (res > 0) {
@@ -265,8 +269,6 @@
rotateState.mPivotX = pivotX;
rotateState.mPivotYRel = pivotYRel;
rotateState.mPivotY = pivotY;
- rotateState.mFramesCount = framesCount;
- rotateState.mFrameDuration = frameDuration;
init();
@@ -275,6 +277,15 @@
}
}
+ public void setFramesCount(int framesCount) {
+ mState.mFramesCount = framesCount;
+ mIncrement = 360.0f / mState.mFramesCount;
+ }
+
+ public void setFramesDuration(int framesDuration) {
+ mState.mFrameDuration = framesDuration;
+ }
+
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 4418e02..88c9155 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -111,8 +111,11 @@
alpha += alpha >> 7; // make it 0..256
int baseAlpha = mState.mBaseColor >>> 24;
int useAlpha = baseAlpha * alpha >> 8;
+ int oldUseColor = mState.mUseColor;
mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
- invalidateSelf();
+ if (oldUseColor != mState.mUseColor) {
+ invalidateSelf();
+ }
}
/**
diff --git a/include/camera/CameraParameters.h b/include/camera/CameraParameters.h
index 6c91dfc..4a4bcfb 100644
--- a/include/camera/CameraParameters.h
+++ b/include/camera/CameraParameters.h
@@ -495,6 +495,25 @@
// Example value: "true" or "false". Read/write.
static const char KEY_RECORDING_HINT[];
+ // Returns true if video snapshot is supported. That is, applications
+ // can call Camera.takePicture during recording. Applications do not need to
+ // call Camera.startPreview after taking a picture. The preview will be
+ // still active. Other than that, taking a picture during recording is
+ // identical to taking a picture normally. All settings and methods related
+ // to takePicture work identically. Ex: KEY_PICTURE_SIZE,
+ // KEY_SUPPORTED_PICTURE_SIZES, KEY_JPEG_QUALITY, KEY_ROTATION, and etc.
+ // The picture will have an EXIF header. FLASH_MODE_AUTO and FLASH_MODE_ON
+ // also still work, but the video will record the flash.
+ //
+ // Applications can set shutter callback as null to avoid the shutter
+ // sound. It is also recommended to set raw picture and post view callbacks
+ // to null to avoid the interrupt of preview display.
+ //
+ // Field-of-view of the recorded video may be different from that of the
+ // captured pictures.
+ // Example value: "true" or "false". Read only.
+ static const char KEY_VIDEO_SNAPSHOT_SUPPORTED[];
+
// Value for KEY_ZOOM_SUPPORTED or KEY_SMOOTH_ZOOM_SUPPORTED.
static const char TRUE[];
static const char FALSE[];
diff --git a/include/media/IMediaPlayer.h b/include/media/IMediaPlayer.h
index d552b2e..0e2cdf7 100644
--- a/include/media/IMediaPlayer.h
+++ b/include/media/IMediaPlayer.h
@@ -20,11 +20,13 @@
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
+#include <utils/KeyedVector.h>
namespace android {
class Parcel;
class Surface;
+class IStreamSource;
class ISurfaceTexture;
class IMediaPlayer: public IInterface
@@ -34,6 +36,10 @@
virtual void disconnect() = 0;
+ virtual status_t setDataSource(const char *url,
+ const KeyedVector<String8, String8>* headers) = 0;
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length) = 0;
+ virtual status_t setDataSource(const sp<IStreamSource>& source) = 0;
virtual status_t setVideoSurface(const sp<Surface>& surface) = 0;
virtual status_t setVideoSurfaceTexture(
const sp<ISurfaceTexture>& surfaceTexture) = 0;
diff --git a/include/media/IMediaPlayerService.h b/include/media/IMediaPlayerService.h
index 7956788..93bbe13 100644
--- a/include/media/IMediaPlayerService.h
+++ b/include/media/IMediaPlayerService.h
@@ -39,17 +39,9 @@
public:
DECLARE_META_INTERFACE(MediaPlayerService);
- virtual sp<IMediaRecorder> createMediaRecorder(pid_t pid) = 0;
+ virtual sp<IMediaRecorder> createMediaRecorder(pid_t pid) = 0;
virtual sp<IMediaMetadataRetriever> createMetadataRetriever(pid_t pid) = 0;
- virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client,
- const char* url, const KeyedVector<String8, String8> *headers = NULL,
- int audioSessionId = 0) = 0;
- virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client,
- int fd, int64_t offset, int64_t length, int audioSessionId) = 0;
-
- virtual sp<IMediaPlayer> create(
- pid_t pid, const sp<IMediaPlayerClient> &client,
- const sp<IStreamSource> &source, int audioSessionId) = 0;
+ virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int audioSessionId = 0) = 0;
virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0;
virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0;
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 4328d3c..2e60f5b 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -46,6 +46,9 @@
// The shared library with the test player is passed passed as an
// argument to the 'test:' url in the setDataSource call.
TEST_PLAYER = 5,
+
+ AAH_RX_PLAYER = 100,
+ AAH_TX_PLAYER = 101,
};
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index 1a67671..e98d55c 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -21,6 +21,7 @@
#include <media/IMediaPlayerClient.h>
#include <media/IMediaPlayer.h>
#include <media/IMediaDeathNotifier.h>
+#include <media/IStreamSource.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
@@ -168,6 +169,7 @@
const KeyedVector<String8, String8> *headers);
status_t setDataSource(int fd, int64_t offset, int64_t length);
+ status_t setDataSource(const sp<IStreamSource> &source);
status_t setVideoSurface(const sp<Surface>& surface);
status_t setVideoSurfaceTexture(
const sp<ISurfaceTexture>& surfaceTexture);
@@ -206,7 +208,7 @@
status_t seekTo_l(int msec);
status_t prepareAsync_l();
status_t getDuration_l(int *msec);
- status_t setDataSource(const sp<IMediaPlayer>& player);
+ status_t attachNewPlayer(const sp<IMediaPlayer>& player);
void disconnectNativeWindow();
status_t reset_l();
diff --git a/include/media/stagefright/SurfaceMediaSource.h b/include/media/stagefright/SurfaceMediaSource.h
index 74d54d1..d0940bb 100644
--- a/include/media/stagefright/SurfaceMediaSource.h
+++ b/include/media/stagefright/SurfaceMediaSource.h
@@ -34,7 +34,7 @@
class SurfaceMediaSource : public BnSurfaceTexture, public MediaSource,
public MediaBufferObserver {
public:
- enum { MIN_UNDEQUEUED_BUFFERS = 3 };
+ enum { MIN_UNDEQUEUED_BUFFERS = 4 };
enum {
MIN_ASYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS + 1,
MIN_SYNC_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS
@@ -347,6 +347,13 @@
// encoder
int mNumFramesEncoded;
+ // mFirstFrameTimestamp is the timestamp of the first received frame.
+ // It is used to offset the output timestamps so recording starts at time 0.
+ int64_t mFirstFrameTimestamp;
+ // mStartTimeNs is the start time passed into the source at start, used to
+ // offset timestamps.
+ int64_t mStartTimeNs;
+
// mFrameAvailableCondition condition used to indicate whether there
// is a frame available for dequeuing
Condition mFrameAvailableCondition;
diff --git a/include/private/surfaceflinger/LayerState.h b/include/private/surfaceflinger/LayerState.h
index d2fed41..3eb5c99 100644
--- a/include/private/surfaceflinger/LayerState.h
+++ b/include/private/surfaceflinger/LayerState.h
@@ -54,8 +54,8 @@
};
SurfaceID surface;
uint32_t what;
- int32_t x;
- int32_t y;
+ float x;
+ float y;
uint32_t z;
uint32_t w;
uint32_t h;
diff --git a/include/surfaceflinger/SurfaceComposerClient.h b/include/surfaceflinger/SurfaceComposerClient.h
index 7fbbfb24..ace0735 100644
--- a/include/surfaceflinger/SurfaceComposerClient.h
+++ b/include/surfaceflinger/SurfaceComposerClient.h
@@ -145,7 +145,7 @@
status_t setAlpha(SurfaceID id, float alpha=1.0f);
status_t setFreezeTint(SurfaceID id, uint32_t tint);
status_t setMatrix(SurfaceID id, float dsdx, float dtdx, float dsdy, float dtdy);
- status_t setPosition(SurfaceID id, int32_t x, int32_t y);
+ status_t setPosition(SurfaceID id, float x, float y);
status_t setSize(SurfaceID id, uint32_t w, uint32_t h);
status_t destroySurface(SurfaceID sid);
diff --git a/libs/camera/CameraParameters.cpp b/libs/camera/CameraParameters.cpp
index 0eb5d50..0dcab6b 100644
--- a/libs/camera/CameraParameters.cpp
+++ b/libs/camera/CameraParameters.cpp
@@ -87,6 +87,7 @@
const char CameraParameters::KEY_MAX_NUM_DETECTED_FACES_HW[] = "max-num-detected-faces-hw";
const char CameraParameters::KEY_MAX_NUM_DETECTED_FACES_SW[] = "max-num-detected-faces-sw";
const char CameraParameters::KEY_RECORDING_HINT[] = "recording-hint";
+const char CameraParameters::KEY_VIDEO_SNAPSHOT_SUPPORTED[] = "video-snapshot-supported";
const char CameraParameters::TRUE[] = "true";
const char CameraParameters::FALSE[] = "false";
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 3b0ffea..00a4bf6 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -102,7 +102,7 @@
public:
status_t setPosition(const sp<SurfaceComposerClient>& client, SurfaceID id,
- int32_t x, int32_t y);
+ float x, float y);
status_t setSize(const sp<SurfaceComposerClient>& client, SurfaceID id,
uint32_t w, uint32_t h);
status_t setLayer(const sp<SurfaceComposerClient>& client, SurfaceID id,
@@ -161,7 +161,7 @@
}
status_t Composer::setPosition(const sp<SurfaceComposerClient>& client,
- SurfaceID id, int32_t x, int32_t y) {
+ SurfaceID id, float x, float y) {
Mutex::Autolock _l(mLock);
layer_state_t* s = getLayerStateLocked(client, id);
if (!s)
@@ -372,7 +372,7 @@
return getComposer().setFreezeTint(this, id, tint);
}
-status_t SurfaceComposerClient::setPosition(SurfaceID id, int32_t x, int32_t y) {
+status_t SurfaceComposerClient::setPosition(SurfaceID id, float x, float y) {
return getComposer().setPosition(this, id, x, y);
}
diff --git a/libs/gui/tests/Android.mk b/libs/gui/tests/Android.mk
index 0308af3..55ac133 100644
--- a/libs/gui/tests/Android.mk
+++ b/libs/gui/tests/Android.mk
@@ -1,4 +1,4 @@
-# Build the unit tests.
+# Build the unit tests,
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
@@ -22,17 +22,15 @@
libui \
libutils \
-LOCAL_STATIC_LIBRARIES := \
- libgtest \
- libgtest_main \
-
LOCAL_C_INCLUDES := \
bionic \
bionic/libstdc++/include \
external/gtest/include \
external/stlport/stlport \
-include $(BUILD_EXECUTABLE)
+# Build the binary to $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
+# to integrate with auto-test framework.
+include $(BUILD_NATIVE_TEST)
# Include subdirectory makefiles
# ============================================================
diff --git a/libs/rs/driver/rsdBcc.cpp b/libs/rs/driver/rsdBcc.cpp
index 44ea79c..0755fb7 100644
--- a/libs/rs/driver/rsdBcc.cpp
+++ b/libs/rs/driver/rsdBcc.cpp
@@ -37,6 +37,7 @@
struct DrvScript {
int (*mRoot)();
void (*mInit)();
+ void (*mFreeChildren)();
BCCScriptRef mBccScript;
@@ -125,6 +126,7 @@
drv->mRoot = reinterpret_cast<int (*)()>(bccGetFuncAddr(drv->mBccScript, "root"));
drv->mInit = reinterpret_cast<void (*)()>(bccGetFuncAddr(drv->mBccScript, "init"));
+ drv->mFreeChildren = reinterpret_cast<void (*)()>(bccGetFuncAddr(drv->mBccScript, ".rs.dtor"));
exportFuncCount = drv->ME->getExportFuncCount();
if (exportFuncCount > 0) {
@@ -430,6 +432,13 @@
}
}
+void rsdScriptInvokeFreeChildren(const Context *dc, Script *script) {
+ DrvScript *drv = (DrvScript *)script->mHal.drv;
+
+ if (drv->mFreeChildren) {
+ drv->mFreeChildren();
+ }
+}
void rsdScriptInvokeFunction(const Context *dc, Script *script,
uint32_t slot,
diff --git a/libs/rs/driver/rsdBcc.h b/libs/rs/driver/rsdBcc.h
index 67929bc..5f83ed2 100644
--- a/libs/rs/driver/rsdBcc.h
+++ b/libs/rs/driver/rsdBcc.h
@@ -43,6 +43,8 @@
android::renderscript::Script *script);
void rsdScriptInvokeInit(const android::renderscript::Context *dc,
android::renderscript::Script *script);
+void rsdScriptInvokeFreeChildren(const android::renderscript::Context *dc,
+ android::renderscript::Script *script);
void rsdScriptSetGlobalVar(const android::renderscript::Context *,
const android::renderscript::Script *,
diff --git a/libs/rs/driver/rsdCore.cpp b/libs/rs/driver/rsdCore.cpp
index 171d045..a38fff7 100644
--- a/libs/rs/driver/rsdCore.cpp
+++ b/libs/rs/driver/rsdCore.cpp
@@ -60,6 +60,7 @@
rsdScriptInvokeRoot,
rsdScriptInvokeForEach,
rsdScriptInvokeInit,
+ rsdScriptInvokeFreeChildren,
rsdScriptSetGlobalVar,
rsdScriptSetGlobalBind,
rsdScriptSetGlobalObj,
diff --git a/libs/rs/rsScript.cpp b/libs/rs/rsScript.cpp
index f62c72e..93513fe 100644
--- a/libs/rs/rsScript.cpp
+++ b/libs/rs/rsScript.cpp
@@ -72,6 +72,12 @@
mRSC->mHal.funcs.script.setGlobalObj(mRSC, this, slot, val);
}
+bool Script::freeChildren() {
+ incSysRef();
+ mRSC->mHal.funcs.script.invokeFreeChildren(mRSC, this);
+ return decSysRef();
+}
+
namespace android {
namespace renderscript {
diff --git a/libs/rs/rsScript.h b/libs/rs/rsScript.h
index c0324dd..d645421 100644
--- a/libs/rs/rsScript.h
+++ b/libs/rs/rsScript.h
@@ -73,6 +73,8 @@
void setVar(uint32_t slot, const void *val, size_t len);
void setVarObj(uint32_t slot, ObjectBase *val);
+ virtual bool freeChildren();
+
virtual void runForEach(Context *rsc,
const Allocation * ain,
Allocation * aout,
diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp
index dccf71f..2e7f213 100644
--- a/libs/rs/rsScriptC.cpp
+++ b/libs/rs/rsScriptC.cpp
@@ -44,6 +44,7 @@
BT = NULL;
}
#endif
+ mRSC->mHal.funcs.script.invokeFreeChildren(mRSC, this);
mRSC->mHal.funcs.script.destroy(mRSC, this);
}
diff --git a/libs/rs/rs_hal.h b/libs/rs/rs_hal.h
index 21dff21..b8d7351 100644
--- a/libs/rs/rs_hal.h
+++ b/libs/rs/rs_hal.h
@@ -90,6 +90,7 @@
uint32_t usrLen,
const RsScriptCall *sc);
void (*invokeInit)(const Context *rsc, Script *s);
+ void (*invokeFreeChildren)(const Context *rsc, Script *s);
void (*setGlobalVar)(const Context *rsc, const Script *s,
uint32_t slot,
diff --git a/libs/rs/scriptc/rs_cl.rsh b/libs/rs/scriptc/rs_cl.rsh
index e402b86..bbc8fc5 100644
--- a/libs/rs/scriptc/rs_cl.rsh
+++ b/libs/rs/scriptc/rs_cl.rsh
@@ -15,7 +15,7 @@
*/
/** @file rs_cl.rsh
- * \brief Additional compute routines
+ * \brief Basic math functions
*
*
*/
@@ -111,225 +111,555 @@
fnc(float4 v1, float4 v2, int4 *v3);
+/**
+ * Return the inverse cosine.
+ *
+ * Supports float, float2, float3, float4
+ */
extern float __attribute__((overloadable)) acos(float);
FN_FUNC_FN(acos)
+/**
+ * Return the inverse hyperbolic cosine.
+ *
+ * Supports float, float2, float3, float4
+ */
extern float __attribute__((overloadable)) acosh(float);
FN_FUNC_FN(acosh)
+/**
+ * Return the inverse cosine divided by PI.
+ *
+ * Supports float, float2, float3, float4
+ */
_RS_RUNTIME float __attribute__((overloadable)) acospi(float v);
-
-
FN_FUNC_FN(acospi)
+/**
+ * Return the inverse sine.
+ *
+ * Supports float, float2, float3, float4
+ */
extern float __attribute__((overloadable)) asin(float);
FN_FUNC_FN(asin)
+/**
+ * Return the inverse hyperbolic sine.
+ *
+ * Supports float, float2, float3, float4
+ */
extern float __attribute__((overloadable)) asinh(float);
FN_FUNC_FN(asinh)
+/**
+ * Return the inverse sine divided by PI.
+ *
+ * Supports float, float2, float3, float4
+ */
_RS_RUNTIME float __attribute__((overloadable)) asinpi(float v);
FN_FUNC_FN(asinpi)
+/**
+ * Return the inverse tangent.
+ *
+ * Supports float, float2, float3, float4
+ */
extern float __attribute__((overloadable)) atan(float);
FN_FUNC_FN(atan)
-extern float __attribute__((overloadable)) atan2(float, float);
+/**
+ * Return the inverse tangent of y / x.
+ *
+ * Supports float, float2, float3, float4. Both arguments must be of the same
+ * type.
+ *
+ * @param y
+ * @param x
+ */
+extern float __attribute__((overloadable)) atan2(float y, float x);
FN_FUNC_FN_FN(atan2)
+/**
+ * Return the inverse hyperbolic tangent.
+ *
+ * Supports float, float2, float3, float4
+ */
extern float __attribute__((overloadable)) atanh(float);
FN_FUNC_FN(atanh)
-
+/**
+ * Return the inverse tangent divided by PI.
+ *
+ * Supports float, float2, float3, float4
+ */
_RS_RUNTIME float __attribute__((overloadable)) atanpi(float v);
FN_FUNC_FN(atanpi)
-
+/**
+ * Return the inverse tangent of y / x, divided by PI.
+ *
+ * Supports float, float2, float3, float4. Both arguments must be of the same
+ * type.
+ *
+ * @param y
+ * @param x
+ */
_RS_RUNTIME float __attribute__((overloadable)) atan2pi(float y, float x);
FN_FUNC_FN_FN(atan2pi)
+
+/**
+ * Return the cube root.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) cbrt(float);
FN_FUNC_FN(cbrt)
+/**
+ * Return the smallest integer not less than a value.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) ceil(float);
FN_FUNC_FN(ceil)
-extern float __attribute__((overloadable)) copysign(float, float);
+/**
+ * Copy the sign bit from y to x.
+ *
+ * Supports float, float2, float3, float4. Both arguments must be of the same
+ * type.
+ *
+ * @param x
+ * @param y
+ */
+extern float __attribute__((overloadable)) copysign(float x, float y);
FN_FUNC_FN_FN(copysign)
+/**
+ * Return the cosine.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) cos(float);
FN_FUNC_FN(cos)
+/**
+ * Return the hypebolic cosine.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) cosh(float);
FN_FUNC_FN(cosh)
-
+/**
+ * Return the cosine of the value * PI.
+ *
+ * Supports float, float2, float3, float4.
+ */
_RS_RUNTIME float __attribute__((overloadable)) cospi(float v);
FN_FUNC_FN(cospi)
+/**
+ * Return the complementary error function.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) erfc(float);
FN_FUNC_FN(erfc)
+/**
+ * Return the error function.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) erf(float);
FN_FUNC_FN(erf)
+/**
+ * Return e ^ value.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) exp(float);
FN_FUNC_FN(exp)
+/**
+ * Return 2 ^ value.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) exp2(float);
FN_FUNC_FN(exp2)
-extern float __attribute__((overloadable)) pow(float, float);
+/**
+ * Return x ^ y.
+ *
+ * Supports float, float2, float3, float4. Both arguments must be of the same
+ * type.
+ */
+extern float __attribute__((overloadable)) pow(float x, float y);
+FN_FUNC_FN_FN(pow)
+/**
+ * Return 10 ^ value.
+ *
+ * Supports float, float2, float3, float4.
+ */
_RS_RUNTIME float __attribute__((overloadable)) exp10(float v);
FN_FUNC_FN(exp10)
+/**
+ * Return (e ^ value) - 1.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) expm1(float);
FN_FUNC_FN(expm1)
+/**
+ * Return the absolute value of a value.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) fabs(float);
FN_FUNC_FN(fabs)
+/**
+ * Return the positive difference between two values.
+ *
+ * Supports float, float2, float3, float4. Both arguments must be of the same
+ * type.
+ */
extern float __attribute__((overloadable)) fdim(float, float);
FN_FUNC_FN_FN(fdim)
+/**
+ * Return the smallest integer not greater than a value.
+ *
+ * Supports float, float2, float3, float4.
+ */
extern float __attribute__((overloadable)) floor(float);
FN_FUNC_FN(floor)
-extern float __attribute__((overloadable)) fma(float, float, float);
+/**
+ * Return a*b + c.
+ *
+ * Supports float, float2, float3, float4.
+ */
+extern float __attribute__((overloadable)) fma(float a, float b, float c);
FN_FUNC_FN_FN_FN(fma)
-extern float __attribute__((overloadable)) fmax(float, float);
+/**
+ * Return (x < y ? y : x)
+ *
+ * Supports float, float2, float3, float4.
+ * @param x: may be float, float2, float3, float4
+ * @param y: may be float or vector. If vector must match type of x.
+ */
+extern float __attribute__((overloadable)) fmax(float x, float y);
FN_FUNC_FN_FN(fmax);
FN_FUNC_FN_F(fmax);
-extern float __attribute__((overloadable)) fmin(float, float);
+/**
+ * Return (x > y ? y : x)
+ *
+ * @param x: may be float, float2, float3, float4
+ * @param y: may be float or vector. If vector must match type of x.
+ */
+extern float __attribute__((overloadable)) fmin(float x, float y);
FN_FUNC_FN_FN(fmin);
FN_FUNC_FN_F(fmin);
-extern float __attribute__((overloadable)) fmod(float, float);
+/**
+ * Return the remainder from x / y
+ *
+ * Supports float, float2, float3, float4.
+ */
+extern float __attribute__((overloadable)) fmod(float x, float y);
FN_FUNC_FN_FN(fmod)
+/**
+ * Return fractional part of v
+ *
+ * @param iptr iptr[0] will be set to the floor of the input value.
+ * Supports float, float2, float3, float4.
+ */
_RS_RUNTIME float __attribute__((overloadable)) fract(float v, float *iptr);
FN_FUNC_FN_PFN(fract)
-extern float __attribute__((overloadable)) frexp(float, int *);
+/**
+ * Return the mantissa and place the exponent into iptr[0]
+ *
+ * @param v Supports float, float2, float3, float4.
+ * @param iptr Must have the same vector size as v.
+ */
+extern float __attribute__((overloadable)) frexp(float v, int *iptr);
FN_FUNC_FN_PIN(frexp)
-extern float __attribute__((overloadable)) hypot(float, float);
+/**
+ * Return sqrt(x*x + y*y)
+ *
+ * Supports float, float2, float3, float4.
+ */
+extern float __attribute__((overloadable)) hypot(float x, float y);
FN_FUNC_FN_FN(hypot)
+/**
+ * Return the integer exponent of a value
+ *
+ * Supports 1,2,3,4 components
+ */
extern int __attribute__((overloadable)) ilogb(float);
IN_FUNC_FN(ilogb)
-extern float __attribute__((overloadable)) ldexp(float, int);
+/**
+ * Return (x * 2^y)
+ *
+ * @param x Supports 1,2,3,4 components
+ * @param y Supports single component or matching vector.
+ */
+extern float __attribute__((overloadable)) ldexp(float x, int y);
FN_FUNC_FN_IN(ldexp)
FN_FUNC_FN_I(ldexp)
+/**
+ * Return the log gamma
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) lgamma(float);
FN_FUNC_FN(lgamma)
-extern float __attribute__((overloadable)) lgamma(float, int*);
+
+/**
+ * Return the log gamma and sign
+ *
+ * @param x Supports 1,2,3,4 components
+ * @param y Supports matching vector.
+ */
+extern float __attribute__((overloadable)) lgamma(float x, int* y);
FN_FUNC_FN_PIN(lgamma)
+/**
+ * Return the natural logarithm
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) log(float);
FN_FUNC_FN(log)
-
+/**
+ * Return the base 10 logarithm
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) log10(float);
FN_FUNC_FN(log10)
-
+/**
+ * Return the base 2 logarithm
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) log2(float v);
FN_FUNC_FN(log2)
-extern float __attribute__((overloadable)) log1p(float);
+/**
+ * Return the natural logarithm of (v + 1.0f)
+ *
+ * Supports 1,2,3,4 components
+ */
+extern float __attribute__((overloadable)) log1p(float v);
FN_FUNC_FN(log1p)
+/**
+ * Compute the exponent of the value.
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) logb(float);
FN_FUNC_FN(logb)
-extern float __attribute__((overloadable)) mad(float, float, float);
+/**
+ * Compute (a * b) + c
+ *
+ * Supports 1,2,3,4 components
+ */
+extern float __attribute__((overloadable)) mad(float a, float b, float c);
FN_FUNC_FN_FN_FN(mad)
-extern float __attribute__((overloadable)) modf(float, float *);
+/**
+ * Return the integral and fractional components of a number
+ * Supports 1,2,3,4 components
+ *
+ * @param x Source value
+ * @param iret iret[0] will be set to the integral portion of the number.
+ * @return The floating point portion of the value.
+ */
+extern float __attribute__((overloadable)) modf(float x, float *iret);
FN_FUNC_FN_PFN(modf);
//extern float __attribute__((overloadable)) nan(uint);
-extern float __attribute__((overloadable)) nextafter(float, float);
+/**
+ * Return the next floating point number from x towards y.
+ *
+ * Supports 1,2,3,4 components
+ */
+extern float __attribute__((overloadable)) nextafter(float x, float y);
FN_FUNC_FN_FN(nextafter)
-FN_FUNC_FN_FN(pow)
-
+/**
+ * Return (v ^ p).
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) pown(float v, int p);
-_RS_RUNTIME float2 __attribute__((overloadable)) pown(float2 v, int2 p);
-_RS_RUNTIME float3 __attribute__((overloadable)) pown(float3 v, int3 p);
-_RS_RUNTIME float4 __attribute__((overloadable)) pown(float4 v, int4 p);
+FN_FUNC_FN_IN(pown)
+/**
+ * Return (v ^ p).
+ * @param v must be greater than 0.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) powr(float v, float p);
-_RS_RUNTIME float2 __attribute__((overloadable)) powr(float2 v, float2 p);
-_RS_RUNTIME float3 __attribute__((overloadable)) powr(float3 v, float3 p);
-_RS_RUNTIME float4 __attribute__((overloadable)) powr(float4 v, float4 p);
+FN_FUNC_FN_FN(powr)
-extern float __attribute__((overloadable)) remainder(float, float);
+/**
+ * Return round x/y to the nearest integer then compute the remander.
+ *
+ * Supports 1,2,3,4 components
+ */
+extern float __attribute__((overloadable)) remainder(float x, float y);
FN_FUNC_FN_FN(remainder)
+// document once we know the precision of bionic
extern float __attribute__((overloadable)) remquo(float, float, int *);
FN_FUNC_FN_FN_PIN(remquo)
+/**
+ * Round to the nearest integral value.
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) rint(float);
FN_FUNC_FN(rint)
+/**
+ * Compute the Nth root of a value.
+ *
+ * Supports 1,2,3,4 components
+ */
+_RS_RUNTIME float __attribute__((overloadable)) rootn(float v, int n);
+FN_FUNC_FN_IN(rootn)
-_RS_RUNTIME float __attribute__((overloadable)) rootn(float v, int r);
-_RS_RUNTIME float2 __attribute__((overloadable)) rootn(float2 v, int2 r);
-_RS_RUNTIME float3 __attribute__((overloadable)) rootn(float3 v, int3 r);
-_RS_RUNTIME float4 __attribute__((overloadable)) rootn(float4 v, int4 r);
-
-
+/**
+ * Round to the nearest integral value. Half values are rounded away from zero.
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) round(float);
FN_FUNC_FN(round)
-
+/**
+ * Return the square root of a value.
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) sqrt(float);
+FN_FUNC_FN(sqrt)
+
+/**
+ * Return (1 / sqrt(value)).
+ *
+ * @param v The incoming value in radians
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) rsqrt(float v);
FN_FUNC_FN(rsqrt)
-extern float __attribute__((overloadable)) sin(float);
+/**
+ * Return the sine of a value specified in radians.
+ *
+ * @param v The incoming value in radians
+ * Supports 1,2,3,4 components
+ */
+extern float __attribute__((overloadable)) sin(float v);
FN_FUNC_FN(sin)
+/**
+ * Return the sine and cosine of a value.
+ *
+ * @return sine
+ * @param v The incoming value in radians
+ * @param *cosptr cosptr[0] will be set to the cosine value.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) sincos(float v, float *cosptr);
-_RS_RUNTIME float2 __attribute__((overloadable)) sincos(float2 v, float2 *cosptr);
-_RS_RUNTIME float3 __attribute__((overloadable)) sincos(float3 v, float3 *cosptr);
-_RS_RUNTIME float4 __attribute__((overloadable)) sincos(float4 v, float4 *cosptr);
+FN_FUNC_FN_PFN(sincos);
+/**
+ * Return the hyperbolic sine of a value specified in radians.
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) sinh(float);
FN_FUNC_FN(sinh)
+/**
+ * Return the sin(v * PI).
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) sinpi(float v);
FN_FUNC_FN(sinpi)
-FN_FUNC_FN(sqrt)
-
-extern float __attribute__((overloadable)) tan(float);
+/**
+ * Return the tangent of a value.
+ *
+ * Supports 1,2,3,4 components
+ * @param v The incoming value in radians
+ */
+extern float __attribute__((overloadable)) tan(float v);
FN_FUNC_FN(tan)
+/**
+ * Return the hyperbolic tangent of a value.
+ *
+ * Supports 1,2,3,4 components
+ * @param v The incoming value in radians
+ */
extern float __attribute__((overloadable)) tanh(float);
FN_FUNC_FN(tanh)
+/**
+ * Return tan(v * PI)
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) tanpi(float v);
FN_FUNC_FN(tanpi)
-
+/**
+ * Compute the gamma function of a value.
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) tgamma(float);
FN_FUNC_FN(tgamma)
+/**
+ * Round to integral using truncation.
+ *
+ * Supports 1,2,3,4 components
+ */
extern float __attribute__((overloadable)) trunc(float);
FN_FUNC_FN(trunc)
-// Int ops (partial), 6.11.3
#define XN_FUNC_YN(typeout, fnc, typein) \
extern typeout __attribute__((overloadable)) fnc(typein); \
@@ -373,14 +703,29 @@
UIN_FUNC_IN(abs)
IN_FUNC_IN(clz)
+/**
+ * Return the minimum of two values.
+ *
+ * Supports 1,2,3,4 components of uchar, char, ushort, short, uint, int, float.
+ */
IN_FUNC_IN_IN_BODY(min, (v1 < v2 ? v1 : v2))
FN_FUNC_FN_F(min)
+/**
+ * Return the maximum of two values.
+ *
+ * Supports 1,2,3,4 components of uchar, char, ushort, short, uint, int, float.
+ */
IN_FUNC_IN_IN_BODY(max, (v1 > v2 ? v1 : v2))
FN_FUNC_FN_F(max)
-// 6.11.4
-
+/**
+ * Clamp a value to a specified high and low bound.
+ *
+ * @param amount value to be clamped. Supports 1,2,3,4 components
+ * @param low Lower bound, must be scalar or matching vector.
+ * @param high High bound, must match type of low
+ */
_RS_RUNTIME float __attribute__((overloadable)) clamp(float amount, float low, float high);
_RS_RUNTIME float2 __attribute__((overloadable)) clamp(float2 amount, float2 low, float2 high);
_RS_RUNTIME float3 __attribute__((overloadable)) clamp(float3 amount, float3 low, float3 high);
@@ -389,9 +734,19 @@
_RS_RUNTIME float3 __attribute__((overloadable)) clamp(float3 amount, float low, float high);
_RS_RUNTIME float4 __attribute__((overloadable)) clamp(float4 amount, float low, float high);
+/**
+ * Convert from radians to degrees.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) degrees(float radians);
FN_FUNC_FN(degrees)
+/**
+ * return start + ((stop - start) * amount);
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) mix(float start, float stop, float amount);
_RS_RUNTIME float2 __attribute__((overloadable)) mix(float2 start, float2 stop, float2 amount);
_RS_RUNTIME float3 __attribute__((overloadable)) mix(float3 start, float3 stop, float3 amount);
@@ -400,9 +755,22 @@
_RS_RUNTIME float3 __attribute__((overloadable)) mix(float3 start, float3 stop, float amount);
_RS_RUNTIME float4 __attribute__((overloadable)) mix(float4 start, float4 stop, float amount);
+/**
+ * Convert from degrees to radians.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) radians(float degrees);
FN_FUNC_FN(radians)
+/**
+ * if (v < edge)
+ * return 0.f;
+ * else
+ * return 1.f;
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) step(float edge, float v);
_RS_RUNTIME float2 __attribute__((overloadable)) step(float2 edge, float2 v);
_RS_RUNTIME float3 __attribute__((overloadable)) step(float3 edge, float3 v);
@@ -411,6 +779,7 @@
_RS_RUNTIME float3 __attribute__((overloadable)) step(float3 edge, float v);
_RS_RUNTIME float4 __attribute__((overloadable)) step(float4 edge, float v);
+// not implemented
extern float __attribute__((overloadable)) smoothstep(float, float, float);
extern float2 __attribute__((overloadable)) smoothstep(float2, float2, float2);
extern float3 __attribute__((overloadable)) smoothstep(float3, float3, float3);
@@ -419,29 +788,59 @@
extern float3 __attribute__((overloadable)) smoothstep(float, float, float3);
extern float4 __attribute__((overloadable)) smoothstep(float, float, float4);
+/**
+ * if (v < 0) return -1.f;
+ * else if (v > 0) return 1.f;
+ * else return 0.f;
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) sign(float v);
FN_FUNC_FN(sign)
-// 6.11.5
+/**
+ * Compute the cross product of two vectors.
+ *
+ * Supports 3,4 components
+ */
_RS_RUNTIME float3 __attribute__((overloadable)) cross(float3 lhs, float3 rhs);
-
_RS_RUNTIME float4 __attribute__((overloadable)) cross(float4 lhs, float4 rhs);
+/**
+ * Compute the dot product of two vectors.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) dot(float lhs, float rhs);
_RS_RUNTIME float __attribute__((overloadable)) dot(float2 lhs, float2 rhs);
_RS_RUNTIME float __attribute__((overloadable)) dot(float3 lhs, float3 rhs);
_RS_RUNTIME float __attribute__((overloadable)) dot(float4 lhs, float4 rhs);
+/**
+ * Compute the length of a vector.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) length(float v);
_RS_RUNTIME float __attribute__((overloadable)) length(float2 v);
_RS_RUNTIME float __attribute__((overloadable)) length(float3 v);
_RS_RUNTIME float __attribute__((overloadable)) length(float4 v);
+/**
+ * Compute the distance between two points.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) distance(float lhs, float rhs);
_RS_RUNTIME float __attribute__((overloadable)) distance(float2 lhs, float2 rhs);
_RS_RUNTIME float __attribute__((overloadable)) distance(float3 lhs, float3 rhs);
_RS_RUNTIME float __attribute__((overloadable)) distance(float4 lhs, float4 rhs);
+/**
+ * Normalize a vector.
+ *
+ * Supports 1,2,3,4 components
+ */
_RS_RUNTIME float __attribute__((overloadable)) normalize(float v);
_RS_RUNTIME float2 __attribute__((overloadable)) normalize(float2 v);
_RS_RUNTIME float3 __attribute__((overloadable)) normalize(float3 v);
diff --git a/libs/rs/scriptc/rs_quaternion.rsh b/libs/rs/scriptc/rs_quaternion.rsh
index 36e6736..23945ae 100644
--- a/libs/rs/scriptc/rs_quaternion.rsh
+++ b/libs/rs/scriptc/rs_quaternion.rsh
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-/** @file rs_matrix.rsh
+/** @file rs_quaternion.rsh
* \brief Quaternion routines
*
*
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index bfc6b5d..cd15718 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1558,21 +1558,29 @@
private boolean checkForRingerModeChange(int oldIndex, int direction) {
boolean adjustVolumeIndex = true;
int newRingerMode = mRingerMode;
+ int uiIndex = (oldIndex + 5) / 10;
if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
- // audible mode, at the bottom of the scale
- if ((direction == AudioManager.ADJUST_LOWER &&
- mPrevVolDirection != AudioManager.ADJUST_LOWER) &&
- ((oldIndex + 5) / 10 == 0)) {
- // "silent mode", but which one?
- newRingerMode = System.getInt(mContentResolver, System.VIBRATE_IN_SILENT, 1) == 1
- ? AudioManager.RINGER_MODE_VIBRATE
- : AudioManager.RINGER_MODE_SILENT;
+ if ((direction == AudioManager.ADJUST_LOWER) && (uiIndex <= 1)) {
+ // enter silent mode if current index is the last audible one and not repeating a
+ // volume key down
+ if (mPrevVolDirection != AudioManager.ADJUST_LOWER) {
+ // "silent mode", but which one?
+ newRingerMode = System.getInt(mContentResolver, System.VIBRATE_IN_SILENT, 1) == 1
+ ? AudioManager.RINGER_MODE_VIBRATE
+ : AudioManager.RINGER_MODE_SILENT;
+ }
+ if (uiIndex == 0) {
+ adjustVolumeIndex = false;
+ }
}
} else {
if (direction == AudioManager.ADJUST_RAISE) {
// exiting silent mode
newRingerMode = AudioManager.RINGER_MODE_NORMAL;
+ if (uiIndex != 0) {
+ adjustVolumeIndex = false;
+ }
} else {
// prevent last audible index to reach 0
adjustVolumeIndex = false;
@@ -1581,13 +1589,6 @@
if (newRingerMode != mRingerMode) {
setRingerMode(newRingerMode);
-
- /*
- * If we are changing ringer modes, do not increment/decrement the
- * volume index. Instead, the handler for the message above will
- * take care of changing the index.
- */
- adjustVolumeIndex = false;
}
mPrevVolDirection = direction;
@@ -2637,7 +2638,7 @@
notifyTopOfAudioFocusStack();
// there's a new top of the stack, let the remote control know
synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
} else {
@@ -2680,7 +2681,7 @@
notifyTopOfAudioFocusStack();
// there's a new top of the stack, let the remote control know
synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
}
@@ -2784,7 +2785,7 @@
// there's a new top of the stack, let the remote control know
synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}//synchronized(mAudioFocusLock)
@@ -3182,7 +3183,7 @@
* Helper function:
* Called synchronized on mRCStack
*/
- private void clearRemoteControlDisplay_syncRcs() {
+ private void clearRemoteControlDisplay_syncAfRcs() {
synchronized(mCurrentRcLock) {
mCurrentRcClient = null;
}
@@ -3191,18 +3192,21 @@
}
/**
- * Helper function:
- * Called synchronized on mRCStack
- * mRCStack.empty() is false
+ * Helper function for code readability: only to be called from
+ * checkUpdateRemoteControlDisplay_syncAfRcs() which checks the preconditions for
+ * this method.
+ * Preconditions:
+ * - called synchronized mAudioFocusLock then on mRCStack
+ * - mRCStack.isEmpty() is false
*/
- private void updateRemoteControlDisplay_syncRcs(int infoChangedFlags) {
+ private void updateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) {
RemoteControlStackEntry rcse = mRCStack.peek();
int infoFlagsAboutToBeUsed = infoChangedFlags;
// this is where we enforce opt-in for information display on the remote controls
// with the new AudioManager.registerRemoteControlClient() API
if (rcse.mRcClient == null) {
//Log.w(TAG, "Can't update remote control display with null remote control client");
- clearRemoteControlDisplay_syncRcs();
+ clearRemoteControlDisplay_syncAfRcs();
return;
}
synchronized(mCurrentRcLock) {
@@ -3219,17 +3223,17 @@
/**
* Helper function:
- * Called synchronized on mFocusLock, then mRCStack
+ * Called synchronized on mAudioFocusLock, then mRCStack
* Check whether the remote control display should be updated, triggers the update if required
* @param infoChangedFlags the flags corresponding to the remote control client information
* that has changed, if applicable (checking for the update conditions might trigger a
* clear, rather than an update event).
*/
- private void checkUpdateRemoteControlDisplay_syncRcs(int infoChangedFlags) {
+ private void checkUpdateRemoteControlDisplay_syncAfRcs(int infoChangedFlags) {
// determine whether the remote control display should be refreshed
// if either stack is empty, there is a mismatch, so clear the RC display
if (mRCStack.isEmpty() || mFocusStack.isEmpty()) {
- clearRemoteControlDisplay_syncRcs();
+ clearRemoteControlDisplay_syncAfRcs();
return;
}
// if the top of the two stacks belong to different packages, there is a mismatch, clear
@@ -3237,17 +3241,18 @@
&& (mFocusStack.peek().mPackageName != null)
&& !(mRCStack.peek().mCallingPackageName.compareTo(
mFocusStack.peek().mPackageName) == 0)) {
- clearRemoteControlDisplay_syncRcs();
+ clearRemoteControlDisplay_syncAfRcs();
return;
}
// if the audio focus didn't originate from the same Uid as the one in which the remote
// control information will be retrieved, clear
if (mRCStack.peek().mCallingUid != mFocusStack.peek().mCallingUid) {
- clearRemoteControlDisplay_syncRcs();
+ clearRemoteControlDisplay_syncAfRcs();
return;
}
// refresh conditions were verified: update the remote controls
- updateRemoteControlDisplay_syncRcs(infoChangedFlags);
+ // ok to call: synchronized mAudioFocusLock then on mRCStack, mRCStack is not empty
+ updateRemoteControlDisplay_syncAfRcs(infoChangedFlags);
}
/** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */
@@ -3258,7 +3263,7 @@
synchronized(mRCStack) {
pushMediaButtonReceiver(eventReceiver);
// new RC client, assume every type of information shall be queried
- checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
}
@@ -3273,7 +3278,7 @@
removeMediaButtonReceiver(eventReceiver);
if (topOfStackWillChange) {
// current RC client will change, assume every type of info needs to be queried
- checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
}
@@ -3282,6 +3287,7 @@
/** see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) */
public void registerRemoteControlClient(ComponentName eventReceiver,
IRemoteControlClient rcClient, String clientName, String callingPackageName) {
+ if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
synchronized(mAudioFocusLock) {
synchronized(mRCStack) {
// store the new display information
@@ -3329,7 +3335,7 @@
// if the eventReceiver is at the top of the stack
// then check for potential refresh of the remote controls
if (isCurrentRcController(eventReceiver)) {
- checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
}
}
}
@@ -3434,34 +3440,36 @@
*/
public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
- synchronized(mRCStack) {
- if ((mRcDisplay == rcd) || (rcd == null)) {
- return;
- }
- // if we had a display before, stop monitoring its death
- rcDisplay_stopDeathMonitor_syncRcStack();
- mRcDisplay = rcd;
- // new display, start monitoring its death
- rcDisplay_startDeathMonitor_syncRcStack();
+ synchronized(mAudioFocusLock) {
+ synchronized(mRCStack) {
+ if ((mRcDisplay == rcd) || (rcd == null)) {
+ return;
+ }
+ // if we had a display before, stop monitoring its death
+ rcDisplay_stopDeathMonitor_syncRcStack();
+ mRcDisplay = rcd;
+ // new display, start monitoring its death
+ rcDisplay_startDeathMonitor_syncRcStack();
- // let all the remote control clients there is a new display
- // no need to unplug the previous because we only support one display
- // and the clients don't track the death of the display
- Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
- while(stackIterator.hasNext()) {
- RemoteControlStackEntry rcse = stackIterator.next();
- if(rcse.mRcClient != null) {
- try {
- rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting remote control display to client: " + e);
- e.printStackTrace();
+ // let all the remote control clients there is a new display
+ // no need to unplug the previous because we only support one display
+ // and the clients don't track the death of the display
+ Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
+ while(stackIterator.hasNext()) {
+ RemoteControlStackEntry rcse = stackIterator.next();
+ if(rcse.mRcClient != null) {
+ try {
+ rcse.mRcClient.plugRemoteControlDisplay(mRcDisplay);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting remote control display to client: " + e);
+ e.printStackTrace();
+ }
}
}
- }
- // we have a new display, of which all the clients are now aware: have it be updated
- updateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
+ // we have a new display, of which all the clients are now aware: have it be updated
+ checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL);
+ }
}
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1ee9a1f..e25f654 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -459,6 +459,9 @@
* android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
* element.
*
+ * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
+ * when used with network-based content.
+ *
* <a name="Callbacks"></a>
* <h3>Callbacks</h3>
* <p>Applications may want to register for informational and error
@@ -828,6 +831,7 @@
fd.close();
}
}
+
Log.d(TAG, "Couldn't open file on client side, trying server side");
setDataSource(uri.toString(), headers);
return;
@@ -839,7 +843,8 @@
* @param path the path of the file, or the http/rtsp URL of the stream you want to play
* @throws IllegalStateException if it is called in an invalid state
*/
- public native void setDataSource(String path) throws IOException, IllegalArgumentException, IllegalStateException;
+ public native void setDataSource(String path)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
/**
* Sets the data source (file-path or http/rtsp URL) to use.
@@ -850,7 +855,7 @@
* @hide pending API council
*/
public void setDataSource(String path, Map<String, String> headers)
- throws IOException, IllegalArgumentException, IllegalStateException
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
{
String[] keys = null;
String[] values = null;
@@ -871,7 +876,7 @@
private native void _setDataSource(
String path, String[] keys, String[] values)
- throws IOException, IllegalArgumentException, IllegalStateException;
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
/**
* Sets the data source (FileDescriptor) to use. It is the caller's responsibility
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index d59bc2b..d7b85e4 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -22,6 +22,7 @@
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
+import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -29,6 +30,7 @@
import android.os.RemoteException;
import android.util.Log;
+import java.lang.IllegalArgumentException;
import java.util.HashMap;
/**
@@ -236,6 +238,23 @@
mEventHandler = new EventHandler(this, looper);
}
+ private static final int[] METADATA_KEYS_TYPE_STRING = {
+ MediaMetadataRetriever.METADATA_KEY_ALBUM,
+ MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
+ MediaMetadataRetriever.METADATA_KEY_TITLE,
+ MediaMetadataRetriever.METADATA_KEY_ARTIST,
+ MediaMetadataRetriever.METADATA_KEY_AUTHOR,
+ MediaMetadataRetriever.METADATA_KEY_COMPILATION,
+ MediaMetadataRetriever.METADATA_KEY_COMPOSER,
+ MediaMetadataRetriever.METADATA_KEY_DATE,
+ MediaMetadataRetriever.METADATA_KEY_GENRE,
+ MediaMetadataRetriever.METADATA_KEY_TITLE,
+ MediaMetadataRetriever.METADATA_KEY_WRITER };
+ private static final int[] METADATA_KEYS_TYPE_LONG = {
+ MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
+ MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
+ MediaMetadataRetriever.METADATA_KEY_DURATION };
+
/**
* Class used to modify metadata in a {@link RemoteControlClient} object.
*/
@@ -256,6 +275,11 @@
}
/**
+ * The metadata key for the content artwork / album art.
+ */
+ public final static int METADATA_KEY_ARTWORK = 100;
+
+ /**
* Adds textual information to be displayed.
* Note that none of the information added after {@link #apply()} has been called,
* will be displayed.
@@ -265,49 +289,73 @@
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER},
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
- * @param value the text for the given key, or null to signify there is no valid
+ * .
+ * @param value the text for the given key, or {@code null} to signify there is no valid
* information for the field.
* @return FIXME description
*/
- public synchronized MetadataEditor putString(int key, String value) {
+ public synchronized MetadataEditor putString(int key, String value)
+ throws IllegalArgumentException {
if (mApplied) {
Log.e(TAG, "Can't edit a previously applied MetadataEditor");
return this;
}
+ if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) {
+ throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
+ }
mEditorMetadata.putString(String.valueOf(key), value);
mMetadataChanged = true;
return this;
}
/**
- * The metadata key for the content artwork / album art.
+ * FIXME javadoc
+ * @param key the identifier of a the metadata field to set. Valid values are
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
+ * expressed in milliseconds),
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
+ * @param value FIXME javadoc
+ * @return FIXME javadoc
+ * @throws IllegalArgumentException
*/
- public final int METADATA_KEY_ARTWORK = 100;
+ public synchronized MetadataEditor putLong(int key, long value)
+ throws IllegalArgumentException {
+ if (mApplied) {
+ Log.e(TAG, "Can't edit a previously applied MetadataEditor");
+ return this;
+ }
+ if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) {
+ throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
+ }
+ mEditorMetadata.putLong(String.valueOf(key), value);
+ mMetadataChanged = true;
+ return this;
+ }
/**
* Sets the album / artwork picture to be displayed on the remote control.
* @param key FIXME description
* @param bitmap the bitmap for the artwork, or null if there isn't any.
* @return FIXME description
+ * @throws IllegalArgumentException
* @see android.graphics.Bitmap
*/
- public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap) {
+ public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
+ throws IllegalArgumentException {
if (mApplied) {
Log.e(TAG, "Can't edit a previously applied MetadataEditor");
return this;
}
if (key != METADATA_KEY_ARTWORK) {
- return this;
+ throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
}
if ((mArtworkExpectedWidth > 0) && (mArtworkExpectedHeight > 0)) {
mEditorArtwork = scaleBitmapIfTooBig(bitmap,
@@ -740,6 +788,24 @@
}
}
return bitmap;
+ }
+ /**
+ * Fast routine to go through an array of allowed keys and return whether the key is part
+ * of that array
+ * @param key the key value
+ * @param validKeys the array of valid keys for a given type
+ * @return true if the key is part of the array, false otherwise
+ */
+ private static boolean validTypeForKey(int key, int[] validKeys) {
+ try {
+ for (int i = 0 ; ; i++) {
+ if (key == validKeys[i]) {
+ return true;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return false;
+ }
}
}
diff --git a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
index b7d129d..2b4e85f 100644
--- a/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
+++ b/media/java/android/media/videoeditor/MediaArtistNativeHelper.java
@@ -29,6 +29,7 @@
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.Matrix;
import android.media.videoeditor.VideoEditor.ExportProgressListener;
import android.media.videoeditor.VideoEditor.PreviewProgressListener;
import android.media.videoeditor.VideoEditor.MediaProcessingProgressListener;
@@ -1050,6 +1051,10 @@
*/
public int rgbWidth;
public int rgbHeight;
+ /**
+ * Video rotation degree.
+ */
+ public int rotationDegree;
}
/**
@@ -1700,6 +1705,11 @@
*/
public int audioVolumeValue;
+ /**
+ * Video rotation degree.
+ */
+ public int videoRotation;
+
public String Id;
}
@@ -2254,6 +2264,7 @@
lclipSettings.panZoomTopLeftXEnd = 0;
lclipSettings.panZoomTopLeftYEnd = 0;
lclipSettings.mediaRendering = 0;
+ lclipSettings.rotationDegree = 0;
}
@@ -3784,7 +3795,8 @@
**/
void getPixelsList(String filename, final int width, final int height,
long startMs, long endMs, int thumbnailCount, int[] indices,
- final MediaItem.GetThumbnailListCallback callback) {
+ final MediaItem.GetThumbnailListCallback callback,
+ final int videoRotation) {
/* Make width and height as even */
final int newWidth = (width + 1) & 0xFFFFFFFE;
final int newHeight = (height + 1) & 0xFFFFFFFE;
@@ -3799,7 +3811,7 @@
final int[] rgb888 = new int[thumbnailSize];
final IntBuffer tmpBuffer = IntBuffer.allocate(thumbnailSize);
nativeGetPixelsList(filename, rgb888, newWidth, newHeight,
- thumbnailCount, startMs, endMs, indices,
+ thumbnailCount, videoRotation, startMs, endMs, indices,
new NativeGetPixelsListCallback() {
public void onThumbnail(int index) {
Bitmap bitmap = Bitmap.createBitmap(
@@ -3821,7 +3833,21 @@
canvas.setBitmap(null);
}
- callback.onThumbnail(bitmap, index);
+
+ if (videoRotation == 0) {
+ callback.onThumbnail(bitmap, index);
+ } else {
+ Matrix mtx = new Matrix();
+ mtx.postRotate(videoRotation);
+ Bitmap rotatedBmp =
+ Bitmap.createBitmap(bitmap, 0, 0, width, height, mtx, false);
+ callback.onThumbnail(rotatedBmp, index);
+
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
+ }
+
}
});
@@ -3943,8 +3969,8 @@
long timeMS);
private native int nativeGetPixelsList(String fileName, int[] pixelArray,
- int width, int height, int nosofTN, long startTimeMs, long endTimeMs,
- int[] indices, NativeGetPixelsListCallback callback);
+ int width, int height, int nosofTN, int videoRotation, long startTimeMs,
+ long endTimeMs, int[] indices, NativeGetPixelsListCallback callback);
/**
* Releases the JNI and cleans up the core native module.. Should be called
diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java
index b2a279a..65a9e19 100755
--- a/media/java/android/media/videoeditor/MediaImageItem.java
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -638,7 +638,7 @@
}
mMANativeHelper.getPixelsList(getGeneratedImageClip(), width,
- height, startMs, endMs, thumbnailCount, indices, callback);
+ height, startMs, endMs, thumbnailCount, indices, callback, 0);
}
}
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
index fea751b..2ce857c 100755
--- a/media/java/android/media/videoeditor/MediaVideoItem.java
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -57,6 +57,7 @@
private String mAudioWaveformFilename;
private MediaArtistNativeHelper mMANativeHelper;
private VideoEditorImpl mVideoEditor;
+ private final int mVideoRotationDegree;
/**
* The audio waveform data
*/
@@ -190,6 +191,7 @@
} else {
mWaveformData = null;
}
+ mVideoRotationDegree = properties.videoRotation;
}
/**
@@ -317,7 +319,8 @@
}
mMANativeHelper.getPixelsList(super.getFilename(), width,
- height, startMs, endMs, thumbnailCount, indices, callback);
+ height, startMs, endMs, thumbnailCount, indices, callback,
+ mVideoRotationDegree);
}
/*
@@ -425,7 +428,12 @@
*/
@Override
public int getWidth() {
- return mWidth;
+ if (mVideoRotationDegree == 90 ||
+ mVideoRotationDegree == 270) {
+ return mHeight;
+ } else {
+ return mWidth;
+ }
}
/*
@@ -433,7 +441,12 @@
*/
@Override
public int getHeight() {
- return mHeight;
+ if (mVideoRotationDegree == 90 ||
+ mVideoRotationDegree == 270) {
+ return mWidth;
+ } else {
+ return mHeight;
+ }
}
/*
@@ -725,6 +738,7 @@
clipSettings.beginCutTime = (int)getBoundaryBeginTime();
clipSettings.endCutTime = (int)getBoundaryEndTime();
clipSettings.mediaRendering = mMANativeHelper.getMediaItemRenderingMode(getRenderingMode());
+ clipSettings.rotationDegree = mVideoRotationDegree;
return clipSettings;
}
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 354f2c9..5dfbe01 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -155,6 +155,8 @@
} else { // Throw exception!
if ( opStatus == (status_t) INVALID_OPERATION ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ } else if ( opStatus == (status_t) PERMISSION_DENIED ) {
+ jniThrowException(env, "java/lang/SecurityException", NULL);
} else if ( opStatus != (status_t) OK ) {
if (strlen(message) > 230) {
// if the message is too long, don't bother displaying the status code
diff --git a/media/jni/mediaeditor/VideoEditorClasses.cpp b/media/jni/mediaeditor/VideoEditorClasses.cpp
index 69735ca..4e0e0f2 100755
--- a/media/jni/mediaeditor/VideoEditorClasses.cpp
+++ b/media/jni/mediaeditor/VideoEditorClasses.cpp
@@ -490,7 +490,8 @@
VIDEOEDIT_JAVA_FIELD_INIT("audioDuration", "I"),
VIDEOEDIT_JAVA_FIELD_INIT("audioBitrate", "I"),
VIDEOEDIT_JAVA_FIELD_INIT("audioChannels", "I"),
- VIDEOEDIT_JAVA_FIELD_INIT("audioSamplingFrequency", "I")
+ VIDEOEDIT_JAVA_FIELD_INIT("audioSamplingFrequency", "I"),
+ VIDEOEDIT_JAVA_FIELD_INIT("videoRotation", "I")
};
VIDEOEDIT_JAVA_DEFINE_FIELD_CLASS(Properties, PROPERTIES_CLASS_NAME)
@@ -540,7 +541,8 @@
VIDEOEDIT_JAVA_FIELD_INIT("panZoomTopLeftYEnd", "I" ),
VIDEOEDIT_JAVA_FIELD_INIT("mediaRendering", "I" ),
VIDEOEDIT_JAVA_FIELD_INIT("rgbWidth", "I" ),
- VIDEOEDIT_JAVA_FIELD_INIT("rgbHeight", "I" )
+ VIDEOEDIT_JAVA_FIELD_INIT("rgbHeight", "I" ),
+ VIDEOEDIT_JAVA_FIELD_INIT("rotationDegree", "I" )
};
VIDEOEDIT_JAVA_DEFINE_FIELD_CLASS(ClipSettings, CLIP_SETTINGS_CLASS_NAME)
@@ -1402,6 +1404,10 @@
VIDEOEDIT_LOG_FUNCTION(ANDROID_LOG_INFO, "VIDEO_EDITOR", \
"getClipSettings-- rgbFileHeight %d ",
pSettings->ClipProperties.uiStillPicHeight);
+
+ // Set the video rotation degree
+ pSettings->ClipProperties.videoRotationDegrees =
+ (M4OSA_UInt32)pEnv->GetIntField(object, fieldIds.rotationDegree);
}
// Check if settings could be set.
@@ -1513,6 +1519,10 @@
pSettings->ClipProperties.uiStillPicWidth ,
pSettings->ClipProperties.uiStillPicHeight);
+ // Set the video rotation
+ pEnv->SetIntField(object, fieldIds.rotationDegree,
+ pSettings->ClipProperties.videoRotationDegrees);
+
// Return the object.
(*pObject) = object;
}
@@ -1609,6 +1619,9 @@
pEnv->SetIntField(object, fieldIds.audioSamplingFrequency,
pProperties->uiSamplingFrequency);
+ // Set the video rotation field.
+ pEnv->SetIntField(object, fieldIds.videoRotation, pProperties->uiRotation);
+
// Return the object.
(*pObject) = object;
}
diff --git a/media/jni/mediaeditor/VideoEditorClasses.h b/media/jni/mediaeditor/VideoEditorClasses.h
index 3c10b1d..a4c82a8 100755
--- a/media/jni/mediaeditor/VideoEditorClasses.h
+++ b/media/jni/mediaeditor/VideoEditorClasses.h
@@ -145,6 +145,7 @@
M4OSA_UInt32 uiAudioBitrate;
M4OSA_UInt32 uiNbChannels;
M4OSA_UInt32 uiSamplingFrequency;
+ M4OSA_UInt32 uiRotation;
} VideoEditPropClass_Properties;
typedef struct
@@ -166,6 +167,7 @@
jfieldID audioBitrate;
jfieldID audioChannels;
jfieldID audioSamplingFrequency;
+ jfieldID videoRotation;
} VideoEditJava_PropertiesFieldIds;
@@ -187,6 +189,7 @@
jfieldID mediaRendering;
jfieldID rgbFileWidth;
jfieldID rgbFileHeight;
+ jfieldID rotationDegree;
} VideoEditJava_ClipSettingsFieldIds;
typedef struct
diff --git a/media/jni/mediaeditor/VideoEditorMain.cpp b/media/jni/mediaeditor/VideoEditorMain.cpp
index b737e5d..4e73581 100755
--- a/media/jni/mediaeditor/VideoEditorMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorMain.cpp
@@ -185,6 +185,7 @@
M4OSA_UInt32 width,
M4OSA_UInt32 height,
M4OSA_UInt32 noOfThumbnails,
+ M4OSA_UInt32 videoRotation,
jlong startTime,
jlong endTime,
jintArray indexArray,
@@ -291,7 +292,7 @@
(void *)videoEditor_release },
{"nativeGetPixels", "(Ljava/lang/String;[IIIJ)I",
(void*)videoEditor_getPixels },
- {"nativeGetPixelsList", "(Ljava/lang/String;[IIIIJJ[ILandroid/media/videoeditor/MediaArtistNativeHelper$NativeGetPixelsListCallback;)I",
+ {"nativeGetPixelsList", "(Ljava/lang/String;[IIIIIJJ[ILandroid/media/videoeditor/MediaArtistNativeHelper$NativeGetPixelsListCallback;)I",
(void*)videoEditor_getPixelsList },
{"getMediaProperties",
"(Ljava/lang/String;)Landroid/media/videoeditor/MediaArtistNativeHelper$Properties;",
@@ -375,6 +376,12 @@
pEnv->GetIntField(object,fid);
M4OSA_TRACE1_1("audioVolumeValue = %d",
pSettings->ClipProperties.uiClipAudioVolumePercentage);
+
+ fid = pEnv->GetFieldID(clazz,"videoRotation","I");
+ pSettings->ClipProperties.videoRotationDegrees =
+ pEnv->GetIntField(object,fid);
+ M4OSA_TRACE1_1("videoRotation = %d",
+ pSettings->ClipProperties.videoRotationDegrees);
}
static void jniPreviewProgressCallback (void* cookie, M4OSA_UInt32 msgType,
@@ -789,6 +796,8 @@
pContext->pEditSettings->pClipList[iCurrentClipIndex]->ClipProperties.uiVideoWidth,
(M4OSA_Void **)&frameStr.pBuffer);
tnTimeMs = (M4OSA_UInt32)timeMs;
+
+ frameStr.videoRotationDegree = 0;
} else {
/* Handle 3gp/mp4 Clips here */
/* get thumbnail*/
@@ -913,6 +922,9 @@
/* Fill up the render structure*/
frameStr.pBuffer = (M4OSA_Void*)yuvPlane[0].pac_data;
+
+ frameStr.videoRotationDegree = pContext->pEditSettings->\
+ pClipList[iCurrentClipIndex]->ClipProperties.videoRotationDegrees;
}
frameStr.timeMs = timeMs; /* timestamp on storyboard*/
@@ -976,13 +988,12 @@
videoEditJava_checkAndThrowRuntimeException(&needToBeLoaded, pEnv,
(M4NO_ERROR != result), result);
- if (pContext->pEditSettings->pClipList[iCurrentClipIndex]->FileType ==\
- /*M4VIDEOEDITING_kFileType_JPG */ M4VIDEOEDITING_kFileType_ARGB8888) {
- free(frameStr.pBuffer);
- } else {
- free(yuvPlane[0].pac_data);
+ free(frameStr.pBuffer);
+ if (pContext->pEditSettings->pClipList[iCurrentClipIndex]->FileType !=
+ M4VIDEOEDITING_kFileType_ARGB8888) {
free(yuvPlane);
}
+
return tnTimeMs;
}
@@ -2275,6 +2286,7 @@
M4OSA_UInt32 width,
M4OSA_UInt32 height,
M4OSA_UInt32 noOfThumbnails,
+ M4OSA_UInt32 videoRotation,
jlong startTime,
jlong endTime,
jintArray indexArray,
diff --git a/media/jni/mediaeditor/VideoEditorMain.h b/media/jni/mediaeditor/VideoEditorMain.h
index ca4a945..4c3b517 100755
--- a/media/jni/mediaeditor/VideoEditorMain.h
+++ b/media/jni/mediaeditor/VideoEditorMain.h
@@ -71,6 +71,7 @@
M4OSA_Bool bApplyEffect; /* Apply video effects before render*/
M4OSA_UInt32 clipBeginCutTime; /* Clip begin cut time relative to storyboard */
M4OSA_UInt32 clipEndCutTime; /* Clip end cut time relative to storyboard */
+ M4OSA_UInt32 videoRotationDegree; /* Video rotation degree */
} VideoEditor_renderPreviewFrameStr;
#endif /*__VIDEO_EDITOR_API_H__*/
diff --git a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
index 2ca3a08..c8fb263 100755
--- a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
@@ -258,6 +258,8 @@
pProperties->uiAudioBitrate = pClipProperties->uiAudioBitrate;
pProperties->uiNbChannels = pClipProperties->uiNbChannels;
pProperties->uiSamplingFrequency = pClipProperties->uiSamplingFrequency;
+ pProperties->uiRotation = pClipProperties->videoRotationDegrees;
+
}
// Free the clip properties.
diff --git a/media/libaah_rtp/Android.mk b/media/libaah_rtp/Android.mk
new file mode 100644
index 0000000..1dceda9
--- /dev/null
+++ b/media/libaah_rtp/Android.mk
@@ -0,0 +1,40 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libaah_rtp
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaah_rtp
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ aah_decoder_pump.cpp \
+ aah_rx_player.cpp \
+ aah_rx_player_core.cpp \
+ aah_rx_player_ring_buffer.cpp \
+ aah_rx_player_substream.cpp \
+ aah_tx_packet.cpp \
+ aah_tx_player.cpp \
+ aah_tx_sender.cpp \
+ pipe_event.cpp
+
+LOCAL_C_INCLUDES := \
+ frameworks/base/include \
+ frameworks/base/include/media/stagefright/openmax \
+ frameworks/base/media \
+ frameworks/base/media/libstagefright
+
+LOCAL_SHARED_LIBRARIES := \
+ libaah_timesrv_client \
+ libbinder \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libutils
+
+LOCAL_LDLIBS := \
+ -lpthread
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/media/libaah_rtp/aah_decoder_pump.cpp b/media/libaah_rtp/aah_decoder_pump.cpp
new file mode 100644
index 0000000..cde9be4
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.cpp
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <poll.h>
+#include <pthread.h>
+
+#include <aah_timesrv/cc_helper.h>
+#include <media/AudioSystem.h>
+#include <media/AudioTrack.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+
+namespace android {
+
+AAH_DecoderPump::AAH_DecoderPump(OMXClient& omx)
+ : omx_(omx)
+ , thread_(this)
+ , thread_status_(OK)
+ , renderer_(NULL)
+ , last_queued_pts_valid_(false)
+ , last_queued_pts_(0)
+ , last_ts_transform_valid_(false)
+ , last_volume_(0xFF) {
+}
+
+AAH_DecoderPump::~AAH_DecoderPump() {
+ shutdown();
+}
+
+status_t AAH_DecoderPump::queueForDecode(MediaBuffer* buf) {
+ if (NULL == buf) {
+ return BAD_VALUE;
+ }
+
+ if (OK != thread_status_) {
+ return thread_status_;
+ }
+
+ { // Explicit scope for AutoMutex pattern.
+ AutoMutex lock(&thread_lock_);
+ in_queue_.push_back(buf);
+ }
+
+ thread_cond_.signal();
+
+ return OK;
+}
+
+void AAH_DecoderPump::queueToRenderer(MediaBuffer* decoded_sample) {
+ Mutex::Autolock lock(&render_lock_);
+ sp<MetaData> meta;
+ int64_t ts;
+ status_t res;
+
+ // Fetch the metadata and make sure the sample has a timestamp. We
+ // cannot render samples which are missing PTSs.
+ meta = decoded_sample->meta_data();
+ if ((meta == NULL) || (!meta->findInt64(kKeyTime, &ts))) {
+ LOGV("Decoded sample missing timestamp, cannot render.");
+ } else {
+ // If we currently are not holding on to a renderer, go ahead and
+ // make one now.
+ if (NULL == renderer_) {
+ renderer_ = new TimedAudioTrack();
+ if (NULL != renderer_) {
+ int frameCount;
+ AudioTrack::getMinFrameCount(&frameCount,
+ AUDIO_STREAM_DEFAULT,
+ static_cast<int>(format_sample_rate_));
+ int ch_format = (format_channels_ == 1)
+ ? AUDIO_CHANNEL_OUT_MONO
+ : AUDIO_CHANNEL_OUT_STEREO;
+
+ res = renderer_->set(AUDIO_STREAM_DEFAULT,
+ format_sample_rate_,
+ AUDIO_FORMAT_PCM_16_BIT,
+ ch_format,
+ frameCount);
+ if (res != OK) {
+ LOGE("Failed to setup audio renderer. (res = %d)", res);
+ delete renderer_;
+ renderer_ = NULL;
+ } else {
+ CHECK(last_ts_transform_valid_);
+
+ res = renderer_->setMediaTimeTransform(
+ last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+ if (res != NO_ERROR) {
+ LOGE("Failed to set media time transform on AudioTrack"
+ " (res = %d)", res);
+ delete renderer_;
+ renderer_ = NULL;
+ } else {
+ float volume = static_cast<float>(last_volume_)
+ / 255.0f;
+ if (renderer_->setVolume(volume, volume) != OK) {
+ LOGW("%s: setVolume failed", __FUNCTION__);
+ }
+
+ renderer_->start();
+ }
+ }
+ } else {
+ LOGE("Failed to allocate AudioTrack to use as a renderer.");
+ }
+ }
+
+ if (NULL != renderer_) {
+ uint8_t* decoded_data =
+ reinterpret_cast<uint8_t*>(decoded_sample->data());
+ uint32_t decoded_amt = decoded_sample->range_length();
+ decoded_data += decoded_sample->range_offset();
+
+ sp<IMemory> pcm_payload;
+ res = renderer_->allocateTimedBuffer(decoded_amt, &pcm_payload);
+ if (res != OK) {
+ LOGE("Failed to allocate %d byte audio track buffer."
+ " (res = %d)", decoded_amt, res);
+ } else {
+ memcpy(pcm_payload->pointer(), decoded_data, decoded_amt);
+
+ res = renderer_->queueTimedBuffer(pcm_payload, ts);
+ if (res != OK) {
+ LOGE("Failed to queue %d byte audio track buffer with media"
+ " PTS %lld. (res = %d)", decoded_amt, ts, res);
+ } else {
+ last_queued_pts_valid_ = true;
+ last_queued_pts_ = ts;
+ }
+ }
+
+ } else {
+ LOGE("No renderer, dropping audio payload.");
+ }
+ }
+}
+
+void AAH_DecoderPump::stopAndCleanupRenderer() {
+ if (NULL == renderer_) {
+ return;
+ }
+
+ renderer_->stop();
+ delete renderer_;
+ renderer_ = NULL;
+}
+
+void AAH_DecoderPump::setRenderTSTransform(const LinearTransform& trans) {
+ Mutex::Autolock lock(&render_lock_);
+
+ if (last_ts_transform_valid_ && !memcmp(&trans,
+ &last_ts_transform_,
+ sizeof(trans))) {
+ return;
+ }
+
+ last_ts_transform_ = trans;
+ last_ts_transform_valid_ = true;
+
+ if (NULL != renderer_) {
+ status_t res = renderer_->setMediaTimeTransform(
+ last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+ if (res != NO_ERROR) {
+ LOGE("Failed to set media time transform on AudioTrack"
+ " (res = %d)", res);
+ }
+ }
+}
+
+void AAH_DecoderPump::setRenderVolume(uint8_t volume) {
+ Mutex::Autolock lock(&render_lock_);
+
+ if (volume == last_volume_) {
+ return;
+ }
+
+ last_volume_ = volume;
+ if (renderer_ != NULL) {
+ float volume = static_cast<float>(last_volume_) / 255.0f;
+ if (renderer_->setVolume(volume, volume) != OK) {
+ LOGW("%s: setVolume failed", __FUNCTION__);
+ }
+ }
+}
+
+// isAboutToUnderflow is something of a hack used to figure out when it might be
+// time to give up on trying to fill in a gap in the RTP sequence and simply
+// move on with a discontinuity. If we had perfect knowledge of when we were
+// going to underflow, it would not be a hack, but unfortunately we do not.
+// Right now, we just take the PTS of the last sample queued, and check to see
+// if its presentation time is within kAboutToUnderflowThreshold from now. If
+// it is, then we say that we are about to underflow. This decision is based on
+// two (possibly invalid) assumptions.
+//
+// 1) The transmitter is leading the clock by more than
+// kAboutToUnderflowThreshold.
+// 2) The delta between the PTS of the last sample queued and the next sample
+// is less than the transmitter's clock lead amount.
+//
+// Right now, the default transmitter lead time is 1 second, which is a pretty
+// large number and greater than the 50mSec that kAboutToUnderflowThreshold is
+// currently set to. This should satisfy assumption #1 for now, but changes to
+// the transmitter clock lead time could effect this.
+//
+// For non-sparse streams with a homogeneous sample rate (the vast majority of
+// streams in the world), the delta between any two adjacent PTSs will always be
+// the homogeneous sample period. It is very uncommon to see a sample period
+// greater than the 1 second clock lead we are currently using, and you
+// certainly will not see it in an MP3 file which should satisfy assumption #2.
+// Sparse audio streams (where no audio is transmitted for long periods of
+// silence) and extremely low framerate video stream (like an MPEG-2 slideshow
+// or the video stream for a pay TV audio channel) are examples of streams which
+// might violate assumption #2.
+bool AAH_DecoderPump::isAboutToUnderflow(int64_t threshold) {
+ Mutex::Autolock lock(&render_lock_);
+
+ // If we have never queued anything to the decoder, we really don't know if
+ // we are going to underflow or not.
+ if (!last_queued_pts_valid_ || !last_ts_transform_valid_) {
+ return false;
+ }
+
+ // Don't have access to Common Time? If so, then things are Very Bad
+ // elsewhere in the system; it pretty much does not matter what we do here.
+ // Since we cannot really tell if we are about to underflow or not, its
+ // probably best to assume that we are not and proceed accordingly.
+ int64_t tt_now;
+ if (OK != CCHelper::getCommonTime(&tt_now)) {
+ return false;
+ }
+
+ // Transform from media time to common time.
+ int64_t last_queued_pts_tt;
+ if (!last_ts_transform_.doForwardTransform(last_queued_pts_,
+ &last_queued_pts_tt)) {
+ return false;
+ }
+
+ // Check to see if we are underflowing.
+ return ((tt_now + threshold - last_queued_pts_tt) > 0);
+}
+
+void* AAH_DecoderPump::workThread() {
+ // No need to lock when accessing decoder_ from the thread. The
+ // implementation of init and shutdown ensure that other threads never touch
+ // decoder_ while the work thread is running.
+ CHECK(decoder_ != NULL);
+ CHECK(format_ != NULL);
+
+ // Start the decoder and note its result code. If something goes horribly
+ // wrong, callers of queueForDecode and getOutput will be able to detect
+ // that the thread encountered a fatal error and shut down by examining
+ // thread_status_.
+ thread_status_ = decoder_->start(format_.get());
+ if (OK != thread_status_) {
+ LOGE("AAH_DecoderPump's work thread failed to start decoder (res = %d)",
+ thread_status_);
+ return NULL;
+ }
+
+ while (!thread_.exitPending()) {
+ status_t res;
+ MediaBuffer* bufOut = NULL;
+
+ res = decoder_->read(&bufOut);
+
+ if (res == INFO_FORMAT_CHANGED) {
+ // Format has changed. Destroy our current renderer so that a new
+ // one can be created during queueToRenderer with the proper format.
+ //
+ // TODO : In order to transition seamlessly, we should change this
+ // to put the old renderer in a queue to play out completely before
+ // we destroy it. We can still create a new renderer, the timed
+ // nature of the renderer should ensure a seamless splice.
+ stopAndCleanupRenderer();
+ res = OK;
+ }
+
+ // Any error aside from INFO_FORMAT_CHANGED is considered to be fatal
+ // and will result in shutdown of the decoder pump thread.
+ if (res != OK) {
+ LOGE("%s: Failed to decode data (res = %d)",
+ __PRETTY_FUNCTION__, res);
+ CHECK(NULL == bufOut);
+ thread_status_ = res;
+ break;
+ }
+
+ if (NULL == bufOut) {
+ LOGW("%s: Successful decode, but no buffer produced",
+ __PRETTY_FUNCTION__);
+ continue;
+ }
+
+ queueToRenderer(bufOut);
+ bufOut->release();
+ }
+
+ decoder_->stop();
+ stopAndCleanupRenderer();
+
+ return NULL;
+}
+
+status_t AAH_DecoderPump::init(sp<MetaData> params) {
+ Mutex::Autolock lock(&init_lock_);
+
+ if (decoder_ != NULL) {
+ // already inited
+ return OK;
+ }
+
+ if (params == NULL) {
+ return BAD_VALUE;
+ }
+
+ if (!params->findInt32(kKeyChannelCount, &format_channels_)) {
+ return BAD_VALUE;
+ }
+
+ if (!params->findInt32(kKeySampleRate, &format_sample_rate_)) {
+ return BAD_VALUE;
+ }
+
+ CHECK(OK == thread_status_);
+ CHECK(decoder_ == NULL);
+
+ status_t ret_val = UNKNOWN_ERROR;
+
+ // Cache the format and attempt to create the decoder.
+ format_ = params;
+ decoder_ = OMXCodec::Create(
+ omx_.interface(), // IOMX Handle
+ format_, // Metadata for substream (indicates codec)
+ false, // Make a decoder, not an encoder
+ sp<MediaSource>(this)); // We will be the source for this codec.
+
+ if (decoder_ == NULL) {
+ LOGE("Failed to allocate decoder in %s", __PRETTY_FUNCTION__);
+ goto bailout;
+ }
+
+ // Fire up the pump thread. It will take care of starting and stopping the
+ // decoder.
+ ret_val = thread_.run("aah_decode_pump", ANDROID_PRIORITY_AUDIO);
+ if (OK != ret_val) {
+ LOGE("Failed to start work thread in %s (res = %d)",
+ __PRETTY_FUNCTION__, ret_val);
+ goto bailout;
+ }
+
+bailout:
+ if (OK != ret_val) {
+ decoder_ = NULL;
+ format_ = NULL;
+ }
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::shutdown() {
+ Mutex::Autolock lock(&init_lock_);
+ return shutdown_l();
+}
+
+status_t AAH_DecoderPump::shutdown_l() {
+ thread_.requestExit();
+ thread_cond_.signal();
+ thread_.requestExitAndWait();
+
+ MBQueue::iterator I;
+ for (I = in_queue_.begin(); I != in_queue_.end(); ++I) {
+ (*I)->release();
+ }
+ in_queue_.clear();
+
+ last_queued_pts_valid_ = false;
+ last_ts_transform_valid_ = false;
+ last_volume_ = 0xFF;
+
+ decoder_ = NULL;
+ format_ = NULL;
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::read(MediaBuffer **buffer,
+ const ReadOptions *options) {
+ if (!buffer) {
+ return BAD_VALUE;
+ }
+
+ *buffer = NULL;
+
+ // While its not time to shut down, and we have no data to process, wait.
+ AutoMutex lock(&thread_lock_);
+ while (!thread_.exitPending() && in_queue_.empty())
+ thread_cond_.wait(thread_lock_);
+
+ // At this point, if its not time to shutdown then we must have something to
+ // process. Go ahead and pop the front of the queue for processing.
+ if (!thread_.exitPending()) {
+ CHECK(!in_queue_.empty());
+
+ *buffer = *(in_queue_.begin());
+ in_queue_.erase(in_queue_.begin());
+ }
+
+ // If we managed to get a buffer, then everything must be OK. If not, then
+ // we must be shutting down.
+ return (NULL == *buffer) ? INVALID_OPERATION : OK;
+}
+
+AAH_DecoderPump::ThreadWrapper::ThreadWrapper(AAH_DecoderPump* owner)
+ : Thread(false /* canCallJava*/ )
+ , owner_(owner) {
+}
+
+bool AAH_DecoderPump::ThreadWrapper::threadLoop() {
+ CHECK(NULL != owner_);
+ owner_->workThread();
+ return false;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_decoder_pump.h b/media/libaah_rtp/aah_decoder_pump.h
new file mode 100644
index 0000000..f5e7d03
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __DECODER_PUMP_H__
+#define __DECODER_PUMP_H__
+
+#include <pthread.h>
+
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class MetaData;
+class OMXClient;
+class TimedAudioTrack;
+
+class AAH_DecoderPump : public MediaSource {
+ public:
+ explicit AAH_DecoderPump(OMXClient& omx);
+
+ status_t queueForDecode(MediaBuffer* buf);
+
+ status_t init(sp<MetaData> params);
+ status_t shutdown();
+
+ void setRenderTSTransform(const LinearTransform& trans);
+ void setRenderVolume(uint8_t volume);
+ bool isAboutToUnderflow(int64_t threshold);
+
+ // MediaSource methods
+ virtual status_t start(MetaData *params) { return OK; }
+ virtual sp<MetaData> getFormat() { return format_; }
+ virtual status_t stop() { return OK; }
+ virtual status_t read(MediaBuffer **buffer,
+ const ReadOptions *options);
+
+ protected:
+ virtual ~AAH_DecoderPump();
+
+ private:
+ class ThreadWrapper : public Thread {
+ public:
+ friend class AAH_DecoderPump;
+ explicit ThreadWrapper(AAH_DecoderPump* owner);
+
+ private:
+ virtual bool threadLoop();
+ AAH_DecoderPump* owner_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+ };
+
+ void* workThread();
+ virtual status_t shutdown_l();
+ void queueToRenderer(MediaBuffer* decoded_sample);
+ void stopAndCleanupRenderer();
+
+ sp<MetaData> format_;
+ int32_t format_channels_;
+ int32_t format_sample_rate_;
+
+ sp<MediaSource> decoder_;
+ OMXClient& omx_;
+ Mutex init_lock_;
+
+ ThreadWrapper thread_;
+ Condition thread_cond_;
+ Mutex thread_lock_;
+ status_t thread_status_;
+
+ Mutex render_lock_;
+ TimedAudioTrack* renderer_;
+ bool last_queued_pts_valid_;
+ int64_t last_queued_pts_;
+ bool last_ts_transform_valid_;
+ LinearTransform last_ts_transform_;
+ uint8_t last_volume_;
+
+ // protected by the thread_lock_
+ typedef List<MediaBuffer*> MBQueue;
+ MBQueue in_queue_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_DecoderPump);
+};
+
+} // namespace android
+#endif // __DECODER_PUMP_H__
diff --git a/media/libaah_rtp/aah_rx_player.cpp b/media/libaah_rtp/aah_rx_player.cpp
new file mode 100644
index 0000000..1fe836c
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <aah_timesrv/cc_helper.h>
+#include <binder/IServiceManager.h>
+#include <media/MediaPlayerInterface.h>
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRTPRingBufferSize = 1 << 10;
+
+sp<MediaPlayerBase> createAAH_RXPlayer() {
+ sp<MediaPlayerBase> ret = new AAH_RXPlayer();
+ return ret;
+}
+
+AAH_RXPlayer::AAH_RXPlayer()
+ : ring_buffer_(kRTPRingBufferSize)
+ , substreams_(NULL) {
+ thread_wrapper_ = new ThreadWrapper(*this);
+
+ is_playing_ = false;
+ multicast_joined_ = false;
+ transmitter_known_ = false;
+ current_epoch_known_ = false;
+ data_source_set_ = false;
+ sock_fd_ = -1;
+
+ substreams_.setCapacity(4);
+
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+ memset(&transmitter_addr_, 0, sizeof(transmitter_addr_));
+
+ fetchAudioFlinger();
+}
+
+AAH_RXPlayer::~AAH_RXPlayer() {
+ reset_l();
+ CHECK(substreams_.size() == 0);
+ omx_.disconnect();
+}
+
+status_t AAH_RXPlayer::initCheck() {
+ if (thread_wrapper_ == NULL) {
+ LOGE("Failed to allocate thread wrapper!");
+ return NO_MEMORY;
+ }
+
+ if (!ring_buffer_.initCheck()) {
+ LOGE("Failed to allocate reassembly ring buffer!");
+ return NO_MEMORY;
+ }
+
+ // Check for the presense of the A@H common time service by attempting to
+ // query for CommonTime's frequency. If we get an error back, we cannot
+ // talk to the service at all and should abort now.
+ status_t res;
+ uint64_t freq;
+ res = CCHelper::getCommonFreq(&freq);
+ if (OK != res) {
+ LOGE("Failed to connect to common time service!");
+ return res;
+ }
+
+ return omx_.connect();
+}
+
+status_t AAH_RXPlayer::setDataSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ AutoMutex api_lock(&api_lock_);
+ uint32_t a, b, c, d;
+ uint16_t port;
+
+ if (data_source_set_) {
+ return INVALID_OPERATION;
+ }
+
+ if (NULL == url) {
+ return BAD_VALUE;
+ }
+
+ if (5 != sscanf(url, "%*[^:/]://%u.%u.%u.%u:%hu", &a, &b, &c, &d, &port)) {
+ LOGE("Failed to parse URL \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ if ((a > 255) || (b > 255) || (c > 255) || (d > 255) || (port == 0)) {
+ LOGE("Bad multicast address \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ LOGI("setDataSource :: %u.%u.%u.%u:%hu", a, b, c, d, port);
+
+ a = (a << 24) | (b << 16) | (c << 8) | d;
+
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+ listen_addr_.sin_family = AF_INET;
+ listen_addr_.sin_port = htons(port);
+ listen_addr_.sin_addr.s_addr = htonl(a);
+ data_source_set_ = true;
+
+ return OK;
+}
+
+status_t AAH_RXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_RXPlayer::setVideoSurface(const sp<Surface>& surface) {
+ return OK;
+}
+
+status_t AAH_RXPlayer::setVideoSurfaceTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ return OK;
+}
+
+status_t AAH_RXPlayer::prepare() {
+ return OK;
+}
+
+status_t AAH_RXPlayer::prepareAsync() {
+ sendEvent(MEDIA_PREPARED);
+ return OK;
+}
+
+status_t AAH_RXPlayer::start() {
+ AutoMutex api_lock(&api_lock_);
+
+ if (is_playing_) {
+ return OK;
+ }
+
+ status_t res = startWorkThread();
+ is_playing_ = (res == OK);
+ return res;
+}
+
+status_t AAH_RXPlayer::stop() {
+ return pause();
+}
+
+status_t AAH_RXPlayer::pause() {
+ AutoMutex api_lock(&api_lock_);
+ stopWorkThread();
+ CHECK(sock_fd_ < 0);
+ is_playing_ = false;
+ return OK;
+}
+
+bool AAH_RXPlayer::isPlaying() {
+ AutoMutex api_lock(&api_lock_);
+ return is_playing_;
+}
+
+status_t AAH_RXPlayer::seekTo(int msec) {
+ sendEvent(MEDIA_SEEK_COMPLETE);
+ return OK;
+}
+
+status_t AAH_RXPlayer::getCurrentPosition(int *msec) {
+ if (NULL != msec) {
+ *msec = 0;
+ }
+ return OK;
+}
+
+status_t AAH_RXPlayer::getDuration(int *msec) {
+ if (NULL != msec) {
+ *msec = 1;
+ }
+ return OK;
+}
+
+status_t AAH_RXPlayer::reset() {
+ AutoMutex api_lock(&api_lock_);
+ reset_l();
+ return OK;
+}
+
+void AAH_RXPlayer::reset_l() {
+ stopWorkThread();
+ CHECK(sock_fd_ < 0);
+ CHECK(!multicast_joined_);
+ is_playing_ = false;
+ data_source_set_ = false;
+ transmitter_known_ = false;
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+}
+
+status_t AAH_RXPlayer::setLooping(int loop) {
+ return OK;
+}
+
+player_type AAH_RXPlayer::playerType() {
+ return AAH_RX_PLAYER;
+}
+
+status_t AAH_RXPlayer::setParameter(int key, const Parcel &request) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::getParameter(int key, Parcel *reply) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::invoke(const Parcel& request, Parcel *reply) {
+ if (!reply) {
+ return BAD_VALUE;
+ }
+
+ int32_t magic;
+ status_t err = request.readInt32(&magic);
+ if (err != OK) {
+ reply->writeInt32(err);
+ return OK;
+ }
+
+ if (magic != 0x12345) {
+ reply->writeInt32(BAD_VALUE);
+ return OK;
+ }
+
+ int32_t methodID;
+ err = request.readInt32(&methodID);
+ if (err != OK) {
+ reply->writeInt32(err);
+ return OK;
+ }
+
+ switch (methodID) {
+ // Get Volume
+ case INVOKE_GET_MASTER_VOLUME: {
+ if (audio_flinger_ != NULL) {
+ reply->writeInt32(OK);
+ reply->writeFloat(audio_flinger_->masterVolume());
+ } else {
+ reply->writeInt32(UNKNOWN_ERROR);
+ }
+ } break;
+
+ // Set Volume
+ case INVOKE_SET_MASTER_VOLUME: {
+ float targetVol = request.readFloat();
+ reply->writeInt32(audio_flinger_->setMasterVolume(targetVol));
+ } break;
+
+ default: return BAD_VALUE;
+ }
+
+ return OK;
+}
+
+void AAH_RXPlayer::fetchAudioFlinger() {
+ if (audio_flinger_ == NULL) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder;
+ binder = sm->getService(String16("media.audio_flinger"));
+
+ if (binder == NULL) {
+ LOGW("AAH_RXPlayer failed to fetch handle to audio flinger."
+ " Master volume control will not be possible.");
+ }
+
+ audio_flinger_ = interface_cast<IAudioFlinger>(binder);
+ }
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player.h b/media/libaah_rtp/aah_rx_player.h
new file mode 100644
index 0000000..44aedc3
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.h
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_RX_PLAYER_H__
+#define __AAH_RX_PLAYER_H__
+
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <netinet/in.h>
+#include <utils/KeyedVector.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+#include "pipe_event.h"
+
+namespace android {
+
+class AAH_RXPlayer : public MediaPlayerInterface {
+ public:
+ AAH_RXPlayer();
+
+ virtual status_t initCheck();
+ virtual status_t setDataSource(const char *url,
+ const KeyedVector<String8, String8>*
+ headers);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setVideoSurface(const sp<Surface>& surface);
+ virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+ surfaceTexture);
+ virtual status_t prepare();
+ virtual status_t prepareAsync();
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t pause();
+ virtual bool isPlaying();
+ virtual status_t seekTo(int msec);
+ virtual status_t getCurrentPosition(int *msec);
+ virtual status_t getDuration(int *msec);
+ virtual status_t reset();
+ virtual status_t setLooping(int loop);
+ virtual player_type playerType();
+ virtual status_t setParameter(int key, const Parcel &request);
+ virtual status_t getParameter(int key, Parcel *reply);
+ virtual status_t invoke(const Parcel& request, Parcel *reply);
+
+ protected:
+ virtual ~AAH_RXPlayer();
+
+ private:
+ class ThreadWrapper : public Thread {
+ public:
+ friend class AAH_RXPlayer;
+ explicit ThreadWrapper(AAH_RXPlayer& player)
+ : Thread(false /* canCallJava */ )
+ , player_(player) { }
+
+ virtual bool threadLoop() { return player_.threadLoop(); }
+
+ private:
+ AAH_RXPlayer& player_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+ };
+
+#pragma pack(push, 1)
+ struct PacketBuffer {
+ ssize_t length_;
+ uint8_t data_[0];
+
+ // TODO : consider changing this to be some form of ring buffer or free
+ // pool system instead of just using the heap in order to avoid heap
+ // fragmentation.
+ static PacketBuffer* allocate(ssize_t length);
+ static void destroy(PacketBuffer* pb);
+
+ private:
+ // Force people to use allocate/destroy instead of new/delete.
+ PacketBuffer() { }
+ ~PacketBuffer() { }
+ };
+
+ struct RetransRequest {
+ uint32_t magic_;
+ uint32_t mcast_ip_;
+ uint16_t mcast_port_;
+ uint16_t start_seq_;
+ uint16_t end_seq_;
+ };
+#pragma pack(pop)
+
+ enum GapStatus {
+ kGS_NoGap = 0,
+ kGS_NormalGap,
+ kGS_FastStartGap,
+ };
+
+ struct SeqNoGap {
+ uint16_t start_seq_;
+ uint16_t end_seq_;
+ };
+
+ class RXRingBuffer {
+ public:
+ explicit RXRingBuffer(uint32_t capacity);
+ ~RXRingBuffer();
+
+ bool initCheck() const { return (ring_ != NULL); }
+ void reset();
+
+ // Push a packet buffer with a given sequence number into the ring
+ // buffer. pushBuffer will always consume the buffer pushed to it,
+ // either destroying it because it was a duplicate or overflow, or
+ // holding on to it in the ring. Callers should not hold any references
+ // to PacketBuffers after they have been pushed to the ring. Returns
+ // false in the case of a serious error (such as ring overflow).
+ // Callers should consider resetting the pipeline entirely in the event
+ // of a serious error.
+ bool pushBuffer(PacketBuffer* buf, uint16_t seq);
+
+ // Fetch the next buffer in the RTP sequence. Returns NULL if there is
+ // no buffer to fetch. If a non-NULL PacketBuffer is returned,
+ // is_discon will be set to indicate whether or not this PacketBuffer is
+ // discontiuous with any previously returned packet buffers. Packet
+ // buffers returned by fetchBuffer are the caller's responsibility; they
+ // must be certain to destroy the buffers when they are done.
+ PacketBuffer* fetchBuffer(bool* is_discon);
+
+ // Returns true and fills out the gap structure if the read pointer of
+ // the ring buffer is currently pointing to a gap which would stall a
+ // fetchBuffer operation. Returns false if the read pointer is not
+ // pointing to a gap in the sequence currently.
+ GapStatus fetchCurrentGap(SeqNoGap* gap);
+
+ // Causes the read pointer to skip over any portion of a gap indicated
+ // by nak. If nak is NULL, any gap currently blocking the read pointer
+ // will be completely skipped. If any portion of a gap is skipped, the
+ // next successful read from fetch buffer will indicate a discontinuity.
+ void processNAK(SeqNoGap* nak = NULL);
+
+ // Compute the number of milliseconds until the inactivity timer for
+ // this RTP stream. Returns -1 if there is no active timeout, or 0 if
+ // the system has already timed out.
+ int computeInactivityTimeout();
+
+ private:
+ Mutex lock_;
+ PacketBuffer** ring_;
+ uint32_t capacity_;
+ uint32_t rd_;
+ uint32_t wr_;
+
+ uint16_t rd_seq_;
+ bool rd_seq_known_;
+ bool waiting_for_fast_start_;
+ bool fetched_first_packet_;
+
+ uint64_t rtp_activity_timeout_;
+ bool rtp_activity_timeout_valid_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(RXRingBuffer);
+ };
+
+ class Substream : public virtual RefBase {
+ public:
+ Substream(uint32_t ssrc, OMXClient& omx);
+
+ void cleanupBufferInProgress();
+ void shutdown();
+ void processPayloadStart(uint8_t* buf,
+ uint32_t amt,
+ int32_t ts_lower);
+ void processPayloadCont (uint8_t* buf,
+ uint32_t amt);
+ void processCompletedBuffer();
+ void processTSTransform(const LinearTransform& trans);
+
+ bool isAboutToUnderflow();
+ uint32_t getSSRC() const { return ssrc_; }
+ uint16_t getProgramID() const { return (ssrc_ >> 5) & 0x1F; }
+
+ protected:
+ virtual ~Substream() {
+ shutdown();
+ }
+
+ private:
+ void cleanupDecoder();
+ bool setupSubstreamType(uint8_t substream_type,
+ uint8_t codec_type);
+
+ uint32_t ssrc_;
+ bool waiting_for_rap_;
+
+ bool substream_details_known_;
+ uint8_t substream_type_;
+ uint8_t codec_type_;
+ sp<MetaData> substream_meta_;
+
+ MediaBuffer* buffer_in_progress_;
+ uint32_t expected_buffer_size_;
+ uint32_t buffer_filled_;
+
+ sp<AAH_DecoderPump> decoder_;
+
+ static int64_t kAboutToUnderflowThreshold;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Substream);
+ };
+
+ typedef DefaultKeyedVector< uint32_t, sp<Substream> > SubstreamVec;
+
+ status_t startWorkThread();
+ void stopWorkThread();
+ virtual bool threadLoop();
+ bool setupSocket();
+ void cleanupSocket();
+ void resetPipeline();
+ void reset_l();
+ bool processRX(PacketBuffer* pb);
+ void processRingBuffer();
+ void processCommandPacket(PacketBuffer* pb);
+ bool processGaps();
+ int computeNextGapRetransmitTimeout();
+ void fetchAudioFlinger();
+
+ PipeEvent wakeup_work_thread_evt_;
+ sp<ThreadWrapper> thread_wrapper_;
+ Mutex api_lock_;
+ bool is_playing_;
+ bool data_source_set_;
+
+ struct sockaddr_in listen_addr_;
+ int sock_fd_;
+ bool multicast_joined_;
+
+ struct sockaddr_in transmitter_addr_;
+ bool transmitter_known_;
+
+ uint32_t current_epoch_;
+ bool current_epoch_known_;
+
+ SeqNoGap current_gap_;
+ GapStatus current_gap_status_;
+ uint64_t next_retrans_req_time_;
+
+ RXRingBuffer ring_buffer_;
+ SubstreamVec substreams_;
+ OMXClient omx_;
+
+ // Connection to audio flinger used to hack a path to setMasterVolume.
+ sp<IAudioFlinger> audio_flinger_;
+
+ static const uint32_t kRTPRingBufferSize;
+ static const uint32_t kRetransRequestMagic;
+ static const uint32_t kFastStartRequestMagic;
+ static const uint32_t kRetransNAKMagic;
+ static const uint32_t kGapRerequestTimeoutUSec;
+ static const uint32_t kFastStartTimeoutUSec;
+ static const uint32_t kRTPActivityTimeoutUSec;
+
+ static const uint32_t INVOKE_GET_MASTER_VOLUME = 3;
+ static const uint32_t INVOKE_SET_MASTER_VOLUME = 4;
+
+ static uint64_t monotonicUSecNow();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_RXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_RX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_rx_player_core.cpp b/media/libaah_rtp/aah_rx_player_core.cpp
new file mode 100644
index 0000000..a86ea17
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_core.cpp
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <utils/misc.h>
+
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRetransRequestMagic =
+ FOURCC('T','r','e','q');
+const uint32_t AAH_RXPlayer::kRetransNAKMagic =
+ FOURCC('T','n','a','k');
+const uint32_t AAH_RXPlayer::kFastStartRequestMagic =
+ FOURCC('T','f','s','t');
+const uint32_t AAH_RXPlayer::kGapRerequestTimeoutUSec = 75000;
+const uint32_t AAH_RXPlayer::kFastStartTimeoutUSec = 800000;
+const uint32_t AAH_RXPlayer::kRTPActivityTimeoutUSec = 10000000;
+
+static inline int16_t fetchInt16(uint8_t* data) {
+ return static_cast<int16_t>(U16_AT(data));
+}
+
+static inline int32_t fetchInt32(uint8_t* data) {
+ return static_cast<int32_t>(U32_AT(data));
+}
+
+static inline int64_t fetchInt64(uint8_t* data) {
+ return static_cast<int64_t>(U64_AT(data));
+}
+
+uint64_t AAH_RXPlayer::monotonicUSecNow() {
+ struct timespec now;
+ int res = clock_gettime(CLOCK_MONOTONIC, &now);
+ CHECK(res >= 0);
+
+ uint64_t ret = static_cast<uint64_t>(now.tv_sec) * 1000000;
+ ret += now.tv_nsec / 1000;
+
+ return ret;
+}
+
+status_t AAH_RXPlayer::startWorkThread() {
+ status_t res;
+ stopWorkThread();
+ res = thread_wrapper_->run("TRX_Player", PRIORITY_AUDIO);
+
+ if (res != OK) {
+ LOGE("Failed to start work thread (res = %d)", res);
+ }
+
+ return res;
+}
+
+void AAH_RXPlayer::stopWorkThread() {
+ thread_wrapper_->requestExit(); // set the exit pending flag
+ wakeup_work_thread_evt_.setEvent();
+
+ status_t res;
+ res = thread_wrapper_->requestExitAndWait(); // block until thread exit.
+ if (res != OK) {
+ LOGE("Failed to stop work thread (res = %d)", res);
+ }
+
+ wakeup_work_thread_evt_.clearPendingEvents();
+}
+
+void AAH_RXPlayer::cleanupSocket() {
+ if (sock_fd_ >= 0) {
+ if (multicast_joined_) {
+ int res;
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = listen_addr_.sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ res = setsockopt(sock_fd_,
+ IPPROTO_IP,
+ IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (res < 0) {
+ LOGW("Failed to leave multicast group. (%d, %d)", res, errno);
+ }
+ multicast_joined_ = false;
+ }
+
+ close(sock_fd_);
+ sock_fd_ = -1;
+ }
+
+ resetPipeline();
+}
+
+void AAH_RXPlayer::resetPipeline() {
+ ring_buffer_.reset();
+
+ // Explicitly shudown all of the active substreams, then call clear out the
+ // collection. Failure to clear out a substream can result in its decoder
+ // holding a reference to itself and therefor not going away when the
+ // collection is cleared.
+ for (size_t i = 0; i < substreams_.size(); ++i)
+ substreams_.valueAt(i)->shutdown();
+
+ substreams_.clear();
+
+ current_gap_status_ = kGS_NoGap;
+}
+
+bool AAH_RXPlayer::setupSocket() {
+ long flags;
+ int res, buf_size;
+ socklen_t opt_size;
+
+ cleanupSocket();
+ CHECK(sock_fd_ < 0);
+
+ // Make the socket
+ sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock_fd_ < 0) {
+ LOGE("Failed to create listen socket (errno %d)", errno);
+ goto bailout;
+ }
+
+ // Set non-blocking operation
+ flags = fcntl(sock_fd_, F_GETFL);
+ res = fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK);
+ if (res < 0) {
+ LOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+ sock_fd_, errno);
+ goto bailout;
+ }
+
+ // Bind to our port
+ struct sockaddr_in bind_addr;
+ memset(&bind_addr, 0, sizeof(bind_addr));
+ bind_addr.sin_family = AF_INET;
+ bind_addr.sin_addr.s_addr = INADDR_ANY;
+ bind_addr.sin_port = listen_addr_.sin_port;
+ res = bind(sock_fd_,
+ reinterpret_cast<const sockaddr*>(&bind_addr),
+ sizeof(bind_addr));
+ if (res < 0) {
+ uint32_t a = ntohl(bind_addr.sin_addr.s_addr);
+ uint16_t p = ntohs(bind_addr.sin_port);
+ LOGE("Failed to bind socket (%d) to %d.%d.%d.%d:%hd. (errno %d)",
+ sock_fd_,
+ (a >> 24) & 0xFF,
+ (a >> 16) & 0xFF,
+ (a >> 8) & 0xFF,
+ (a ) & 0xFF,
+ p,
+ errno);
+
+ goto bailout;
+ }
+
+ buf_size = 1 << 16; // 64k
+ res = setsockopt(sock_fd_,
+ SOL_SOCKET, SO_RCVBUF,
+ &buf_size, sizeof(buf_size));
+ if (res < 0) {
+ LOGW("Failed to increase socket buffer size to %d. (errno %d)",
+ buf_size, errno);
+ }
+
+ buf_size = 0;
+ opt_size = sizeof(buf_size);
+ res = getsockopt(sock_fd_,
+ SOL_SOCKET, SO_RCVBUF,
+ &buf_size, &opt_size);
+ if (res < 0) {
+ LOGW("Failed to increase socket buffer size to %d. (errno %d)",
+ buf_size, errno);
+ } else {
+ LOGI("RX socket buffer size is now %d bytes", buf_size);
+ }
+
+ if (listen_addr_.sin_addr.s_addr) {
+ // Join the multicast group and we should be good to go.
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = listen_addr_.sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ res = setsockopt(sock_fd_,
+ IPPROTO_IP,
+ IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (res < 0) {
+ LOGE("Failed to join multicast group. (errno %d)", errno);
+ goto bailout;
+ }
+ multicast_joined_ = true;
+ }
+
+ return true;
+
+bailout:
+ cleanupSocket();
+ return false;
+}
+
+bool AAH_RXPlayer::threadLoop() {
+ struct pollfd poll_fds[2];
+ bool process_more_right_now = false;
+
+ if (!setupSocket()) {
+ sendEvent(MEDIA_ERROR);
+ goto bailout;
+ }
+
+ while (!thread_wrapper_->exitPending()) {
+ // Step 1: Wait until there is something to do.
+ int gap_timeout = computeNextGapRetransmitTimeout();
+ int ring_timeout = ring_buffer_.computeInactivityTimeout();
+ int timeout = -1;
+
+ if (!ring_timeout) {
+ LOGW("RTP inactivity timeout reached, resetting pipeline.");
+ resetPipeline();
+ timeout = gap_timeout;
+ } else {
+ if (gap_timeout < 0) {
+ timeout = ring_timeout;
+ } else if (ring_timeout < 0) {
+ timeout = gap_timeout;
+ } else {
+ timeout = (gap_timeout < ring_timeout) ? gap_timeout
+ : ring_timeout;
+ }
+ }
+
+ if ((0 != timeout) && (!process_more_right_now)) {
+ // Set up the events to wait on. Start with the wakeup pipe.
+ memset(&poll_fds, 0, sizeof(poll_fds));
+ poll_fds[0].fd = wakeup_work_thread_evt_.getWakeupHandle();
+ poll_fds[0].events = POLLIN;
+
+ // Add the RX socket.
+ poll_fds[1].fd = sock_fd_;
+ poll_fds[1].events = POLLIN;
+
+ // Wait for something interesing to happen.
+ int poll_res = poll(poll_fds, NELEM(poll_fds), timeout);
+ if (poll_res < 0) {
+ LOGE("Fatal error (%d,%d) while waiting on events",
+ poll_res, errno);
+ sendEvent(MEDIA_ERROR);
+ goto bailout;
+ }
+ }
+
+ if (thread_wrapper_->exitPending()) {
+ break;
+ }
+
+ wakeup_work_thread_evt_.clearPendingEvents();
+ process_more_right_now = false;
+
+ // Step 2: Do we have data waiting in the socket? If so, drain the
+ // socket moving valid RTP information into the ring buffer to be
+ // processed.
+ if (poll_fds[1].revents) {
+ struct sockaddr_in from;
+ socklen_t from_len;
+
+ ssize_t res = 0;
+ while (!thread_wrapper_->exitPending()) {
+ // Check the size of any pending packet.
+ res = recv(sock_fd_, NULL, 0, MSG_PEEK | MSG_TRUNC);
+
+ // Error?
+ if (res < 0) {
+ // If the error is anything other than would block,
+ // something has gone very wrong.
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) {
+ LOGE("Fatal socket error during recvfrom (%d, %d)",
+ (int)res, errno);
+ goto bailout;
+ }
+
+ // Socket is out of data, just break out of processing and
+ // wait for more.
+ break;
+ }
+
+ // Allocate a payload.
+ PacketBuffer* pb = PacketBuffer::allocate(res);
+ if (NULL == pb) {
+ LOGE("Fatal error, failed to allocate packet buffer of"
+ " length %u", static_cast<uint32_t>(res));
+ goto bailout;
+ }
+
+ // Fetch the data.
+ from_len = sizeof(from);
+ res = recvfrom(sock_fd_, pb->data_, pb->length_, 0,
+ reinterpret_cast<struct sockaddr*>(&from),
+ &from_len);
+ if (res != pb->length_) {
+ LOGE("Fatal error, fetched packet length (%d) does not"
+ " match peeked packet length (%u). This should never"
+ " happen. (errno = %d)",
+ static_cast<int>(res),
+ static_cast<uint32_t>(pb->length_),
+ errno);
+ }
+
+ bool drop_packet = false;
+ if (transmitter_known_) {
+ if (from.sin_addr.s_addr !=
+ transmitter_addr_.sin_addr.s_addr) {
+ uint32_t a = ntohl(from.sin_addr.s_addr);
+ uint16_t p = ntohs(from.sin_port);
+ LOGV("Dropping packet from unknown transmitter"
+ " %u.%u.%u.%u:%hu",
+ ((a >> 24) & 0xFF),
+ ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF),
+ ( a & 0xFF),
+ p);
+
+ drop_packet = true;
+ } else {
+ transmitter_addr_.sin_port = from.sin_port;
+ }
+ } else {
+ memcpy(&transmitter_addr_, &from, sizeof(from));
+ transmitter_known_ = true;
+ }
+
+ if (!drop_packet) {
+ bool serious_error = !processRX(pb);
+
+ if (serious_error) {
+ // Something went "seriously wrong". Currently, the
+ // only trigger for this should be a ring buffer
+ // overflow. The current failsafe behavior for when
+ // something goes seriously wrong is to just reset the
+ // pipeline. The system should behave as if this
+ // AAH_RXPlayer was just set up for the first time.
+ LOGE("Something just went seriously wrong with the"
+ " pipeline. Resetting.");
+ resetPipeline();
+ }
+ } else {
+ PacketBuffer::destroy(pb);
+ }
+ }
+ }
+
+ // Step 3: Process any data we mave have accumulated in the ring buffer
+ // so far.
+ if (!thread_wrapper_->exitPending()) {
+ processRingBuffer();
+ }
+
+ // Step 4: At this point in time, the ring buffer should either be
+ // empty, or stalled in front of a gap caused by some dropped packets.
+ // Check on the current gap situation and deal with it in an appropriate
+ // fashion. If processGaps returns true, it means that it has given up
+ // on a gap and that we should try to process some more data
+ // immediately.
+ if (!thread_wrapper_->exitPending()) {
+ process_more_right_now = processGaps();
+ }
+ }
+
+bailout:
+ cleanupSocket();
+ return false;
+}
+
+bool AAH_RXPlayer::processRX(PacketBuffer* pb) {
+ CHECK(NULL != pb);
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+ uint32_t nak_magic;
+ uint16_t seq_no;
+ uint32_t epoch;
+
+ // Every packet either starts with an RTP header which is at least 12 bytes
+ // long or is a retry NAK which is 14 bytes long. If there are fewer than
+ // 12 bytes here, this cannot be a proper RTP packet.
+ if (amt < 12) {
+ LOGV("Dropping packet, too short to contain RTP header (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto drop_packet;
+ }
+
+ // Check to see if this is the special case of a NAK packet.
+ nak_magic = ntohl(*(reinterpret_cast<uint32_t*>(data)));
+ if (nak_magic == kRetransNAKMagic) {
+ // Looks like a NAK packet; make sure its long enough.
+
+ if (amt < static_cast<ssize_t>(sizeof(RetransRequest))) {
+ LOGV("Dropping packet, too short to contain NAK payload (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto drop_packet;
+ }
+
+ SeqNoGap gap;
+ RetransRequest* rtr = reinterpret_cast<RetransRequest*>(data);
+ gap.start_seq_ = ntohs(rtr->start_seq_);
+ gap.end_seq_ = ntohs(rtr->end_seq_);
+
+ LOGV("Process NAK for gap at [%hu, %hu]", gap.start_seq_, gap.end_seq_);
+ ring_buffer_.processNAK(&gap);
+
+ return true;
+ }
+
+ // According to the TRTP spec, version should be 2, padding should be 0,
+ // extension should be 0 and CSRCCnt should be 0. If any of these tests
+ // fail, we chuck the packet.
+ if (data[0] != 0x80) {
+ LOGV("Dropping packet, bad V/P/X/CSRCCnt field (0x%02x)",
+ data[0]);
+ goto drop_packet;
+ }
+
+ // Check the payload type. For TRTP, it should always be 100.
+ if ((data[1] & 0x7F) != 100) {
+ LOGV("Dropping packet, bad payload type. (%u)",
+ data[1] & 0x7F);
+ goto drop_packet;
+ }
+
+ // Check whether the transmitter has begun a new epoch.
+ epoch = (U32_AT(data + 8) >> 10) & 0x3FFFFF;
+ if (current_epoch_known_) {
+ if (epoch != current_epoch_) {
+ LOGV("%s: new epoch %u", __PRETTY_FUNCTION__, epoch);
+ current_epoch_ = epoch;
+ resetPipeline();
+ }
+ } else {
+ current_epoch_ = epoch;
+ current_epoch_known_ = true;
+ }
+
+ // Extract the sequence number and hand the packet off to the ring buffer
+ // for dropped packet detection and later processing.
+ seq_no = U16_AT(data + 2);
+ return ring_buffer_.pushBuffer(pb, seq_no);
+
+drop_packet:
+ PacketBuffer::destroy(pb);
+ return true;
+}
+
+void AAH_RXPlayer::processRingBuffer() {
+ PacketBuffer* pb;
+ bool is_discon;
+ sp<Substream> substream;
+ LinearTransform trans;
+ bool foundTrans = false;
+
+ while (NULL != (pb = ring_buffer_.fetchBuffer(&is_discon))) {
+ if (is_discon) {
+ // Abort all partially assembled payloads.
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ CHECK(substreams_.valueAt(i) != NULL);
+ substreams_.valueAt(i)->cleanupBufferInProgress();
+ }
+ }
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+
+ // Should not have any non-RTP packets in the ring buffer. RTP packets
+ // must be at least 12 bytes long.
+ CHECK(amt >= 12);
+
+ // Extract the marker bit and the SSRC field.
+ bool marker = (data[1] & 0x80) != 0;
+ uint32_t ssrc = U32_AT(data + 8);
+
+ // Is this the start of a new TRTP payload? If so, the marker bit
+ // should be set and there are some things we should be checking for.
+ if (marker) {
+ // TRTP headers need to have at least a byte for version, a byte for
+ // payload type and flags, and 4 bytes for length.
+ if (amt < 18) {
+ LOGV("Dropping packet, too short to contain TRTP header"
+ " (%u bytes)", static_cast<uint32_t>(amt));
+ goto process_next_packet;
+ }
+
+ // Check the TRTP version and extract the payload type/flags.
+ uint8_t trtp_version = data[12];
+ uint8_t payload_type = (data[13] >> 4) & 0xF;
+ uint8_t trtp_flags = data[13] & 0xF;
+
+ if (1 != trtp_version) {
+ LOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+ goto process_next_packet;
+ }
+
+ // Is there a timestamp transformation present on this packet? If
+ // so, extract it and pass it to the appropriate substreams.
+ if (trtp_flags & 0x02) {
+ ssize_t offset = 18 + ((trtp_flags & 0x01) ? 4 : 0);
+ if (amt < (offset + 24)) {
+ LOGV("Dropping packet, too short to contain TRTP Timestamp"
+ " Transformation (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto process_next_packet;
+ }
+
+ trans.a_zero = fetchInt64(data + offset);
+ trans.b_zero = fetchInt64(data + offset + 16);
+ trans.a_to_b_numer = static_cast<int32_t>(
+ fetchInt32 (data + offset + 8));
+ trans.a_to_b_denom = U32_AT(data + offset + 12);
+ foundTrans = true;
+
+ uint32_t program_id = (ssrc >> 5) & 0x1F;
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ sp<Substream> iter = substreams_.valueAt(i);
+ CHECK(iter != NULL);
+
+ if (iter->getProgramID() == program_id) {
+ iter->processTSTransform(trans);
+ }
+ }
+ }
+
+ // Is this a command packet? If so, its not necessarily associate
+ // with one particular substream. Just give it to the command
+ // packet handler and then move on.
+ if (4 == payload_type) {
+ processCommandPacket(pb);
+ goto process_next_packet;
+ }
+ }
+
+ // If we got to here, then we are a normal packet. Find (or allocate)
+ // the substream we belong to and send the packet off to be processed.
+ substream = substreams_.valueFor(ssrc);
+ if (substream == NULL) {
+ substream = new Substream(ssrc, omx_);
+ if (substream == NULL) {
+ LOGE("Failed to allocate substream for SSRC 0x%08x", ssrc);
+ goto process_next_packet;
+ }
+ substreams_.add(ssrc, substream);
+
+ if (foundTrans) {
+ substream->processTSTransform(trans);
+ }
+ }
+
+ CHECK(substream != NULL);
+
+ if (marker) {
+ // Start of a new TRTP payload for this substream. Extract the
+ // lower 32 bits of the timestamp and hand the buffer to the
+ // substream for processing.
+ uint32_t ts_lower = U32_AT(data + 4);
+ substream->processPayloadStart(data + 12, amt - 12, ts_lower);
+ } else {
+ // Continuation of an existing TRTP payload. Just hand it off to
+ // the substream for processing.
+ substream->processPayloadCont(data + 12, amt - 12);
+ }
+
+process_next_packet:
+ PacketBuffer::destroy(pb);
+ } // end of main processing while loop.
+}
+
+void AAH_RXPlayer::processCommandPacket(PacketBuffer* pb) {
+ CHECK(NULL != pb);
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+
+ // verify that this packet meets the minimum length of a command packet
+ if (amt < 20) {
+ return;
+ }
+
+ uint8_t trtp_version = data[12];
+ uint8_t trtp_flags = data[13] & 0xF;
+
+ if (1 != trtp_version) {
+ LOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+ return;
+ }
+
+ // calculate the start of the command payload
+ ssize_t offset = 18;
+ if (trtp_flags & 0x01) {
+ // timestamp is present (4 bytes)
+ offset += 4;
+ }
+ if (trtp_flags & 0x02) {
+ // transform is present (24 bytes)
+ offset += 24;
+ }
+
+ // the packet must contain 2 bytes of command payload beyond the TRTP header
+ if (amt < offset + 2) {
+ return;
+ }
+
+ uint16_t command_id = U16_AT(data + offset);
+
+ switch (command_id) {
+ case TRTPControlPacket::kCommandNop:
+ break;
+
+ case TRTPControlPacket::kCommandEOS:
+ case TRTPControlPacket::kCommandFlush: {
+ uint16_t program_id = (U32_AT(data + 8) >> 5) & 0x1F;
+ LOGI("*** %s flushing program_id=%d",
+ __PRETTY_FUNCTION__, program_id);
+
+ Vector<uint32_t> substreams_to_remove;
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ sp<Substream> iter = substreams_.valueAt(i);
+ if (iter->getProgramID() == program_id) {
+ iter->shutdown();
+ substreams_to_remove.add(iter->getSSRC());
+ }
+ }
+
+ for (size_t i = 0; i < substreams_to_remove.size(); ++i) {
+ substreams_.removeItem(substreams_to_remove[i]);
+ }
+ } break;
+ }
+}
+
+bool AAH_RXPlayer::processGaps() {
+ // Deal with the current gap situation. Specifically...
+ //
+ // 1) If a new gap has shown up, send a retransmit request to the
+ // transmitter.
+ // 2) If a gap we were working on has had a packet in the middle or at
+ // the end filled in, send another retransmit request for the begining
+ // portion of the gap. TRTP was designed for LANs where packet
+ // re-ordering is very unlikely; so see the middle or end of a gap
+ // filled in before the begining is an almost certain indication that
+ // a retransmission packet was also dropped.
+ // 3) If we have been working on a gap for a while and it still has not
+ // been filled in, send another retransmit request.
+ // 4) If the are no more gaps in the ring, clear the current_gap_status_
+ // flag to indicate that all is well again.
+
+ // Start by fetching the active gap status.
+ SeqNoGap gap;
+ bool send_retransmit_request = false;
+ bool ret_val = false;
+ GapStatus gap_status;
+ if (kGS_NoGap != (gap_status = ring_buffer_.fetchCurrentGap(&gap))) {
+ // Note: checking for a change in the end sequence number should cover
+ // moving on to an entirely new gap for case #1 as well as resending the
+ // begining of a gap range for case #2.
+ send_retransmit_request = (kGS_NoGap == current_gap_status_) ||
+ (current_gap_.end_seq_ != gap.end_seq_);
+
+ // If this is the same gap we have been working on, and it has timed
+ // out, then check to see if our substreams are about to underflow. If
+ // so, instead of sending another retransmit request, just give up on
+ // this gap and move on.
+ if (!send_retransmit_request &&
+ (kGS_NoGap != current_gap_status_) &&
+ (0 == computeNextGapRetransmitTimeout())) {
+
+ // If out current gap is the fast-start gap, don't bother to skip it
+ // because substreams look like the are about to underflow.
+ if ((kGS_FastStartGap != gap_status) ||
+ (current_gap_.end_seq_ != gap.end_seq_)) {
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ if (substreams_.valueAt(i)->isAboutToUnderflow()) {
+ LOGV("About to underflow, giving up on gap [%hu, %hu]",
+ gap.start_seq_, gap.end_seq_);
+ ring_buffer_.processNAK();
+ current_gap_status_ = kGS_NoGap;
+ return true;
+ }
+ }
+ }
+
+ // Looks like no one is about to underflow. Just go ahead and send
+ // the request.
+ send_retransmit_request = true;
+ }
+ } else {
+ current_gap_status_ = kGS_NoGap;
+ }
+
+ if (send_retransmit_request) {
+ // If we have been working on a fast start, and it is still not filled
+ // in, even after the extended retransmit time out, give up and skip it.
+ // The system should fall back into its normal slow-start behavior.
+ if ((kGS_FastStartGap == current_gap_status_) &&
+ (current_gap_.end_seq_ == gap.end_seq_)) {
+ LOGV("Fast start is taking forever; giving up.");
+ ring_buffer_.processNAK();
+ current_gap_status_ = kGS_NoGap;
+ return true;
+ }
+
+ // Send the request.
+ RetransRequest req;
+ uint32_t magic = (kGS_FastStartGap == gap_status)
+ ? kFastStartRequestMagic
+ : kRetransRequestMagic;
+ req.magic_ = htonl(magic);
+ req.mcast_ip_ = listen_addr_.sin_addr.s_addr;
+ req.mcast_port_ = listen_addr_.sin_port;
+ req.start_seq_ = htons(gap.start_seq_);
+ req.end_seq_ = htons(gap.end_seq_);
+
+ {
+ uint32_t a = ntohl(transmitter_addr_.sin_addr.s_addr);
+ uint16_t p = ntohs(transmitter_addr_.sin_port);
+ LOGV("Sending to transmitter %u.%u.%u.%u:%hu",
+ ((a >> 24) & 0xFF),
+ ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF),
+ ( a & 0xFF),
+ p);
+ }
+
+ int res = sendto(sock_fd_, &req, sizeof(req), 0,
+ reinterpret_cast<struct sockaddr*>(&transmitter_addr_),
+ sizeof(transmitter_addr_));
+ if (res < 0) {
+ LOGE("Error when sending retransmit request (%d)", errno);
+ } else {
+ LOGV("%s request for range [%hu, %hu] sent",
+ (kGS_FastStartGap == gap_status) ? "Fast Start" : "Retransmit",
+ gap.start_seq_, gap.end_seq_);
+ }
+
+ // Update the current gap info.
+ current_gap_ = gap;
+ current_gap_status_ = gap_status;
+ next_retrans_req_time_ = monotonicUSecNow() +
+ ((kGS_FastStartGap == current_gap_status_)
+ ? kFastStartTimeoutUSec
+ : kGapRerequestTimeoutUSec);
+ }
+
+ return false;
+}
+
+// Compute when its time to send the next gap retransmission in milliseconds.
+// Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit
+// right now.
+int AAH_RXPlayer::computeNextGapRetransmitTimeout() {
+ if (kGS_NoGap == current_gap_status_) {
+ return -1;
+ }
+
+ int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow();
+
+ timeout_delta /= 1000;
+ if (timeout_delta <= 0) {
+ return 0;
+ }
+
+ return static_cast<uint32_t>(timeout_delta);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_ring_buffer.cpp b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
new file mode 100644
index 0000000..22467f7
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+AAH_RXPlayer::RXRingBuffer::RXRingBuffer(uint32_t capacity) {
+ capacity_ = capacity;
+ rd_ = wr_ = 0;
+ ring_ = new PacketBuffer*[capacity];
+ memset(ring_, 0, sizeof(PacketBuffer*) * capacity);
+ reset();
+}
+
+AAH_RXPlayer::RXRingBuffer::~RXRingBuffer() {
+ reset();
+ delete[] ring_;
+}
+
+void AAH_RXPlayer::RXRingBuffer::reset() {
+ AutoMutex lock(&lock_);
+
+ if (NULL != ring_) {
+ while (rd_ != wr_) {
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ PacketBuffer::destroy(ring_[rd_]);
+ ring_[rd_] = NULL;
+ }
+ rd_ = (rd_ + 1) % capacity_;
+ }
+ }
+
+ rd_ = wr_ = 0;
+ rd_seq_known_ = false;
+ waiting_for_fast_start_ = true;
+ fetched_first_packet_ = false;
+ rtp_activity_timeout_valid_ = false;
+}
+
+bool AAH_RXPlayer::RXRingBuffer::pushBuffer(PacketBuffer* buf,
+ uint16_t seq) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != buf);
+
+ rtp_activity_timeout_valid_ = true;
+ rtp_activity_timeout_ = monotonicUSecNow() + kRTPActivityTimeoutUSec;
+
+ // If the ring buffer is totally reset (we have never received a single
+ // payload) then we don't know the rd sequence number and this should be
+ // simple. We just store the payload, advance the wr pointer and record the
+ // initial sequence number.
+ if (!rd_seq_known_) {
+ CHECK(rd_ == wr_);
+ CHECK(NULL == ring_[wr_]);
+ CHECK(wr_ < capacity_);
+
+ ring_[wr_] = buf;
+ wr_ = (wr_ + 1) % capacity_;
+ rd_seq_ = seq;
+ rd_seq_known_ = true;
+ return true;
+ }
+
+ // Compute the seqence number of this payload and of the write pointer,
+ // normalized around the read pointer. IOW - transform the payload seq no
+ // and the wr pointer seq no into a space where the rd pointer seq no is
+ // zero. This will define 4 cases we can consider...
+ //
+ // 1) norm_seq == norm_wr_seq
+ // This payload is contiguous with the last. All is good.
+ //
+ // 2) ((norm_seq < norm_wr_seq) && (norm_seq >= norm_rd_seq)
+ // aka ((norm_seq < norm_wr_seq) && (norm_seq >= 0)
+ // This payload is in the past, in the unprocessed region of the ring
+ // buffer. It is probably a retransmit intended to fill in a dropped
+ // payload; it may be a duplicate.
+ //
+ // 3) ((norm_seq - norm_wr_seq) & 0x8000) != 0
+ // This payload is in the past compared to the write pointer (or so very
+ // far in the future that it has wrapped the seq no space), but not in
+ // the unprocessed region of the ring buffer. This could be a duplicate
+ // retransmit; we just drop these payloads unless we are waiting for our
+ // first fast start packet. If we are waiting for fast start, than this
+ // packet is probably the first packet of the fast start retransmission.
+ // If it will fit in the buffer, back up the read pointer to its position
+ // and clear the fast start flag, otherwise just drop it.
+ //
+ // 4) ((norm_seq - norm_wr_seq) & 0x8000) == 0
+ // This payload which is ahead of the next write pointer. This indicates
+ // that we have missed some payloads and need to request a retransmit.
+ // If norm_seq >= (capacity - 1), then the gap is so large that it would
+ // overflow the ring buffer and we should probably start to panic.
+
+ uint16_t norm_wr_seq = ((wr_ + capacity_ - rd_) % capacity_);
+ uint16_t norm_seq = seq - rd_seq_;
+
+ // Check for overflow first.
+ if ((!(norm_seq & 0x8000)) && (norm_seq >= (capacity_ - 1))) {
+ LOGW("Ring buffer overflow; cap = %u, [rd, wr] = [%hu, %hu], seq = %hu",
+ capacity_, rd_seq_, norm_wr_seq + rd_seq_, seq);
+ PacketBuffer::destroy(buf);
+ return false;
+ }
+
+ // Check for case #1
+ if (norm_seq == norm_wr_seq) {
+ CHECK(wr_ < capacity_);
+ CHECK(NULL == ring_[wr_]);
+
+ ring_[wr_] = buf;
+ wr_ = (wr_ + 1) % capacity_;
+
+ CHECK(wr_ != rd_);
+ return true;
+ }
+
+ // Check case #2
+ uint32_t ring_pos = (rd_ + norm_seq) % capacity_;
+ if ((norm_seq < norm_wr_seq) && (!(norm_seq & 0x8000))) {
+ // Do we already have a payload for this slot? If so, then this looks
+ // like a duplicate retransmit. Just ignore it.
+ if (NULL != ring_[ring_pos]) {
+ LOGD("RXed duplicate retransmit, seq = %hu", seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ // Looks like we were missing this payload. Go ahead and store it.
+ ring_[ring_pos] = buf;
+ }
+
+ return true;
+ }
+
+ // Check case #3
+ if ((norm_seq - norm_wr_seq) & 0x8000) {
+ if (!waiting_for_fast_start_) {
+ LOGD("RXed duplicate retransmit from before rd pointer, seq = %hu",
+ seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ // Looks like a fast start fill-in. Go ahead and store it, assuming
+ // that we can fit it in the buffer.
+ uint32_t implied_ring_size = static_cast<uint32_t>(norm_wr_seq)
+ + (rd_seq_ - seq);
+
+ if (implied_ring_size >= (capacity_ - 1)) {
+ LOGD("RXed what looks like a fast start packet (seq = %hu),"
+ " but packet is too far in the past to fit into the ring"
+ " buffer. Dropping.", seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ ring_pos = (rd_ + capacity_ + seq - rd_seq_) % capacity_;
+ rd_seq_ = seq;
+ rd_ = ring_pos;
+ waiting_for_fast_start_ = false;
+
+ CHECK(ring_pos < capacity_);
+ CHECK(NULL == ring_[ring_pos]);
+ ring_[ring_pos] = buf;
+ }
+
+ }
+ return true;
+ }
+
+ // Must be in case #4 with no overflow. This packet fits in the current
+ // ring buffer, but is discontiuguous. Advance the write pointer leaving a
+ // gap behind.
+ uint32_t gap_len = (ring_pos + capacity_ - wr_) % capacity_;
+ LOGD("Drop detected; %u packets, seq_range [%hu, %hu]",
+ gap_len,
+ rd_seq_ + norm_wr_seq,
+ rd_seq_ + norm_wr_seq + gap_len - 1);
+
+ CHECK(NULL == ring_[ring_pos]);
+ ring_[ring_pos] = buf;
+ wr_ = (ring_pos + 1) % capacity_;
+ CHECK(wr_ != rd_);
+
+ return true;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::RXRingBuffer::fetchBuffer(bool* is_discon) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != is_discon);
+
+ // If the read seqence number is not known, then this ring buffer has not
+ // received a packet since being reset and there cannot be any packets to
+ // return. If we are still waiting for the first fast start packet to show
+ // up, we don't want to let any buffer be consumed yet because we expect to
+ // see a packet before the initial read sequence number show up shortly.
+ if (!rd_seq_known_ || waiting_for_fast_start_) {
+ *is_discon = false;
+ return NULL;
+ }
+
+ PacketBuffer* ret = NULL;
+ *is_discon = !fetched_first_packet_;
+
+ while ((rd_ != wr_) && (NULL == ret)) {
+ CHECK(rd_ < capacity_);
+
+ // If we hit a gap, stall and do not advance the read pointer. Let the
+ // higher level code deal with requesting retries and/or deciding to
+ // skip the current gap.
+ ret = ring_[rd_];
+ if (NULL == ret) {
+ break;
+ }
+
+ ring_[rd_] = NULL;
+ rd_ = (rd_ + 1) % capacity_;
+ ++rd_seq_;
+ }
+
+ if (NULL != ret) {
+ fetched_first_packet_ = true;
+ }
+
+ return ret;
+}
+
+AAH_RXPlayer::GapStatus
+AAH_RXPlayer::RXRingBuffer::fetchCurrentGap(SeqNoGap* gap) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != gap);
+
+ // If the read seqence number is not known, then this ring buffer has not
+ // received a packet since being reset and there cannot be any gaps.
+ if (!rd_seq_known_) {
+ return kGS_NoGap;
+ }
+
+ // If we are waiting for fast start, then the current gap is a fast start
+ // gap and it includes all packets before the read sequence number.
+ if (waiting_for_fast_start_) {
+ gap->start_seq_ =
+ gap->end_seq_ = rd_seq_ - 1;
+ return kGS_FastStartGap;
+ }
+
+ // If rd == wr, then the buffer is empty and there cannot be any gaps.
+ if (rd_ == wr_) {
+ return kGS_NoGap;
+ }
+
+ // If rd_ is currently pointing at an unprocessed packet, then there is no
+ // current gap.
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ return kGS_NoGap;
+ }
+
+ // Looks like there must be a gap here. The start of the gap is the current
+ // rd sequence number, all we need to do now is determine its length in
+ // order to compute the end sequence number.
+ gap->start_seq_ = rd_seq_;
+ uint16_t end = rd_seq_;
+ uint32_t tmp = (rd_ + 1) % capacity_;
+ while ((tmp != wr_) && (NULL == ring_[tmp])) {
+ ++end;
+ tmp = (tmp + 1) % capacity_;
+ }
+ gap->end_seq_ = end;
+
+ return kGS_NormalGap;
+}
+
+void AAH_RXPlayer::RXRingBuffer::processNAK(SeqNoGap* nak) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+
+ // If we were waiting for our first fast start fill-in packet, and we
+ // received a NAK, then apparantly we are not getting our fast start. Just
+ // clear the waiting flag and go back to normal behavior.
+ if (waiting_for_fast_start_) {
+ waiting_for_fast_start_ = false;
+ }
+
+ // If we have not received a packet since last reset, or there is no data in
+ // the ring, then there is nothing to skip.
+ if ((!rd_seq_known_) || (rd_ == wr_)) {
+ return;
+ }
+
+ // If rd_ is currently pointing at an unprocessed packet, then there is no
+ // gap to skip.
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ return;
+ }
+
+ // Looks like there must be a gap here. Advance rd until we have passed
+ // over the portion of it indicated by nak (or all of the gap if nak is
+ // NULL). Then reset fetched_first_packet_ so that the next read will show
+ // up as being discontiguous.
+ uint16_t seq_after_gap = (NULL == nak) ? 0 : nak->end_seq_ + 1;
+ while ((rd_ != wr_) &&
+ (NULL == ring_[rd_]) &&
+ ((NULL == nak) || (seq_after_gap != rd_seq_))) {
+ rd_ = (rd_ + 1) % capacity_;
+ ++rd_seq_;
+ }
+ fetched_first_packet_ = false;
+}
+
+int AAH_RXPlayer::RXRingBuffer::computeInactivityTimeout() {
+ AutoMutex lock(&lock_);
+
+ if (!rtp_activity_timeout_valid_) {
+ return -1;
+ }
+
+ uint64_t now = monotonicUSecNow();
+ if (rtp_activity_timeout_ <= now) {
+ return 0;
+ }
+
+ return (rtp_activity_timeout_ - now) / 1000;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::PacketBuffer::allocate(ssize_t length) {
+ if (length <= 0) {
+ return NULL;
+ }
+
+ uint32_t alloc_len = sizeof(PacketBuffer) + length;
+ PacketBuffer* ret = reinterpret_cast<PacketBuffer*>(
+ new uint8_t[alloc_len]);
+
+ if (NULL != ret) {
+ ret->length_ = length;
+ }
+
+ return ret;
+}
+
+void AAH_RXPlayer::PacketBuffer::destroy(PacketBuffer* pb) {
+ uint8_t* kill_me = reinterpret_cast<uint8_t*>(pb);
+ delete[] kill_me;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_substream.cpp b/media/libaah_rtp/aah_rx_player_substream.cpp
new file mode 100644
index 0000000..b015fc9
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_substream.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include <include/avc_utils.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+int64_t AAH_RXPlayer::Substream::kAboutToUnderflowThreshold =
+ 50ull * 1000;
+
+AAH_RXPlayer::Substream::Substream(uint32_t ssrc, OMXClient& omx) {
+ ssrc_ = ssrc;
+ substream_details_known_ = false;
+ buffer_in_progress_ = NULL;
+
+ decoder_ = new AAH_DecoderPump(omx);
+ if (decoder_ == NULL) {
+ LOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__);
+ }
+
+ // cleanupBufferInProgress will reset most of the internal state variables.
+ // Just need to make sure that buffer_in_progress_ is NULL before calling.
+ cleanupBufferInProgress();
+}
+
+
+void AAH_RXPlayer::Substream::shutdown() {
+ substream_meta_ = NULL;
+ cleanupBufferInProgress();
+ cleanupDecoder();
+}
+
+void AAH_RXPlayer::Substream::cleanupBufferInProgress() {
+ if (NULL != buffer_in_progress_) {
+ buffer_in_progress_->release();
+ buffer_in_progress_ = NULL;
+ }
+
+ expected_buffer_size_ = 0;
+ buffer_filled_ = 0;
+ waiting_for_rap_ = true;
+}
+
+void AAH_RXPlayer::Substream::cleanupDecoder() {
+ if (decoder_ != NULL) {
+ decoder_->shutdown();
+ }
+}
+
+void AAH_RXPlayer::Substream::processPayloadStart(uint8_t* buf,
+ uint32_t amt,
+ int32_t ts_lower) {
+ uint32_t min_length = 6;
+ // Do we have a buffer in progress already? If so, abort the buffer. In
+ // theory, this should never happen. If there were a discontinutity in the
+ // stream, the discon in the seq_nos at the RTP level should have already
+ // triggered a cleanup of the buffer in progress. To see a problem at this
+ // level is an indication either of a bug in the transmitter, or some form
+ // of terrible corruption/tampering on the wire.
+ if (NULL != buffer_in_progress_) {
+ LOGE("processPayloadStart is aborting payload already in progress.");
+ cleanupBufferInProgress();
+ }
+
+ // Parse enough of the header to know where we stand. Since this is a
+ // payload start, it should begin with a TRTP header which has to be at
+ // least 6 bytes long.
+ if (amt < min_length) {
+ LOGV("Discarding payload too short to contain TRTP header (len = %u)",
+ amt);
+ return;
+ }
+
+ // Check the TRTP version number.
+ if (0x01 != buf[0]) {
+ LOGV("Unexpected TRTP version (%u) in header. Expected %u.",
+ buf[0], 1);
+ return;
+ }
+
+ // Extract the substream type field and make sure its one we understand (and
+ // one that does not conflict with any previously received substream type.
+ uint8_t header_type = (buf[1] >> 4) & 0xF;
+ switch (header_type) {
+ case 0x01:
+ // Audio, yay! Just break. We understand audio payloads.
+ break;
+ case 0x02:
+ LOGV("RXed packet with unhandled TRTP header type (Video).");
+ return;
+ case 0x03:
+ LOGV("RXed packet with unhandled TRTP header type (Subpicture).");
+ return;
+ case 0x04:
+ LOGV("RXed packet with unhandled TRTP header type (Control).");
+ return;
+ default:
+ LOGV("RXed packet with unhandled TRTP header type (%u).",
+ header_type);
+ return;
+ }
+
+ if (substream_details_known_ && (header_type != substream_type_)) {
+ LOGV("RXed TRTP Payload for SSRC=0x%08x where header type (%u) does not"
+ " match previously received header type (%u)",
+ ssrc_, header_type, substream_type_);
+ return;
+ }
+
+ // Check the flags to see if there is another 32 bits of timestamp present.
+ uint32_t trtp_header_len = 6;
+ bool ts_valid = buf[1] & 0x1;
+ if (ts_valid) {
+ min_length += 4;
+ trtp_header_len += 4;
+ if (amt < min_length) {
+ LOGV("Discarding payload too short to contain TRTP timestamp"
+ " (len = %u)", amt);
+ return;
+ }
+ }
+
+ // Extract the TRTP length field and sanity check it.
+ uint32_t trtp_len;
+ trtp_len = (static_cast<uint32_t>(buf[2]) << 24) |
+ (static_cast<uint32_t>(buf[3]) << 16) |
+ (static_cast<uint32_t>(buf[4]) << 8) |
+ static_cast<uint32_t>(buf[5]);
+ if (trtp_len < min_length) {
+ LOGV("TRTP length (%u) is too short to be valid. Must be at least %u"
+ " bytes.", trtp_len, min_length);
+ return;
+ }
+
+ // Extract the rest of the timestamp field if valid.
+ int64_t ts = 0;
+ uint32_t parse_offset = 6;
+ if (ts_valid) {
+ ts = (static_cast<int64_t>(buf[parse_offset ]) << 56) |
+ (static_cast<int64_t>(buf[parse_offset + 1]) << 48) |
+ (static_cast<int64_t>(buf[parse_offset + 2]) << 40) |
+ (static_cast<int64_t>(buf[parse_offset + 3]) << 32);
+ ts |= ts_lower;
+ parse_offset += 4;
+ }
+
+ // Check the flags to see if there is another 24 bytes of timestamp
+ // transformation present.
+ if (buf[1] & 0x2) {
+ min_length += 24;
+ parse_offset += 24;
+ trtp_header_len += 24;
+ if (amt < min_length) {
+ LOGV("Discarding payload too short to contain TRTP timestamp"
+ " transformation (len = %u)", amt);
+ return;
+ }
+ }
+
+ // TODO : break the parsing into individual parsers for the different
+ // payload types (audio, video, etc).
+ //
+ // At this point in time, we know that this is audio. Go ahead and parse
+ // the basic header, check the codec type, and find the payload portion of
+ // the packet.
+ min_length += 3;
+ if (trtp_len < min_length) {
+ LOGV("TRTP length (%u) is too short to be a valid audio payload. Must"
+ " be at least %u bytes.", trtp_len, min_length);
+ return;
+ }
+
+ if (amt < min_length) {
+ LOGV("TRTP porttion of RTP payload (%u bytes) too small to contain"
+ " entire TRTP header. TRTP does not currently support fragmenting"
+ " TRTP headers across RTP payloads", amt);
+ return;
+ }
+
+ uint8_t codec_type = buf[parse_offset ];
+ uint8_t flags = buf[parse_offset + 1];
+ uint8_t volume = buf[parse_offset + 2];
+ parse_offset += 3;
+ trtp_header_len += 3;
+
+ if (!setupSubstreamType(header_type, codec_type)) {
+ return;
+ }
+
+ if (decoder_ != NULL) {
+ decoder_->setRenderVolume(volume);
+ }
+
+ // TODO : move all of the constant flag and offset definitions for TRTP up
+ // into some sort of common header file.
+ if (waiting_for_rap_ && !(flags & 0x08)) {
+ LOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP.");
+ return;
+ }
+
+ if (flags & 0x10) {
+ LOGV("Dropping TRTP Audio Payload with aux codec data present (only"
+ " handle MP3 right now, and it has no aux data)");
+ return;
+ }
+
+ // OK - everything left is just payload. Compute the payload size, start
+ // the buffer in progress and pack as much payload as we can into it. If
+ // the payload is finished once we are done, go ahead and send the payload
+ // to the decoder.
+ expected_buffer_size_ = trtp_len - trtp_header_len;
+ if (!expected_buffer_size_) {
+ LOGV("Dropping TRTP Audio Payload with 0 Access Unit length");
+ return;
+ }
+
+ CHECK(amt >= trtp_header_len);
+ uint32_t todo = amt - trtp_header_len;
+ if (expected_buffer_size_ < todo) {
+ LOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;"
+ " dropping payload.", todo, expected_buffer_size_);
+ return;
+ }
+
+ buffer_filled_ = 0;
+ buffer_in_progress_ = new MediaBuffer(expected_buffer_size_);
+ if ((NULL == buffer_in_progress_) ||
+ (NULL == buffer_in_progress_->data())) {
+ LOGV("Failed to allocate MediaBuffer of length %u",
+ expected_buffer_size_);
+ cleanupBufferInProgress();
+ return;
+ }
+
+ sp<MetaData> meta = buffer_in_progress_->meta_data();
+ if (meta == NULL) {
+ LOGV("Missing metadata structure in allocated MediaBuffer; dropping"
+ " payload");
+ cleanupBufferInProgress();
+ return;
+ }
+
+ // TODO : set this based on the codec type indicated in the TRTP stream.
+ // Right now, we only support MP3, so the choice is obvious.
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+ if (ts_valid) {
+ meta->setInt64(kKeyTime, ts);
+ }
+
+ if (amt > 0) {
+ uint8_t* tgt =
+ reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+ memcpy(tgt + buffer_filled_, buf + trtp_header_len, todo);
+ buffer_filled_ += amt;
+ }
+
+ if (buffer_filled_ >= expected_buffer_size_) {
+ processCompletedBuffer();
+ }
+}
+
+void AAH_RXPlayer::Substream::processPayloadCont(uint8_t* buf,
+ uint32_t amt) {
+ if (NULL == buffer_in_progress_) {
+ LOGV("TRTP Receiver skipping payload continuation; no buffer currently"
+ " in progress.");
+ return;
+ }
+
+ CHECK(buffer_filled_ < expected_buffer_size_);
+ uint32_t buffer_left = expected_buffer_size_ - buffer_filled_;
+ if (amt > buffer_left) {
+ LOGV("Extra data (%u > %u) present in continued TRTP Audio Payload;"
+ " dropping payload.", amt, buffer_left);
+ cleanupBufferInProgress();
+ return;
+ }
+
+ if (amt > 0) {
+ uint8_t* tgt =
+ reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+ memcpy(tgt + buffer_filled_, buf, amt);
+ buffer_filled_ += amt;
+ }
+
+ if (buffer_filled_ >= expected_buffer_size_) {
+ processCompletedBuffer();
+ }
+}
+
+void AAH_RXPlayer::Substream::processCompletedBuffer() {
+ const uint8_t* buffer_data = NULL;
+ int sample_rate;
+ int channel_count;
+ size_t frame_size;
+ status_t res;
+
+ CHECK(NULL != buffer_in_progress_);
+
+ if (decoder_ == NULL) {
+ LOGV("Dropping complete buffer, no decoder pump allocated");
+ goto bailout;
+ }
+
+ buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data());
+ if (buffer_in_progress_->size() < 4) {
+ LOGV("MP3 payload too short to contain header, dropping payload.");
+ goto bailout;
+ }
+
+ // Extract the channel count and the sample rate from the MP3 header. The
+ // stagefright MP3 requires that these be delivered before decoing can
+ // begin.
+ if (!GetMPEGAudioFrameSize(U32_AT(buffer_data),
+ &frame_size,
+ &sample_rate,
+ &channel_count,
+ NULL,
+ NULL)) {
+ LOGV("Failed to parse MP3 header in payload, droping payload.");
+ goto bailout;
+ }
+
+
+ // Make sure that our substream metadata is set up properly. If there has
+ // been a format change, be sure to reset the underlying decoder. In
+ // stagefright, it seems like the only way to do this is to destroy and
+ // recreate the decoder.
+ if (substream_meta_ == NULL) {
+ substream_meta_ = new MetaData();
+
+ if (substream_meta_ == NULL) {
+ LOGE("Failed to allocate MetaData structure for substream");
+ goto bailout;
+ }
+
+ substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+ substream_meta_->setInt32 (kKeyChannelCount, channel_count);
+ substream_meta_->setInt32 (kKeySampleRate, sample_rate);
+ } else {
+ int32_t prev_sample_rate;
+ int32_t prev_channel_count;
+ substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate);
+ substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count);
+
+ if ((prev_channel_count != channel_count) ||
+ (prev_sample_rate != sample_rate)) {
+ LOGW("Format change detected, forcing decoder reset.");
+ cleanupDecoder();
+
+ substream_meta_->setInt32(kKeyChannelCount, channel_count);
+ substream_meta_->setInt32(kKeySampleRate, sample_rate);
+ }
+ }
+
+ // If our decoder has not be set up, do so now.
+ res = decoder_->init(substream_meta_);
+ if (OK != res) {
+ LOGE("Failed to init decoder (res = %d)", res);
+ cleanupDecoder();
+ substream_meta_ = NULL;
+ goto bailout;
+ }
+
+ // Queue the payload for decode.
+ res = decoder_->queueForDecode(buffer_in_progress_);
+
+ if (res != OK) {
+ LOGD("Failed to queue payload for decode, resetting decoder pump!"
+ " (res = %d)", res);
+ cleanupDecoder();
+ cleanupBufferInProgress();
+ }
+
+ // NULL out buffer_in_progress before calling the cleanup helper.
+ //
+ // MediaBuffers use something of a hybrid ref-counting pattern which prevent
+ // the AAH_DecoderPump's input queue from adding their own reference to the
+ // MediaBuffer. MediaBuffers start life with a reference count of 0, as
+ // well as an observer which starts as NULL. Before being given an
+ // observer, the ref count cannot be allowed to become non-zero as it will
+ // cause calls to release() to assert. Basically, before a MediaBuffer has
+ // an observer, they behave like non-ref counted obects where release()
+ // serves the roll of delete. After a MediaBuffer has an observer, they
+ // become more like ref counted objects where add ref and release can be
+ // used, and when the ref count hits zero, the MediaBuffer is handed off to
+ // the observer.
+ //
+ // Given all of this, when we give the buffer to the decoder pump to wait in
+ // the to-be-processed queue, the decoder cannot add a ref to the buffer as
+ // it would in a traditional ref counting system. Instead it needs to
+ // "steal" the non-existent ref. In the case of queue failure, we need to
+ // make certain to release this non-existent reference so that the buffer is
+ // cleaned up during the cleanupBufferInProgress helper. In the case of a
+ // successful queue operation, we need to make certain that the
+ // cleanupBufferInProgress helper does not release the buffer since it needs
+ // to remain alive in the queue. We acomplish this by NULLing out the
+ // buffer pointer before calling the cleanup helper.
+ buffer_in_progress_ = NULL;
+
+bailout:
+ cleanupBufferInProgress();
+}
+
+
+void AAH_RXPlayer::Substream::processTSTransform(const LinearTransform& trans) {
+ if (decoder_ != NULL) {
+ decoder_->setRenderTSTransform(trans);
+ }
+}
+
+bool AAH_RXPlayer::Substream::isAboutToUnderflow() {
+ if (decoder_ == NULL) {
+ return false;
+ }
+
+ return decoder_->isAboutToUnderflow(kAboutToUnderflowThreshold);
+}
+
+bool AAH_RXPlayer::Substream::setupSubstreamType(uint8_t substream_type,
+ uint8_t codec_type) {
+ // Sanity check the codec type. Right now we only support MP3. Also check
+ // for conflicts with previously delivered codec types.
+ if (substream_details_known_ && (codec_type != codec_type_)) {
+ LOGV("RXed TRTP Payload for SSRC=0x%08x where codec type (%u) does not"
+ " match previously received codec type (%u)",
+ ssrc_, codec_type, codec_type_);
+ return false;
+ }
+
+ if (codec_type != 0x03) {
+ LOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported codec"
+ " type (%u)", ssrc_, codec_type);
+ return false;
+ }
+
+ if (!substream_details_known_) {
+ substream_type_ = substream_type;
+ codec_type_ = codec_type;
+ substream_details_known_ = true;
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.cpp b/media/libaah_rtp/aah_tx_packet.cpp
new file mode 100644
index 0000000..9a3b40a
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+#define LOG_TAG "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const int TRTPPacket::kRTPHeaderLen;
+const uint32_t TRTPPacket::kTRTPEpochMask;
+
+TRTPPacket::~TRTPPacket() {
+ delete mPacket;
+}
+
+void TRTPPacket::writeU8(uint8_t*& buf, uint8_t val) {
+ *buf = val;
+ buf++;
+}
+
+void TRTPPacket::writeU16(uint8_t*& buf, uint16_t val) {
+ *reinterpret_cast<uint16_t*>(buf) = htons(val);
+ buf += 2;
+}
+
+void TRTPPacket::writeU32(uint8_t*& buf, uint32_t val) {
+ *reinterpret_cast<uint32_t*>(buf) = htonl(val);
+ buf += 4;
+}
+
+void TRTPPacket::writeU64(uint8_t*& buf, uint64_t val) {
+ buf[0] = static_cast<uint8_t>(val >> 56);
+ buf[1] = static_cast<uint8_t>(val >> 48);
+ buf[2] = static_cast<uint8_t>(val >> 40);
+ buf[3] = static_cast<uint8_t>(val >> 32);
+ buf[4] = static_cast<uint8_t>(val >> 24);
+ buf[5] = static_cast<uint8_t>(val >> 16);
+ buf[6] = static_cast<uint8_t>(val >> 8);
+ buf[7] = static_cast<uint8_t>(val);
+ buf += 8;
+}
+
+void TRTPPacket::writeTRTPHeader(uint8_t*& buf,
+ bool isFirstFragment,
+ int totalPacketLen) {
+ // RTP header
+ writeU8(buf,
+ ((mVersion & 0x03) << 6) |
+ (static_cast<int>(mPadding) << 5) |
+ (static_cast<int>(mExtension) << 4) |
+ (mCsrcCount & 0x0F));
+ writeU8(buf,
+ (static_cast<int>(isFirstFragment) << 7) |
+ (mPayloadType & 0x7F));
+ writeU16(buf, mSeqNumber);
+ if (isFirstFragment && mPTSValid) {
+ writeU32(buf, mPTS & 0xFFFFFFFF);
+ } else {
+ writeU32(buf, 0);
+ }
+ writeU32(buf,
+ ((mEpoch & kTRTPEpochMask) << 10) |
+ ((mProgramID & 0x1F) << 5) |
+ (mSubstreamID & 0x1F));
+
+ // TRTP header
+ writeU8(buf, mTRTPVersion);
+ writeU8(buf,
+ ((mTRTPHeaderType & 0x0F) << 4) |
+ (mClockTranformValid ? 0x02 : 0x00) |
+ (mPTSValid ? 0x01 : 0x00));
+ writeU32(buf, totalPacketLen - kRTPHeaderLen);
+ if (mPTSValid) {
+ writeU32(buf, mPTS >> 32);
+ }
+
+ if (mClockTranformValid) {
+ writeU64(buf, mClockTranform.a_zero);
+ writeU32(buf, mClockTranform.a_to_b_numer);
+ writeU32(buf, mClockTranform.a_to_b_denom);
+ writeU64(buf, mClockTranform.b_zero);
+ }
+}
+
+bool TRTPAudioPacket::pack() {
+ int packetLen = kRTPHeaderLen +
+ mAccessUnitLen +
+ TRTPHeaderLen();
+
+ // TODO : support multiple fragments
+ const int kMaxUDPPayloadLen = 65507;
+ if (packetLen > kMaxUDPPayloadLen) {
+ return false;
+ }
+
+ mPacket = new uint8_t[packetLen];
+ if (!mPacket) {
+ return false;
+ }
+
+ mPacketLen = packetLen;
+
+ uint8_t* cur = mPacket;
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU8(cur, mCodecType);
+ writeU8(cur,
+ (static_cast<int>(mRandomAccessPoint) << 3) |
+ (static_cast<int>(mDropable) << 2) |
+ (static_cast<int>(mDiscontinuity) << 1) |
+ (static_cast<int>(mEndOfStream)));
+ writeU8(cur, mVolume);
+
+ memcpy(cur, mAccessUnitData, mAccessUnitLen);
+
+ return true;
+}
+
+int TRTPPacket::TRTPHeaderLen() const {
+ // 6 bytes for version, payload type, flags and length. An additional 4 if
+ // there are upper timestamp bits present and another 24 if there is a clock
+ // transformation present.
+ return 6 +
+ (mClockTranformValid ? 24 : 0) +
+ (mPTSValid ? 4 : 0);
+}
+
+int TRTPAudioPacket::TRTPHeaderLen() const {
+ // TRTPPacket::TRTPHeaderLen() for the base TRTPHeader. 3 bytes for audio's
+ // codec type, flags and volume field. Another 5 bytes if the codec type is
+ // PCM and we are sending sample rate/channel count. as well as however long
+ // the aux data (if present) is.
+
+ int pcmParamLength;
+ switch(mCodecType) {
+ case kCodecPCMBigEndian:
+ case kCodecPCMLittleEndian:
+ pcmParamLength = 5;
+ break;
+
+ default:
+ pcmParamLength = 0;
+ break;
+ }
+
+
+ // TODO : properly compute aux data length. Currently, nothing
+ // uses aux data, so its length is always 0.
+ int auxDataLength = 0;
+ return TRTPPacket::TRTPHeaderLen() +
+ 3 +
+ auxDataLength +
+ pcmParamLength;
+}
+
+bool TRTPControlPacket::pack() {
+ // command packets contain a 2-byte command ID
+ int packetLen = kRTPHeaderLen +
+ TRTPHeaderLen() +
+ 2;
+
+ mPacket = new uint8_t[packetLen];
+ if (!mPacket) {
+ return false;
+ }
+
+ mPacketLen = packetLen;
+
+ uint8_t* cur = mPacket;
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU16(cur, mCommandID);
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.h b/media/libaah_rtp/aah_tx_packet.h
new file mode 100644
index 0000000..6e1b52a
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_TX_PACKET_H__
+#define __AAH_TX_PACKET_H__
+
+#include <utils/LinearTransform.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class TRTPPacket : public RefBase {
+ protected:
+ enum TRTPHeaderType {
+ kHeaderTypeAudio = 1,
+ kHeaderTypeVideo = 2,
+ kHeaderTypeSubpicture = 3,
+ kHeaderTypeControl = 4,
+ };
+
+ TRTPPacket(TRTPHeaderType headerType)
+ : mVersion(2)
+ , mPadding(false)
+ , mExtension(false)
+ , mCsrcCount(0)
+ , mPayloadType(100)
+ , mSeqNumber(0)
+ , mPTSValid(false)
+ , mPTS(0)
+ , mEpoch(0)
+ , mProgramID(0)
+ , mSubstreamID(0)
+ , mClockTranformValid(false)
+ , mTRTPVersion(1)
+ , mTRTPLength(0)
+ , mTRTPHeaderType(headerType)
+ , mPacket(NULL)
+ , mPacketLen(0) { }
+
+ public:
+ virtual ~TRTPPacket();
+
+ void setSeqNumber(uint16_t val) { mSeqNumber = val; }
+ uint16_t getSeqNumber() const { return mSeqNumber; }
+ void setPTS(int64_t val) {
+ mPTS = val;
+ mPTSValid = true;
+ }
+ int64_t getPTS() const { return mPTS; }
+ void setEpoch(uint32_t val) { mEpoch = val; }
+ void setProgramID(uint16_t val) { mProgramID = val; }
+ void setSubstreamID(uint16_t val) { mSubstreamID = val; }
+ void setClockTransform(const LinearTransform& trans) {
+ mClockTranform = trans;
+ mClockTranformValid = true;
+ }
+
+ uint8_t* getPacket() const { return mPacket; }
+ int getPacketLen() const { return mPacketLen; }
+
+ void setExpireTime(nsecs_t val) { mExpireTime = val; }
+ nsecs_t getExpireTime() const { return mExpireTime; }
+
+ virtual bool pack() = 0;
+
+ // mask for the number of bits in a TRTP epoch
+ static const uint32_t kTRTPEpochMask = (1 << 22) - 1;
+
+ protected:
+ static const int kRTPHeaderLen = 12;
+ virtual int TRTPHeaderLen() const;
+
+ void writeTRTPHeader(uint8_t*& buf,
+ bool isFirstFragment,
+ int totalPacketLen);
+
+ void writeU8(uint8_t*& buf, uint8_t val);
+ void writeU16(uint8_t*& buf, uint16_t val);
+ void writeU32(uint8_t*& buf, uint32_t val);
+ void writeU64(uint8_t*& buf, uint64_t val);
+
+ uint8_t mVersion;
+ bool mPadding;
+ bool mExtension;
+ uint8_t mCsrcCount;
+ uint8_t mPayloadType;
+ uint16_t mSeqNumber;
+ bool mPTSValid;
+ int64_t mPTS;
+ uint32_t mEpoch;
+ uint16_t mProgramID;
+ uint16_t mSubstreamID;
+ LinearTransform mClockTranform;
+ bool mClockTranformValid;
+ uint8_t mTRTPVersion;
+ uint32_t mTRTPLength;
+ TRTPHeaderType mTRTPHeaderType;
+
+ uint8_t* mPacket;
+ int mPacketLen;
+
+ nsecs_t mExpireTime;
+};
+
+class TRTPAudioPacket : public TRTPPacket {
+ public:
+ TRTPAudioPacket()
+ : TRTPPacket(kHeaderTypeAudio)
+ , mCodecType(kCodecInvalid)
+ , mRandomAccessPoint(false)
+ , mDropable(false)
+ , mDiscontinuity(false)
+ , mEndOfStream(false)
+ , mVolume(0)
+ , mAccessUnitData(NULL) { }
+
+ enum TRTPAudioCodecType {
+ kCodecInvalid = 0,
+ kCodecPCMBigEndian = 1,
+ kCodecPCMLittleEndian = 2,
+ kCodecMPEG1Audio = 3,
+ };
+
+ void setCodecType(TRTPAudioCodecType val) { mCodecType = val; }
+ void setRandomAccessPoint(bool val) { mRandomAccessPoint = val; }
+ void setDropable(bool val) { mDropable = val; }
+ void setDiscontinuity(bool val) { mDiscontinuity = val; }
+ void setEndOfStream(bool val) { mEndOfStream = val; }
+ void setVolume(uint8_t val) { mVolume = val; }
+ void setAccessUnitData(void* data, int len) {
+ mAccessUnitData = data;
+ mAccessUnitLen = len;
+ }
+
+ virtual bool pack();
+
+ protected:
+ virtual int TRTPHeaderLen() const;
+
+ private:
+ TRTPAudioCodecType mCodecType;
+ bool mRandomAccessPoint;
+ bool mDropable;
+ bool mDiscontinuity;
+ bool mEndOfStream;
+ uint8_t mVolume;
+ void* mAccessUnitData;
+ int mAccessUnitLen;
+};
+
+class TRTPControlPacket : public TRTPPacket {
+ public:
+ TRTPControlPacket()
+ : TRTPPacket(kHeaderTypeControl)
+ , mCommandID(kCommandNop) {}
+
+ enum TRTPCommandID {
+ kCommandNop = 1,
+ kCommandFlush = 2,
+ kCommandEOS = 3,
+ };
+
+ void setCommandID(TRTPCommandID val) { mCommandID = val; }
+
+ virtual bool pack();
+
+ private:
+ TRTPCommandID mCommandID;
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp
new file mode 100644
index 0000000..72d02a5
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.cpp
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <netdb.h>
+#include <netinet/ip.h>
+
+#include <aah_timesrv/cc_helper.h>
+#include <media/IMediaPlayer.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/Timers.h>
+
+#include "aah_tx_packet.h"
+#include "aah_tx_player.h"
+
+namespace android {
+
+static int64_t kLowWaterMarkUs = 2000000ll; // 2secs
+static int64_t kHighWaterMarkUs = 10000000ll; // 10secs
+static const size_t kLowWaterMarkBytes = 40000;
+static const size_t kHighWaterMarkBytes = 200000;
+
+// When we start up, how much lead time should we put on the first access unit?
+static const int64_t kAAHStartupLeadTimeUs = 300000LL;
+
+// How much time do we attempt to lead the clock by in steady state?
+static const int64_t kAAHBufferTimeUs = 1000000LL;
+
+// how long do we keep data in our retransmit buffer after sending it.
+const int64_t AAH_TXPlayer::kAAHRetryKeepAroundTimeNs =
+ kAAHBufferTimeUs * 1100;
+
+sp<MediaPlayerBase> createAAH_TXPlayer() {
+ sp<MediaPlayerBase> ret = new AAH_TXPlayer();
+ return ret;
+}
+
+template <typename T> static T clamp(T val, T min, T max) {
+ if (val < min) {
+ return min;
+ } else if (val > max) {
+ return max;
+ } else {
+ return val;
+ }
+}
+
+struct AAH_TXEvent : public TimedEventQueue::Event {
+ AAH_TXEvent(AAH_TXPlayer *player,
+ void (AAH_TXPlayer::*method)()) : mPlayer(player)
+ , mMethod(method) {}
+
+ protected:
+ virtual ~AAH_TXEvent() {}
+
+ virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
+ (mPlayer->*mMethod)();
+ }
+
+ private:
+ AAH_TXPlayer *mPlayer;
+ void (AAH_TXPlayer::*mMethod)();
+
+ AAH_TXEvent(const AAH_TXEvent &);
+ AAH_TXEvent& operator=(const AAH_TXEvent &);
+};
+
+AAH_TXPlayer::AAH_TXPlayer()
+ : mQueueStarted(false)
+ , mFlags(0)
+ , mExtractorFlags(0) {
+ DataSource::RegisterDefaultSniffers();
+
+ mBufferingEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onBufferingUpdate);
+ mBufferingEventPending = false;
+
+ mPumpAudioEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onPumpAudio);
+ mPumpAudioEventPending = false;
+
+ reset();
+}
+
+AAH_TXPlayer::~AAH_TXPlayer() {
+ if (mQueueStarted) {
+ mQueue.stop();
+ }
+
+ reset();
+}
+
+void AAH_TXPlayer::cancelPlayerEvents(bool keepBufferingGoing) {
+ if (!keepBufferingGoing) {
+ mQueue.cancelEvent(mBufferingEvent->eventID());
+ mBufferingEventPending = false;
+
+ mQueue.cancelEvent(mPumpAudioEvent->eventID());
+ mPumpAudioEventPending = false;
+ }
+}
+
+status_t AAH_TXPlayer::initCheck() {
+ // Check for the presense of the A@H common time service by attempting to
+ // query for CommonTime's frequency. If we get an error back, we cannot
+ // talk to the service at all and should abort now.
+ status_t res;
+ uint64_t freq;
+ res = CCHelper::getCommonFreq(&freq);
+ if (OK != res) {
+ LOGE("Failed to connect to common time service!");
+ return res;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ Mutex::Autolock autoLock(mLock);
+ return setDataSource_l(url, headers);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ reset_l();
+
+ // the URL must consist of "aahTX://" followed by the real URL of
+ // the data source
+ const char *kAAHPrefix = "aahTX://";
+ if (strncasecmp(url, kAAHPrefix, strlen(kAAHPrefix))) {
+ return INVALID_OPERATION;
+ }
+
+ mUri.setTo(url + strlen(kAAHPrefix));
+
+ if (headers) {
+ mUriHeaders = *headers;
+
+ ssize_t index = mUriHeaders.indexOfKey(String8("x-hide-urls-from-log"));
+ if (index >= 0) {
+ // Browser is in "incognito" mode, suppress logging URLs.
+
+ // This isn't something that should be passed to the server.
+ mUriHeaders.removeItemsAt(index);
+
+ mFlags |= INCOGNITO;
+ }
+ }
+
+ // The URL may optionally contain a "#" character followed by a Skyjam
+ // cookie. Ideally the cookie header should just be passed in the headers
+ // argument, but the Java API for supplying headers is apparently not yet
+ // exposed in the SDK used by application developers.
+ const char kSkyjamCookieDelimiter = '#';
+ char* skyjamCookie = strrchr(mUri.string(), kSkyjamCookieDelimiter);
+ if (skyjamCookie) {
+ skyjamCookie++;
+ mUriHeaders.add(String8("Cookie"), String8(skyjamCookie));
+ mUri = String8(mUri.string(), skyjamCookie - mUri.string());
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::setVideoSurface(const sp<Surface>& surface) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVideoSurfaceTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::prepare() {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::prepareAsync() {
+ Mutex::Autolock autoLock(mLock);
+
+ return prepareAsync_l();
+}
+
+status_t AAH_TXPlayer::prepareAsync_l() {
+ if (mFlags & PREPARING) {
+ return UNKNOWN_ERROR; // async prepare already pending
+ }
+
+ mAAH_Sender = AAH_TXSender::GetInstance();
+ if (mAAH_Sender == NULL) {
+ return NO_MEMORY;
+ }
+
+ if (!mQueueStarted) {
+ mQueue.start();
+ mQueueStarted = true;
+ }
+
+ mFlags |= PREPARING;
+ mAsyncPrepareEvent = new AAH_TXEvent(
+ this, &AAH_TXPlayer::onPrepareAsyncEvent);
+
+ mQueue.postEvent(mAsyncPrepareEvent);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::finishSetDataSource_l() {
+ sp<DataSource> dataSource;
+
+ if (!strncasecmp("http://", mUri.string(), 7) ||
+ !strncasecmp("https://", mUri.string(), 8)) {
+
+ mConnectingDataSource = HTTPBase::Create(
+ (mFlags & INCOGNITO)
+ ? HTTPBase::kFlagIncognito
+ : 0);
+
+ mLock.unlock();
+ status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
+ mLock.lock();
+
+ if (err != OK) {
+ mConnectingDataSource.clear();
+
+ LOGI("mConnectingDataSource->connect() returned %d", err);
+ return err;
+ }
+
+ mCachedSource = new NuCachedSource2(mConnectingDataSource);
+ mConnectingDataSource.clear();
+
+ dataSource = mCachedSource;
+
+ // We're going to prefill the cache before trying to instantiate
+ // the extractor below, as the latter is an operation that otherwise
+ // could block on the datasource for a significant amount of time.
+ // During that time we'd be unable to abort the preparation phase
+ // without this prefill.
+
+ mLock.unlock();
+
+ for (;;) {
+ status_t finalStatus;
+ size_t cachedDataRemaining =
+ mCachedSource->approxDataRemaining(&finalStatus);
+
+ if (finalStatus != OK ||
+ cachedDataRemaining >= kHighWaterMarkBytes ||
+ (mFlags & PREPARE_CANCELLED)) {
+ break;
+ }
+
+ usleep(200000);
+ }
+
+ mLock.lock();
+
+ if (mFlags & PREPARE_CANCELLED) {
+ LOGI("Prepare cancelled while waiting for initial cache fill.");
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
+ }
+
+ if (dataSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+ if (extractor == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
+ // Attempt to approximate overall stream bitrate by summing all
+ // tracks' individual bitrates, if not all of them advertise bitrate,
+ // we have to fail.
+
+ int64_t totalBitRate = 0;
+
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ int32_t bitrate;
+ if (!meta->findInt32(kKeyBitRate, &bitrate)) {
+ totalBitRate = -1;
+ break;
+ }
+
+ totalBitRate += bitrate;
+ }
+
+ mBitrate = totalBitRate;
+
+ LOGV("mBitrate = %lld bits/sec", mBitrate);
+
+ bool haveAudio = false;
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ if (!strncasecmp(mime, "audio/", 6)) {
+ mAudioSource = extractor->getTrack(i);
+ CHECK(mAudioSource != NULL);
+ haveAudio = true;
+ break;
+ }
+ }
+
+ if (!haveAudio) {
+ return UNKNOWN_ERROR;
+ }
+
+ mExtractorFlags = extractor->flags();
+
+ return OK;
+}
+
+void AAH_TXPlayer::abortPrepare(status_t err) {
+ CHECK(err != OK);
+
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
+
+ mPrepareResult = err;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mPreparedCondition.broadcast();
+}
+
+void AAH_TXPlayer::onPrepareAsyncEvent() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mFlags & PREPARE_CANCELLED) {
+ LOGI("prepare was cancelled before doing anything");
+ abortPrepare(UNKNOWN_ERROR);
+ return;
+ }
+
+ if (mUri.size() > 0) {
+ status_t err = finishSetDataSource_l();
+
+ if (err != OK) {
+ abortPrepare(err);
+ return;
+ }
+ }
+
+ mAudioSource->getFormat()->findInt64(kKeyDuration, &mDurationUs);
+
+ mAudioSource->start();
+
+ mFlags |= PREPARING_CONNECTED;
+
+ if (mCachedSource != NULL) {
+ postBufferingEvent_l();
+ } else {
+ finishAsyncPrepare_l();
+ }
+}
+
+void AAH_TXPlayer::finishAsyncPrepare_l() {
+ notifyListener_l(MEDIA_PREPARED);
+
+ mPrepareResult = OK;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mFlags |= PREPARED;
+ mPreparedCondition.broadcast();
+}
+
+status_t AAH_TXPlayer::start() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return play_l();
+}
+
+status_t AAH_TXPlayer::play_l() {
+ if (mFlags & PLAYING) {
+ return OK;
+ }
+
+ if (!(mFlags & PREPARED)) {
+ return INVALID_OPERATION;
+ }
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return INVALID_OPERATION;
+ }
+ if (!mEndpointRegistered) {
+ mProgramID = mAAH_Sender->registerEndpoint(mEndpoint);
+ mEndpointRegistered = true;
+ }
+ }
+
+ mFlags |= PLAYING;
+
+ updateClockTransform_l(false);
+
+ postPumpAudioEvent_l(-1);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::stop() {
+ status_t ret = pause();
+ sendEOS_l();
+ return ret;
+}
+
+status_t AAH_TXPlayer::pause() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return pause_l();
+}
+
+status_t AAH_TXPlayer::pause_l(bool doClockUpdate) {
+ if (!(mFlags & PLAYING)) {
+ return OK;
+ }
+
+ cancelPlayerEvents(true /* keepBufferingGoing */);
+
+ mFlags &= ~PLAYING;
+
+ if (doClockUpdate) {
+ updateClockTransform_l(true);
+ }
+
+ return OK;
+}
+
+void AAH_TXPlayer::updateClockTransform_l(bool pause) {
+ // record the new pause status so that onPumpAudio knows what rate to apply
+ // when it initializes the transform
+ mPlayRateIsPaused = pause;
+
+ // if we haven't yet established a valid clock transform, then we can't
+ // do anything here
+ if (!mCurrentClockTransformValid) {
+ return;
+ }
+
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != CCHelper::getCommonTime(&commonTimeNow)) {
+ LOGE("updateClockTransform_l get common time failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // convert the current common time to media time using the old
+ // transform
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(
+ commonTimeNow, &mediaTimeNow)) {
+ LOGE("updateClockTransform_l reverse transform failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // calculate a new transform that preserves the old transform's
+ // result for the current time
+ mCurrentClockTransform.a_zero = mediaTimeNow;
+ mCurrentClockTransform.b_zero = commonTimeNow;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = pause ? 0 : 1;
+
+ // send a packet announcing the new transform
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setCommandID(TRTPControlPacket::kCommandNop);
+ queuePacketToSender_l(packet);
+}
+
+void AAH_TXPlayer::sendEOS_l() {
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandEOS);
+ queuePacketToSender_l(packet);
+}
+
+bool AAH_TXPlayer::isPlaying() {
+ return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN);
+}
+
+status_t AAH_TXPlayer::seekTo(int msec) {
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ Mutex::Autolock autoLock(mLock);
+ return seekTo_l(static_cast<int64_t>(msec) * 1000);
+ }
+
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ return OK;
+}
+
+status_t AAH_TXPlayer::seekTo_l(int64_t timeUs) {
+ mIsSeeking = true;
+ mSeekTimeUs = timeUs;
+
+ mCurrentClockTransformValid = false;
+ mLastQueuedMediaTimePTSValid = false;
+
+ // send a flush command packet
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandFlush);
+ queuePacketToSender_l(packet);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getCurrentPosition(int *msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ int position;
+
+ if (mIsSeeking) {
+ position = mSeekTimeUs / 1000;
+ } else if (mCurrentClockTransformValid) {
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != CCHelper::getCommonTime(&commonTimeNow)) {
+ LOGE("getCurrentPosition get common time failed");
+ return INVALID_OPERATION;
+ }
+
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(commonTimeNow,
+ &mediaTimeNow)) {
+ LOGE("getCurrentPosition reverse transform failed");
+ return INVALID_OPERATION;
+ }
+
+ position = static_cast<int>(mediaTimeNow / 1000);
+ } else {
+ position = 0;
+ }
+
+ int duration;
+ if (getDuration_l(&duration) == OK) {
+ *msec = clamp(position, 0, duration);
+ } else {
+ *msec = (position >= 0) ? position : 0;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getDuration(int* msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ return getDuration_l(msec);
+}
+
+status_t AAH_TXPlayer::getDuration_l(int* msec) {
+ if (mDurationUs < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ *msec = (mDurationUs + 500) / 1000;
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::reset() {
+ Mutex::Autolock autoLock(mLock);
+ reset_l();
+ return OK;
+}
+
+void AAH_TXPlayer::reset_l() {
+ if (mFlags & PREPARING) {
+ mFlags |= PREPARE_CANCELLED;
+ if (mConnectingDataSource != NULL) {
+ LOGI("interrupting the connection process");
+ mConnectingDataSource->disconnect();
+ }
+
+ if (mFlags & PREPARING_CONNECTED) {
+ // We are basically done preparing, we're just buffering
+ // enough data to start playback, we can safely interrupt that.
+ finishAsyncPrepare_l();
+ }
+ }
+
+ while (mFlags & PREPARING) {
+ mPreparedCondition.wait(mLock);
+ }
+
+ cancelPlayerEvents();
+
+ sendEOS_l();
+
+ mCachedSource.clear();
+
+ if (mAudioSource != NULL) {
+ mAudioSource->stop();
+ }
+ mAudioSource.clear();
+
+ mFlags = 0;
+ mExtractorFlags = 0;
+
+ mDurationUs = -1;
+ mIsSeeking = false;
+ mSeekTimeUs = 0;
+
+ mUri.setTo("");
+ mUriHeaders.clear();
+
+ mBitrate = -1;
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (mAAH_Sender != NULL && mEndpointRegistered) {
+ mAAH_Sender->unregisterEndpoint(mEndpoint);
+ }
+ mEndpointRegistered = false;
+ mEndpointValid = false;
+ }
+
+ mProgramID = 0;
+
+ mAAH_Sender.clear();
+ mLastQueuedMediaTimePTSValid = false;
+ mCurrentClockTransformValid = false;
+ mPlayRateIsPaused = false;
+
+ mTRTPVolume = 255;
+}
+
+status_t AAH_TXPlayer::setLooping(int loop) {
+ return OK;
+}
+
+player_type AAH_TXPlayer::playerType() {
+ return AAH_TX_PLAYER;
+}
+
+status_t AAH_TXPlayer::setParameter(int key, const Parcel &request) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::getParameter(int key, Parcel *reply) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::invoke(const Parcel& request, Parcel *reply) {
+ if (!reply) {
+ return BAD_VALUE;
+ }
+
+ int32_t methodID;
+ status_t err = request.readInt32(&methodID);
+ if (err != android::OK) {
+ return err;
+ }
+
+ switch (methodID) {
+ case kInvokeSetAAHDstIPPort: {
+ if (mEndpointValid) {
+ return INVALID_OPERATION;
+ }
+
+ String16 addr = request.readString16();
+
+ int32_t port32;
+ err = request.readInt32(&port32);
+ if (err != android::OK) {
+ return err;
+ }
+
+ struct hostent* ent = gethostbyname(String8(addr).string());
+ if (ent == NULL) {
+ return ERROR_UNKNOWN_HOST;
+ }
+ if (!(ent->h_addrtype == AF_INET && ent->h_length == 4)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mEndpointLock);
+ mEndpoint = AAH_TXSender::Endpoint(
+ reinterpret_cast<struct in_addr*>(ent->h_addr)->s_addr,
+ static_cast<uint16_t>(port32));
+ mEndpointValid = true;
+ return OK;
+ };
+
+ default:
+ return INVALID_OPERATION;
+ }
+}
+
+status_t AAH_TXPlayer::getMetadata(const media::Metadata::Filter& ids,
+ Parcel* records) {
+ using media::Metadata;
+
+ Metadata metadata(records);
+
+ metadata.appendBool(Metadata::kPauseAvailable, true);
+ metadata.appendBool(Metadata::kSeekBackwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekForwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekAvailable, false);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVolume(float leftVolume, float rightVolume) {
+ if (leftVolume != rightVolume) {
+ LOGE("%s does not support per channel volume: %f, %f",
+ __PRETTY_FUNCTION__, leftVolume, rightVolume);
+ }
+
+ float volume = clamp(leftVolume, 0.0f, 1.0f);
+
+ Mutex::Autolock lock(mLock);
+ mTRTPVolume = static_cast<uint8_t>((leftVolume * 255.0) + 0.5);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setAudioStreamType(int streamType) {
+ return OK;
+}
+
+void AAH_TXPlayer::notifyListener_l(int msg, int ext1, int ext2) {
+ sendEvent(msg, ext1, ext2);
+}
+
+bool AAH_TXPlayer::getBitrate_l(int64_t *bitrate) {
+ off64_t size;
+ if (mDurationUs >= 0 &&
+ mCachedSource != NULL &&
+ mCachedSource->getSize(&size) == OK) {
+ *bitrate = size * 8000000ll / mDurationUs; // in bits/sec
+ return true;
+ }
+
+ if (mBitrate >= 0) {
+ *bitrate = mBitrate;
+ return true;
+ }
+
+ *bitrate = 0;
+
+ return false;
+}
+
+// Returns true iff cached duration is available/applicable.
+bool AAH_TXPlayer::getCachedDuration_l(int64_t *durationUs, bool *eos) {
+ int64_t bitrate;
+
+ if (mCachedSource != NULL && getBitrate_l(&bitrate)) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ *durationUs = cachedDataRemaining * 8000000ll / bitrate;
+ *eos = (finalStatus != OK);
+ return true;
+ }
+
+ return false;
+}
+
+void AAH_TXPlayer::ensureCacheIsFetching_l() {
+ if (mCachedSource != NULL) {
+ mCachedSource->resumeFetchingIfNecessary();
+ }
+}
+
+void AAH_TXPlayer::postBufferingEvent_l() {
+ if (mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = true;
+ mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
+}
+
+void AAH_TXPlayer::postPumpAudioEvent_l(int64_t delayUs) {
+ if (mPumpAudioEventPending) {
+ return;
+ }
+ mPumpAudioEventPending = true;
+ mQueue.postEventWithDelay(mPumpAudioEvent, delayUs < 0 ? 10000 : delayUs);
+}
+
+void AAH_TXPlayer::onBufferingUpdate() {
+ Mutex::Autolock autoLock(mLock);
+ if (!mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = false;
+
+ if (mCachedSource != NULL) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ bool eos = (finalStatus != OK);
+
+ if (eos) {
+ if (finalStatus == ERROR_END_OF_STREAM) {
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ }
+ if (mFlags & PREPARING) {
+ LOGV("cache has reached EOS, prepare is done.");
+ finishAsyncPrepare_l();
+ }
+ } else {
+ int64_t bitrate;
+ if (getBitrate_l(&bitrate)) {
+ size_t cachedSize = mCachedSource->cachedSize();
+ int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate;
+
+ int percentage = (100.0 * (double) cachedDurationUs)
+ / mDurationUs;
+ if (percentage > 100) {
+ percentage = 100;
+ }
+
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage);
+ } else {
+ // We don't know the bitrate of the stream, use absolute size
+ // limits to maintain the cache.
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDataRemaining < kLowWaterMarkBytes)) {
+ LOGI("cache is running low (< %d) , pausing.",
+ kLowWaterMarkBytes);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDataRemaining > kHighWaterMarkBytes) {
+ if (mFlags & CACHE_UNDERRUN) {
+ LOGI("cache has filled up (> %d), resuming.",
+ kHighWaterMarkBytes);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ LOGV("cache has filled up (> %d), prepare is done",
+ kHighWaterMarkBytes);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+ }
+ }
+
+ int64_t cachedDurationUs;
+ bool eos;
+ if (getCachedDuration_l(&cachedDurationUs, &eos)) {
+ LOGV("cachedDurationUs = %.2f secs, eos=%d",
+ cachedDurationUs / 1E6, eos);
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDurationUs < kLowWaterMarkUs)) {
+ LOGI("cache is running low (%.2f secs) , pausing.",
+ cachedDurationUs / 1E6);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDurationUs > kHighWaterMarkUs) {
+ if (mFlags & CACHE_UNDERRUN) {
+ LOGI("cache has filled up (%.2f secs), resuming.",
+ cachedDurationUs / 1E6);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ LOGV("cache has filled up (%.2f secs), prepare is done",
+ cachedDurationUs / 1E6);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+
+ postBufferingEvent_l();
+}
+
+void AAH_TXPlayer::onPumpAudio() {
+ while (true) {
+ Mutex::Autolock autoLock(mLock);
+ // If this flag is clear, its because someone has externally canceled
+ // this pump operation (probably because we a resetting/shutting down).
+ // Get out immediately, do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Start by checking if there is still work to be doing. If we have
+ // never queued a payload (so we don't know what the last queued PTS is)
+ // or we have never established a MediaTime->CommonTime transformation,
+ // then we have work to do (one time through this loop should establish
+ // both). Otherwise, we want to keep a fixed amt of presentation time
+ // worth of data buffered. If we cannot get common time (service is
+ // unavailable, or common time is undefined)) then we don't have a lot
+ // of good options here. For now, signal an error up to the app level
+ // and shut down the transmission pump.
+ int64_t commonTimeNow;
+ if (OK != CCHelper::getCommonTime(&commonTimeNow)) {
+ // Failed to get common time; either the service is down or common
+ // time is not synced. Raise an error and shutdown the player.
+ LOGE("*** Cannot pump audio, unable to fetch common time."
+ " Shutting down.");
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+
+ if (mCurrentClockTransformValid && mLastQueuedMediaTimePTSValid) {
+ int64_t mediaTimeNow;
+ bool conversionResult = mCurrentClockTransform.doReverseTransform(
+ commonTimeNow,
+ &mediaTimeNow);
+ CHECK(conversionResult);
+
+ if ((mediaTimeNow +
+ kAAHBufferTimeUs -
+ mLastQueuedMediaTimePTS) <= 0) {
+ break;
+ }
+ }
+
+ MediaSource::ReadOptions options;
+ if (mIsSeeking) {
+ options.setSeekTo(mSeekTimeUs);
+ }
+
+ MediaBuffer* mediaBuffer;
+ status_t err = mAudioSource->read(&mediaBuffer, &options);
+ if (err != NO_ERROR) {
+ if (err == ERROR_END_OF_STREAM) {
+ LOGI("*** %s reached end of stream", __PRETTY_FUNCTION__);
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+ pause_l(false);
+ sendEOS_l();
+ } else {
+ LOGE("*** %s read failed err=%d", __PRETTY_FUNCTION__, err);
+ }
+ return;
+ }
+
+ if (mIsSeeking) {
+ mIsSeeking = false;
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ }
+
+ uint8_t* data = (static_cast<uint8_t*>(mediaBuffer->data()) +
+ mediaBuffer->range_offset());
+ LOGV("*** %s got media buffer data=[%02hhx %02hhx %02hhx %02hhx]"
+ " offset=%d length=%d", __PRETTY_FUNCTION__,
+ data[0], data[1], data[2], data[3],
+ mediaBuffer->range_offset(), mediaBuffer->range_length());
+
+ int64_t mediaTimeUs;
+ CHECK(mediaBuffer->meta_data()->findInt64(kKeyTime, &mediaTimeUs));
+ LOGV("*** timeUs=%lld", mediaTimeUs);
+
+ if (!mCurrentClockTransformValid) {
+ if (OK == CCHelper::getCommonTime(&commonTimeNow)) {
+ mCurrentClockTransform.a_zero = mediaTimeUs;
+ mCurrentClockTransform.b_zero = commonTimeNow +
+ kAAHStartupLeadTimeUs;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = mPlayRateIsPaused ? 0 : 1;
+ mCurrentClockTransformValid = true;
+ } else {
+ // Failed to get common time; either the service is down or
+ // common time is not synced. Raise an error and shutdown the
+ // player.
+ LOGE("*** Cannot begin transmission, unable to fetch common"
+ " time. Dropping sample with pts=%lld", mediaTimeUs);
+ notifyListener_l(MEDIA_ERROR,
+ MEDIA_ERROR_UNKNOWN,
+ UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+ }
+
+ LOGV("*** transmitting packet with pts=%lld", mediaTimeUs);
+
+ sp<TRTPAudioPacket> packet = new TRTPAudioPacket();
+ packet->setPTS(mediaTimeUs);
+ packet->setSubstreamID(1);
+
+ packet->setCodecType(TRTPAudioPacket::kCodecMPEG1Audio);
+ packet->setVolume(mTRTPVolume);
+ // TODO : introduce a throttle for this so we can control the
+ // frequency with which transforms get sent.
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setAccessUnitData(data, mediaBuffer->range_length());
+ packet->setRandomAccessPoint(true);
+
+ queuePacketToSender_l(packet);
+ mediaBuffer->release();
+
+ mLastQueuedMediaTimePTSValid = true;
+ mLastQueuedMediaTimePTS = mediaTimeUs;
+ }
+
+ { // Explicit scope for the autolock pattern.
+ Mutex::Autolock autoLock(mLock);
+
+ // If someone externally has cleared this flag, its because we should be
+ // shutting down. Do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Looks like no one canceled us explicitly. Clear our flag and post a
+ // new event to ourselves.
+ mPumpAudioEventPending = false;
+ postPumpAudioEvent_l(10000);
+ }
+}
+
+void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) {
+ if (mAAH_Sender == NULL) {
+ return;
+ }
+
+ sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket,
+ mAAH_Sender->handlerID());
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return;
+ }
+
+ mAAH_Sender->assignSeqNumber(mEndpoint, packet);
+ message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr);
+ message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port);
+ }
+
+ packet->setProgramID(mProgramID);
+ packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet);
+
+ message->post();
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_player.h b/media/libaah_rtp/aah_tx_player.h
new file mode 100644
index 0000000..190b432
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_TX_PLAYER_H__
+#define __AAH_TX_PLAYER_H__
+
+#include <libstagefright/include/HTTPBase.h>
+#include <libstagefright/include/NuCachedSource2.h>
+#include <libstagefright/include/TimedEventQueue.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+#include "aah_tx_sender.h"
+
+namespace android {
+
+class AAH_TXPlayer : public MediaPlayerHWInterface {
+ public:
+ AAH_TXPlayer();
+
+ virtual status_t initCheck();
+ virtual status_t setDataSource(const char *url,
+ const KeyedVector<String8, String8>*
+ headers);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setVideoSurface(const sp<Surface>& surface);
+ virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+ surfaceTexture);
+ virtual status_t prepare();
+ virtual status_t prepareAsync();
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t pause();
+ virtual bool isPlaying();
+ virtual status_t seekTo(int msec);
+ virtual status_t getCurrentPosition(int *msec);
+ virtual status_t getDuration(int *msec);
+ virtual status_t reset();
+ virtual status_t setLooping(int loop);
+ virtual player_type playerType();
+ virtual status_t setParameter(int key, const Parcel &request);
+ virtual status_t getParameter(int key, Parcel *reply);
+ virtual status_t invoke(const Parcel& request, Parcel *reply);
+ virtual status_t getMetadata(const media::Metadata::Filter& ids,
+ Parcel* records);
+ virtual status_t setVolume(float leftVolume, float rightVolume);
+ virtual status_t setAudioStreamType(int streamType);
+
+ // invoke method IDs
+ enum {
+ // set the IP address and port of the A@H receiver
+ kInvokeSetAAHDstIPPort = 1,
+ };
+
+ static const int64_t kAAHRetryKeepAroundTimeNs;
+
+ protected:
+ virtual ~AAH_TXPlayer();
+
+ private:
+ friend struct AwesomeEvent;
+
+ enum {
+ PLAYING = 1,
+ PREPARING = 8,
+ PREPARED = 16,
+ PREPARE_CANCELLED = 64,
+ CACHE_UNDERRUN = 128,
+
+ // We are basically done preparing but are currently buffering
+ // sufficient data to begin playback and finish the preparation
+ // phase for good.
+ PREPARING_CONNECTED = 2048,
+
+ INCOGNITO = 32768,
+ };
+
+ status_t setDataSource_l(const char *url,
+ const KeyedVector<String8, String8> *headers);
+ status_t setDataSource_l(const sp<MediaExtractor>& extractor);
+ status_t finishSetDataSource_l();
+ status_t prepareAsync_l();
+ void onPrepareAsyncEvent();
+ void finishAsyncPrepare_l();
+ void abortPrepare(status_t err);
+ status_t play_l();
+ status_t pause_l(bool doClockUpdate = true);
+ status_t seekTo_l(int64_t timeUs);
+ void updateClockTransform_l(bool pause);
+ void sendEOS_l();
+ void cancelPlayerEvents(bool keepBufferingGoing = false);
+ void reset_l();
+ void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0);
+ bool getBitrate_l(int64_t* bitrate);
+ status_t getDuration_l(int* msec);
+ bool getCachedDuration_l(int64_t* durationUs, bool* eos);
+ void ensureCacheIsFetching_l();
+ void postBufferingEvent_l();
+ void postPumpAudioEvent_l(int64_t delayUs);
+ void onBufferingUpdate();
+ void onPumpAudio();
+ void queuePacketToSender_l(const sp<TRTPPacket>& packet);
+
+ Mutex mLock;
+
+ TimedEventQueue mQueue;
+ bool mQueueStarted;
+
+ sp<TimedEventQueue::Event> mBufferingEvent;
+ bool mBufferingEventPending;
+
+ uint32_t mFlags;
+ uint32_t mExtractorFlags;
+
+ String8 mUri;
+ KeyedVector<String8, String8> mUriHeaders;
+
+ sp<TimedEventQueue::Event> mAsyncPrepareEvent;
+ Condition mPreparedCondition;
+ status_t mPrepareResult;
+
+ bool mIsSeeking;
+ int64_t mSeekTimeUs;
+
+ sp<TimedEventQueue::Event> mPumpAudioEvent;
+ bool mPumpAudioEventPending;
+
+ sp<HTTPBase> mConnectingDataSource;
+ sp<NuCachedSource2> mCachedSource;
+
+ sp<MediaSource> mAudioSource;
+ int64_t mDurationUs;
+ int64_t mBitrate;
+
+ sp<AAH_TXSender> mAAH_Sender;
+ LinearTransform mCurrentClockTransform;
+ bool mCurrentClockTransformValid;
+ int64_t mLastQueuedMediaTimePTS;
+ bool mLastQueuedMediaTimePTSValid;
+ bool mPlayRateIsPaused;
+
+ Mutex mEndpointLock;
+ AAH_TXSender::Endpoint mEndpoint;
+ bool mEndpointValid;
+ bool mEndpointRegistered;
+ uint16_t mProgramID;
+
+ uint8_t mTRTPVolume;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_TXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_sender.cpp b/media/libaah_rtp/aah_tx_sender.cpp
new file mode 100644
index 0000000..779d809
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.cpp
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+#include <media/stagefright/foundation/ADebug.h>
+
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <media/stagefright/foundation/AMessage.h>
+#include <utils/misc.h>
+
+#include "aah_tx_player.h"
+#include "aah_tx_sender.h"
+
+namespace android {
+
+const char* AAH_TXSender::kSendPacketIPAddr = "ipaddr";
+const char* AAH_TXSender::kSendPacketPort = "port";
+const char* AAH_TXSender::kSendPacketTRTPPacket = "trtp";
+
+const int AAH_TXSender::kRetryTrimIntervalUs = 100000;
+const int AAH_TXSender::kHeartbeatIntervalUs = 1000000;
+const int AAH_TXSender::kRetryBufferCapacity = 100;
+
+Mutex AAH_TXSender::sLock;
+wp<AAH_TXSender> AAH_TXSender::sInstance;
+uint32_t AAH_TXSender::sNextEpoch;
+bool AAH_TXSender::sNextEpochValid = false;
+
+AAH_TXSender::AAH_TXSender() : mSocket(-1) { }
+
+sp<AAH_TXSender> AAH_TXSender::GetInstance() {
+ Mutex::Autolock autoLock(sLock);
+
+ sp<AAH_TXSender> sender = sInstance.promote();
+
+ if (sender == NULL) {
+ sender = new AAH_TXSender();
+ if (sender == NULL) {
+ return NULL;
+ }
+
+ sender->mLooper = new ALooper();
+ if (sender->mLooper == NULL) {
+ return NULL;
+ }
+
+ sender->mReflector = new AHandlerReflector<AAH_TXSender>(sender.get());
+ if (sender->mReflector == NULL) {
+ return NULL;
+ }
+
+ sender->mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sender->mSocket == -1) {
+ LOGW("%s unable to create socket", __PRETTY_FUNCTION__);
+ return NULL;
+ }
+
+ struct sockaddr_in bind_addr;
+ memset(&bind_addr, 0, sizeof(bind_addr));
+ bind_addr.sin_family = AF_INET;
+ if (bind(sender->mSocket,
+ reinterpret_cast<const sockaddr*>(&bind_addr),
+ sizeof(bind_addr)) < 0) {
+ LOGW("%s unable to bind socket (errno %d)",
+ __PRETTY_FUNCTION__, errno);
+ return NULL;
+ }
+
+ sender->mRetryReceiver = new RetryReceiver(sender.get());
+ if (sender->mRetryReceiver == NULL) {
+ return NULL;
+ }
+
+ sender->mLooper->setName("AAH_TXSender");
+ sender->mLooper->registerHandler(sender->mReflector);
+ sender->mLooper->start(false, false, PRIORITY_AUDIO);
+
+ if (sender->mRetryReceiver->run("AAH_TXSenderRetry", PRIORITY_AUDIO)
+ != OK) {
+ LOGW("%s unable to start retry thread", __PRETTY_FUNCTION__);
+ return NULL;
+ }
+
+ sInstance = sender;
+ }
+
+ return sender;
+}
+
+AAH_TXSender::~AAH_TXSender() {
+ mLooper->stop();
+ mLooper->unregisterHandler(mReflector->id());
+
+ if (mRetryReceiver != NULL) {
+ mRetryReceiver->requestExit();
+ mRetryReceiver->mWakeupEvent.setEvent();
+ if (mRetryReceiver->requestExitAndWait() != OK) {
+ LOGW("%s shutdown of retry receiver failed", __PRETTY_FUNCTION__);
+ }
+ mRetryReceiver->mSender = NULL;
+ mRetryReceiver.clear();
+ }
+
+ if (mSocket != -1) {
+ close(mSocket);
+ }
+}
+
+// Return the next epoch number usable for a newly instantiated endpoint.
+uint32_t AAH_TXSender::getNextEpoch() {
+ Mutex::Autolock autoLock(sLock);
+
+ if (sNextEpochValid) {
+ sNextEpoch = (sNextEpoch + 1) & TRTPPacket::kTRTPEpochMask;
+ } else {
+ sNextEpoch = ns2ms(systemTime()) & TRTPPacket::kTRTPEpochMask;
+ sNextEpochValid = true;
+ }
+
+ return sNextEpoch;
+}
+
+// Notify the sender that a player has started sending to this endpoint.
+// Returns a program ID for use by the calling player.
+uint16_t AAH_TXSender::registerEndpoint(const Endpoint& endpoint) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (eps) {
+ eps->playerRefCount++;
+ } else {
+ eps = new EndpointState(getNextEpoch());
+ mEndpointMap.add(endpoint, eps);
+ }
+
+ // if this is the first registered endpoint, then send a message to start
+ // trimming retry buffers and a message to start sending heartbeats.
+ if (mEndpointMap.size() == 1) {
+ sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+ handlerID());
+ trimMessage->post(kRetryTrimIntervalUs);
+
+ sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+ handlerID());
+ heartbeatMessage->post(kHeartbeatIntervalUs);
+ }
+
+ eps->nextProgramID++;
+ return eps->nextProgramID;
+}
+
+// Notify the sender that a player has ceased sending to this endpoint.
+// An endpoint's state can not be deleted until all of the endpoint's
+// registered players have called unregisterEndpoint.
+void AAH_TXSender::unregisterEndpoint(const Endpoint& endpoint) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (eps) {
+ eps->playerRefCount--;
+ CHECK(eps->playerRefCount >= 0);
+ }
+}
+
+void AAH_TXSender::assignSeqNumber(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet) {
+ Mutex::Autolock lock(mEndpointLock);
+ assignSeqNumber_l(endpoint, packet);
+}
+
+void AAH_TXSender::assignSeqNumber_l(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet) {
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (!eps) {
+ // the endpoint state has disappeared, so the player that sent this
+ // packet must be dead.
+ return;
+ }
+ packet->setEpoch(eps->epoch);
+ packet->setSeqNumber(eps->trtpSeqNumber++);
+}
+
+void AAH_TXSender::onMessageReceived(const sp<AMessage>& msg) {
+ switch (msg->what()) {
+ case kWhatSendPacket:
+ onSendPacket(msg);
+ break;
+
+ case kWhatTrimRetryBuffers:
+ trimRetryBuffers();
+ break;
+
+ case kWhatSendHeartbeats:
+ sendHeartbeats();
+ break;
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+void AAH_TXSender::onSendPacket(const sp<AMessage>& msg) {
+ LOGV("*** %s", __PRETTY_FUNCTION__);
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject(kSendPacketTRTPPacket, &obj));
+ sp<TRTPPacket> packet = static_cast<TRTPPacket*>(obj.get());
+
+ uint32_t ipAddr;
+ CHECK(msg->findInt32(kSendPacketIPAddr,
+ reinterpret_cast<int32_t*>(&ipAddr)));
+
+ int32_t port32;
+ CHECK(msg->findInt32(kSendPacketPort, &port32));
+ uint16_t port = port32;
+
+ doSendPacket(packet, ipAddr, port);
+
+ addToRetryBuffer(Endpoint(ipAddr, port), packet);
+}
+
+void AAH_TXSender::doSendPacket(sp<TRTPPacket> packet,
+ uint32_t ipAddr,
+ uint16_t port) {
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = ipAddr;
+ addr.sin_port = htons(port);
+
+ ssize_t result = sendto(mSocket,
+ packet->getPacket(),
+ packet->getPacketLen(),
+ 0,
+ (const struct sockaddr *) &addr,
+ sizeof(addr));
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+}
+
+void AAH_TXSender::addToRetryBuffer(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (!eps) {
+ return;
+ }
+
+ addToRetryBuffer_l(eps, packet);
+}
+
+void AAH_TXSender::addToRetryBuffer_l(EndpointState* eps,
+ const sp<TRTPPacket>& packet) {
+ RetryBuffer& retry = eps->retry;
+ retry.push_back(packet);
+
+ LOGV("*** %s seq=%hu size=%d",
+ __PRETTY_FUNCTION__,
+ packet->getSeqNumber(),
+ retry.size());
+}
+
+void AAH_TXSender::trimRetryBuffers() {
+ LOGV("*** %s", __PRETTY_FUNCTION__);
+
+ Mutex::Autolock lock(mEndpointLock);
+
+ nsecs_t localTimeNow = systemTime();
+
+ Vector<Endpoint> endpointsToRemove;
+
+ for (size_t i = 0; i < mEndpointMap.size(); i++) {
+ EndpointState* eps = mEndpointMap.editValueAt(i);
+ RetryBuffer& retry = eps->retry;
+
+ while (!retry.isEmpty()) {
+ if (retry[0]->getExpireTime() < localTimeNow) {
+ retry.pop_front();
+ } else {
+ break;
+ }
+ }
+
+ LOGV("*** %s size=%d", __PRETTY_FUNCTION__, retry.size());
+
+ if (retry.isEmpty() && eps->playerRefCount == 0) {
+ endpointsToRemove.add(mEndpointMap.keyAt(i));
+ }
+ }
+
+ // remove the state for any endpoints that are no longer in use
+ for (size_t i = 0; i < endpointsToRemove.size(); i++) {
+ Endpoint& e = endpointsToRemove.editItemAt(i);
+ LOGD("*** %s removing endpoint addr=%08x", __PRETTY_FUNCTION__, e.addr);
+ size_t index = mEndpointMap.indexOfKey(e);
+ delete mEndpointMap.valueAt(index);
+ mEndpointMap.removeItemsAt(index);
+ }
+
+ // schedule the next trim
+ if (mEndpointMap.size()) {
+ sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+ handlerID());
+ trimMessage->post(kRetryTrimIntervalUs);
+ }
+}
+
+void AAH_TXSender::sendHeartbeats() {
+ Mutex::Autolock lock(mEndpointLock);
+
+ for (size_t i = 0; i < mEndpointMap.size(); i++) {
+ EndpointState* eps = mEndpointMap.editValueAt(i);
+ const Endpoint& ep = mEndpointMap.keyAt(i);
+
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandNop);
+
+ assignSeqNumber_l(ep, packet);
+ packet->setExpireTime(systemTime() +
+ AAH_TXPlayer::kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ doSendPacket(packet, ep.addr, ep.port);
+ addToRetryBuffer_l(eps, packet);
+ }
+
+ // schedule the next heartbeat
+ if (mEndpointMap.size()) {
+ sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+ handlerID());
+ heartbeatMessage->post(kHeartbeatIntervalUs);
+ }
+}
+
+// Receiver
+
+// initial 4-byte ID of a retry request packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryRequestID = 'Treq';
+
+// initial 4-byte ID of a retry NAK packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryNakID = 'Tnak';
+
+// initial 4-byte ID of a fast start request packet
+const uint32_t AAH_TXSender::RetryReceiver::kFastStartRequestID = 'Tfst';
+
+AAH_TXSender::RetryReceiver::RetryReceiver(AAH_TXSender* sender)
+ : Thread(false),
+ mSender(sender) {}
+
+ AAH_TXSender::RetryReceiver::~RetryReceiver() {
+ mWakeupEvent.clearPendingEvents();
+ }
+
+// Returns true if val is within the interval bounded inclusively by
+// start and end. Also handles the case where there is a rollover of the
+// range between start and end.
+template <typename T>
+static inline bool withinIntervalWithRollover(T val, T start, T end) {
+ return ((start <= end && val >= start && val <= end) ||
+ (start > end && (val >= start || val <= end)));
+}
+
+bool AAH_TXSender::RetryReceiver::threadLoop() {
+ struct pollfd pollFds[2];
+ pollFds[0].fd = mSender->mSocket;
+ pollFds[0].events = POLLIN;
+ pollFds[0].revents = 0;
+ pollFds[1].fd = mWakeupEvent.getWakeupHandle();
+ pollFds[1].events = POLLIN;
+ pollFds[1].revents = 0;
+
+ int pollResult = poll(pollFds, NELEM(pollFds), -1);
+ if (pollResult == -1) {
+ LOGE("%s poll failed", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (exitPending()) {
+ LOGI("*** %s exiting", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (pollFds[0].revents) {
+ handleRetryRequest();
+ }
+
+ return true;
+}
+
+void AAH_TXSender::RetryReceiver::handleRetryRequest() {
+ LOGV("*** RX %s start", __PRETTY_FUNCTION__);
+
+ RetryPacket request;
+ struct sockaddr requestSrcAddr;
+ socklen_t requestSrcAddrLen = sizeof(requestSrcAddr);
+
+ ssize_t result = recvfrom(mSender->mSocket, &request, sizeof(request), 0,
+ &requestSrcAddr, &requestSrcAddrLen);
+ if (result == -1) {
+ LOGE("%s recvfrom failed, errno=%d", __PRETTY_FUNCTION__, errno);
+ return;
+ }
+
+ if (static_cast<size_t>(result) < sizeof(RetryPacket)) {
+ LOGW("%s short packet received", __PRETTY_FUNCTION__);
+ return;
+ }
+
+ uint32_t host_request_id = ntohl(request.id);
+ if ((host_request_id != kRetryRequestID) &&
+ (host_request_id != kFastStartRequestID)) {
+ LOGW("%s received retry request with bogus ID (%08x)",
+ __PRETTY_FUNCTION__, host_request_id);
+ return;
+ }
+
+ Endpoint endpoint(request.endpointIP, ntohs(request.endpointPort));
+
+ Mutex::Autolock lock(mSender->mEndpointLock);
+
+ EndpointState* eps = mSender->mEndpointMap.valueFor(endpoint);
+
+ if (eps == NULL || eps->retry.isEmpty()) {
+ // we have no retry buffer or an empty retry buffer for this endpoint,
+ // so NAK the entire request
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ return;
+ }
+
+ RetryBuffer& retry = eps->retry;
+
+ uint16_t startSeq = ntohs(request.seqStart);
+ uint16_t endSeq = ntohs(request.seqEnd);
+
+ uint16_t retryFirstSeq = retry[0]->getSeqNumber();
+ uint16_t retryLastSeq = retry[retry.size() - 1]->getSeqNumber();
+
+ // If this is a fast start, then force the start of the retry to match the
+ // start of the retransmit ring buffer (unless the end of the retransmit
+ // ring buffer is already past the point of fast start)
+ if ((host_request_id == kFastStartRequestID) &&
+ !((startSeq - retryFirstSeq) & 0x8000)) {
+ startSeq = retryFirstSeq;
+ }
+
+ int startIndex;
+ if (withinIntervalWithRollover(startSeq, retryFirstSeq, retryLastSeq)) {
+ startIndex = static_cast<uint16_t>(startSeq - retryFirstSeq);
+ } else {
+ startIndex = -1;
+ }
+
+ int endIndex;
+ if (withinIntervalWithRollover(endSeq, retryFirstSeq, retryLastSeq)) {
+ endIndex = static_cast<uint16_t>(endSeq - retryFirstSeq);
+ } else {
+ endIndex = -1;
+ }
+
+ if (startIndex == -1 && endIndex == -1) {
+ // no part of the request range is found in the retry buffer
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ return;
+ }
+
+ if (startIndex == -1) {
+ // NAK a subrange at the front of the request range
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ nak.seqEnd = htons(retryFirstSeq - 1);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+
+ startIndex = 0;
+ } else if (endIndex == -1) {
+ // NAK a subrange at the back of the request range
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ nak.seqStart = htons(retryLastSeq + 1);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+
+ endIndex = retry.size() - 1;
+ }
+
+ // send the retry packets
+ for (int i = startIndex; i <= endIndex; i++) {
+ const sp<TRTPPacket>& replyPacket = retry[i];
+
+ result = sendto(mSender->mSocket,
+ replyPacket->getPacket(),
+ replyPacket->getPacketLen(),
+ 0,
+ &requestSrcAddr,
+ requestSrcAddrLen);
+
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ }
+}
+
+// Endpoint
+
+AAH_TXSender::Endpoint::Endpoint()
+ : addr(0)
+ , port(0) { }
+
+AAH_TXSender::Endpoint::Endpoint(uint32_t a, uint16_t p)
+ : addr(a)
+ , port(p) {}
+
+bool AAH_TXSender::Endpoint::operator<(const Endpoint& other) const {
+ return ((addr < other.addr) ||
+ (addr == other.addr && port < other.port));
+}
+
+// EndpointState
+
+AAH_TXSender::EndpointState::EndpointState(uint32_t _epoch)
+ : retry(kRetryBufferCapacity)
+ , playerRefCount(1)
+ , trtpSeqNumber(0)
+ , nextProgramID(0)
+ , epoch(_epoch) { }
+
+// CircularBuffer
+
+template <typename T>
+CircularBuffer<T>::CircularBuffer(size_t capacity)
+ : mCapacity(capacity)
+ , mHead(0)
+ , mTail(0)
+ , mFillCount(0) {
+ mBuffer = new T[capacity];
+}
+
+template <typename T>
+CircularBuffer<T>::~CircularBuffer() {
+ delete [] mBuffer;
+}
+
+template <typename T>
+void CircularBuffer<T>::push_back(const T& item) {
+ if (this->isFull()) {
+ this->pop_front();
+ }
+ mBuffer[mHead] = item;
+ mHead = (mHead + 1) % mCapacity;
+ mFillCount++;
+}
+
+template <typename T>
+void CircularBuffer<T>::pop_front() {
+ CHECK(!isEmpty());
+ mBuffer[mTail] = T();
+ mTail = (mTail + 1) % mCapacity;
+ mFillCount--;
+}
+
+template <typename T>
+size_t CircularBuffer<T>::size() const {
+ return mFillCount;
+}
+
+template <typename T>
+bool CircularBuffer<T>::isFull() const {
+ return (mFillCount == mCapacity);
+}
+
+template <typename T>
+bool CircularBuffer<T>::isEmpty() const {
+ return (mFillCount == 0);
+}
+
+template <typename T>
+const T& CircularBuffer<T>::itemAt(size_t index) const {
+ CHECK(index < mFillCount);
+ return mBuffer[(mTail + index) % mCapacity];
+}
+
+template <typename T>
+const T& CircularBuffer<T>::operator[](size_t index) const {
+ return itemAt(index);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_sender.h b/media/libaah_rtp/aah_tx_sender.h
new file mode 100644
index 0000000..a4baf5f
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_TX_SENDER_H__
+#define __AAH_TX_SENDER_H__
+
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+
+#include "aah_tx_packet.h"
+#include "pipe_event.h"
+
+namespace android {
+
+template <typename T> class CircularBuffer {
+ public:
+ CircularBuffer(size_t capacity);
+ ~CircularBuffer();
+ void push_back(const T& item);;
+ void pop_front();
+ size_t size() const;
+ bool isFull() const;
+ bool isEmpty() const;
+ const T& itemAt(size_t index) const;
+ const T& operator[](size_t index) const;
+
+ private:
+ T* mBuffer;
+ size_t mCapacity;
+ size_t mHead;
+ size_t mTail;
+ size_t mFillCount;
+};
+
+class AAH_TXSender : public virtual RefBase {
+ public:
+ ~AAH_TXSender();
+
+ static sp<AAH_TXSender> GetInstance();
+
+ ALooper::handler_id handlerID() { return mReflector->id(); }
+
+ // an IP address and port
+ struct Endpoint {
+ Endpoint();
+ Endpoint(uint32_t a, uint16_t p);
+ bool operator<(const Endpoint& other) const;
+
+ uint32_t addr;
+ uint16_t port;
+ };
+
+ uint16_t registerEndpoint(const Endpoint& endpoint);
+ void unregisterEndpoint(const Endpoint& endpoint);
+ void assignSeqNumber(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet);
+
+ enum {
+ kWhatSendPacket,
+ kWhatTrimRetryBuffers,
+ kWhatSendHeartbeats,
+ };
+
+ // fields for SendPacket messages
+ static const char* kSendPacketIPAddr;
+ static const char* kSendPacketPort;
+ static const char* kSendPacketTRTPPacket;
+
+ private:
+ AAH_TXSender();
+
+ static Mutex sLock;
+ static wp<AAH_TXSender> sInstance;
+ static uint32_t sNextEpoch;
+ static bool sNextEpochValid;
+
+ static uint32_t getNextEpoch();
+
+ typedef CircularBuffer<sp<TRTPPacket> > RetryBuffer;
+
+ // state maintained on a per-endpoint basis
+ struct EndpointState {
+ EndpointState(uint32_t epoch);
+ RetryBuffer retry;
+ int playerRefCount;
+ uint16_t trtpSeqNumber;
+ uint16_t nextProgramID;
+ uint32_t epoch;
+ };
+
+ friend class AHandlerReflector<AAH_TXSender>;
+ void onMessageReceived(const sp<AMessage>& msg);
+ void onSendPacket(const sp<AMessage>& msg);
+ void doSendPacket(sp<TRTPPacket> packet, uint32_t ipAddr, uint16_t port);
+ void addToRetryBuffer(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet);
+ void addToRetryBuffer_l(EndpointState* eps,
+ const sp<TRTPPacket>& packet);
+ void trimRetryBuffers();
+ void sendHeartbeats();
+ void assignSeqNumber_l(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet);
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<AAH_TXSender> > mReflector;
+
+ int mSocket;
+
+ DefaultKeyedVector<Endpoint, EndpointState*> mEndpointMap;
+ Mutex mEndpointLock;
+
+ static const int kRetryTrimIntervalUs;
+ static const int kHeartbeatIntervalUs;
+ static const int kRetryBufferCapacity;
+
+ class RetryReceiver : public Thread {
+ private:
+ friend class AAH_TXSender;
+
+ RetryReceiver(AAH_TXSender* sender);
+ virtual ~RetryReceiver();
+ virtual bool threadLoop();
+ void handleRetryRequest();
+
+ static const int kMaxReceiverPacketLen;
+ static const uint32_t kRetryRequestID;
+ static const uint32_t kFastStartRequestID;
+ static const uint32_t kRetryNakID;
+
+ AAH_TXSender* mSender;
+ PipeEvent mWakeupEvent;
+ };
+
+ sp<RetryReceiver> mRetryReceiver;
+};
+
+struct RetryPacket {
+ uint32_t id;
+ uint32_t endpointIP;
+ uint16_t endpointPort;
+ uint16_t seqStart;
+ uint16_t seqEnd;
+} __attribute__((packed));
+
+} // namespace android
+
+#endif // __AAH_TX_SENDER_H__
diff --git a/media/libaah_rtp/pipe_event.cpp b/media/libaah_rtp/pipe_event.cpp
new file mode 100644
index 0000000..96c674b
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include "pipe_event.h"
+
+namespace android {
+
+PipeEvent::PipeEvent() {
+ pipe_[0] = -1;
+ pipe_[1] = -1;
+
+ // Create the pipe.
+ if (pipe(pipe_) >= 0) {
+ // Set non-blocking mode on the read side of the pipe so we can
+ // easily drain it whenever we wakeup.
+ fcntl(pipe_[0], F_SETFL, O_NONBLOCK);
+ } else {
+ LOGE("Failed to create pipe event %d %d %d",
+ pipe_[0], pipe_[1], errno);
+ pipe_[0] = -1;
+ pipe_[1] = -1;
+ }
+}
+
+PipeEvent::~PipeEvent() {
+ if (pipe_[0] >= 0) {
+ close(pipe_[0]);
+ }
+
+ if (pipe_[1] >= 0) {
+ close(pipe_[1]);
+ }
+}
+
+void PipeEvent::clearPendingEvents() {
+ char drain_buffer[16];
+ while (read(pipe_[0], drain_buffer, sizeof(drain_buffer)) > 0) {
+ // No body.
+ }
+}
+
+bool PipeEvent::wait(int timeout) {
+ struct pollfd wait_fd;
+
+ wait_fd.fd = getWakeupHandle();
+ wait_fd.events = POLLIN;
+ wait_fd.revents = 0;
+
+ int res = poll(&wait_fd, 1, timeout);
+
+ if (res < 0) {
+ LOGE("Wait error in PipeEvent; sleeping to prevent overload!");
+ usleep(1000);
+ }
+
+ return (res > 0);
+}
+
+void PipeEvent::setEvent() {
+ char foo = 'q';
+ write(pipe_[1], &foo, 1);
+}
+
+} // namespace android
+
diff --git a/media/libaah_rtp/pipe_event.h b/media/libaah_rtp/pipe_event.h
new file mode 100644
index 0000000..637bfef
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __PIPE_EVENT_H__
+#define __PIPE_EVENT_H__
+
+namespace android {
+
+class PipeEvent {
+ public:
+ PipeEvent();
+ ~PipeEvent();
+
+ bool initCheck() const {
+ return ((pipe_[0] >= 0) && (pipe_[1] >= 0));
+ }
+
+ int getWakeupHandle() const { return pipe_[0]; }
+
+ // block until the event fires; returns true if the event fired and false if
+ // the wait timed out. Timeout is expressed in milliseconds; negative
+ // values mean wait forever.
+ bool wait(int timeout = -1);
+
+ void clearPendingEvents();
+ void setEvent();
+
+ private:
+ int pipe_[2];
+};
+
+} // namespace android
+
+#endif // __PIPE_EVENT_H__
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index 52885d2..bd89ad8 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -21,14 +21,20 @@
#include <binder/Parcel.h>
#include <media/IMediaPlayer.h>
+#include <media/IStreamSource.h>
+
#include <surfaceflinger/ISurface.h>
#include <surfaceflinger/Surface.h>
#include <gui/ISurfaceTexture.h>
+#include <utils/String8.h>
namespace android {
enum {
DISCONNECT = IBinder::FIRST_CALL_TRANSACTION,
+ SET_DATA_SOURCE_URL,
+ SET_DATA_SOURCE_FD,
+ SET_DATA_SOURCE_STREAM,
SET_VIDEO_SURFACE,
PREPARE_ASYNC,
START,
@@ -68,6 +74,43 @@
remote()->transact(DISCONNECT, data, &reply);
}
+ status_t setDataSource(const char* url,
+ const KeyedVector<String8, String8>* headers)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+ data.writeCString(url);
+ if (headers == NULL) {
+ data.writeInt32(0);
+ } else {
+ // serialize the headers
+ data.writeInt32(headers->size());
+ for (size_t i = 0; i < headers->size(); ++i) {
+ data.writeString8(headers->keyAt(i));
+ data.writeString8(headers->valueAt(i));
+ }
+ }
+ remote()->transact(SET_DATA_SOURCE_URL, data, &reply);
+ return reply.readInt32();
+ }
+
+ status_t setDataSource(int fd, int64_t offset, int64_t length) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+ data.writeFileDescriptor(fd);
+ data.writeInt64(offset);
+ data.writeInt64(length);
+ remote()->transact(SET_DATA_SOURCE_FD, data, &reply);
+ return reply.readInt32();
+ }
+
+ status_t setDataSource(const sp<IStreamSource> &source) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+ data.writeStrongBinder(source->asBinder());
+ return reply.readInt32();
+ }
+
// pass the buffered Surface to the media player service
status_t setVideoSurface(const sp<Surface>& surface)
{
@@ -273,6 +316,34 @@
disconnect();
return NO_ERROR;
} break;
+ case SET_DATA_SOURCE_URL: {
+ CHECK_INTERFACE(IMediaPlayer, data, reply);
+ const char* url = data.readCString();
+ KeyedVector<String8, String8> headers;
+ int32_t numHeaders = data.readInt32();
+ for (int i = 0; i < numHeaders; ++i) {
+ String8 key = data.readString8();
+ String8 value = data.readString8();
+ headers.add(key, value);
+ }
+ reply->writeInt32(setDataSource(url, numHeaders > 0 ? &headers : NULL));
+ return NO_ERROR;
+ } break;
+ case SET_DATA_SOURCE_FD: {
+ CHECK_INTERFACE(IMediaPlayer, data, reply);
+ int fd = data.readFileDescriptor();
+ int64_t offset = data.readInt64();
+ int64_t length = data.readInt64();
+ reply->writeInt32(setDataSource(fd, offset, length));
+ return NO_ERROR;
+ }
+ case SET_DATA_SOURCE_STREAM: {
+ CHECK_INTERFACE(IMediaPlayer, data, reply);
+ sp<IStreamSource> source =
+ interface_cast<IStreamSource>(data.readStrongBinder());
+ reply->writeInt32(setDataSource(source));
+ return NO_ERROR;
+ }
case SET_VIDEO_SURFACE: {
CHECK_INTERFACE(IMediaPlayer, data, reply);
sp<Surface> surface = Surface::readFromParcel(data);
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index 17a0362..8e4dd04 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -30,9 +30,7 @@
namespace android {
enum {
- CREATE_URL = IBinder::FIRST_CALL_TRANSACTION,
- CREATE_FD,
- CREATE_STREAM,
+ CREATE = IBinder::FIRST_CALL_TRANSACTION,
DECODE_URL,
DECODE_FD,
CREATE_MEDIA_RECORDER,
@@ -60,28 +58,14 @@
}
virtual sp<IMediaPlayer> create(
- pid_t pid, const sp<IMediaPlayerClient>& client,
- const char* url, const KeyedVector<String8, String8> *headers, int audioSessionId) {
+ pid_t pid, const sp<IMediaPlayerClient>& client, int audioSessionId) {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
data.writeInt32(pid);
data.writeStrongBinder(client->asBinder());
- data.writeCString(url);
-
- if (headers == NULL) {
- data.writeInt32(0);
- } else {
- // serialize the headers
- data.writeInt32(headers->size());
- for (size_t i = 0; i < headers->size(); ++i) {
- data.writeString8(headers->keyAt(i));
- data.writeString8(headers->valueAt(i));
- }
- }
data.writeInt32(audioSessionId);
- remote()->transact(CREATE_URL, data, &reply);
-
+ remote()->transact(CREATE, data, &reply);
return interface_cast<IMediaPlayer>(reply.readStrongBinder());
}
@@ -94,38 +78,6 @@
return interface_cast<IMediaRecorder>(reply.readStrongBinder());
}
- virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd,
- int64_t offset, int64_t length, int audioSessionId)
- {
- Parcel data, reply;
- data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
- data.writeInt32(pid);
- data.writeStrongBinder(client->asBinder());
- data.writeFileDescriptor(fd);
- data.writeInt64(offset);
- data.writeInt64(length);
- data.writeInt32(audioSessionId);
-
- remote()->transact(CREATE_FD, data, &reply);
-
- return interface_cast<IMediaPlayer>(reply.readStrongBinder());;
- }
-
- virtual sp<IMediaPlayer> create(
- pid_t pid, const sp<IMediaPlayerClient> &client,
- const sp<IStreamSource> &source, int audioSessionId) {
- Parcel data, reply;
- data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
- data.writeInt32(static_cast<int32_t>(pid));
- data.writeStrongBinder(client->asBinder());
- data.writeStrongBinder(source->asBinder());
- data.writeInt32(static_cast<int32_t>(audioSessionId));
-
- remote()->transact(CREATE_STREAM, data, &reply);
-
- return interface_cast<IMediaPlayer>(reply.readStrongBinder());;
- }
-
virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat)
{
Parcel data, reply;
@@ -181,62 +133,16 @@
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
- case CREATE_URL: {
+ case CREATE: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
pid_t pid = data.readInt32();
sp<IMediaPlayerClient> client =
interface_cast<IMediaPlayerClient>(data.readStrongBinder());
- const char* url = data.readCString();
-
- KeyedVector<String8, String8> headers;
- int32_t numHeaders = data.readInt32();
- for (int i = 0; i < numHeaders; ++i) {
- String8 key = data.readString8();
- String8 value = data.readString8();
- headers.add(key, value);
- }
int audioSessionId = data.readInt32();
-
- sp<IMediaPlayer> player = create(
- pid, client, url, numHeaders > 0 ? &headers : NULL, audioSessionId);
-
+ sp<IMediaPlayer> player = create(pid, client, audioSessionId);
reply->writeStrongBinder(player->asBinder());
return NO_ERROR;
} break;
- case CREATE_FD: {
- CHECK_INTERFACE(IMediaPlayerService, data, reply);
- pid_t pid = data.readInt32();
- sp<IMediaPlayerClient> client = interface_cast<IMediaPlayerClient>(data.readStrongBinder());
- int fd = dup(data.readFileDescriptor());
- int64_t offset = data.readInt64();
- int64_t length = data.readInt64();
- int audioSessionId = data.readInt32();
-
- sp<IMediaPlayer> player = create(pid, client, fd, offset, length, audioSessionId);
- reply->writeStrongBinder(player->asBinder());
- return NO_ERROR;
- } break;
- case CREATE_STREAM:
- {
- CHECK_INTERFACE(IMediaPlayerService, data, reply);
-
- pid_t pid = static_cast<pid_t>(data.readInt32());
-
- sp<IMediaPlayerClient> client =
- interface_cast<IMediaPlayerClient>(data.readStrongBinder());
-
- sp<IStreamSource> source =
- interface_cast<IStreamSource>(data.readStrongBinder());
-
- int audioSessionId = static_cast<int>(data.readInt32());
-
- sp<IMediaPlayer> player =
- create(pid, client, source, audioSessionId);
-
- reply->writeStrongBinder(player->asBinder());
- return OK;
- break;
- }
case DECODE_URL: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
const char* url = data.readCString();
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index 67a66a2..0fc6a8a 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -108,7 +108,7 @@
}
-status_t MediaPlayer::setDataSource(const sp<IMediaPlayer>& player)
+status_t MediaPlayer::attachNewPlayer(const sp<IMediaPlayer>& player)
{
status_t err = UNKNOWN_ERROR;
sp<IMediaPlayer> p;
@@ -117,7 +117,7 @@
if ( !( (mCurrentState & MEDIA_PLAYER_IDLE) ||
(mCurrentState == MEDIA_PLAYER_STATE_ERROR ) ) ) {
- LOGE("setDataSource called in state %d", mCurrentState);
+ LOGE("attachNewPlayer called in state %d", mCurrentState);
return INVALID_OPERATION;
}
@@ -147,9 +147,11 @@
if (url != NULL) {
const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != 0) {
- sp<IMediaPlayer> player(
- service->create(getpid(), this, url, headers, mAudioSessionId));
- err = setDataSource(player);
+ sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
+ err = attachNewPlayer(player);
+ if (err == NO_ERROR) {
+ err = mPlayer->setDataSource(url, headers);
+ }
}
}
return err;
@@ -161,8 +163,26 @@
status_t err = UNKNOWN_ERROR;
const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != 0) {
- sp<IMediaPlayer> player(service->create(getpid(), this, fd, offset, length, mAudioSessionId));
- err = setDataSource(player);
+ sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
+ err = attachNewPlayer(player);
+ if (err == NO_ERROR) {
+ err = mPlayer->setDataSource(fd, offset, length);
+ }
+ }
+ return err;
+}
+
+status_t MediaPlayer::setDataSource(const sp<IStreamSource> &source)
+{
+ LOGV("setDataSource");
+ status_t err = UNKNOWN_ERROR;
+ const sp<IMediaPlayerService>& service(getMediaPlayerService());
+ if (service != 0) {
+ sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
+ err = attachNewPlayer(player);
+ if (err == NO_ERROR) {
+ err = mPlayer->setDataSource(source);
+ }
}
return err;
}
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index ec7d8a0..b5a60d2 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -29,7 +29,8 @@
libstagefright_omx \
libstagefright_foundation \
libgui \
- libdl
+ libdl \
+ libaah_rtp
LOCAL_STATIC_LIBRARIES := \
libstagefright_rtsp \
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 2051b3b..a434614 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -68,6 +68,11 @@
#include <OMX.h>
+namespace android {
+sp<MediaPlayerBase> createAAH_TXPlayer();
+sp<MediaPlayerBase> createAAH_RXPlayer();
+}
+
namespace {
using android::media::Metadata;
using android::status_t;
@@ -176,6 +181,16 @@
namespace android {
+static bool checkPermission(const char* permissionString) {
+#ifndef HAVE_ANDROID_OS
+ return true;
+#endif
+ if (getpid() == IPCThreadState::self()->getCallingPid()) return true;
+ bool ok = checkCallingPermission(String16(permissionString));
+ if (!ok) LOGE("Request requires %s", permissionString);
+ return ok;
+}
+
// TODO: Temp hack until we can register players
typedef struct {
const char *extension;
@@ -245,31 +260,8 @@
return retriever;
}
-sp<IMediaPlayer> MediaPlayerService::create(
- pid_t pid, const sp<IMediaPlayerClient>& client, const char* url,
- const KeyedVector<String8, String8> *headers, int audioSessionId)
-{
- int32_t connId = android_atomic_inc(&mNextConnId);
-
- sp<Client> c = new Client(
- this, pid, connId, client, audioSessionId,
- IPCThreadState::self()->getCallingUid());
-
- LOGV("Create new client(%d) from pid %d, uid %d, url=%s, connId=%d, audioSessionId=%d",
- connId, pid, IPCThreadState::self()->getCallingUid(), url, connId, audioSessionId);
- if (NO_ERROR != c->setDataSource(url, headers))
- {
- c.clear();
- return c;
- }
- wp<Client> w = c;
- Mutex::Autolock lock(mLock);
- mClients.add(w);
- return c;
-}
-
sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client,
- int fd, int64_t offset, int64_t length, int audioSessionId)
+ int audioSessionId)
{
int32_t connId = android_atomic_inc(&mNextConnId);
@@ -277,40 +269,14 @@
this, pid, connId, client, audioSessionId,
IPCThreadState::self()->getCallingUid());
- LOGV("Create new client(%d) from pid %d, uid %d, fd=%d, offset=%lld, "
- "length=%lld, audioSessionId=%d", connId, pid,
- IPCThreadState::self()->getCallingUid(), fd, offset, length, audioSessionId);
- if (NO_ERROR != c->setDataSource(fd, offset, length)) {
- c.clear();
- } else {
- wp<Client> w = c;
+ LOGV("Create new client(%d) from pid %d, uid %d, ", connId, pid,
+ IPCThreadState::self()->getCallingUid());
+
+ wp<Client> w = c;
+ {
Mutex::Autolock lock(mLock);
mClients.add(w);
}
- ::close(fd);
- return c;
-}
-
-sp<IMediaPlayer> MediaPlayerService::create(
- pid_t pid, const sp<IMediaPlayerClient> &client,
- const sp<IStreamSource> &source, int audioSessionId) {
- int32_t connId = android_atomic_inc(&mNextConnId);
-
- sp<Client> c = new Client(
- this, pid, connId, client, audioSessionId,
- IPCThreadState::self()->getCallingUid());
-
- LOGV("Create new client(%d) from pid %d, audioSessionId=%d",
- connId, pid, audioSessionId);
-
- if (OK != c->setDataSource(source)) {
- c.clear();
- } else {
- wp<Client> w = c;
- Mutex::Autolock lock(mLock);
- mClients.add(w);
- }
-
return c;
}
@@ -622,6 +588,14 @@
}
}
+ if (!strncasecmp("aahRX://", url, 8)) {
+ return AAH_RX_PLAYER;
+ }
+
+ if (!strncasecmp("aahTX://", url, 8)) {
+ return AAH_TX_PLAYER;
+ }
+
// use MidiFile for MIDI extensions
int lenURL = strlen(url);
for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
@@ -658,6 +632,14 @@
LOGV("Create Test Player stub");
p = new TestPlayerStub();
break;
+ case AAH_RX_PLAYER:
+ LOGV(" create A@H RX Player");
+ p = createAAH_RXPlayer();
+ break;
+ case AAH_TX_PLAYER:
+ LOGV(" create A@H TX Player");
+ p = createAAH_TXPlayer();
+ break;
default:
LOGE("Unknown player type: %d", playerType);
return NULL;
@@ -701,6 +683,14 @@
if (url == NULL)
return UNKNOWN_ERROR;
+ if ((strncmp(url, "http://", 7) == 0) ||
+ (strncmp(url, "https://", 8) == 0) ||
+ (strncmp(url, "rtsp://", 7) == 0)) {
+ if (!checkPermission("android.permission.INTERNET")) {
+ return PERMISSION_DENIED;
+ }
+ }
+
if (strncmp(url, "content://", 10) == 0) {
// get a filedescriptor for the content Uri and
// pass it to the setDataSource(fd) method
@@ -781,6 +771,7 @@
// now set data source
mStatus = p->setDataSource(fd, offset, length);
if (mStatus == NO_ERROR) mPlayer = p;
+
return mStatus;
}
@@ -1005,9 +996,21 @@
status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume)
{
LOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume);
- // TODO: for hardware output, call player instead
- Mutex::Autolock l(mLock);
- if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+
+ // for hardware output, call player instead
+ sp<MediaPlayerBase> p = getPlayer();
+ {
+ Mutex::Autolock l(mLock);
+ if (p != 0 && p->hardwareOutput()) {
+ MediaPlayerHWInterface* hwp =
+ reinterpret_cast<MediaPlayerHWInterface*>(p.get());
+ return hwp->setVolume(leftVolume, rightVolume);
+ } else {
+ if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+ return NO_ERROR;
+ }
+ }
+
return NO_ERROR;
}
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index e32b92a..53e625a 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -188,16 +188,7 @@
void removeMediaRecorderClient(wp<MediaRecorderClient> client);
virtual sp<IMediaMetadataRetriever> createMetadataRetriever(pid_t pid);
- // House keeping for media player clients
- virtual sp<IMediaPlayer> create(
- pid_t pid, const sp<IMediaPlayerClient>& client, const char* url,
- const KeyedVector<String8, String8> *headers, int audioSessionId);
-
- virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length, int audioSessionId);
-
- virtual sp<IMediaPlayer> create(
- pid_t pid, const sp<IMediaPlayerClient> &client,
- const sp<IStreamSource> &source, int audioSessionId);
+ virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int audioSessionId);
virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);
virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);
@@ -284,13 +275,13 @@
sp<MediaPlayerBase> createPlayer(player_type playerType);
- status_t setDataSource(
+ virtual status_t setDataSource(
const char *url,
const KeyedVector<String8, String8> *headers);
- status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
- status_t setDataSource(const sp<IStreamSource> &source);
+ virtual status_t setDataSource(const sp<IStreamSource> &source);
static void notify(void* cookie, int msg,
int ext1, int ext2, const Parcel *obj);
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index 0251baf..605d056 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -124,7 +124,14 @@
: ATSParser::DISCONTINUITY_FORMATCHANGE,
extra);
} else {
- mTSParser->feedTSPacket(buffer, sizeof(buffer));
+ status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer));
+
+ if (err != OK) {
+ LOGE("TS Parser returned error %d", err);
+ mTSParser->signalEOS(err);
+ mEOS = true;
+ break;
+ }
}
mOffset += n;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 1f08a91..ee77f47 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -663,6 +663,19 @@
sp<AMessage> reply;
CHECK(msg->findMessage("reply", &reply));
+ if (IsFlushingState(audio ? mFlushingAudio : mFlushingVideo)) {
+ // We're currently attempting to flush the decoder, in order
+ // to complete this, the decoder wants all its buffers back,
+ // so we don't want any output buffers it sent us (from before
+ // we initiated the flush) to be stuck in the renderer's queue.
+
+ LOGV("we're still flushing the %s decoder, sending its output buffer"
+ " right back.", audio ? "audio" : "video");
+
+ reply->post();
+ return;
+ }
+
sp<RefBase> obj;
CHECK(msg->findObject("buffer", &obj));
diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
index a6a3a18..a741987 100644
--- a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
@@ -42,7 +42,7 @@
void NuPlayer::StreamingSource::start() {
mStreamListener = new NuPlayerStreamListener(mSource, 0);
- mTSParser = new ATSParser;
+ mTSParser = new ATSParser(ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE);
mStreamListener->start();
}
@@ -86,7 +86,15 @@
: ATSParser::DISCONTINUITY_FORMATCHANGE,
extra);
} else {
- mTSParser->feedTSPacket(buffer, sizeof(buffer));
+ status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer));
+
+ if (err != OK) {
+ LOGE("TS Parser returned error %d", err);
+
+ mTSParser->signalEOS(err);
+ mEOS = true;
+ break;
+ }
}
}
}
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 142dda0..47224cc 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -513,7 +513,8 @@
// If we did this later, audio would continue playing while we
// shutdown the video-related resources and the player appear to
// not be as responsive to a reset request.
- if (mAudioPlayer == NULL && mAudioSource != NULL) {
+ if ((mAudioPlayer == NULL || !(mFlags & AUDIOPLAYER_STARTED))
+ && mAudioSource != NULL) {
// If we had an audio player, it would have effectively
// taken possession of the audio source and stopped it when
// _it_ is stopped. Otherwise this is still our responsibility.
@@ -977,7 +978,7 @@
CHECK(!mAudioPlayer->isSeeking());
// We will have finished the seek while starting the audio player.
- postAudioSeekComplete_l();
+ postAudioSeekComplete();
}
} else {
mAudioPlayer->resume();
@@ -1876,7 +1877,8 @@
mQueue.postEventWithDelay(mVideoLagEvent, 1000000ll);
}
-void AwesomePlayer::postCheckAudioStatusEvent_l(int64_t delayUs) {
+void AwesomePlayer::postCheckAudioStatusEvent(int64_t delayUs) {
+ Mutex::Autolock autoLock(mAudioLock);
if (mAudioStatusEventPending) {
return;
}
@@ -1885,14 +1887,18 @@
}
void AwesomePlayer::onCheckAudioStatus() {
- Mutex::Autolock autoLock(mLock);
- if (!mAudioStatusEventPending) {
- // Event was dispatched and while we were blocking on the mutex,
- // has already been cancelled.
- return;
+ {
+ Mutex::Autolock autoLock(mAudioLock);
+ if (!mAudioStatusEventPending) {
+ // Event was dispatched and while we were blocking on the mutex,
+ // has already been cancelled.
+ return;
+ }
+
+ mAudioStatusEventPending = false;
}
- mAudioStatusEventPending = false;
+ Mutex::Autolock autoLock(mLock);
if (mWatchForAudioSeekComplete && !mAudioPlayer->isSeeking()) {
mWatchForAudioSeekComplete = false;
@@ -2238,17 +2244,11 @@
}
void AwesomePlayer::postAudioEOS(int64_t delayUs) {
- Mutex::Autolock autoLock(mLock);
- postCheckAudioStatusEvent_l(delayUs);
+ postCheckAudioStatusEvent(delayUs);
}
void AwesomePlayer::postAudioSeekComplete() {
- Mutex::Autolock autoLock(mLock);
- postAudioSeekComplete_l();
-}
-
-void AwesomePlayer::postAudioSeekComplete_l() {
- postCheckAudioStatusEvent_l(0 /* delayUs */);
+ postCheckAudioStatusEvent(0);
}
status_t AwesomePlayer::setParameter(int key, const Parcel &request) {
diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp
index f2f3500..0794f57 100644
--- a/media/libstagefright/FileSource.cpp
+++ b/media/libstagefright/FileSource.cpp
@@ -110,6 +110,8 @@
}
status_t FileSource::getSize(off64_t *size) {
+ Mutex::Autolock autoLock(mLock);
+
if (mFd < 0) {
return NO_INIT;
}
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 7f09319..d5b013d 100755
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -1990,7 +1990,7 @@
CHECK(mIsEncoder);
if (mDecodingTimeList.empty()) {
- CHECK(mNoMoreOutputData);
+ CHECK(mSignalledEOS || mNoMoreOutputData);
// No corresponding input frame available.
// This could happen when EOS is reached.
return 0;
diff --git a/media/libstagefright/SurfaceMediaSource.cpp b/media/libstagefright/SurfaceMediaSource.cpp
index 91b81c2..50dd804 100644
--- a/media/libstagefright/SurfaceMediaSource.cpp
+++ b/media/libstagefright/SurfaceMediaSource.cpp
@@ -46,9 +46,10 @@
mSynchronousMode(true),
mConnectedApi(NO_CONNECTED_API),
mFrameRate(30),
+ mStopped(false),
mNumFramesReceived(0),
mNumFramesEncoded(0),
- mStopped(false) {
+ mFirstFrameTimestamp(0) {
LOGV("SurfaceMediaSource::SurfaceMediaSource");
sp<ISurfaceComposer> composer(ComposerService::getComposerService());
mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
@@ -471,10 +472,25 @@
return -EINVAL;
}
+ if (mNumFramesReceived == 0) {
+ mFirstFrameTimestamp = timestamp;
+ // Initial delay
+ if (mStartTimeNs > 0) {
+ if (timestamp < mStartTimeNs) {
+ // This frame predates start of record, discard
+ mSlots[bufIndex].mBufferState = BufferSlot::FREE;
+ mDequeueCondition.signal();
+ return OK;
+ }
+ mStartTimeNs = timestamp - mStartTimeNs;
+ }
+ }
+ timestamp = mStartTimeNs + (timestamp - mFirstFrameTimestamp);
+
+ mNumFramesReceived++;
if (mSynchronousMode) {
// in synchronous mode we queue all buffers in a FIFO
mQueue.push_back(bufIndex);
- mNumFramesReceived++;
LOGV("Client queued buf# %d @slot: %d, Q size = %d, handle = %p, timestamp = %lld",
mNumFramesReceived, bufIndex, mQueue.size(),
mSlots[bufIndex].mGraphicBuffer->handle, timestamp);
@@ -684,6 +700,13 @@
status_t SurfaceMediaSource::start(MetaData *params)
{
LOGV("started!");
+
+ mStartTimeNs = 0;
+ int64_t startTimeUs;
+ if (params && params->findInt64(kKeyTime, &startTimeUs)) {
+ mStartTimeNs = startTimeUs * 1000;
+ }
+
return OK;
}
@@ -753,6 +776,7 @@
mCurrentBuf = mSlots[mCurrentSlot].mGraphicBuffer;
int64_t prevTimeStamp = mCurrentTimestamp;
mCurrentTimestamp = mSlots[mCurrentSlot].mTimestamp;
+
mNumFramesEncoded++;
// Pass the data to the MediaBuffer. Pass in only the metadata
passMetadataBufferLocked(buffer);
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 24cf77c..8e73121 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -148,6 +148,7 @@
mutable Mutex mLock;
Mutex mMiscStateLock;
mutable Mutex mStatsLock;
+ Mutex mAudioLock;
OMXClient mClient;
TimedEventQueue mQueue;
@@ -223,7 +224,7 @@
void postVideoEvent_l(int64_t delayUs = -1);
void postBufferingEvent_l();
void postStreamDoneEvent_l(status_t status);
- void postCheckAudioStatusEvent_l(int64_t delayUs);
+ void postCheckAudioStatusEvent(int64_t delayUs);
void postVideoLagEvent_l();
status_t play_l();
@@ -295,7 +296,6 @@
void ensureCacheIsFetching_l();
status_t startAudioPlayer_l(bool sendErrorNotification = true);
- void postAudioSeekComplete_l();
void shutdownVideoDecoder_l();
status_t setNativeWindow_l(const sp<ANativeWindow> &native);
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 5bbc2b4..e13464e 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -48,7 +48,7 @@
bool parsePID(
unsigned pid, unsigned payload_unit_start_indicator,
- ABitReader *br);
+ ABitReader *br, status_t *err);
void signalDiscontinuity(
DiscontinuityType type, const sp<AMessage> &extra);
@@ -77,7 +77,7 @@
bool mFirstPTSValid;
uint64_t mFirstPTS;
- void parseProgramMap(ABitReader *br);
+ status_t parseProgramMap(ABitReader *br);
DISALLOW_EVIL_CONSTRUCTORS(Program);
};
@@ -89,7 +89,7 @@
unsigned pid() const { return mElementaryPID; }
void setPID(unsigned pid) { mElementaryPID = pid; }
- void parse(
+ status_t parse(
unsigned payload_unit_start_indicator,
ABitReader *br);
@@ -111,13 +111,11 @@
sp<ABuffer> mBuffer;
sp<AnotherPacketSource> mSource;
bool mPayloadStarted;
- DiscontinuityType mPendingDiscontinuity;
- sp<AMessage> mPendingDiscontinuityExtra;
ElementaryStreamQueue *mQueue;
- void flush();
- void parsePES(ABitReader *br);
+ status_t flush();
+ status_t parsePES(ABitReader *br);
void onPayloadData(
unsigned PTS_DTS_flags, uint64_t PTS, uint64_t DTS,
@@ -125,9 +123,6 @@
void extractAACFrames(const sp<ABuffer> &buffer);
- void deferDiscontinuity(
- DiscontinuityType type, const sp<AMessage> &extra);
-
DISALLOW_EVIL_CONSTRUCTORS(Stream);
};
@@ -145,14 +140,17 @@
bool ATSParser::Program::parsePID(
unsigned pid, unsigned payload_unit_start_indicator,
- ABitReader *br) {
+ ABitReader *br, status_t *err) {
+ *err = OK;
+
if (pid == mProgramMapPID) {
if (payload_unit_start_indicator) {
unsigned skip = br->getBits(8);
br->skipBits(skip * 8);
}
- parseProgramMap(br);
+ *err = parseProgramMap(br);
+
return true;
}
@@ -161,7 +159,7 @@
return false;
}
- mStreams.editValueAt(index)->parse(
+ *err = mStreams.editValueAt(index)->parse(
payload_unit_start_indicator, br);
return true;
@@ -185,7 +183,7 @@
unsigned mPID;
};
-void ATSParser::Program::parseProgramMap(ABitReader *br) {
+status_t ATSParser::Program::parseProgramMap(ABitReader *br) {
unsigned table_id = br->getBits(8);
LOGV(" table_id = %u", table_id);
CHECK_EQ(table_id, 0x02u);
@@ -288,7 +286,60 @@
}
if (PIDsChanged) {
- mStreams.clear();
+#if 0
+ LOGI("before:");
+ for (size_t i = 0; i < mStreams.size(); ++i) {
+ sp<Stream> stream = mStreams.editValueAt(i);
+
+ LOGI("PID 0x%08x => type 0x%02x", stream->pid(), stream->type());
+ }
+
+ LOGI("after:");
+ for (size_t i = 0; i < infos.size(); ++i) {
+ StreamInfo &info = infos.editItemAt(i);
+
+ LOGI("PID 0x%08x => type 0x%02x", info.mPID, info.mType);
+ }
+#endif
+
+ // The only case we can recover from is if we have two streams
+ // and they switched PIDs.
+
+ bool success = false;
+
+ if (mStreams.size() == 2 && infos.size() == 2) {
+ const StreamInfo &info1 = infos.itemAt(0);
+ const StreamInfo &info2 = infos.itemAt(1);
+
+ sp<Stream> s1 = mStreams.editValueAt(0);
+ sp<Stream> s2 = mStreams.editValueAt(1);
+
+ bool caseA =
+ info1.mPID == s1->pid() && info1.mType == s2->type()
+ && info2.mPID == s2->pid() && info2.mType == s1->type();
+
+ bool caseB =
+ info1.mPID == s2->pid() && info1.mType == s1->type()
+ && info2.mPID == s1->pid() && info2.mType == s2->type();
+
+ if (caseA || caseB) {
+ unsigned pid1 = s1->pid();
+ unsigned pid2 = s2->pid();
+ s1->setPID(pid2);
+ s2->setPID(pid1);
+
+ mStreams.clear();
+ mStreams.add(s1->pid(), s1);
+ mStreams.add(s2->pid(), s2);
+
+ success = true;
+ }
+ }
+
+ if (!success) {
+ LOGI("Stream PIDs changed and we cannot recover.");
+ return ERROR_MALFORMED;
+ }
}
for (size_t i = 0; i < infos.size(); ++i) {
@@ -299,13 +350,10 @@
if (index < 0) {
sp<Stream> stream = new Stream(this, info.mPID, info.mType);
mStreams.add(info.mPID, stream);
-
- if (PIDsChanged) {
- sp<AMessage> extra;
- stream->signalDiscontinuity(DISCONTINUITY_FORMATCHANGE, extra);
- }
}
}
+
+ return OK;
}
sp<MediaSource> ATSParser::Program::getSource(SourceType type) {
@@ -325,14 +373,16 @@
}
int64_t ATSParser::Program::convertPTSToTimestamp(uint64_t PTS) {
- if (!mFirstPTSValid) {
- mFirstPTSValid = true;
- mFirstPTS = PTS;
- PTS = 0;
- } else if (PTS < mFirstPTS) {
- PTS = 0;
- } else {
- PTS -= mFirstPTS;
+ if (!(mParser->mFlags & TS_TIMESTAMPS_ARE_ABSOLUTE)) {
+ if (!mFirstPTSValid) {
+ mFirstPTSValid = true;
+ mFirstPTS = PTS;
+ PTS = 0;
+ } else if (PTS < mFirstPTS) {
+ PTS = 0;
+ } else {
+ PTS -= mFirstPTS;
+ }
}
return (PTS * 100) / 9;
@@ -345,12 +395,8 @@
: mProgram(program),
mElementaryPID(elementaryPID),
mStreamType(streamType),
- mBuffer(new ABuffer(192 * 1024)),
mPayloadStarted(false),
- mPendingDiscontinuity(DISCONTINUITY_NONE),
mQueue(NULL) {
- mBuffer->setRange(0, 0);
-
switch (mStreamType) {
case STREAMTYPE_H264:
mQueue = new ElementaryStreamQueue(ElementaryStreamQueue::H264);
@@ -380,6 +426,11 @@
}
LOGV("new stream PID 0x%02x, type 0x%02x", elementaryPID, streamType);
+
+ if (mQueue != NULL) {
+ mBuffer = new ABuffer(192 * 1024);
+ mBuffer->setRange(0, 0);
+ }
}
ATSParser::Stream::~Stream() {
@@ -387,22 +438,30 @@
mQueue = NULL;
}
-void ATSParser::Stream::parse(
+status_t ATSParser::Stream::parse(
unsigned payload_unit_start_indicator, ABitReader *br) {
+ if (mQueue == NULL) {
+ return OK;
+ }
+
if (payload_unit_start_indicator) {
if (mPayloadStarted) {
// Otherwise we run the danger of receiving the trailing bytes
// of a PES packet that we never saw the start of and assuming
// we have a a complete PES packet.
- flush();
+ status_t err = flush();
+
+ if (err != OK) {
+ return err;
+ }
}
mPayloadStarted = true;
}
if (!mPayloadStarted) {
- return;
+ return OK;
}
size_t payloadSizeBits = br->numBitsLeft();
@@ -423,10 +482,16 @@
memcpy(mBuffer->data() + mBuffer->size(), br->data(), payloadSizeBits / 8);
mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);
+
+ return OK;
}
void ATSParser::Stream::signalDiscontinuity(
DiscontinuityType type, const sp<AMessage> &extra) {
+ if (mQueue == NULL) {
+ return;
+ }
+
mPayloadStarted = false;
mBuffer->setRange(0, 0);
@@ -451,8 +516,6 @@
if (mSource != NULL) {
mSource->queueDiscontinuity(type, extra);
- } else {
- deferDiscontinuity(type, extra);
}
break;
}
@@ -463,22 +526,13 @@
}
}
-void ATSParser::Stream::deferDiscontinuity(
- DiscontinuityType type, const sp<AMessage> &extra) {
- if (type > mPendingDiscontinuity) {
- // Only upgrade discontinuities.
- mPendingDiscontinuity = type;
- mPendingDiscontinuityExtra = extra;
- }
-}
-
void ATSParser::Stream::signalEOS(status_t finalResult) {
if (mSource != NULL) {
mSource->signalEOS(finalResult);
}
}
-void ATSParser::Stream::parsePES(ABitReader *br) {
+status_t ATSParser::Stream::parsePES(ABitReader *br) {
unsigned packet_startcode_prefix = br->getBits(24);
LOGV("packet_startcode_prefix = 0x%08x", packet_startcode_prefix);
@@ -486,7 +540,8 @@
if (packet_startcode_prefix != 1) {
LOGV("Supposedly payload_unit_start=1 unit does not start "
"with startcode.");
- return;
+
+ return ERROR_MALFORMED;
}
CHECK_EQ(packet_startcode_prefix, 0x000001u);
@@ -613,6 +668,14 @@
unsigned dataLength =
PES_packet_length - 3 - PES_header_data_length;
+ if (br->numBitsLeft() < dataLength * 8) {
+ LOGE("PES packet does not carry enough data to contain "
+ "payload. (numBitsLeft = %d, required = %d)",
+ br->numBitsLeft(), dataLength * 8);
+
+ return ERROR_MALFORMED;
+ }
+
CHECK_GE(br->numBitsLeft(), dataLength * 8);
onPayloadData(
@@ -636,19 +699,24 @@
CHECK_NE(PES_packet_length, 0u);
br->skipBits(PES_packet_length * 8);
}
+
+ return OK;
}
-void ATSParser::Stream::flush() {
+status_t ATSParser::Stream::flush() {
if (mBuffer->size() == 0) {
- return;
+ return OK;
}
LOGV("flushing stream 0x%04x size = %d", mElementaryPID, mBuffer->size());
ABitReader br(mBuffer->data(), mBuffer->size());
- parsePES(&br);
+
+ status_t err = parsePES(&br);
mBuffer->setRange(0, 0);
+
+ return err;
}
void ATSParser::Stream::onPayloadData(
@@ -656,10 +724,6 @@
const uint8_t *data, size_t size) {
LOGV("onPayloadData mStreamType=0x%02x", mStreamType);
- if (mQueue == NULL) {
- return;
- }
-
CHECK(PTS_DTS_flags == 2 || PTS_DTS_flags == 3);
int64_t timeUs = mProgram->convertPTSToTimestamp(PTS);
@@ -679,14 +743,6 @@
mElementaryPID, mStreamType);
mSource = new AnotherPacketSource(meta);
-
- if (mPendingDiscontinuity != DISCONTINUITY_NONE) {
- mSource->queueDiscontinuity(
- mPendingDiscontinuity, mPendingDiscontinuityExtra);
- mPendingDiscontinuity = DISCONTINUITY_NONE;
- mPendingDiscontinuityExtra.clear();
- }
-
mSource->queueAccessUnit(accessUnit);
}
} else if (mQueue->getFormat() != NULL) {
@@ -734,17 +790,18 @@
////////////////////////////////////////////////////////////////////////////////
-ATSParser::ATSParser() {
+ATSParser::ATSParser(uint32_t flags)
+ : mFlags(flags) {
}
ATSParser::~ATSParser() {
}
-void ATSParser::feedTSPacket(const void *data, size_t size) {
+status_t ATSParser::feedTSPacket(const void *data, size_t size) {
CHECK_EQ(size, kTSPacketSize);
ABitReader br((const uint8_t *)data, kTSPacketSize);
- parseTS(&br);
+ return parseTS(&br);
}
void ATSParser::signalDiscontinuity(
@@ -822,7 +879,7 @@
MY_LOGV(" CRC = 0x%08x", br->getBits(32));
}
-void ATSParser::parsePID(
+status_t ATSParser::parsePID(
ABitReader *br, unsigned PID,
unsigned payload_unit_start_indicator) {
if (PID == 0) {
@@ -831,13 +888,18 @@
br->skipBits(skip * 8);
}
parseProgramAssociationTable(br);
- return;
+ return OK;
}
bool handled = false;
for (size_t i = 0; i < mPrograms.size(); ++i) {
+ status_t err;
if (mPrograms.editItemAt(i)->parsePID(
- PID, payload_unit_start_indicator, br)) {
+ PID, payload_unit_start_indicator, br, &err)) {
+ if (err != OK) {
+ return err;
+ }
+
handled = true;
break;
}
@@ -846,6 +908,8 @@
if (!handled) {
LOGV("PID 0x%04x not handled.", PID);
}
+
+ return OK;
}
void ATSParser::parseAdaptationField(ABitReader *br) {
@@ -855,7 +919,7 @@
}
}
-void ATSParser::parseTS(ABitReader *br) {
+status_t ATSParser::parseTS(ABitReader *br) {
LOGV("---");
unsigned sync_byte = br->getBits(8);
@@ -886,8 +950,10 @@
}
if (adaptation_field_control == 1 || adaptation_field_control == 3) {
- parsePID(br, PID, payload_unit_start_indicator);
+ return parsePID(br, PID, payload_unit_start_indicator);
}
+
+ return OK;
}
sp<MediaSource> ATSParser::getSource(SourceType type) {
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 1e6451d..388cb54 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -38,9 +38,18 @@
DISCONTINUITY_FORMATCHANGE
};
- ATSParser();
+ enum Flags {
+ // The 90kHz clock (PTS/DTS) is absolute, i.e. PTS=0 corresponds to
+ // a media time of 0.
+ // If this flag is _not_ specified, the first PTS encountered in a
+ // program of this stream will be assumed to correspond to media time 0
+ // instead.
+ TS_TIMESTAMPS_ARE_ABSOLUTE = 1
+ };
- void feedTSPacket(const void *data, size_t size);
+ ATSParser(uint32_t flags = 0);
+
+ status_t feedTSPacket(const void *data, size_t size);
void signalDiscontinuity(
DiscontinuityType type, const sp<AMessage> &extra);
@@ -73,18 +82,19 @@
struct Program;
struct Stream;
+ uint32_t mFlags;
Vector<sp<Program> > mPrograms;
void parseProgramAssociationTable(ABitReader *br);
void parseProgramMap(ABitReader *br);
void parsePES(ABitReader *br);
- void parsePID(
+ status_t parsePID(
ABitReader *br, unsigned PID,
unsigned payload_unit_start_indicator);
void parseAdaptationField(ABitReader *br);
- void parseTS(ABitReader *br);
+ status_t parseTS(ABitReader *br);
DISALLOW_EVIL_CONSTRUCTORS(ATSParser);
};
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index 8250ad1..17cf45a 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -210,13 +210,10 @@
if (n < (ssize_t)kTSPacketSize) {
return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;
- } else {
- mParser->feedTSPacket(packet, kTSPacketSize);
}
mOffset += n;
-
- return OK;
+ return mParser->feedTSPacket(packet, kTSPacketSize);
}
void MPEG2TSExtractor::setLiveSession(const sp<LiveSession> &liveSession) {
diff --git a/media/libstagefright/tests/SurfaceMediaSource_test.cpp b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
index d643a0b..d663602 100644
--- a/media/libstagefright/tests/SurfaceMediaSource_test.cpp
+++ b/media/libstagefright/tests/SurfaceMediaSource_test.cpp
@@ -106,13 +106,14 @@
mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
window.get(), NULL);
} else {
- EGLint pbufferAttribs[] = {
- EGL_WIDTH, getSurfaceWidth(),
- EGL_HEIGHT, getSurfaceHeight(),
- EGL_NONE };
+ LOGV("No actual display. Choosing EGLSurface based on SurfaceMediaSource");
+ sp<SurfaceMediaSource> sms = new SurfaceMediaSource(
+ getSurfaceWidth(), getSurfaceHeight());
+ sp<SurfaceTextureClient> stc = new SurfaceTextureClient(sms);
+ sp<ANativeWindow> window = stc;
- mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig,
- pbufferAttribs);
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ window.get(), NULL);
}
ASSERT_EQ(EGL_SUCCESS, eglGetError());
ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
@@ -408,7 +409,6 @@
mSTC.clear();
mANW.clear();
GLTest::TearDown();
- eglDestroySurface(mEglDisplay, mSmsEglSurface);
}
void setUpEGLSurfaceFromMediaRecorder(sp<MediaRecorder>& mr);
@@ -419,8 +419,6 @@
sp<SurfaceMediaSource> mSMS;
sp<SurfaceTextureClient> mSTC;
sp<ANativeWindow> mANW;
- EGLConfig mSMSGlConfig;
- EGLSurface mSmsEglSurface;
};
/////////////////////////////////////////////////////////////////////
@@ -462,7 +460,7 @@
glClear(GL_COLOR_BUFFER_BIT);
// The following call dequeues and queues the buffer
- eglSwapBuffers(mEglDisplay, mSmsEglSurface);
+ eglSwapBuffers(mEglDisplay, mEglSurface);
glDisable(GL_SCISSOR_TEST);
}
@@ -488,19 +486,12 @@
mSTC = new SurfaceTextureClient(iST);
mANW = mSTC;
- EGLint numConfigs = 0;
- EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mSMSGlConfig,
- 1, &numConfigs));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- LOGV("Native Window = %p, mSTC = %p", mANW.get(), mSTC.get());
-
- mSmsEglSurface = eglCreateWindowSurface(mEglDisplay, mSMSGlConfig,
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
mANW.get(), NULL);
ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_SURFACE, mSmsEglSurface) ;
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface) ;
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mSmsEglSurface, mSmsEglSurface,
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
mEglContext));
ASSERT_EQ(EGL_SUCCESS, eglGetError());
}
@@ -778,9 +769,9 @@
// Test to examine whether we can choose the Recordable Android GLConfig
// DummyRecorder used- no real encoding here
-TEST_F(SurfaceMediaSourceGLTest, ChooseAndroidRecordableEGLConfigDummyWrite) {
+TEST_F(SurfaceMediaSourceGLTest, ChooseAndroidRecordableEGLConfigDummyWriter) {
LOGV("Test # %d", testId++);
- LOGV("Test to verify creating a surface w/ right config *********");
+ LOGV("Verify creating a surface w/ right config + dummy writer*********");
mSMS = new SurfaceMediaSource(mYuvTexWidth, mYuvTexHeight);
mSTC = new SurfaceTextureClient(mSMS);
@@ -789,17 +780,12 @@
DummyRecorder writer(mSMS);
writer.start();
- EGLint numConfigs = 0;
- EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mSMSGlConfig,
- 1, &numConfigs));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- mSmsEglSurface = eglCreateWindowSurface(mEglDisplay, mSMSGlConfig,
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
mANW.get(), NULL);
ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_SURFACE, mSmsEglSurface) ;
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface) ;
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mSmsEglSurface, mSmsEglSurface,
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
mEglContext));
ASSERT_EQ(EGL_SUCCESS, eglGetError());
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index ddad2d3..ca62908 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -95,12 +95,12 @@
if (fgets(cmdline, sizeof(cmdline) - 1, file))
{
if (!strcmp(value, cmdline))
- sEGLTraceLevel = 1;
+ gEGLDebugLevel = 1;
}
fclose(file);
}
- if (sEGLTraceLevel > 0)
+ if (gEGLDebugLevel > 0)
{
property_get("debug.egl.debug_port", value, "5039");
const unsigned short port = (unsigned short)atoi(value);
@@ -117,7 +117,7 @@
if (sEGLTraceLevel > 0) {
setGlTraceThreadSpecific(value);
setGlThreadSpecific(&gHooksTrace);
- } else if (sEGLTraceLevel > 0 && value != &gHooksNoContext) {
+ } else if (gEGLDebugLevel > 0 && value != &gHooksNoContext) {
setGlTraceThreadSpecific(value);
setGlThreadSpecific(&gHooksDebug);
} else {
diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
index ff9be3c..9e77665 100644
--- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
+++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
@@ -88,7 +88,7 @@
msg.set_arg1(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
Send(msg, cmd);
- *(DbgContext **)pthread_getspecific(dbgEGLThreadLocalStorageKey) = dbg;
+ pthread_setspecific(dbgEGLThreadLocalStorageKey, dbg);
return dbg;
}
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_bg_protect_tile.png b/packages/SystemUI/res/drawable-hdpi/recents_bg_protect_tile.png
deleted file mode 100644
index 87c7be6..0000000
--- a/packages/SystemUI/res/drawable-hdpi/recents_bg_protect_tile.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-hdpi/recents_blue_glow.9.png
index 4f4ae78..4362836 100644
--- a/packages/SystemUI/res/drawable-hdpi/recents_blue_glow.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/recents_blue_glow.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_callout_line.9.png b/packages/SystemUI/res/drawable-hdpi/recents_callout_line.9.png
new file mode 100644
index 0000000..335d5a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/recents_callout_line.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_callout_line.png b/packages/SystemUI/res/drawable-hdpi/recents_callout_line.png
deleted file mode 100644
index 5f4c035..0000000
--- a/packages/SystemUI/res/drawable-hdpi/recents_callout_line.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg.9.png b/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg.9.png
new file mode 100644
index 0000000..1ad16f7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg.png b/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg.png
deleted file mode 100644
index 23aabce..0000000
--- a/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg_press.9.png b/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg_press.9.png
new file mode 100644
index 0000000..6e806ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg_press.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg_press.png b/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg_press.png
deleted file mode 100644
index 0b0765b..0000000
--- a/packages/SystemUI/res/drawable-hdpi/recents_thumbnail_bg_press.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-hdpi/app_icon.png b/packages/SystemUI/res/drawable-large-hdpi/app_icon.png
deleted file mode 100644
index 52354bd..0000000
--- a/packages/SystemUI/res/drawable-large-hdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-hdpi/recents_bg_protect_tile.png b/packages/SystemUI/res/drawable-large-hdpi/recents_bg_protect_tile.png
deleted file mode 100644
index ce01276..0000000
--- a/packages/SystemUI/res/drawable-large-hdpi/recents_bg_protect_tile.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-hdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-large-hdpi/recents_blue_glow.9.png
deleted file mode 100644
index 1848fcd..0000000
--- a/packages/SystemUI/res/drawable-large-hdpi/recents_blue_glow.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-hdpi/recents_callout_line.png b/packages/SystemUI/res/drawable-large-hdpi/recents_callout_line.png
deleted file mode 100644
index 61a3f87..0000000
--- a/packages/SystemUI/res/drawable-large-hdpi/recents_callout_line.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-hdpi/recents_thumbnail_bg.png b/packages/SystemUI/res/drawable-large-hdpi/recents_thumbnail_bg.png
deleted file mode 100644
index b6aca49..0000000
--- a/packages/SystemUI/res/drawable-large-hdpi/recents_thumbnail_bg.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-hdpi/recents_thumbnail_bg_press.png b/packages/SystemUI/res/drawable-large-hdpi/recents_thumbnail_bg_press.png
deleted file mode 100644
index 226aaac..0000000
--- a/packages/SystemUI/res/drawable-large-hdpi/recents_thumbnail_bg_press.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-mdpi/app_icon.png b/packages/SystemUI/res/drawable-large-mdpi/app_icon.png
deleted file mode 100644
index 001811f..0000000
--- a/packages/SystemUI/res/drawable-large-mdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-mdpi/recents_bg_protect_tile.png b/packages/SystemUI/res/drawable-large-mdpi/recents_bg_protect_tile.png
deleted file mode 100644
index 3d0fbf2..0000000
--- a/packages/SystemUI/res/drawable-large-mdpi/recents_bg_protect_tile.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-mdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-large-mdpi/recents_blue_glow.9.png
deleted file mode 100644
index 4362836..0000000
--- a/packages/SystemUI/res/drawable-large-mdpi/recents_blue_glow.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-mdpi/recents_callout_line.png b/packages/SystemUI/res/drawable-large-mdpi/recents_callout_line.png
deleted file mode 100644
index f4ccd7e..0000000
--- a/packages/SystemUI/res/drawable-large-mdpi/recents_callout_line.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-mdpi/recents_thumbnail_bg.png b/packages/SystemUI/res/drawable-large-mdpi/recents_thumbnail_bg.png
deleted file mode 100644
index 6392fa1..0000000
--- a/packages/SystemUI/res/drawable-large-mdpi/recents_thumbnail_bg.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-large-mdpi/recents_thumbnail_bg_press.png b/packages/SystemUI/res/drawable-large-mdpi/recents_thumbnail_bg_press.png
deleted file mode 100644
index f6ee596..0000000
--- a/packages/SystemUI/res/drawable-large-mdpi/recents_thumbnail_bg_press.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_bg_protect_tile.png b/packages/SystemUI/res/drawable-mdpi/recents_bg_protect_tile.png
deleted file mode 100644
index 87c7be6..0000000
--- a/packages/SystemUI/res/drawable-mdpi/recents_bg_protect_tile.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-mdpi/recents_blue_glow.9.png
index 4f4ae78..4362836 100644
--- a/packages/SystemUI/res/drawable-mdpi/recents_blue_glow.9.png
+++ b/packages/SystemUI/res/drawable-mdpi/recents_blue_glow.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_callout_line.9.png b/packages/SystemUI/res/drawable-mdpi/recents_callout_line.9.png
new file mode 100644
index 0000000..724a5cd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/recents_callout_line.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_callout_line.png b/packages/SystemUI/res/drawable-mdpi/recents_callout_line.png
deleted file mode 100644
index 5f4c035..0000000
--- a/packages/SystemUI/res/drawable-mdpi/recents_callout_line.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg.9.png b/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg.9.png
new file mode 100644
index 0000000..82ba091
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg.png b/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg.png
deleted file mode 100644
index 23aabce..0000000
--- a/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg_press.9.png b/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg_press.9.png
new file mode 100644
index 0000000..7376085
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg_press.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg_press.png b/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg_press.png
deleted file mode 100644
index 0b0765b..0000000
--- a/packages/SystemUI/res/drawable-mdpi/recents_thumbnail_bg_press.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/recents_blue_glow.9.png
new file mode 100644
index 0000000..4ac131a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/recents_blue_glow.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_blue_glow.9.png
index 4f4ae78..4362836 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_blue_glow.9.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_blue_glow.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_callout_line.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_callout_line.png
deleted file mode 100644
index 5f4c035..0000000
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_callout_line.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_thumbnail_bg.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_thumbnail_bg.png
deleted file mode 100644
index 87a67c9..0000000
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_thumbnail_bg.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_thumbnail_bg_press.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_thumbnail_bg_press.png
deleted file mode 100644
index a1c39e6..0000000
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/recents_thumbnail_bg_press.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/recents_bg_protect_tile.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/recents_bg_protect_tile.png
new file mode 100644
index 0000000..59908ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/recents_bg_protect_tile.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/recents_blue_glow.9.png
new file mode 100644
index 0000000..3938502
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/recents_blue_glow.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_blue_glow.9.png
new file mode 100644
index 0000000..e1e08c6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/recents_blue_glow.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_callout_line.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_callout_line.9.png
new file mode 100644
index 0000000..1bd018a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/recents_callout_line.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_thumbnail_bg.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_thumbnail_bg.9.png
new file mode 100644
index 0000000..0352aca
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/recents_thumbnail_bg.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/recents_thumbnail_bg_press.9.png b/packages/SystemUI/res/drawable-xhdpi/recents_thumbnail_bg_press.9.png
new file mode 100644
index 0000000..507ee22
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/recents_thumbnail_bg_press.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-hdpi/app_icon.png b/packages/SystemUI/res/drawable-xlarge-hdpi/app_icon.png
deleted file mode 100644
index 52354bd..0000000
--- a/packages/SystemUI/res/drawable-xlarge-hdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_bg_protect_tile.png b/packages/SystemUI/res/drawable-xlarge-hdpi/recents_bg_protect_tile.png
deleted file mode 100644
index ce01276..0000000
--- a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_bg_protect_tile.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-xlarge-hdpi/recents_blue_glow.9.png
deleted file mode 100644
index 1848fcd..0000000
--- a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_blue_glow.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_callout_line.png b/packages/SystemUI/res/drawable-xlarge-hdpi/recents_callout_line.png
deleted file mode 100644
index 846bc49..0000000
--- a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_callout_line.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_thumbnail_bg.png b/packages/SystemUI/res/drawable-xlarge-hdpi/recents_thumbnail_bg.png
deleted file mode 100644
index a983e12..0000000
--- a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_thumbnail_bg.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_thumbnail_bg_press.png b/packages/SystemUI/res/drawable-xlarge-hdpi/recents_thumbnail_bg_press.png
deleted file mode 100644
index 7c6e44e..0000000
--- a/packages/SystemUI/res/drawable-xlarge-hdpi/recents_thumbnail_bg_press.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-mdpi/app_icon.png b/packages/SystemUI/res/drawable-xlarge-mdpi/app_icon.png
deleted file mode 100644
index 001811f..0000000
--- a/packages/SystemUI/res/drawable-xlarge-mdpi/app_icon.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_bg_protect_tile.png b/packages/SystemUI/res/drawable-xlarge-mdpi/recents_bg_protect_tile.png
deleted file mode 100644
index 3d0fbf2..0000000
--- a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_bg_protect_tile.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_blue_glow.9.png b/packages/SystemUI/res/drawable-xlarge-mdpi/recents_blue_glow.9.png
deleted file mode 100644
index 4362836..0000000
--- a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_blue_glow.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_callout_line.png b/packages/SystemUI/res/drawable-xlarge-mdpi/recents_callout_line.png
deleted file mode 100644
index f4ccd7e..0000000
--- a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_callout_line.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_thumbnail_bg.png b/packages/SystemUI/res/drawable-xlarge-mdpi/recents_thumbnail_bg.png
deleted file mode 100644
index 6392fa1..0000000
--- a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_thumbnail_bg.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_thumbnail_bg_press.png b/packages/SystemUI/res/drawable-xlarge-mdpi/recents_thumbnail_bg_press.png
deleted file mode 100644
index f6ee596..0000000
--- a/packages/SystemUI/res/drawable-xlarge-mdpi/recents_thumbnail_bg_press.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable/recents_thumbnail_overlay.xml b/packages/SystemUI/res/drawable/recents_thumbnail_overlay.xml
index 200bac4..6d05095 100644
--- a/packages/SystemUI/res/drawable/recents_thumbnail_overlay.xml
+++ b/packages/SystemUI/res/drawable/recents_thumbnail_overlay.xml
@@ -15,5 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/recents_thumbnail_bg_press" android:state_pressed="true" />
+ <item android:drawable="@drawable/recents_thumbnail_bg" android:state_activated="true" />
<item android:drawable="@*android:color/transparent"/>
</selector>
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
index 16008a3..0d17b55 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
@@ -19,70 +19,73 @@
-->
<!-- android:background="@drawable/status_bar_closed_default_background" -->
-<RelativeLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="@dimen/status_bar_recents_thumbnail_view_width">
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content">
- <FrameLayout android:id="@+id/app_thumbnail"
- android:layout_width="wrap_content"
+ <RelativeLayout android:id="@+id/recent_item"
+ android:layout_gravity="bottom"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
- android:scaleType="center"
- android:clickable="true"
- android:background="@drawable/recents_thumbnail_bg"
- android:foreground="@drawable/recents_thumbnail_overlay">
- <ImageView android:id="@+id/app_thumbnail_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:paddingBottom="@*android:dimen/status_bar_height">
+
+ <FrameLayout android:id="@+id/app_thumbnail"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
+ android:scaleType="center"
+ android:background="@drawable/recents_thumbnail_overlay">
+ <ImageView android:id="@+id/app_thumbnail_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible"
+ />
+ </FrameLayout>
+
+ <ImageView android:id="@+id/app_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignLeft="@id/app_thumbnail"
+ android:layout_alignTop="@id/app_thumbnail"
+ android:layout_marginTop="@dimen/status_bar_recents_app_icon_top_margin"
+ android:layout_marginLeft="@dimen/status_bar_recents_app_icon_left_margin"
+ android:maxWidth="@dimen/status_bar_recents_app_icon_max_width"
+ android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"
+ android:adjustViewBounds="true"
android:visibility="invisible"
/>
- </FrameLayout>
- <ImageView android:id="@+id/app_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignLeft="@id/app_thumbnail"
- android:layout_alignTop="@id/app_thumbnail"
- android:layout_marginTop="@dimen/status_bar_recents_app_icon_top_margin"
- android:layout_marginLeft="@dimen/status_bar_recents_app_icon_left_margin"
- android:maxWidth="@dimen/status_bar_recents_thumbnail_max_width"
- android:maxHeight="@dimen/status_bar_recents_thumbnail_max_height"
- android:adjustViewBounds="true"
- android:visibility="invisible"
- />
+ <TextView android:id="@+id/app_label"
+ android:layout_width="@dimen/status_bar_recents_app_label_width"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/status_bar_recents_app_label_text_size"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
+ android:scrollHorizontally="true"
+ android:layout_alignLeft="@id/app_thumbnail"
+ android:layout_below="@id/app_thumbnail"
+ android:layout_marginTop="@dimen/status_bar_recents_text_description_padding"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:visibility="invisible"
+ />
- <TextView android:id="@+id/app_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="@dimen/status_bar_recents_app_label_text_size"
- android:fadingEdge="horizontal"
- android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
- android:scrollHorizontally="true"
- android:layout_alignLeft="@id/app_thumbnail"
- android:layout_below="@id/app_thumbnail"
- android:layout_marginTop="@dimen/status_bar_recents_text_description_padding"
- android:layout_marginLeft="@dimen/recents_thumbnail_bg_padding_left"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:visibility="invisible"
- />
+ <TextView android:id="@+id/app_description"
+ android:layout_width="@dimen/status_bar_recents_app_label_width"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/status_bar_recents_app_description_text_size"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
+ android:scrollHorizontally="true"
+ android:layout_alignLeft="@id/app_thumbnail"
+ android:layout_below="@id/app_label"
+ android:layout_marginTop="@dimen/status_bar_recents_text_description_padding"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ />
- <TextView android:id="@+id/app_description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="@dimen/status_bar_recents_app_description_text_size"
- android:fadingEdge="horizontal"
- android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
- android:scrollHorizontally="true"
- android:layout_alignLeft="@id/app_thumbnail"
- android:layout_below="@id/app_label"
- android:layout_marginTop="@dimen/status_bar_recents_text_description_padding"
- android:layout_marginLeft="@dimen/recents_thumbnail_bg_padding_left"
- android:singleLine="true"
- android:ellipsize="marquee"
- />
-
-</RelativeLayout>
+ </RelativeLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
index 20ef7cf..f84cc19 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
@@ -30,13 +30,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
- android:paddingBottom="@*android:dimen/status_bar_height"
android:clipToPadding="false"
android:clipChildren="false">
<LinearLayout android:id="@+id/recents_glow"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_gravity="bottom|right"
android:orientation="horizontal"
android:clipToPadding="false"
@@ -44,7 +43,7 @@
>
<com.android.systemui.recent.RecentsHorizontalScrollView android:id="@+id/recents_container"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_marginRight="@dimen/status_bar_recents_right_glow_margin"
android:divider="@null"
android:stackFromBottom="true"
@@ -58,7 +57,7 @@
<LinearLayout android:id="@+id/recents_linear_layout"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:orientation="horizontal"
android:clipToPadding="false"
android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
index c0fce71..84c89f7 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
@@ -19,79 +19,82 @@
-->
<!-- android:background="@drawable/status_bar_closed_default_background" -->
-<RelativeLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="@dimen/status_bar_recents_thumbnail_view_width">
+ android:layout_width="match_parent">
- <FrameLayout android:id="@+id/app_thumbnail"
- android:layout_width="wrap_content"
+ <RelativeLayout android:id="@+id/recent_item"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:clickable="true"
- android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
- android:scaleType="center"
- android:background="@drawable/recents_thumbnail_bg"
- android:foreground="@drawable/recents_thumbnail_overlay">
- <ImageView android:id="@+id/app_thumbnail_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible"
+ android:layout_width="match_parent">
+
+ <FrameLayout android:id="@+id/app_thumbnail"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
+ android:scaleType="center"
+ android:background="@drawable/recents_thumbnail_overlay">
+ <ImageView android:id="@+id/app_thumbnail_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible"
+ />
+ </FrameLayout>
+
+ <ImageView android:id="@+id/app_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignLeft="@id/app_thumbnail"
+ android:layout_alignTop="@id/app_thumbnail"
+ android:layout_marginLeft="@dimen/status_bar_recents_app_icon_left_margin"
+ android:layout_marginTop="@dimen/status_bar_recents_app_icon_top_margin"
+ android:maxWidth="@dimen/status_bar_recents_app_icon_max_width"
+ android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"
+ android:adjustViewBounds="true"
/>
- </FrameLayout>
- <ImageView android:id="@+id/app_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignLeft="@id/app_thumbnail"
- android:layout_alignTop="@id/app_thumbnail"
- android:layout_marginLeft="@dimen/status_bar_recents_app_icon_left_margin"
- android:layout_marginTop="@dimen/status_bar_recents_app_icon_top_margin"
- android:maxWidth="@dimen/status_bar_recents_thumbnail_max_width"
- android:maxHeight="@dimen/status_bar_recents_thumbnail_max_height"
- android:adjustViewBounds="true"
- />
+ <TextView android:id="@+id/app_label"
+ android:layout_width="@dimen/status_bar_recents_app_label_width"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/status_bar_recents_app_label_text_size"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
+ android:scrollHorizontally="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignTop="@id/app_icon"
+ android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ />
- <TextView android:id="@+id/app_label"
- android:layout_width="@dimen/status_bar_recents_app_label_width"
- android:layout_height="wrap_content"
- android:textSize="@dimen/status_bar_recents_app_label_text_size"
- android:fadingEdge="horizontal"
- android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
- android:scrollHorizontally="true"
- android:layout_alignParentLeft="true"
- android:layout_alignTop="@id/app_icon"
- android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"
- android:singleLine="true"
- android:ellipsize="marquee"
- />
+ <View android:id="@+id/recents_callout_line"
+ android:layout_width="@dimen/status_bar_recents_app_label_width"
+ android:layout_height="1dip"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"
+ android:layout_toLeftOf="@id/app_thumbnail"
+ android:layout_below="@id/app_label"
+ android:layout_marginRight="3dip"
+ android:layout_marginTop="3dip"
+ android:background="@drawable/recents_callout_line"
+ />
- <View android:id="@+id/recents_callout_line"
- android:layout_width="@dimen/status_bar_recents_app_label_width"
- android:layout_height="1dip"
- android:layout_alignParentLeft="true"
- android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"
- android:layout_toLeftOf="@id/app_thumbnail"
- android:layout_below="@id/app_label"
- android:layout_marginRight="3dip"
- android:layout_marginTop="3dip"
- android:background="@drawable/recents_callout_line"
- />
+ <TextView android:id="@+id/app_description"
+ android:layout_width="@dimen/status_bar_recents_app_label_width"
+ android:layout_height="wrap_content"
+ android:textSize="@dimen/status_bar_recents_app_description_text_size"
+ android:fadingEdge="horizontal"
+ android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
+ android:scrollHorizontally="true"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"
+ android:layout_below="@id/recents_callout_line"
+ android:layout_marginTop="3dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ />
- <TextView android:id="@+id/app_description"
- android:layout_width="@dimen/status_bar_recents_app_label_width"
- android:layout_height="wrap_content"
- android:textSize="@dimen/status_bar_recents_app_description_text_size"
- android:fadingEdge="horizontal"
- android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length"
- android:scrollHorizontally="true"
- android:layout_alignParentLeft="true"
- android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"
- android:layout_below="@id/recents_callout_line"
- android:layout_marginTop="3dip"
- android:singleLine="true"
- android:ellipsize="marquee"
- />
-
-</RelativeLayout>
+ </RelativeLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
index c680b8e..ed9ea7a 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
@@ -38,9 +38,10 @@
android:orientation="horizontal"
android:clipChildren="false"
android:layout_marginTop="@*android:dimen/status_bar_height">
+
<com.android.systemui.recent.RecentsVerticalScrollView
android:id="@+id/recents_container"
- android:layout_width="@dimen/status_bar_recents_width"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="0dp"
android:divider="@null"
@@ -53,7 +54,7 @@
android:clipChildren="false">
<LinearLayout android:id="@+id/recents_linear_layout"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipToPadding="false"
@@ -62,7 +63,6 @@
</com.android.systemui.recent.RecentsVerticalScrollView>
-
</LinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
index 5306508..9dc6378 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
@@ -19,10 +19,10 @@
-->
<!-- android:background="@drawable/status_bar_closed_default_background" -->
-<RelativeLayout
+<RelativeLayout android:id="@+id/recent_item"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="@dimen/status_bar_recents_thumbnail_view_width">
+ android:layout_width="wrap_content">
<FrameLayout android:id="@+id/app_thumbnail"
android:layout_width="wrap_content"
@@ -31,9 +31,7 @@
android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
android:scaleType="center"
- android:clickable="true"
- android:background="@drawable/recents_thumbnail_bg"
- android:foreground="@drawable/recents_thumbnail_overlay">
+ android:background="@drawable/recents_thumbnail_overlay">
<ImageView android:id="@+id/app_thumbnail_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -48,8 +46,8 @@
android:layout_alignTop="@id/app_thumbnail"
android:layout_marginLeft="@dimen/status_bar_recents_app_icon_left_margin"
android:layout_marginTop="@dimen/status_bar_recents_app_icon_top_margin"
- android:maxWidth="@dimen/status_bar_recents_thumbnail_max_width"
- android:maxHeight="@dimen/status_bar_recents_thumbnail_max_height"
+ android:maxWidth="@dimen/status_bar_recents_app_icon_max_width"
+ android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"
android:adjustViewBounds="true"
/>
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_panel.xml
index 2c9a152..4ef602e 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_panel.xml
@@ -47,7 +47,7 @@
android:clipChildren="false"
>
<com.android.systemui.recent.RecentsVerticalScrollView android:id="@+id/recents_container"
- android:layout_width="@dimen/status_bar_recents_width"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/status_bar_recents_right_glow_margin"
android:divider="@null"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index ed13ace..f3d0bee 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -71,6 +71,7 @@
</LinearLayout>
<com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values-hdpi/dimens.xml b/packages/SystemUI/res/values-hdpi/dimens.xml
index 287e0d1..6b6fd4d 100644
--- a/packages/SystemUI/res/values-hdpi/dimens.xml
+++ b/packages/SystemUI/res/values-hdpi/dimens.xml
@@ -16,12 +16,6 @@
*/
-->
<resources>
- <!-- Offsets for rendering thumbnails over drawable recents_thumbnail_bg -->
- <dimen name="recents_thumbnail_bg_padding_left">6px</dimen>
- <dimen name="recents_thumbnail_bg_padding_top">7px</dimen>
- <dimen name="recents_thumbnail_bg_padding_right">6px</dimen>
- <dimen name="recents_thumbnail_bg_padding_bottom">6px</dimen>
-
<!-- thickness (height) of each notification row, including any separators or padding -->
<!-- Note: this is 64dip + 1px divider = 97px. -->
<dimen name="notification_height">97px</dimen>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 3919685..ca74b8b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -20,18 +20,14 @@
<dimen name="navigation_bar_size">@*android:dimen/navigation_bar_width</dimen>
<!-- Recent Applications parameters -->
- <!-- Width of a recent app view, including all content -->
- <dimen name="status_bar_recents_thumbnail_view_width">156dp</dimen>
<!-- How far the thumbnail for a recent app appears from left edge -->
<dimen name="status_bar_recents_thumbnail_left_margin">8dp</dimen>
<!-- How far the thumbnail for a recent app appears from top edge -->
<dimen name="status_bar_recents_thumbnail_top_margin">12dp</dimen>
- <!-- Width of scrollable area in recents -->
- <dimen name="status_bar_recents_width">128dp</dimen>
<!-- Padding for text descriptions -->
<dimen name="status_bar_recents_text_description_padding">8dp</dimen>
<!-- Width of application label text -->
- <dimen name="status_bar_recents_app_label_width">97dip</dimen>
+ <dimen name="status_bar_recents_app_label_width">156dip</dimen>
<!-- Left margin of application label text -->
<dimen name="status_bar_recents_app_label_left_margin">16dip</dimen>
<!-- Margin between recents container and glow on the right -->
diff --git a/packages/SystemUI/res/values-mdpi/dimens.xml b/packages/SystemUI/res/values-mdpi/dimens.xml
deleted file mode 100644
index 741b75a..0000000
--- a/packages/SystemUI/res/values-mdpi/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2011, 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.
-*/
--->
-<resources>
- <!-- Offsets for rendering thumbnails over drawable recents_thumbnail_bg -->
- <dimen name="recents_thumbnail_bg_padding_left">6px</dimen>
- <dimen name="recents_thumbnail_bg_padding_top">7px</dimen>
- <dimen name="recents_thumbnail_bg_padding_right">6px</dimen>
- <dimen name="recents_thumbnail_bg_padding_bottom">6px</dimen>
-</resources>
diff --git a/packages/SystemUI/res/values-port/dimens.xml b/packages/SystemUI/res/values-port/dimens.xml
index 54c25fa..b89a610 100644
--- a/packages/SystemUI/res/values-port/dimens.xml
+++ b/packages/SystemUI/res/values-port/dimens.xml
@@ -17,18 +17,14 @@
-->
<resources>
<!-- Recent Applications parameters -->
- <!-- Width of a recent app view, including all content -->
- <dimen name="status_bar_recents_thumbnail_view_width">156dp</dimen>
<!-- How far the thumbnail for a recent app appears from left edge -->
<dimen name="status_bar_recents_thumbnail_left_margin">110dp</dimen>
- <!-- Width of scrollable area in recents -->
- <dimen name="status_bar_recents_width">356dp</dimen>
<!-- Padding for text descriptions -->
<dimen name="status_bar_recents_text_description_padding">8dp</dimen>
<!-- Width of application label text -->
<dimen name="status_bar_recents_app_label_width">97dip</dimen>
<!-- Left margin of application label text -->
- <dimen name="status_bar_recents_app_label_left_margin">16dip</dimen>
+ <dimen name="status_bar_recents_app_label_left_margin">8dip</dimen>
<!-- Margin between recents container and glow on the right -->
<dimen name="status_bar_recents_right_glow_margin">100dip</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..3e2ec59
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, 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.
+*/
+-->
+
+<resources>
+
+ <!-- Whether we're using the tablet-optimized recents interface (we use this
+ value at runtime for some things) -->
+ <bool name="config_recents_interface_for_tablets">true</bool>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index a5bea5c..fe9245d 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -30,16 +30,19 @@
<dimen name="panel_float">56dp</dimen>
<!-- Recent Applications parameters -->
- <!-- Width of a recent app view, including all content -->
- <dimen name="status_bar_recents_thumbnail_view_width">156dp</dimen>
<!-- How far the thumbnail for a recent app appears from left edge -->
- <dimen name="status_bar_recents_thumbnail_left_margin">110dp</dimen>
+ <dimen name="status_bar_recents_thumbnail_left_margin">121dp</dimen>
<!-- Upper width limit for application icon -->
- <dimen name="status_bar_recents_thumbnail_max_width">64dp</dimen>
+ <dimen name="status_bar_recents_app_icon_max_width">64dp</dimen>
<!-- Upper height limit for application icon -->
- <dimen name="status_bar_recents_thumbnail_max_height">64dp</dimen>
- <!-- Width of scrollable area in recents -->
- <dimen name="status_bar_recents_width">356dp</dimen>
+ <dimen name="status_bar_recents_app_icon_max_height">64dp</dimen>
+
+ <!-- Size of application icon -->
+ <dimen name="status_bar_recents_thumbnail_width">245dp</dimen>
+ <dimen name="status_bar_recents_thumbnail_height">144dp</dimen>
+
+ <!-- Width of recents panel -->
+ <dimen name="status_bar_recents_width">600dp</dimen>
<!-- Padding for text descriptions -->
<dimen name="status_bar_recents_text_description_padding">8dp</dimen>
<!-- Size of application label text -->
@@ -55,12 +58,6 @@
<!-- Margin between recents container and glow on the right -->
<dimen name="status_bar_recents_right_glow_margin">100dip</dimen>
- <!-- Offsets for rendering thumbnails over drawable recents_thumbnail_bg -->
- <dimen name="recents_thumbnail_bg_padding_left">15px</dimen>
- <dimen name="recents_thumbnail_bg_padding_top">8px</dimen>
- <dimen name="recents_thumbnail_bg_padding_right">12px</dimen>
- <dimen name="recents_thumbnail_bg_padding_bottom">8px</dimen>
-
<!-- Where to place the app icon over the thumbnail -->
<dimen name="status_bar_recents_app_icon_left_margin">13dp</dimen>
<dimen name="status_bar_recents_app_icon_top_margin">13dp</dimen>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5d14fa8..4c222f9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -22,5 +22,6 @@
<drawable name="ticker_background_color">#ff1d1d1d</drawable>
<drawable name="status_bar_background">#ff000000</drawable>
<drawable name="status_bar_recents_background">#b3000000</drawable>
+ <drawable name="status_bar_recents_app_thumbnail_background">#88000000</drawable>
<drawable name="status_bar_notification_row_background_color">#ff000000</drawable>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d7d7817..4ac89b2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -21,6 +21,10 @@
for different hardware and product builds. -->
<resources>
+ <!-- Whether we're using the tablet-optimized recents interface (we use this
+ value at runtime for some things) -->
+ <bool name="config_recents_interface_for_tablets">false</bool>
+
<!-- Control whether status bar should distinguish HSPA data icon form UMTS
data icon on devices -->
<bool name="config_hspa_data_distinguishable">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f633825..d0ece6c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -21,17 +21,21 @@
<!-- Recent Applications parameters -->
<!-- Upper width limit for application icon -->
- <dimen name="status_bar_recents_thumbnail_max_width">64dp</dimen>
+ <dimen name="status_bar_recents_app_icon_max_width">64dp</dimen>
<!-- Upper height limit for application icon -->
- <dimen name="status_bar_recents_thumbnail_max_height">64dp</dimen>
+ <dimen name="status_bar_recents_app_icon_max_height">64dp</dimen>
<!-- Where to place the app icon over the thumbnail -->
<dimen name="status_bar_recents_app_icon_left_margin">13dp</dimen>
<dimen name="status_bar_recents_app_icon_top_margin">13dp</dimen>
+ <!-- Size of application thumbnail -->
+ <dimen name="status_bar_recents_thumbnail_width">164dp</dimen>
+ <dimen name="status_bar_recents_thumbnail_height">164dp</dimen>
+
<!-- Size of application label text -->
- <dimen name="status_bar_recents_app_label_text_size">18dip</dimen>
+ <dimen name="status_bar_recents_app_label_text_size">16dip</dimen>
<!-- Size of application description text -->
- <dimen name="status_bar_recents_app_description_text_size">18dip</dimen>
+ <dimen name="status_bar_recents_app_description_text_size">16dip</dimen>
<!-- Size of fading edge for scroll effect -->
<dimen name="status_bar_recents_fading_edge_length">20dip</dimen>
<!-- Margin between recents container and glow on the right -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7779703..bad7e1f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -36,10 +36,10 @@
<string name="status_bar_please_disturb_button">Show notifications</string>
<!-- Title shown in recents popup for removing an application from the list -->
- <string name="status_bar_recent_remove_item_title">Remove</string>
+ <string name="status_bar_recent_remove_item_title">Remove from list</string>
<!-- Title shown in recents popup for inspecting an application's properties -->
- <string name="status_bar_recent_inspect_item_title">Inspect</string>
+ <string name="status_bar_recent_inspect_item_title">App info</string>
<!-- The label in the bar at the top of the status bar when there are no notifications
showing. [CHAR LIMIT=40]-->
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index e7ed052..1ebdfa1 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -33,20 +33,19 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_INVALIDATE = false;
private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
+ private static final boolean CONSTRAIN_SWIPE = true;
+ private static final boolean FADE_OUT_DURING_SWIPE = true;
+ private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
public static final int X = 0;
public static final int Y = 1;
- private boolean CONSTRAIN_SWIPE = true;
- private boolean FADE_OUT_DURING_SWIPE = true;
- private boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
-
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
private int MAX_ESCAPE_ANIMATION_DURATION = 500; // ms
private int MAX_DISMISS_VELOCITY = 1000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
- public static float ALPHA_FADE_START = 0.8f; // fraction of thumbnail width
+ public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width
// where fade starts
static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width
// beyond which alpha->0
@@ -59,6 +58,8 @@
private float mInitialTouchPos;
private boolean mDragging;
private View mCurrView;
+ private View mCurrAnimView;
+ private boolean mCanCurrViewBeDimissed;
private float mDensityScale;
public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
@@ -82,8 +83,8 @@
return mSwipeDirection == X ? ev.getX() : ev.getY();
}
- private float getPos(View v) {
- return mSwipeDirection == X ? v.getX() : v.getY();
+ private float getTranslation(View v) {
+ return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
}
private float getVelocity(VelocityTracker vt) {
@@ -115,19 +116,15 @@
v.getMeasuredHeight();
}
- private float getContentSize(View v) {
- View content = mCallback.getChildContentView(v);
- return getSize(content);
- }
-
- private float getAlphaForOffset(View view, float thumbSize) {
- final float fadeSize = ALPHA_FADE_END * thumbSize;
+ private float getAlphaForOffset(View view) {
+ float viewSize = getSize(view);
+ final float fadeSize = ALPHA_FADE_END * viewSize;
float result = 1.0f;
- float pos = getPos(view);
- if (pos >= thumbSize * ALPHA_FADE_START) {
- result = 1.0f - (pos - thumbSize * ALPHA_FADE_START) / fadeSize;
- } else if (pos < thumbSize * (1.0f - ALPHA_FADE_START)) {
- result = 1.0f + (thumbSize * ALPHA_FADE_START + pos) / fadeSize;
+ float pos = getTranslation(view);
+ if (pos >= viewSize * ALPHA_FADE_START) {
+ result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
+ } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
+ result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
}
return result;
}
@@ -168,6 +165,8 @@
case MotionEvent.ACTION_DOWN:
mDragging = false;
mCurrView = mCallback.getChildAtPosition(ev);
+ mCurrAnimView = mCallback.getChildContentView(mCurrView);
+ mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.clear();
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
@@ -180,21 +179,28 @@
if (Math.abs(delta) > mPagingTouchSlop) {
mCallback.onBeginDrag(mCurrView);
mDragging = true;
- mInitialTouchPos = getPos(ev) - getPos(mCurrView);
+ mInitialTouchPos = getPos(ev) - getTranslation(mCurrAnimView);
}
}
break;
case MotionEvent.ACTION_UP:
mDragging = false;
mCurrView = null;
+ mCurrAnimView = null;
break;
}
return mDragging;
}
- public void dismissChild(final View animView, float velocity) {
+ public void dismissChild(final View view, float velocity) {
+ final View animView = mCallback.getChildContentView(view);
+ final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
float newPos;
- if (velocity < 0 || (velocity == 0 && getPos(animView) < 0)) {
+
+ if (velocity < 0
+ || (velocity == 0 && getTranslation(animView) < 0)
+ // if we use the Menu to dismiss an item in landscape, animate up
+ || (velocity == 0 && getTranslation(animView) == 0 && mSwipeDirection == Y)) {
newPos = -getSize(animView);
} else {
newPos = getSize(animView);
@@ -202,7 +208,7 @@
int duration = MAX_ESCAPE_ANIMATION_DURATION;
if (velocity != 0) {
duration = Math.min(duration,
- (int) (Math.abs(newPos - getPos(animView)) * 1000f / Math
+ (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
.abs(velocity)));
}
ObjectAnimator anim = createTranslationAnimation(animView, newPos);
@@ -216,17 +222,17 @@
}
public void onAnimationEnd(Animator animation) {
- mCallback.onChildDismissed(animView);
+ mCallback.onChildDismissed(view);
}
public void onAnimationCancel(Animator animation) {
- mCallback.onChildDismissed(animView);
+ mCallback.onChildDismissed(view);
}
});
anim.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
- if (FADE_OUT_DURING_SWIPE) {
- animView.setAlpha(getAlphaForOffset(animView, getContentSize(animView)));
+ if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
+ animView.setAlpha(getAlphaForOffset(animView));
}
invalidateGlobalRegion(animView);
}
@@ -234,14 +240,16 @@
anim.start();
}
- public void snapChild(final View animView, float velocity) {
+ public void snapChild(final View view, float velocity) {
+ final View animView = mCallback.getChildContentView(view);
+ final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView);
ObjectAnimator anim = createTranslationAnimation(animView, 0);
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
- if (FADE_OUT_DURING_SWIPE) {
- animView.setAlpha(getAlphaForOffset(animView, getContentSize(animView)));
+ if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
+ animView.setAlpha(getAlphaForOffset(animView));
}
invalidateGlobalRegion(animView);
}
@@ -264,7 +272,7 @@
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
- float size = getSize(mCurrView);
+ float size = getSize(mCurrAnimView);
float maxScrollDistance = 0.15f * size;
if (Math.abs(delta) >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
@@ -272,9 +280,9 @@
delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
}
}
- setTranslation(mCurrView, delta);
- if (FADE_OUT_DURING_SWIPE) {
- mCurrView.setAlpha(getAlphaForOffset(mCurrView, getContentSize(mCurrView)));
+ setTranslation(mCurrAnimView, delta);
+ if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
+ mCurrAnimView.setAlpha(getAlphaForOffset(mCurrAnimView));
}
invalidateGlobalRegion(mCurrView);
}
@@ -290,10 +298,10 @@
// Decide whether to dismiss the current view
boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
- Math.abs(getPos(mCurrView)) > 0.4 * getSize(mCurrView);
+ Math.abs(getTranslation(mCurrAnimView)) > 0.4 * getSize(mCurrAnimView);
boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
(Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
- (velocity > 0) == (getPos(mCurrView) > 0);
+ (velocity > 0) == (getTranslation(mCurrAnimView) > 0);
boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) &&
(childSwipedFastEnough || childSwipedFarEnough);
@@ -303,6 +311,7 @@
dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
} else {
// snappity
+ mCallback.onDragCancelled(mCurrView);
snapChild(mCurrView, velocity);
}
}
@@ -321,5 +330,7 @@
void onBeginDrag(View v);
void onChildDismissed(View v);
+
+ void onDragCancelled(View v);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java
index 5609ead..e3c4eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java
@@ -27,4 +27,9 @@
void handleOnClick(View selectedView);
void handleSwipe(View selectedView);
void handleLongPress(View selectedView, View anchorView);
+ void handleShowBackground(boolean show);
+ void dismiss();
+
+ // TODO: find another way to get this info from RecentsPanelView
+ boolean isRecentsVisible();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 8da2db6..85cde7c 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.database.DataSetObserver;
+import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -41,6 +42,8 @@
private RecentsCallback mCallback;
protected int mLastScrollPosition;
private SwipeHelper mSwipeHelper;
+ private RecentsScrollViewPerformanceHelper mPerformanceHelper;
+
private OnLongClickListener mOnLongClick = new OnLongClickListener() {
public boolean onLongClick(View v) {
final View anchorView = v.findViewById(R.id.app_description);
@@ -49,15 +52,12 @@
}
};
- public RecentsHorizontalScrollView(Context context) {
- this(context, null);
- }
-
public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
float densityScale = getResources().getDisplayMetrics().density;
float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop);
+ mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, false);
}
private int scrollPositionOfMostRecent() {
@@ -71,7 +71,11 @@
view.setLongClickable(true);
view.setOnLongClickListener(mOnLongClick);
- final View thumbnail = getChildContentView(view);
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.addViewCallback(view);
+ }
+
+ final View thumbnail = view.findViewById(R.id.app_thumbnail);
// thumbnail is set to clickable in the layout file
thumbnail.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
@@ -123,6 +127,11 @@
// We do this so the underlying ScrollView knows that it won't get
// the chance to intercept events anymore
requestDisallowInterceptTouchEvent(true);
+ v.setActivated(true);
+ }
+
+ public void onDragCancelled(View v) {
+ v.setActivated(false);
}
public View getChildAtPosition(MotionEvent ev) {
@@ -139,7 +148,60 @@
}
public View getChildContentView(View v) {
- return v.findViewById(R.id.app_thumbnail);
+ return v.findViewById(R.id.recent_item);
+ }
+
+ @Override
+ protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.onLayoutCallback();
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mPerformanceHelper != null) {
+ int paddingLeft = mPaddingLeft;
+ final boolean offsetRequired = isPaddingOffsetRequired();
+ if (offsetRequired) {
+ paddingLeft += getLeftPaddingOffset();
+ }
+
+ int left = mScrollX + paddingLeft;
+ int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
+ int top = mScrollY + getFadeTop(offsetRequired);
+ int bottom = top + getFadeHeight(offsetRequired);
+
+ if (offsetRequired) {
+ right += getRightPaddingOffset();
+ bottom += getBottomPaddingOffset();
+ }
+ mPerformanceHelper.drawCallback(canvas,
+ left, right, top, bottom, mScrollX, mScrollY,
+ 0, 0,
+ getLeftFadingEdgeStrength(), getRightFadingEdgeStrength());
+ }
+ }
+
+ @Override
+ public int getVerticalFadingEdgeLength() {
+ if (mPerformanceHelper != null) {
+ return mPerformanceHelper.getVerticalFadingEdgeLengthCallback();
+ } else {
+ return super.getVerticalFadingEdgeLength();
+ }
+ }
+
+ @Override
+ public int getHorizontalFadingEdgeLength() {
+ if (mPerformanceHelper != null) {
+ return mPerformanceHelper.getHorizontalFadingEdgeLengthCallback();
+ } else {
+ return super.getHorizontalFadingEdgeLength();
+ }
}
@Override
@@ -153,6 +215,14 @@
}
@Override
+ public void onAttachedToWindow() {
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.onAttachedToWindowCallback(
+ mCallback, mLinearLayout, isHardwareAccelerated());
+ }
+ }
+
+ @Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
float densityScale = getResources().getDisplayMetrics().density;
@@ -192,6 +262,12 @@
});
}
+ public void onRecentsVisibilityChanged() {
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.updateShowBackground();
+ }
+ }
+
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -220,6 +296,9 @@
@Override
public void setLayoutTransition(LayoutTransition transition) {
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.setLayoutTransitionCallback(transition);
+ }
// The layout transition applies to our embedded LinearLayout
mLinearLayout.setLayoutTransition(transition);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsListView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsListView.java
deleted file mode 100644
index d8b086b..0000000
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsListView.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2011 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 com.android.systemui.recent;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ListView;
-
-import com.android.systemui.R;
-
-public class RecentsListView extends ListView {
- private int mLastVisiblePosition;
- private RecentsCallback mCallback;
-
- public RecentsListView(Context context) {
- this(context, null);
- }
-
- public RecentsListView(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- LayoutInflater inflater = (LayoutInflater)
- mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- View footer = inflater.inflate(R.layout.status_bar_recent_panel_footer, this, false);
- setScrollbarFadingEnabled(true);
- addFooterView(footer, null, false);
- final int leftPadding = mContext.getResources()
- .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
- setOverScrollEffectPadding(leftPadding, 0);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- // Keep track of the last visible item in the list so we can restore it
- // to the bottom when the orientation changes.
- final int childCount = getChildCount();
- if (childCount > 0) {
- mLastVisiblePosition = getFirstVisiblePosition() + childCount - 1;
- View view = getChildAt(childCount - 1);
- final int distanceFromBottom = getHeight() - view.getTop();
-
- // This has to happen post-layout, so run it "in the future"
- post(new Runnable() {
- public void run() {
- setSelectionFromTop(mLastVisiblePosition, getHeight() - distanceFromBottom);
- }
- });
- }
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- // scroll to bottom after reloading
- int count = getAdapter().getCount();
- mLastVisiblePosition = count - 1;
- if (visibility == View.VISIBLE && changedView == this) {
- post(new Runnable() {
- public void run() {
- setSelection(mLastVisiblePosition);
- }
- });
- }
- }
-
- public void setCallback(RecentsCallback callback) {
- mCallback = callback;
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 9cc2c29..d7bb3c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -56,7 +56,6 @@
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
@@ -64,6 +63,7 @@
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBar;
@@ -73,7 +73,7 @@
public class RecentsPanelView extends RelativeLayout
implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener {
- static final String TAG = "RecentsListView";
+ static final String TAG = "RecentsPanelView";
static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
private static final int DISPLAY_TASKS = 20;
private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
@@ -84,12 +84,8 @@
private View mRecentsScrim;
private View mRecentsGlowView;
private ViewGroup mRecentsContainer;
- private Bitmap mGlowBitmap;
- // TODO: add these widgets attributes to the layout file
- private int mGlowBitmapPaddingLeftPx;
- private int mGlowBitmapPaddingTopPx;
- private int mGlowBitmapPaddingRightPx;
- private int mGlowBitmapPaddingBottomPx;
+ private Bitmap mAppThumbnailBackground;
+
private boolean mShowing;
private Choreographer mChoreo;
private View mRecentsDismissButton;
@@ -129,7 +125,7 @@
}
public void setThumbnail(Bitmap thumbnail) {
- mThumbnail = compositeBitmap(mGlowBitmap, thumbnail);
+ mThumbnail = compositeBitmap(mAppThumbnailBackground, thumbnail);
}
public Bitmap getThumbnail() {
@@ -178,7 +174,7 @@
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
- convertView = mInflater.inflate(R.layout.status_bar_recent_item, null);
+ convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false);
holder = new ViewHolder();
holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
holder.thumbnailViewImage = (ImageView) convertView.findViewById(
@@ -247,6 +243,31 @@
}
}
+ public void dismiss() {
+ hide(true);
+ }
+
+ public void hide(boolean animate) {
+ if (!animate) {
+ setVisibility(View.GONE);
+ }
+ if (mBar != null) {
+ mBar.animateCollapse();
+ }
+ }
+
+ public void handleShowBackground(boolean show) {
+ if (show) {
+ mRecentsScrim.setBackgroundResource(R.drawable.status_bar_recents_background);
+ } else {
+ mRecentsScrim.setBackgroundDrawable(null);
+ }
+ }
+
+ public boolean isRecentsVisible() {
+ return getVisibility() == VISIBLE;
+ }
+
public void onAnimationCancel(Animator animation) {
}
@@ -315,15 +336,12 @@
mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
- mGlowBitmap = BitmapFactory.decodeResource(res, R.drawable.recents_thumbnail_bg);
- mGlowBitmapPaddingLeftPx =
- res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_left);
- mGlowBitmapPaddingTopPx =
- res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_top);
- mGlowBitmapPaddingRightPx =
- res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_right);
- mGlowBitmapPaddingBottomPx =
- res.getDimensionPixelSize(R.dimen.recents_thumbnail_bg_padding_bottom);
+ int width = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_width);
+ int height = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_height);
+ int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
+ mAppThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(mAppThumbnailBackground);
+ c.drawColor(color);
}
@Override
@@ -332,12 +350,7 @@
mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container);
mListAdapter = new ActivityDescriptionAdapter(mContext);
- if (mRecentsContainer instanceof RecentsListView) {
- RecentsListView listView = (RecentsListView) mRecentsContainer;
- listView.setAdapter(mListAdapter);
- listView.setOnItemClickListener(this);
- listView.setCallback(this);
- } else if (mRecentsContainer instanceof RecentsHorizontalScrollView){
+ if (mRecentsContainer instanceof RecentsHorizontalScrollView){
RecentsHorizontalScrollView scrollView
= (RecentsHorizontalScrollView) mRecentsContainer;
scrollView.setAdapter(mListAdapter);
@@ -349,7 +362,7 @@
scrollView.setCallback(this);
}
else {
- throw new IllegalArgumentException("missing RecentsListView/RecentsScrollView");
+ throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
}
@@ -382,6 +395,14 @@
if (visibility == View.VISIBLE && changedView == this) {
refreshApplicationList();
}
+
+ if (mRecentsContainer instanceof RecentsHorizontalScrollView) {
+ ((RecentsHorizontalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
+ } else if (mRecentsContainer instanceof RecentsVerticalScrollView) {
+ ((RecentsVerticalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
+ } else {
+ throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
+ }
}
Drawable getFullResDefaultActivityIcon() {
@@ -555,6 +576,9 @@
mThumbnailLoader = null;
}
mActivityDescriptions = getRecentTasks();
+ for (ActivityDescription ad : mActivityDescriptions) {
+ ad.setThumbnail(mAppThumbnailBackground);
+ }
mListAdapter.notifyDataSetInvalidated();
if (mActivityDescriptions.size() > 0) {
if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps");
@@ -629,14 +653,8 @@
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setAlpha(255);
- final int srcWidth = thumbnail.getWidth();
- final int srcHeight = thumbnail.getHeight();
- if (DEBUG) Log.v(TAG, "Source thumb: " + srcWidth + "x" + srcHeight);
- canvas.drawBitmap(thumbnail,
- new Rect(0, 0, srcWidth-1, srcHeight-1),
- new RectF(mGlowBitmapPaddingLeftPx, mGlowBitmapPaddingTopPx,
- outBitmap.getWidth() - mGlowBitmapPaddingRightPx,
- outBitmap.getHeight() - mGlowBitmapPaddingBottomPx), paint);
+ canvas.drawBitmap(thumbnail, null,
+ new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint);
canvas.setBitmap(null);
}
return outBitmap;
@@ -649,15 +667,6 @@
mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
}
- public void hide(boolean animate) {
- if (!animate) {
- setVisibility(View.GONE);
- }
- if (mBar != null) {
- mBar.animateCollapse();
- }
- }
-
public void handleOnClick(View view) {
ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
final Context context = view.getContext();
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsScrollViewPerformanceHelper.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsScrollViewPerformanceHelper.java
new file mode 100644
index 0000000..b7e656e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsScrollViewPerformanceHelper.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2011 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 com.android.systemui.recent;
+
+import android.animation.LayoutTransition;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+
+public class RecentsScrollViewPerformanceHelper {
+ public static final boolean OPTIMIZE_SW_RENDERED_RECENTS = true;
+ public static final boolean USE_DARK_FADE_IN_HW_ACCELERATED_MODE = true;
+ private View mScrollView;
+ private LinearLayout mLinearLayout;
+ private RecentsCallback mCallback;
+
+ private boolean mShowBackground = false;
+ private int mFadingEdgeLength;
+ private Drawable.ConstantState mBackgroundDrawable;
+ private Context mContext;
+ private boolean mIsVertical;
+ private boolean mFirstTime = true;
+ private boolean mSoftwareRendered = false;
+ private boolean mAttachedToWindow = false;
+
+ public static RecentsScrollViewPerformanceHelper create(Context context,
+ AttributeSet attrs, View scrollView, boolean isVertical) {
+ boolean isTablet = context.getResources().
+ getBoolean(R.bool.config_recents_interface_for_tablets);
+ if (!isTablet && (OPTIMIZE_SW_RENDERED_RECENTS || USE_DARK_FADE_IN_HW_ACCELERATED_MODE)) {
+ return new RecentsScrollViewPerformanceHelper(context, attrs, scrollView, isVertical);
+ } else {
+ return null;
+ }
+ }
+
+ public RecentsScrollViewPerformanceHelper(Context context,
+ AttributeSet attrs, View scrollView, boolean isVertical) {
+ mScrollView = scrollView;
+ mContext = context;
+ TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View);
+ mFadingEdgeLength = a.getDimensionPixelSize(android.R.styleable.View_fadingEdgeLength,
+ ViewConfiguration.get(context).getScaledFadingEdgeLength());
+ mIsVertical = isVertical;
+ }
+
+ public void onAttachedToWindowCallback(
+ RecentsCallback callback, LinearLayout layout, boolean hardwareAccelerated) {
+ mSoftwareRendered = !hardwareAccelerated;
+ if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS)
+ || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) {
+ mScrollView.setVerticalFadingEdgeEnabled(false);
+ mScrollView.setHorizontalFadingEdgeEnabled(false);
+ }
+ if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
+ mCallback = callback;
+ mLinearLayout = layout;
+ mAttachedToWindow = true;
+ mBackgroundDrawable = mContext.getResources()
+ .getDrawable(R.drawable.status_bar_recents_background).getConstantState();
+ updateShowBackground();
+ }
+
+ }
+
+ public void addViewCallback(View newLinearLayoutChild) {
+ if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
+ final View view = newLinearLayoutChild;
+ if (mShowBackground) {
+ view.setBackgroundDrawable(mBackgroundDrawable.newDrawable());
+ view.setDrawingCacheEnabled(true);
+ view.buildDrawingCache();
+ } else {
+ view.setBackgroundDrawable(null);
+ view.setDrawingCacheEnabled(false);
+ view.destroyDrawingCache();
+ }
+ }
+ }
+
+ public void onLayoutCallback() {
+ if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
+ mScrollView.post(new Runnable() {
+ public void run() {
+ updateShowBackground();
+ }
+ });
+ }
+ }
+
+ public void drawCallback(Canvas canvas,
+ int left, int right, int top, int bottom, int scrollX, int scrollY,
+ float topFadingEdgeStrength, float bottomFadingEdgeStrength,
+ float leftFadingEdgeStrength, float rightFadingEdgeStrength) {
+ if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
+ if (mIsVertical) {
+ if (scrollY < 0) {
+ Drawable d = mBackgroundDrawable.newDrawable().getCurrent();
+ d.setBounds(0, scrollY, mScrollView.getWidth(), 0);
+ d.draw(canvas);
+ } else {
+ final int childHeight = mLinearLayout.getHeight();
+ if (scrollY + mScrollView.getHeight() > childHeight) {
+ Drawable d = mBackgroundDrawable.newDrawable().getCurrent();
+ d.setBounds(0, childHeight, mScrollView.getWidth(),
+ scrollY + mScrollView.getHeight());
+ d.draw(canvas);
+ }
+ }
+ } else {
+ if (scrollX < 0) {
+ Drawable d = mBackgroundDrawable.newDrawable().getCurrent();
+ d.setBounds(scrollX, 0, 0, mScrollView.getHeight());
+ d.draw(canvas);
+ } else {
+ final int childWidth = mLinearLayout.getWidth();
+ if (scrollX + mScrollView.getWidth() > childWidth) {
+ Drawable d = mBackgroundDrawable.newDrawable().getCurrent();
+ d.setBounds(childWidth, 0,
+ scrollX + mScrollView.getWidth(), mScrollView.getHeight());
+ d.draw(canvas);
+ }
+ }
+ }
+ }
+
+ if ((mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS)
+ || USE_DARK_FADE_IN_HW_ACCELERATED_MODE) {
+ Paint p = new Paint();
+ Matrix matrix = new Matrix();
+ // use use a height of 1, and then wack the matrix each time we
+ // actually use it.
+ Shader fade = new LinearGradient(0, 0, 0, 1, 0xCC000000, 0, Shader.TileMode.CLAMP);
+ // PULL OUT THIS CONSTANT
+
+ p.setShader(fade);
+
+ // draw the fade effect
+ boolean drawTop = false;
+ boolean drawBottom = false;
+ boolean drawLeft = false;
+ boolean drawRight = false;
+
+ float topFadeStrength = 0.0f;
+ float bottomFadeStrength = 0.0f;
+ float leftFadeStrength = 0.0f;
+ float rightFadeStrength = 0.0f;
+
+ final float fadeHeight = mFadingEdgeLength;
+ int length = (int) fadeHeight;
+
+ // clip the fade length if top and bottom fades overlap
+ // overlapping fades produce odd-looking artifacts
+ if (mIsVertical && (top + length > bottom - length)) {
+ length = (bottom - top) / 2;
+ }
+
+ // also clip horizontal fades if necessary
+ if (!mIsVertical && (left + length > right - length)) {
+ length = (right - left) / 2;
+ }
+
+ if (mIsVertical) {
+ topFadeStrength = Math.max(0.0f, Math.min(1.0f, topFadingEdgeStrength));
+ drawTop = topFadeStrength * fadeHeight > 1.0f;
+ bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, bottomFadingEdgeStrength));
+ drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
+ }
+
+ if (!mIsVertical) {
+ leftFadeStrength = Math.max(0.0f, Math.min(1.0f, leftFadingEdgeStrength));
+ drawLeft = leftFadeStrength * fadeHeight > 1.0f;
+ rightFadeStrength = Math.max(0.0f, Math.min(1.0f, rightFadingEdgeStrength));
+ drawRight = rightFadeStrength * fadeHeight > 1.0f;
+ }
+
+ if (drawTop) {
+ matrix.setScale(1, fadeHeight * topFadeStrength);
+ matrix.postTranslate(left, top);
+ fade.setLocalMatrix(matrix);
+ canvas.drawRect(left, top, right, top + length, p);
+ }
+
+ if (drawBottom) {
+ matrix.setScale(1, fadeHeight * bottomFadeStrength);
+ matrix.postRotate(180);
+ matrix.postTranslate(left, bottom);
+ fade.setLocalMatrix(matrix);
+ canvas.drawRect(left, bottom - length, right, bottom, p);
+ }
+
+ if (drawLeft) {
+ matrix.setScale(1, fadeHeight * leftFadeStrength);
+ matrix.postRotate(-90);
+ matrix.postTranslate(left, top);
+ fade.setLocalMatrix(matrix);
+ canvas.drawRect(left, top, left + length, bottom, p);
+ }
+
+ if (drawRight) {
+ matrix.setScale(1, fadeHeight * rightFadeStrength);
+ matrix.postRotate(90);
+ matrix.postTranslate(right, top);
+ fade.setLocalMatrix(matrix);
+ canvas.drawRect(right - length, top, right, bottom, p);
+ }
+ }
+ }
+
+ public int getVerticalFadingEdgeLengthCallback() {
+ return mFadingEdgeLength;
+ }
+
+ public int getHorizontalFadingEdgeLengthCallback() {
+ return mFadingEdgeLength;
+ }
+
+ public void setLayoutTransitionCallback(LayoutTransition transition) {
+ if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
+ if (transition != null) {
+ transition.addTransitionListener(new LayoutTransition.TransitionListener() {
+ @Override
+ public void startTransition(LayoutTransition transition,
+ ViewGroup container, View view, int transitionType) {
+ updateShowBackground();
+ }
+
+ @Override
+ public void endTransition(LayoutTransition transition,
+ ViewGroup container, View view, int transitionType) {
+ updateShowBackground();
+ }
+ });
+ }
+ }
+ }
+
+ // Turn on/off drawing the background in our ancestor, and turn on/off drawing
+ // in the items in LinearLayout contained by this scrollview.
+ // Moving the background drawing to our children, and turning on a drawing cache
+ // for each of them, gives us a ~20fps gain when Recents is rendered in software
+ public void updateShowBackground() {
+ if (!mAttachedToWindow) {
+ // We haven't been initialized yet-- we'll get called again when we are
+ return;
+ }
+ if (mSoftwareRendered && OPTIMIZE_SW_RENDERED_RECENTS) {
+ LayoutTransition transition = mLinearLayout.getLayoutTransition();
+ int linearLayoutSize =
+ mIsVertical ? mLinearLayout.getHeight() : mLinearLayout.getWidth();
+ int scrollViewSize =
+ mIsVertical ? mScrollView.getHeight() : mScrollView.getWidth();
+ boolean show = !mScrollView.isHardwareAccelerated() &&
+ (linearLayoutSize > scrollViewSize) &&
+ !(transition != null && transition.isRunning()) &&
+ mCallback.isRecentsVisible();
+
+ if (!mFirstTime && show == mShowBackground) return;
+ mShowBackground = show;
+ mFirstTime = false;
+
+ mCallback.handleShowBackground(!show);
+ for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
+ View v = mLinearLayout.getChildAt(i);
+ if (show) {
+ v.setBackgroundDrawable(mBackgroundDrawable.newDrawable());
+ v.setDrawingCacheEnabled(true);
+ v.buildDrawingCache();
+ } else {
+ v.setDrawingCacheEnabled(false);
+ v.destroyDrawingCache();
+ v.setBackgroundDrawable(null);
+ }
+ }
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index b1a30d9..1978d69 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.database.DataSetObserver;
+import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -40,6 +41,7 @@
private RecentsCallback mCallback;
protected int mLastScrollPosition;
private SwipeHelper mSwipeHelper;
+ private RecentsScrollViewPerformanceHelper mPerformanceHelper;
private OnLongClickListener mOnLongClick = new OnLongClickListener() {
public boolean onLongClick(View v) {
@@ -49,15 +51,13 @@
}
};
- public RecentsVerticalScrollView(Context context) {
- this(context, null);
- }
-
public RecentsVerticalScrollView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
float densityScale = getResources().getDisplayMetrics().density;
float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
+
+ mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, true);
}
private int scrollPositionOfMostRecent() {
@@ -77,18 +77,33 @@
}
final View view = mAdapter.getView(i, old, mLinearLayout);
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.addViewCallback(view);
+ }
+
if (old == null) {
view.setClickable(true);
view.setOnLongClickListener(mOnLongClick);
-
- final View thumbnail = getChildContentView(view);
- // thumbnail is set to clickable in the layout file
- thumbnail.setOnClickListener(new OnClickListener() {
+ view.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
- mCallback.handleOnClick(view);
+ mCallback.dismiss();
}
});
+ OnClickListener launchAppListener = new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.handleOnClick(view);
+ }
+ };
+ final View thumbnail = view.findViewById(R.id.app_thumbnail);
+ thumbnail.setClickable(true);
+ thumbnail.setOnClickListener(launchAppListener);
+ final View appTitle = view.findViewById(R.id.app_label);
+ appTitle.setClickable(true);
+ appTitle.setOnClickListener(launchAppListener);
+ final View calloutLine = view.findViewById(R.id.recents_callout_line);
+ calloutLine.setClickable(true);
+ calloutLine.setOnClickListener(launchAppListener);
mLinearLayout.addView(view);
}
}
@@ -138,6 +153,11 @@
// We do this so the underlying ScrollView knows that it won't get
// the chance to intercept events anymore
requestDisallowInterceptTouchEvent(true);
+ v.setActivated(true);
+ }
+
+ public void onDragCancelled(View v) {
+ v.setActivated(false);
}
public View getChildAtPosition(MotionEvent ev) {
@@ -155,7 +175,60 @@
}
public View getChildContentView(View v) {
- return v.findViewById(R.id.app_thumbnail);
+ return v.findViewById(R.id.recent_item);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.onLayoutCallback();
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mPerformanceHelper != null) {
+ int paddingLeft = mPaddingLeft;
+ final boolean offsetRequired = isPaddingOffsetRequired();
+ if (offsetRequired) {
+ paddingLeft += getLeftPaddingOffset();
+ }
+
+ int left = mScrollX + paddingLeft;
+ int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
+ int top = mScrollY + getFadeTop(offsetRequired);
+ int bottom = top + getFadeHeight(offsetRequired);
+
+ if (offsetRequired) {
+ right += getRightPaddingOffset();
+ bottom += getBottomPaddingOffset();
+ }
+ mPerformanceHelper.drawCallback(canvas,
+ left, right, top, bottom, mScrollX, mScrollY,
+ getTopFadingEdgeStrength(), getBottomFadingEdgeStrength(),
+ 0, 0);
+ }
+ }
+
+ @Override
+ public int getVerticalFadingEdgeLength() {
+ if (mPerformanceHelper != null) {
+ return mPerformanceHelper.getVerticalFadingEdgeLengthCallback();
+ } else {
+ return super.getVerticalFadingEdgeLength();
+ }
+ }
+
+ @Override
+ public int getHorizontalFadingEdgeLength() {
+ if (mPerformanceHelper != null) {
+ return mPerformanceHelper.getHorizontalFadingEdgeLengthCallback();
+ } else {
+ return super.getHorizontalFadingEdgeLength();
+ }
}
@Override
@@ -169,6 +242,14 @@
}
@Override
+ public void onAttachedToWindow() {
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.onAttachedToWindowCallback(
+ mCallback, mLinearLayout, isHardwareAccelerated());
+ }
+ }
+
+ @Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
float densityScale = getResources().getDisplayMetrics().density;
@@ -208,6 +289,12 @@
});
}
+ public void onRecentsVisibilityChanged() {
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.updateShowBackground();
+ }
+ }
+
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -236,6 +323,9 @@
@Override
public void setLayoutTransition(LayoutTransition transition) {
+ if (mPerformanceHelper != null) {
+ mPerformanceHelper.setLayoutTransitionCallback(transition);
+ }
// The layout transition applies to our embedded LinearLayout
mLinearLayout.setLayoutTransition(transition);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index e6c0b96..98dca92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -70,6 +70,14 @@
return mCurrentView.findViewById(R.id.menu);
}
+ public View getBackButton() {
+ return mCurrentView.findViewById(R.id.back);
+ }
+
+ public View getHomeButton() {
+ return mCurrentView.findViewById(R.id.home);
+ }
+
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 6e6567b..3d23abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -380,7 +380,7 @@
}
protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) {
- boolean translucent = false;
+ boolean opaque = false;
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
layoutParams.width,
layoutParams.height,
@@ -388,7 +388,7 @@
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- (translucent ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
+ (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
if (ActivityManager.isHighEndGfx(mDisplay)) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
@@ -404,7 +404,7 @@
// Recents Panel
boolean visible = false;
if (mRecentsPanel != null) {
- visible = mRecentsPanel.getVisibility() == View.VISIBLE;
+ visible = mRecentsPanel.isShowing();
WindowManagerImpl.getDefault().removeView(mRecentsPanel);
}
@@ -1060,6 +1060,12 @@
*/
}
+ public void showClock(boolean show) {
+ View clock = mStatusBarView.findViewById(R.id.clock);
+ if (clock != null) {
+ clock.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ }
/**
* State is one or more of the DISABLE constants from StatusBarManager.
@@ -1074,6 +1080,10 @@
old, state, diff));
}
+ if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
+ boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
+ showClock(show);
+ }
if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
Slog.d(TAG, "DISABLE_EXPAND: yes");
@@ -1081,18 +1091,9 @@
}
}
- if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) {
- if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) {
- Slog.d(TAG, "DISABLE_NAVIGATION: yes");
-
- // close recents if it's visible
- mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
- mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
- }
-
- if (mNavigationBarView != null) {
- mNavigationBarView.setEnabled((state & StatusBarManager.DISABLE_NAVIGATION) == 0);
- }
+ if ((diff & (StatusBarManager.DISABLE_NAVIGATION | StatusBarManager.DISABLE_BACK)) != 0) {
+ setNavigationVisibility(state &
+ (StatusBarManager.DISABLE_NAVIGATION | StatusBarManager.DISABLE_BACK));
}
if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
@@ -1117,6 +1118,32 @@
}
}
+ private void setNavigationVisibility(int visibility) {
+ boolean disableNavigation = ((visibility & StatusBarManager.DISABLE_NAVIGATION) != 0);
+ boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0);
+
+ Slog.i(TAG, "DISABLE_BACK: " + (disableBack ? "yes" : "no"));
+ Slog.i(TAG, "DISABLE_NAVIGATION: " + (disableNavigation ? "yes" : "no"));
+
+ if (mNavigationBarView != null) {
+ if (disableNavigation && disableBack) {
+ mNavigationBarView.setEnabled(false);
+ } else {
+ mNavigationBarView.getBackButton().setEnabled(!disableBack);
+ mNavigationBarView.getHomeButton().setEnabled(!disableNavigation);
+ mNavigationBarView.getRecentsButton().setEnabled(!disableNavigation);
+
+ mNavigationBarView.setEnabled(true);
+ }
+ }
+
+ if (disableNavigation) {
+ // close recents if it's visible
+ mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL);
+ mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL);
+ }
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 60dfdac..3c85814 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -130,6 +130,8 @@
int mLastDataTypeIconId = -1;
String mLastLabel = "";
+ private boolean mHasMobileDataFeature;
+
boolean mDataAndWifiStacked = false;
// yuck -- stop doing this here and put it in the framework
@@ -147,6 +149,10 @@
public NetworkController(Context context) {
mContext = context;
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ mHasMobileDataFeature = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+
// set up the default wifi icon, used when no radios have ever appeared
updateWifiIcons();
@@ -229,7 +235,7 @@
mWifiIconId,
mWifiActivityIconId);
cluster.setMobileDataIndicators(
- hasMobileDataFeature(),
+ mHasMobileDataFeature,
mPhoneSignalIconId,
mMobileActivityIconId,
mDataTypeIconId);
@@ -376,12 +382,6 @@
}
}
- private boolean hasMobileDataFeature() {
- // XXX: HAX: replace when a more reliable method is available
- return (! "wifi-only".equals(SystemProperties.get("ro.carrier")));
- }
-
-
private void updateAirplaneMode() {
mAirplaneMode = (Settings.System.getInt(mContext.getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, 0) == 1);
@@ -828,8 +828,8 @@
label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
// On devices without mobile radios, we want to show the wifi icon
combinedSignalIconId =
- hasMobileDataFeature() ? mDataSignalIconId : mWifiIconId;
- mContentDescriptionCombinedSignal = hasMobileDataFeature()
+ mHasMobileDataFeature ? mDataSignalIconId : mWifiIconId;
+ mContentDescriptionCombinedSignal = mHasMobileDataFeature
? mContentDescriptionDataType : mContentDescriptionWifi;
mDataTypeIconId = 0;
}
@@ -866,7 +866,7 @@
mWifiIconId,
mWifiActivityIconId);
cluster.setMobileDataIndicators(
- hasMobileDataFeature(),
+ mHasMobileDataFeature,
mPhoneSignalIconId,
mMobileActivityIconId,
mDataTypeIconId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
index 469b462..e287b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
@@ -124,6 +124,9 @@
requestDisallowInterceptTouchEvent(true);
}
+ public void onDragCancelled(View v) {
+ }
+
public View getChildAtPosition(MotionEvent ev) {
// find the view under the pointer, accounting for GONE views
final int count = getChildCount();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
index 1e417ac..5911378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
@@ -428,7 +428,7 @@
private CharSequence getIMIName(InputMethodInfo imi) {
if (imi == null) return null;
- return mPackageManager.getApplicationLabel(imi.getServiceInfo().applicationInfo);
+ return imi.loadLabel(mPackageManager);
}
private CharSequence getSubtypeName(InputMethodInfo imi, InputMethodSubtype subtype) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
index d9cb4e8..4a1cafd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
@@ -28,6 +28,10 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.widget.RelativeLayout;
import com.android.systemui.R;
@@ -52,6 +56,8 @@
ViewGroup mContentParent;
TabletStatusBar mBar;
View mClearButton;
+ static Interpolator sAccelerateInterpolator = new AccelerateInterpolator();
+ static Interpolator sDecelerateInterpolator = new DecelerateInterpolator();
// amount to slide mContentParent down by when mContentFrame is missing
float mContentFrameMissingTranslation;
@@ -117,8 +123,13 @@
mShowing = show;
if (show) {
setVisibility(View.VISIBLE);
+ // Don't start the animation until we've created the layer, which is done
+ // right before we are drawn
+ mContentParent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
+ } else {
+ mChoreo.startAnimation(show);
}
- mChoreo.startAnimation(show);
}
} else {
mShowing = show;
@@ -127,6 +138,20 @@
}
/**
+ * This is used only when we've created a hardware layer and are waiting until it's
+ * been created in order to start the appearing animation.
+ */
+ private ViewTreeObserver.OnPreDrawListener mPreDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ mChoreo.startAnimation(true);
+ return true;
+ }
+ };
+
+ /**
* Whether the panel is showing, or, if it's animating, whether it will be
* when the animation is done.
*/
@@ -330,8 +355,8 @@
AnimatorSet mContentAnim;
// should group this into a multi-property animation
- final static int OPEN_DURATION = 300;
- final static int CLOSE_DURATION = 300;
+ final static int OPEN_DURATION = 250;
+ final static int CLOSE_DURATION = 250;
// the panel will start to appear this many px from the end
final int HYPERSPACE_OFFRAMP = 200;
@@ -362,19 +387,15 @@
Animator posAnim = ObjectAnimator.ofFloat(mContentParent, "translationY",
start, end);
- posAnim.setInterpolator(appearing
- ? new android.view.animation.DecelerateInterpolator(1.0f)
- : new android.view.animation.AccelerateInterpolator(1.0f));
+ posAnim.setInterpolator(appearing ? sDecelerateInterpolator : sAccelerateInterpolator);
if (mContentAnim != null && mContentAnim.isRunning()) {
mContentAnim.cancel();
}
Animator fadeAnim = ObjectAnimator.ofFloat(mContentParent, "alpha",
- mContentParent.getAlpha(), appearing ? 1.0f : 0.0f);
- fadeAnim.setInterpolator(appearing
- ? new android.view.animation.AccelerateInterpolator(2.0f)
- : new android.view.animation.DecelerateInterpolator(2.0f));
+ appearing ? 1.0f : 0.0f);
+ fadeAnim.setInterpolator(appearing ? sAccelerateInterpolator : sDecelerateInterpolator);
mContentAnim = new AnimatorSet();
mContentAnim
@@ -389,8 +410,6 @@
if (DEBUG) Slog.d(TAG, "startAnimation(appearing=" + appearing + ")");
createAnimation(appearing);
-
- mContentParent.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mContentAnim.start();
mVisible = appearing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 2ab667d..cc73d7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -306,7 +306,7 @@
mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel);
lp = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
+ (int) res.getDimension(R.dimen.status_bar_recents_width),
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
@@ -949,29 +949,34 @@
mTicker.halt();
}
}
- if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) {
- if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) {
- Slog.i(TAG, "DISABLE_NAVIGATION: yes");
- mNavigationArea.setVisibility(View.INVISIBLE);
- mInputMethodSwitchButton.setScreenLocked(true);
- } else {
- Slog.i(TAG, "DISABLE_NAVIGATION: no");
- mNavigationArea.setVisibility(View.VISIBLE);
- mInputMethodSwitchButton.setScreenLocked(false);
- }
+ if ((diff & (StatusBarManager.DISABLE_NAVIGATION | StatusBarManager.DISABLE_BACK)) != 0) {
+ setNavigationVisibility(state &
+ (StatusBarManager.DISABLE_NAVIGATION | StatusBarManager.DISABLE_BACK));
}
- if ((diff & StatusBarManager.DISABLE_BACK) != 0) {
- if ((state & StatusBarManager.DISABLE_BACK) != 0) {
- Slog.i(TAG, "DISABLE_BACK: yes");
- mBackButton.setEnabled(false);
- mInputMethodSwitchButton.setScreenLocked(true);
- } else {
- Slog.i(TAG, "DISABLE_BACK: no");
- mBackButton.setEnabled(true);
- mInputMethodSwitchButton.setScreenLocked(false);
- }
+ }
+
+ private void setNavigationVisibility(int visibility) {
+ boolean disableNavigation = ((visibility & StatusBarManager.DISABLE_NAVIGATION) != 0);
+ boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0);
+
+ Slog.i(TAG, "DISABLE_BACK: " + (disableBack ? "yes" : "no"));
+ Slog.i(TAG, "DISABLE_NAVIGATION: " + (disableNavigation ? "yes" : "no"));
+
+ if (disableNavigation && disableBack) {
+ mNavigationArea.setVisibility(View.INVISIBLE);
+ } else {
+ int backVisiblity = (disableBack ? View.INVISIBLE : View.VISIBLE);
+ int navVisibility = (disableNavigation ? View.INVISIBLE : View.VISIBLE);
+
+ mBackButton.setVisibility(backVisiblity);
+ mHomeButton.setVisibility(navVisibility);
+ mRecentButton.setVisibility(navVisibility);
+ // don't change menu button visibility here
+
+ mNavigationArea.setVisibility(View.VISIBLE);
}
+ mInputMethodSwitchButton.setScreenLocked(disableNavigation);
}
private boolean hasTicker(Notification n) {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
index 40cc7d8..8654a25 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
@@ -589,6 +589,11 @@
public void onPhoneStateChanged(String newState) {
updateEmergencyCallButtonState();
}
+
+ /** {@inheritDoc} */
+ public void onClockVisibilityChanged() {
+ // ignored
+ }
};
private SimStateCallback mSimStateCallback = new SimStateCallback() {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
index 2955de3..958f555 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
@@ -81,6 +81,8 @@
private int mFailedAttempts = 0;
+ private boolean mClockVisible;
+
private Handler mHandler;
private ArrayList<InfoCallback> mInfoCallbacks = Lists.newArrayList();
@@ -94,6 +96,7 @@
private static final int MSG_SIM_STATE_CHANGE = 304;
private static final int MSG_RINGER_MODE_CHANGED = 305;
private static final int MSG_PHONE_STATE_CHANGED = 306;
+ private static final int MSG_CLOCK_VISIBILITY_CHANGED = 307;
/**
* When we receive a
@@ -170,6 +173,9 @@
case MSG_PHONE_STATE_CHANGED:
handlePhoneStateChanged((String)msg.obj);
break;
+ case MSG_CLOCK_VISIBILITY_CHANGED:
+ handleClockVisibilityChanged();
+ break;
}
}
};
@@ -334,6 +340,13 @@
}
}
+ private void handleClockVisibilityChanged() {
+ if (DEBUG) Log.d(TAG, "handleClockVisibilityChanged()");
+ for (int i = 0; i < mInfoCallbacks.size(); i++) {
+ mInfoCallbacks.get(i).onClockVisibilityChanged();
+ }
+ }
+
/**
* @param status One of the statuses of {@link android.os.BatteryManager}
* @return Whether the status maps to a status for being plugged in.
@@ -448,6 +461,12 @@
*/
void onPhoneStateChanged(String newState);
+ /**
+ * Called when visibility of lockscreen clock changes, such as when
+ * obscured by a widget.
+ */
+ void onClockVisibilityChanged();
+
}
/**
@@ -484,6 +503,11 @@
}
}
+ public void reportClockVisible(boolean visible) {
+ mClockVisible = visible;
+ mHandler.obtainMessage(MSG_CLOCK_VISIBILITY_CHANGED).sendToTarget();
+ }
+
public IccCard.State getSimState() {
return mSimState;
}
@@ -546,4 +570,8 @@
mFailedAttempts++;
}
+ public boolean isClockVisible() {
+ return mClockVisible;
+ }
+
}
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
index 5661116..2a34f18 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -92,7 +92,7 @@
* thread of the keyguard.
*/
public class KeyguardViewMediator implements KeyguardViewCallback,
- KeyguardUpdateMonitor.SimStateCallback {
+ KeyguardUpdateMonitor.InfoCallback, KeyguardUpdateMonitor.SimStateCallback {
private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
private final static boolean DEBUG = false;
private final static boolean DBG_WAKE = false;
@@ -145,10 +145,10 @@
private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
/**
- * Allow the user to operate the status bar when the keyguard is engaged (without a pattern or
- * password).
+ * Allow the user to expand the status bar when the keyguard is engaged
+ * (without a pattern or password).
*/
- private static final boolean ENABLE_STATUS_BAR_IN_KEYGUARD = true;
+ private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true;
private Context mContext;
private AlarmManager mAlarmManager;
@@ -284,6 +284,7 @@
mUpdateMonitor = new KeyguardUpdateMonitor(context);
+ mUpdateMonitor.registerInfoCallback(this);
mUpdateMonitor.registerSimStateCallback(this);
mLockPatternUtils = new LockPatternUtils(mContext);
@@ -1184,19 +1185,32 @@
}
}
- // if the keyguard is shown, allow the status bar to open only if the keyguard is
- // insecure and (is covered by another window OR this feature is enabled in general)
- boolean enable = !mShowing
- || ((ENABLE_STATUS_BAR_IN_KEYGUARD || mHidden) && !isSecure());
- if (DEBUG) {
- Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mHidden=" + mHidden
- + " isSecure=" + isSecure() + " --> enable=" + enable);
+ int flags = StatusBarManager.DISABLE_NONE;
+ if (mShowing) {
+ // disable navigation status bar components if lock screen is up
+ flags |= StatusBarManager.DISABLE_NAVIGATION;
+ if (!mHidden) {
+ // showing lockscreen exclusively (no activities in front of it)
+ // disable back button too
+ flags |= StatusBarManager.DISABLE_BACK;
+ if (mUpdateMonitor.isClockVisible()) {
+ // lockscreen showing a clock, so hide statusbar clock
+ flags |= StatusBarManager.DISABLE_CLOCK;
+ }
+ }
+ if (isSecure() || !ENABLE_INSECURE_STATUS_BAR_EXPAND) {
+ // showing secure lockscreen; disable expanding.
+ flags |= StatusBarManager.DISABLE_EXPAND;
+ }
}
- mStatusBarManager.disable(enable ?
- StatusBarManager.DISABLE_NONE :
- ( StatusBarManager.DISABLE_EXPAND
- | StatusBarManager.DISABLE_NAVIGATION
- | StatusBarManager.DISABLE_CLOCK));
+
+ if (DEBUG) {
+ Log.d(TAG,
+ "adjustStatusBarLocked: mShowing=" + mShowing + " mHidden=" + mHidden
+ + " isSecure=" + isSecure() + " --> flags=" + flags);
+ }
+
+ mStatusBarManager.disable(flags);
}
}
@@ -1273,4 +1287,34 @@
mKeyguardViewManager.onScreenTurnedOn();
}
}
+
+ /** {@inheritDoc} */
+ public void onClockVisibilityChanged() {
+ adjustStatusBarLocked();
+ }
+
+ /** {@inheritDoc} */
+ public void onPhoneStateChanged(String newState) {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void onRingerModeChanged(int state) {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void onTimeChanged() {
+ // ignored
+ }
}
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 9c14734..62abf20 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -21,6 +21,7 @@
import com.android.internal.telephony.IccCard;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockScreenWidgetCallback;
+import com.android.internal.widget.LockScreenWidgetInterface;
import com.android.internal.widget.TransportControlView;
import android.accounts.Account;
@@ -191,11 +192,17 @@
public void requestShow(View view) {
if (DEBUG) Log.v(TAG, "View " + view + " requested show transports");
view.setVisibility(View.VISIBLE);
+
+ // TODO: examine all widgets to derive clock status
+ mUpdateMonitor.reportClockVisible(false);
}
public void requestHide(View view) {
if (DEBUG) Log.v(TAG, "View " + view + " requested hide transports");
view.setVisibility(View.GONE);
+
+ // TODO: examine all widgets to derive clock status
+ mUpdateMonitor.reportClockVisible(true);
}
};
@@ -743,6 +750,7 @@
if (tcv == null) {
if (DEBUG) Log.w(TAG, "Couldn't find transport control widget");
} else {
+ mUpdateMonitor.reportClockVisible(true);
tcv.setVisibility(View.GONE); // hide tcv until we get the callback below to show it.
tcv.setCallback(mWidgetCallback);
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index bbd5ded..c6eedb1 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -38,6 +38,7 @@
import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -266,6 +267,7 @@
int mLidOpen = LID_ABSENT;
boolean mSystemReady;
+ boolean mSystemBooted;
boolean mHdmiPlugged;
int mUiMode = Configuration.UI_MODE_TYPE_NORMAL;
int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -903,6 +905,7 @@
lp.setTitle("PointerLocation");
WindowManager wm = (WindowManager)
mContext.getSystemService(Context.WINDOW_SERVICE);
+ lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
wm.addView(addView, lp);
if (mPointerLocationInputChannel == null) {
@@ -2265,7 +2268,7 @@
// incorrectly think it does cover it when it doesn't. We'll revisit
// this later when we re-do the phone status bar.
if (mStatusBar != null && mStatusBar.isVisibleLw()) {
- Rect rect = new Rect(mStatusBar.getShownFrameLw());
+ RectF rect = new RectF(mStatusBar.getShownFrameLw());
for (int i=mStatusBarPanels.size()-1; i>=0; i--) {
WindowState w = mStatusBarPanels.get(i);
if (w.isVisibleLw()) {
@@ -2502,6 +2505,11 @@
mKeyguardMediator.isShowingAndNotHidden() :
mKeyguardMediator.isShowing()));
+ if (!mSystemBooted) {
+ // If we have not yet booted, don't let key events do anything.
+ return 0;
+ }
+
if (false) {
Log.d(TAG, "interceptKeyTq keycode=" + keyCode
+ " screenIsOn=" + isScreenOn + " keyguardActive=" + keyguardActive);
@@ -3119,6 +3127,13 @@
}
}
+ /** {@inheritDoc} */
+ public void systemBooted() {
+ synchronized (mLock) {
+ mSystemBooted = true;
+ }
+ }
+
ProgressDialog mBootMsgDialog = null;
/** {@inheritDoc} */
@@ -3514,7 +3529,8 @@
public void dump(String prefix, FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
- pw.print(" mSystemRead="); pw.println(mSystemReady);
+ pw.print(" mSystemReady="); pw.print(mSystemReady);
+ pw.print(" mSystemBooted="); pw.println(mSystemBooted);
pw.print(prefix); pw.print("mLidOpen="); pw.print(mLidOpen);
pw.print(" mLidOpenRotation="); pw.print(mLidOpenRotation);
pw.print(" mHdmiPlugged="); pw.println(mHdmiPlugged);
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index e193be0..b178fd9 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -769,11 +769,6 @@
status_t result = checkPidAndHardware();
if (result != NO_ERROR) return result;
- if (mHardware->recordingEnabled()) {
- LOGE("Cannot take picture during recording.");
- return INVALID_OPERATION;
- }
-
if ((msgType & CAMERA_MSG_RAW_IMAGE) &&
(msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) {
LOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY"
diff --git a/services/input/Android.mk b/services/input/Android.mk
index afbe546..86c6c8a 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -18,6 +18,7 @@
LOCAL_SRC_FILES:= \
EventHub.cpp \
+ InputApplication.cpp \
InputDispatcher.cpp \
InputListener.cpp \
InputManager.cpp \
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 06dea36..80ee28e 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -78,6 +78,40 @@
return value ? "true" : "false";
}
+// --- Global Functions ---
+
+uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) {
+ // Touch devices get dibs on touch-related axes.
+ if (deviceClasses & INPUT_DEVICE_CLASS_TOUCH) {
+ switch (axis) {
+ case ABS_X:
+ case ABS_Y:
+ case ABS_PRESSURE:
+ case ABS_TOOL_WIDTH:
+ case ABS_DISTANCE:
+ case ABS_TILT_X:
+ case ABS_TILT_Y:
+ case ABS_MT_SLOT:
+ case ABS_MT_TOUCH_MAJOR:
+ case ABS_MT_TOUCH_MINOR:
+ case ABS_MT_WIDTH_MAJOR:
+ case ABS_MT_WIDTH_MINOR:
+ case ABS_MT_ORIENTATION:
+ case ABS_MT_POSITION_X:
+ case ABS_MT_POSITION_Y:
+ case ABS_MT_TOOL_TYPE:
+ case ABS_MT_BLOB_ID:
+ case ABS_MT_TRACKING_ID:
+ case ABS_MT_PRESSURE:
+ case ABS_MT_DISTANCE:
+ return INPUT_DEVICE_CLASS_TOUCH;
+ }
+ }
+
+ // Joystick devices get the rest.
+ return deviceClasses & INPUT_DEVICE_CLASS_JOYSTICK;
+}
+
// --- EventHub::Device ---
EventHub::Device::Device(int fd, int32_t id, const String8& path,
@@ -936,13 +970,17 @@
}
// See if this device is a joystick.
- // Ignore touchscreens because they use the same absolute axes for other purposes.
// Assumes that joysticks always have gamepad buttons in order to distinguish them
// from other devices such as accelerometers that also have absolute axes.
- if (haveGamepadButtons
- && !(device->classes & INPUT_DEVICE_CLASS_TOUCH)
- && containsNonZeroByte(device->absBitmask, 0, sizeof_bit_array(ABS_MAX + 1))) {
- device->classes |= INPUT_DEVICE_CLASS_JOYSTICK;
+ if (haveGamepadButtons) {
+ uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK;
+ for (int i = 0; i <= ABS_MAX; i++) {
+ if (test_bit(i, device->absBitmask)
+ && (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ device->classes = assumedClasses;
+ break;
+ }
+ }
}
// Check whether this device has switches.
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index fae5d4f..d37549a 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -112,6 +112,12 @@
};
/*
+ * Gets the class that owns an axis, in cases where multiple classes might claim
+ * the same axis for different purposes.
+ */
+extern uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses);
+
+/*
* Grand Central Station for events.
*
* The event hub aggregates input events received across all known input
diff --git a/services/input/InputApplication.cpp b/services/input/InputApplication.cpp
new file mode 100644
index 0000000..a99e637
--- /dev/null
+++ b/services/input/InputApplication.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "InputApplication"
+
+#include "InputApplication.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+// --- InputApplicationHandle ---
+
+InputApplicationHandle::InputApplicationHandle() :
+ mInfo(NULL) {
+}
+
+InputApplicationHandle::~InputApplicationHandle() {
+ delete mInfo;
+}
+
+void InputApplicationHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
+} // namespace android
diff --git a/services/input/InputApplication.h b/services/input/InputApplication.h
index 8902f7a..67ae94b 100644
--- a/services/input/InputApplication.h
+++ b/services/input/InputApplication.h
@@ -27,14 +27,32 @@
/*
* Describes the properties of an application that can receive input.
+ */
+struct InputApplicationInfo {
+ String8 name;
+ nsecs_t dispatchingTimeout;
+};
+
+
+/*
+ * Handle for an application that can receive input.
*
* Used by the native input dispatcher as a handle for the window manager objects
* that describe an application.
*/
class InputApplicationHandle : public RefBase {
public:
- String8 name;
- nsecs_t dispatchingTimeout;
+ inline const InputApplicationInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
/**
* Requests that the state of this object be updated to reflect
@@ -45,11 +63,19 @@
*
* Returns true on success, or false if the handle is no longer valid.
*/
- virtual bool update() = 0;
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
protected:
- InputApplicationHandle() { }
- virtual ~InputApplicationHandle() { }
+ InputApplicationHandle();
+ virtual ~InputApplicationHandle();
+
+ InputApplicationInfo* mInfo;
};
} // namespace android
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index cf167ae..1eb5f0e 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -306,7 +306,12 @@
}
}
}
+
+ // Nothing to do if there is no pending event.
if (! mPendingEvent) {
+ if (mActiveConnections.isEmpty()) {
+ dispatchIdleLocked();
+ }
return;
}
} else {
@@ -462,6 +467,16 @@
}
}
+void InputDispatcher::dispatchIdleLocked() {
+#if DEBUG_FOCUS
+ LOGD("Dispatcher idle. There are no pending events or active connections.");
+#endif
+
+ // Reset targets when idle, to release input channels and other resources
+ // they are holding onto.
+ resetTargetsLocked();
+}
+
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
bool needWake = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(entry);
@@ -525,20 +540,21 @@
size_t numWindows = mWindowHandles.size();
for (size_t i = 0; i < numWindows; i++) {
sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
- int32_t flags = windowHandle->layoutParamsFlags;
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ int32_t flags = windowInfo->layoutParamsFlags;
- if (windowHandle->visible) {
- if (!(flags & InputWindowHandle::FLAG_NOT_TOUCHABLE)) {
- bool isTouchModal = (flags & (InputWindowHandle::FLAG_NOT_FOCUSABLE
- | InputWindowHandle::FLAG_NOT_TOUCH_MODAL)) == 0;
- if (isTouchModal || windowHandle->touchableRegionContainsPoint(x, y)) {
+ if (windowInfo->visible) {
+ if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
+ bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
+ | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
// Found window.
return windowHandle;
}
}
}
- if (flags & InputWindowHandle::FLAG_SYSTEM_ERROR) {
+ if (flags & InputWindowInfo::FLAG_SYSTEM_ERROR) {
// Error window is on top but not visible, so touch is dropped.
return NULL;
}
@@ -1051,9 +1067,15 @@
LOGD("Waiting for application to become ready for input: %s",
getApplicationWindowLabelLocked(applicationHandle, windowHandle).string());
#endif
- nsecs_t timeout = windowHandle != NULL ? windowHandle->dispatchingTimeout :
- applicationHandle != NULL ?
- applicationHandle->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+ nsecs_t timeout;
+ if (windowHandle != NULL) {
+ timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else if (applicationHandle != NULL) {
+ timeout = applicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else {
+ timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+ }
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
mInputTargetWaitStartTime = currentTime;
@@ -1168,7 +1190,7 @@
}
// If the currently focused window is paused then keep waiting.
- if (mFocusedWindowHandle->paused) {
+ if (mFocusedWindowHandle->getInfo()->paused) {
#if DEBUG_FOCUS
LOGD("Waiting because focused window is paused.");
#endif
@@ -1302,21 +1324,22 @@
size_t numWindows = mWindowHandles.size();
for (size_t i = 0; i < numWindows; i++) {
sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
- int32_t flags = windowHandle->layoutParamsFlags;
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ int32_t flags = windowInfo->layoutParamsFlags;
- if (flags & InputWindowHandle::FLAG_SYSTEM_ERROR) {
+ if (flags & InputWindowInfo::FLAG_SYSTEM_ERROR) {
if (topErrorWindowHandle == NULL) {
topErrorWindowHandle = windowHandle;
}
}
- if (windowHandle->visible) {
- if (! (flags & InputWindowHandle::FLAG_NOT_TOUCHABLE)) {
- isTouchModal = (flags & (InputWindowHandle::FLAG_NOT_FOCUSABLE
- | InputWindowHandle::FLAG_NOT_TOUCH_MODAL)) == 0;
- if (isTouchModal || windowHandle->touchableRegionContainsPoint(x, y)) {
+ if (windowInfo->visible) {
+ if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
+ isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
+ | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
if (! screenWasOff
- || (flags & InputWindowHandle::FLAG_TOUCHABLE_WHEN_WAKING)) {
+ || (flags & InputWindowInfo::FLAG_TOUCHABLE_WHEN_WAKING)) {
newTouchedWindowHandle = windowHandle;
}
break; // found touched window, exit window loop
@@ -1324,7 +1347,7 @@
}
if (maskedAction == AMOTION_EVENT_ACTION_DOWN
- && (flags & InputWindowHandle::FLAG_WATCH_OUTSIDE_TOUCH)) {
+ && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE;
if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
@@ -1350,7 +1373,8 @@
}
// Figure out whether splitting will be allowed for this window.
- if (newTouchedWindowHandle != NULL && newTouchedWindowHandle->supportsSplitTouch()) {
+ if (newTouchedWindowHandle != NULL
+ && newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
// New window supports splitting.
isSplit = true;
} else if (isSplit) {
@@ -1396,7 +1420,7 @@
// within the touched window.
if (!isTouchModal) {
while (sample->next) {
- if (!newHoverWindowHandle->touchableRegionContainsPoint(
+ if (!newHoverWindowHandle->getInfo()->touchableRegionContainsPoint(
sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X),
sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y))) {
*outSplitBatchAfterSample = sample;
@@ -1444,15 +1468,15 @@
&& newTouchedWindowHandle != NULL) {
#if DEBUG_FOCUS
LOGD("Touch is slipping out of window %s into window %s.",
- oldTouchedWindowHandle->name.string(),
- newTouchedWindowHandle->name.string());
+ oldTouchedWindowHandle->getName().string(),
+ newTouchedWindowHandle->getName().string());
#endif
// Make a slippery exit from the old window.
mTempTouchState.addOrUpdateWindow(oldTouchedWindowHandle,
InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT, BitSet32(0));
// Make a slippery entrance into the new window.
- if (newTouchedWindowHandle->supportsSplitTouch()) {
+ if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
isSplit = true;
}
@@ -1484,7 +1508,8 @@
// Let the previous window know that the hover sequence is over.
if (mLastHoverWindowHandle != NULL) {
#if DEBUG_HOVER
- LOGD("Sending hover exit event to window %s.", mLastHoverWindowHandle->name.string());
+ LOGD("Sending hover exit event to window %s.",
+ mLastHoverWindowHandle->getName().string());
#endif
mTempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
@@ -1493,7 +1518,8 @@
// Let the new window know that the hover sequence is starting.
if (newHoverWindowHandle != NULL) {
#if DEBUG_HOVER
- LOGD("Sending hover enter event to window %s.", newHoverWindowHandle->name.string());
+ LOGD("Sending hover enter event to window %s.",
+ newHoverWindowHandle->getName().string());
#endif
mTempTouchState.addOrUpdateWindow(newHoverWindowHandle,
InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, BitSet32(0));
@@ -1533,12 +1559,12 @@
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
sp<InputWindowHandle> foregroundWindowHandle =
mTempTouchState.getFirstForegroundWindowHandle();
- const int32_t foregroundWindowUid = foregroundWindowHandle->ownerUid;
+ const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
sp<InputWindowHandle> inputWindowHandle = touchedWindow.windowHandle;
- if (inputWindowHandle->ownerUid != foregroundWindowUid) {
+ if (inputWindowHandle->getInfo()->ownerUid != foregroundWindowUid) {
mTempTouchState.addOrUpdateWindow(inputWindowHandle,
InputTarget::FLAG_ZERO_COORDS, BitSet32(0));
}
@@ -1551,7 +1577,7 @@
const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
// If the touched window is paused then keep waiting.
- if (touchedWindow.windowHandle->paused) {
+ if (touchedWindow.windowHandle->getInfo()->paused) {
#if DEBUG_FOCUS
LOGD("Waiting because touched window is paused.");
#endif
@@ -1581,10 +1607,11 @@
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
sp<InputWindowHandle> foregroundWindowHandle =
mTempTouchState.getFirstForegroundWindowHandle();
- if (foregroundWindowHandle->hasWallpaper) {
+ if (foregroundWindowHandle->getInfo()->hasWallpaper) {
for (size_t i = 0; i < mWindowHandles.size(); i++) {
sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
- if (windowHandle->layoutParamsType == InputWindowHandle::TYPE_WALLPAPER) {
+ if (windowHandle->getInfo()->layoutParamsType
+ == InputWindowInfo::TYPE_WALLPAPER) {
mTempTouchState.addOrUpdateWindow(windowHandle,
InputTarget::FLAG_WINDOW_IS_OBSCURED
| InputTarget::FLAG_DISPATCH_AS_IS,
@@ -1708,12 +1735,13 @@
int32_t targetFlags, BitSet32 pointerIds) {
mCurrentInputTargets.push();
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
InputTarget& target = mCurrentInputTargets.editTop();
- target.inputChannel = windowHandle->inputChannel;
+ target.inputChannel = windowInfo->inputChannel;
target.flags = targetFlags;
- target.xOffset = - windowHandle->frameLeft;
- target.yOffset = - windowHandle->frameTop;
- target.scaleFactor = windowHandle->scaleFactor;
+ target.xOffset = - windowInfo->frameLeft;
+ target.yOffset = - windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
target.pointerIds = pointerIds;
}
@@ -1734,14 +1762,15 @@
bool InputDispatcher::checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
const InjectionState* injectionState) {
if (injectionState
- && (windowHandle == NULL || windowHandle->ownerUid != injectionState->injectorUid)
+ && (windowHandle == NULL
+ || windowHandle->getInfo()->ownerUid != injectionState->injectorUid)
&& !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
if (windowHandle != NULL) {
LOGW("Permission denied: injecting event from pid %d uid %d to window %s "
"owned by uid %d",
injectionState->injectorPid, injectionState->injectorUid,
- windowHandle->name.string(),
- windowHandle->ownerUid);
+ windowHandle->getName().string(),
+ windowHandle->getInfo()->ownerUid);
} else {
LOGW("Permission denied: injecting event from pid %d uid %d",
injectionState->injectorPid, injectionState->injectorUid);
@@ -1759,8 +1788,10 @@
if (otherHandle == windowHandle) {
break;
}
- if (otherHandle->visible && ! otherHandle->isTrustedOverlay()
- && otherHandle->frameContainsPoint(x, y)) {
+
+ const InputWindowInfo* otherInfo = otherHandle->getInfo();
+ if (otherInfo->visible && ! otherInfo->isTrustedOverlay()
+ && otherInfo->frameContainsPoint(x, y)) {
return true;
}
}
@@ -1769,7 +1800,7 @@
bool InputDispatcher::isWindowFinishedWithPreviousInputLocked(
const sp<InputWindowHandle>& windowHandle) {
- ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->inputChannel);
+ ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
return connection->outboundQueue.isEmpty();
@@ -1783,15 +1814,15 @@
const sp<InputWindowHandle>& windowHandle) {
if (applicationHandle != NULL) {
if (windowHandle != NULL) {
- String8 label(applicationHandle->name);
+ String8 label(applicationHandle->getName());
label.append(" - ");
- label.append(windowHandle->name);
+ label.append(windowHandle->getName());
return label;
} else {
- return applicationHandle->name;
+ return applicationHandle->getName();
}
} else if (windowHandle != NULL) {
- return windowHandle->name;
+ return windowHandle->getName();
} else {
return String8("<unknown application or window>");
}
@@ -2127,7 +2158,7 @@
if (status) {
LOGE("channel '%s' ~ Could not publish key event, "
"status=%d", connection->getInputChannelName(), status);
- abortBrokenDispatchCycleLocked(currentTime, connection);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
return;
}
break;
@@ -2190,7 +2221,7 @@
if (status) {
LOGE("channel '%s' ~ Could not publish motion event, "
"status=%d", connection->getInputChannelName(), status);
- abortBrokenDispatchCycleLocked(currentTime, connection);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
return;
}
@@ -2223,7 +2254,7 @@
LOGE("channel '%s' ~ Could not append motion sample "
"for a reason other than out of memory, status=%d",
connection->getInputChannelName(), status);
- abortBrokenDispatchCycleLocked(currentTime, connection);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
return;
}
}
@@ -2245,7 +2276,7 @@
if (status) {
LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
connection->getInputChannelName(), status);
- abortBrokenDispatchCycleLocked(currentTime, connection);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
return;
}
@@ -2280,7 +2311,7 @@
if (status) {
LOGE("channel '%s' ~ Could not reset publisher, status=%d",
connection->getInputChannelName(), status);
- abortBrokenDispatchCycleLocked(currentTime, connection);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
return;
}
@@ -2324,10 +2355,10 @@
}
void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection) {
+ const sp<Connection>& connection, bool notify) {
#if DEBUG_DISPATCH_CYCLE
- LOGD("channel '%s' ~ abortBrokenDispatchCycle",
- connection->getInputChannelName());
+ LOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
+ connection->getInputChannelName(), toString(notify));
#endif
// Clear the outbound queue.
@@ -2338,8 +2369,10 @@
if (connection->status == Connection::STATUS_NORMAL) {
connection->status = Connection::STATUS_BROKEN;
- // Notify other system components.
- onDispatchCycleBrokenLocked(currentTime, connection);
+ if (notify) {
+ // Notify other system components.
+ onDispatchCycleBrokenLocked(currentTime, connection);
+ }
}
}
@@ -2368,36 +2401,41 @@
return 0; // remove the callback
}
- nsecs_t currentTime = now();
-
+ bool notify;
sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
- if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
- LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. "
- "events=0x%x", connection->getInputChannelName(), events);
- d->abortBrokenDispatchCycleLocked(currentTime, connection);
- d->runCommandsLockedInterruptible();
- return 0; // remove the callback
- }
+ if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
+ if (!(events & ALOOPER_EVENT_INPUT)) {
+ LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ return 1;
+ }
- if (! (events & ALOOPER_EVENT_INPUT)) {
- LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
- "events=0x%x", connection->getInputChannelName(), events);
- return 1;
- }
+ bool handled = false;
+ status_t status = connection->inputPublisher.receiveFinishedSignal(&handled);
+ if (!status) {
+ nsecs_t currentTime = now();
+ d->finishDispatchCycleLocked(currentTime, connection, handled);
+ d->runCommandsLockedInterruptible();
+ return 1;
+ }
- bool handled = false;
- status_t status = connection->inputPublisher.receiveFinishedSignal(&handled);
- if (status) {
LOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
connection->getInputChannelName(), status);
- d->abortBrokenDispatchCycleLocked(currentTime, connection);
- d->runCommandsLockedInterruptible();
- return 0; // remove the callback
+ notify = true;
+ } else {
+ // Monitor channels are never explicitly unregistered.
+ // We do it automatically when the remote endpoint is closed so don't warn
+ // about them.
+ notify = !connection->monitor;
+ if (notify) {
+ LOGW("channel '%s' ~ Consumer closed input channel or an error occurred. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ }
}
- d->finishDispatchCycleLocked(currentTime, connection, handled);
- d->runCommandsLockedInterruptible();
- return 1;
+ // Unregister the channel.
+ d->unregisterInputChannelLocked(connection->inputChannel, notify);
+ return 0; // remove the callback
} // release lock
}
@@ -2450,9 +2488,10 @@
InputTarget target;
sp<InputWindowHandle> windowHandle = getWindowHandleLocked(connection->inputChannel);
if (windowHandle != NULL) {
- target.xOffset = -windowHandle->frameLeft;
- target.yOffset = -windowHandle->frameTop;
- target.scaleFactor = windowHandle->scaleFactor;
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ target.xOffset = -windowInfo->frameLeft;
+ target.yOffset = -windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
} else {
target.xOffset = 0;
target.yOffset = 0;
@@ -2854,9 +2893,9 @@
#if DEBUG_BATCHING
LOGD("Not streaming hover move because the last hovered window "
"is '%s' but the currently hovered window is '%s'.",
- mLastHoverWindowHandle->name.string(),
+ mLastHoverWindowHandle->getName().string(),
hoverWindowHandle != NULL
- ? hoverWindowHandle->name.string() : "<null>");
+ ? hoverWindowHandle->getName().string() : "<null>");
#endif
goto NoBatchingOrStreaming;
}
@@ -3183,7 +3222,7 @@
size_t numWindows = mWindowHandles.size();
for (size_t i = 0; i < numWindows; i++) {
const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
- if (windowHandle->inputChannel == inputChannel) {
+ if (windowHandle->getInputChannel() == inputChannel) {
return windowHandle;
}
}
@@ -3208,17 +3247,18 @@
{ // acquire lock
AutoMutex _l(mLock);
+ Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
mWindowHandles = inputWindowHandles;
sp<InputWindowHandle> newFocusedWindowHandle;
bool foundHoveredWindow = false;
for (size_t i = 0; i < mWindowHandles.size(); i++) {
const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
- if (!windowHandle->update() || windowHandle->inputChannel == NULL) {
+ if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {
mWindowHandles.removeAt(i--);
continue;
}
- if (windowHandle->hasFocus) {
+ if (windowHandle->getInfo()->hasFocus) {
newFocusedWindowHandle = windowHandle;
}
if (windowHandle == mLastHoverWindowHandle) {
@@ -3234,19 +3274,20 @@
if (mFocusedWindowHandle != NULL) {
#if DEBUG_FOCUS
LOGD("Focus left window: %s",
- mFocusedWindowHandle->name.string());
+ mFocusedWindowHandle->getName().string());
#endif
- if (mFocusedWindowHandle->inputChannel != NULL) {
+ sp<InputChannel> focusedInputChannel = mFocusedWindowHandle->getInputChannel();
+ if (focusedInputChannel != NULL) {
CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
"focus left window");
synthesizeCancelationEventsForInputChannelLocked(
- mFocusedWindowHandle->inputChannel, options);
+ focusedInputChannel, options);
}
}
if (newFocusedWindowHandle != NULL) {
#if DEBUG_FOCUS
LOGD("Focus entered window: %s",
- newFocusedWindowHandle->name.string());
+ newFocusedWindowHandle->getName().string());
#endif
}
mFocusedWindowHandle = newFocusedWindowHandle;
@@ -3256,17 +3297,34 @@
TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i);
if (!hasWindowHandleLocked(touchedWindow.windowHandle)) {
#if DEBUG_FOCUS
- LOGD("Touched window was removed: %s", touchedWindow.windowHandle->name.string());
+ LOGD("Touched window was removed: %s",
+ touchedWindow.windowHandle->getName().string());
#endif
- if (touchedWindow.windowHandle->inputChannel != NULL) {
+ sp<InputChannel> touchedInputChannel =
+ touchedWindow.windowHandle->getInputChannel();
+ if (touchedInputChannel != NULL) {
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
"touched window was removed");
synthesizeCancelationEventsForInputChannelLocked(
- touchedWindow.windowHandle->inputChannel, options);
+ touchedInputChannel, options);
}
mTouchState.windows.removeAt(i--);
}
}
+
+ // Release information for windows that are no longer present.
+ // This ensures that unused input channels are released promptly.
+ // Otherwise, they might stick around until the window handle is destroyed
+ // which might not happen until the next GC.
+ for (size_t i = 0; i < oldWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& oldWindowHandle = oldWindowHandles.itemAt(i);
+ if (!hasWindowHandleLocked(oldWindowHandle)) {
+#if DEBUG_FOCUS
+ LOGD("Window went away: %s", oldWindowHandle->getName().string());
+#endif
+ oldWindowHandle->releaseInfo();
+ }
+ }
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
@@ -3281,15 +3339,17 @@
{ // acquire lock
AutoMutex _l(mLock);
- if (inputApplicationHandle != NULL && inputApplicationHandle->update()) {
+ if (inputApplicationHandle != NULL && inputApplicationHandle->updateInfo()) {
if (mFocusedApplicationHandle != inputApplicationHandle) {
if (mFocusedApplicationHandle != NULL) {
resetTargetsLocked();
+ mFocusedApplicationHandle->releaseInfo();
}
mFocusedApplicationHandle = inputApplicationHandle;
}
} else if (mFocusedApplicationHandle != NULL) {
resetTargetsLocked();
+ mFocusedApplicationHandle->releaseInfo();
mFocusedApplicationHandle.clear();
}
@@ -3469,13 +3529,14 @@
if (mFocusedApplicationHandle != NULL) {
dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
- mFocusedApplicationHandle->name.string(),
- mFocusedApplicationHandle->dispatchingTimeout / 1000000.0);
+ mFocusedApplicationHandle->getName().string(),
+ mFocusedApplicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT) / 1000000.0);
} else {
dump.append(INDENT "FocusedApplication: <null>\n");
}
dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
- mFocusedWindowHandle != NULL ? mFocusedWindowHandle->name.string() : "<null>");
+ mFocusedWindowHandle != NULL ? mFocusedWindowHandle->getName().string() : "<null>");
dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down));
dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split));
@@ -3486,7 +3547,8 @@
for (size_t i = 0; i < mTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTouchState.windows[i];
dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
- i, touchedWindow.windowHandle->name.string(), touchedWindow.pointerIds.value,
+ i, touchedWindow.windowHandle->getName().string(),
+ touchedWindow.pointerIds.value,
touchedWindow.targetFlags);
}
} else {
@@ -3497,26 +3559,28 @@
dump.append(INDENT "Windows:\n");
for (size_t i = 0; i < mWindowHandles.size(); i++) {
const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+
dump.appendFormat(INDENT2 "%d: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
"visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
"frame=[%d,%d][%d,%d], scale=%f, "
"touchableRegion=",
- i, windowHandle->name.string(),
- toString(windowHandle->paused),
- toString(windowHandle->hasFocus),
- toString(windowHandle->hasWallpaper),
- toString(windowHandle->visible),
- toString(windowHandle->canReceiveKeys),
- windowHandle->layoutParamsFlags, windowHandle->layoutParamsType,
- windowHandle->layer,
- windowHandle->frameLeft, windowHandle->frameTop,
- windowHandle->frameRight, windowHandle->frameBottom,
- windowHandle->scaleFactor);
- dumpRegion(dump, windowHandle->touchableRegion);
- dump.appendFormat(", inputFeatures=0x%08x", windowHandle->inputFeatures);
+ i, windowInfo->name.string(),
+ toString(windowInfo->paused),
+ toString(windowInfo->hasFocus),
+ toString(windowInfo->hasWallpaper),
+ toString(windowInfo->visible),
+ toString(windowInfo->canReceiveKeys),
+ windowInfo->layoutParamsFlags, windowInfo->layoutParamsType,
+ windowInfo->layer,
+ windowInfo->frameLeft, windowInfo->frameTop,
+ windowInfo->frameRight, windowInfo->frameBottom,
+ windowInfo->scaleFactor);
+ dumpRegion(dump, windowInfo->touchableRegion);
+ dump.appendFormat(", inputFeatures=0x%08x", windowInfo->inputFeatures);
dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
- windowHandle->ownerPid, windowHandle->ownerUid,
- windowHandle->dispatchingTimeout / 1000000.0);
+ windowInfo->ownerPid, windowInfo->ownerUid,
+ windowInfo->dispatchingTimeout / 1000000.0);
}
} else {
dump.append(INDENT "Windows: <none>\n");
@@ -3572,7 +3636,7 @@
return BAD_VALUE;
}
- sp<Connection> connection = new Connection(inputChannel, inputWindowHandle);
+ sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
status_t status = connection->initialize();
if (status) {
LOGE("Failed to initialize input publisher for input channel '%s', status=%d",
@@ -3602,31 +3666,10 @@
{ // acquire lock
AutoMutex _l(mLock);
- ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
- if (connectionIndex < 0) {
- LOGW("Attempted to unregister already unregistered input channel '%s'",
- inputChannel->getName().string());
- return BAD_VALUE;
+ status_t status = unregisterInputChannelLocked(inputChannel, false /*notify*/);
+ if (status) {
+ return status;
}
-
- sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
- mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
-
- connection->status = Connection::STATUS_ZOMBIE;
-
- for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
- if (mMonitoringChannels[i] == inputChannel) {
- mMonitoringChannels.removeAt(i);
- break;
- }
- }
-
- mLooper->removeFd(inputChannel->getReceivePipeFd());
-
- nsecs_t currentTime = now();
- abortBrokenDispatchCycleLocked(currentTime, connection);
-
- runCommandsLockedInterruptible();
} // release lock
// Wake the poll loop because removing the connection may have changed the current
@@ -3635,6 +3678,42 @@
return OK;
}
+status_t InputDispatcher::unregisterInputChannelLocked(const sp<InputChannel>& inputChannel,
+ bool notify) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex < 0) {
+ LOGW("Attempted to unregister already unregistered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
+
+ if (connection->monitor) {
+ removeMonitorChannelLocked(inputChannel);
+ }
+
+ mLooper->removeFd(inputChannel->getReceivePipeFd());
+
+ nsecs_t currentTime = now();
+ abortBrokenDispatchCycleLocked(currentTime, connection, notify);
+
+ runCommandsLockedInterruptible();
+
+ connection->status = Connection::STATUS_ZOMBIE;
+ return OK;
+}
+
+void InputDispatcher::removeMonitorChannelLocked(const sp<InputChannel>& inputChannel) {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ if (mMonitoringChannels[i] == inputChannel) {
+ mMonitoringChannels.removeAt(i);
+ break;
+ }
+ }
+}
+
ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd());
if (connectionIndex >= 0) {
@@ -3736,7 +3815,7 @@
resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
commandEntry->inputWindowHandle != NULL
- ? commandEntry->inputWindowHandle->inputChannel : NULL);
+ ? commandEntry->inputWindowHandle->getInputChannel() : NULL);
}
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
@@ -4495,8 +4574,9 @@
// --- InputDispatcher::Connection ---
InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
- const sp<InputWindowHandle>& inputWindowHandle) :
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) :
status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
+ monitor(monitor),
inputPublisher(inputChannel),
lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) {
}
@@ -4627,8 +4707,9 @@
for (size_t i = 0; i < windows.size(); i++) {
const TouchedWindow& window = windows.itemAt(i);
if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
- if (haveSlipperyForegroundWindow || !(window.windowHandle->layoutParamsFlags
- & InputWindowHandle::FLAG_SLIPPERY)) {
+ if (haveSlipperyForegroundWindow
+ || !(window.windowHandle->getInfo()->layoutParamsFlags
+ & InputWindowInfo::FLAG_SLIPPERY)) {
return false;
}
haveSlipperyForegroundWindow = true;
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 3c83691..e78f7bd 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -814,6 +814,7 @@
Status status;
sp<InputChannel> inputChannel; // never null
sp<InputWindowHandle> inputWindowHandle; // may be null
+ bool monitor;
InputPublisher inputPublisher;
InputState inputState;
Queue<DispatchEntry> outboundQueue;
@@ -822,7 +823,7 @@
nsecs_t lastDispatchTime; // the time when the last event was dispatched
explicit Connection(const sp<InputChannel>& inputChannel,
- const sp<InputWindowHandle>& inputWindowHandle);
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
@@ -868,6 +869,7 @@
Vector<EventEntry*> mTempCancelationEvents;
void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime);
+ void dispatchIdleLocked();
// Batches a new sample onto a motion entry.
// Assumes that the we have already checked that we can append samples.
@@ -1073,7 +1075,8 @@
void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
bool handled);
void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
- void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ bool notify);
void drainOutboundQueueLocked(Connection* connection);
static int handleReceiveCallback(int receiveFd, int events, void* data);
@@ -1094,6 +1097,10 @@
void dumpDispatchStateLocked(String8& dump);
void logDispatchStateLocked();
+ // Registration.
+ void removeMonitorChannelLocked(const sp<InputChannel>& inputChannel);
+ status_t unregisterInputChannelLocked(const sp<InputChannel>& inputChannel, bool notify);
+
// Add or remove a connection to the mActiveConnections vector.
void activateConnectionLocked(Connection* connection);
void deactivateConnectionLocked(Connection* connection);
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index e39712e..88c19a4 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -390,7 +390,7 @@
InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
const String8& name, uint32_t classes) {
- InputDevice* device = new InputDevice(&mContext, deviceId, name);
+ InputDevice* device = new InputDevice(&mContext, deviceId, name, classes);
// External devices.
if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
@@ -842,9 +842,10 @@
// --- InputDevice ---
-InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name) :
- mContext(context), mId(id), mName(name), mSources(0),
- mIsExternal(false), mDropUntilNextSync(false) {
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name,
+ uint32_t classes) :
+ mContext(context), mId(id), mName(name), mClasses(classes),
+ mSources(0), mIsExternal(false), mDropUntilNextSync(false) {
}
InputDevice::~InputDevice() {
@@ -5759,6 +5760,11 @@
if (!changes) { // first time only
// Collect all axes.
for (int32_t abs = 0; abs <= ABS_MAX; abs++) {
+ if (!(getAbsAxisUsage(abs, getDevice()->getClasses())
+ & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ continue; // axis must be claimed by a different device
+ }
+
RawAbsoluteAxisInfo rawAxisInfo;
getAbsoluteAxisInfo(abs, &rawAxisInfo);
if (rawAxisInfo.valid) {
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index cd3ea37..a122c97 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -430,12 +430,13 @@
/* Represents the state of a single input device. */
class InputDevice {
public:
- InputDevice(InputReaderContext* context, int32_t id, const String8& name);
+ InputDevice(InputReaderContext* context, int32_t id, const String8& name, uint32_t classes);
~InputDevice();
inline InputReaderContext* getContext() { return mContext; }
inline int32_t getId() { return mId; }
inline const String8& getName() { return mName; }
+ inline uint32_t getClasses() { return mClasses; }
inline uint32_t getSources() { return mSources; }
inline bool isExternal() { return mIsExternal; }
@@ -483,10 +484,11 @@
private:
InputReaderContext* mContext;
int32_t mId;
+ String8 mName;
+ uint32_t mClasses;
Vector<InputMapper*> mMappers;
- String8 mName;
uint32_t mSources;
bool mIsExternal;
bool mDropUntilNextSync;
diff --git a/services/input/InputWindow.cpp b/services/input/InputWindow.cpp
index 0ce8867..fe61918 100644
--- a/services/input/InputWindow.cpp
+++ b/services/input/InputWindow.cpp
@@ -22,25 +22,43 @@
namespace android {
-// --- InputWindowHandle ---
+// --- InputWindowInfo ---
-bool InputWindowHandle::touchableRegionContainsPoint(int32_t x, int32_t y) const {
+bool InputWindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
return touchableRegion.contains(x, y);
}
-bool InputWindowHandle::frameContainsPoint(int32_t x, int32_t y) const {
+bool InputWindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
return x >= frameLeft && x <= frameRight
&& y >= frameTop && y <= frameBottom;
}
-bool InputWindowHandle::isTrustedOverlay() const {
+bool InputWindowInfo::isTrustedOverlay() const {
return layoutParamsType == TYPE_INPUT_METHOD
|| layoutParamsType == TYPE_INPUT_METHOD_DIALOG
|| layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
}
-bool InputWindowHandle::supportsSplitTouch() const {
+bool InputWindowInfo::supportsSplitTouch() const {
return layoutParamsFlags & FLAG_SPLIT_TOUCH;
}
+
+// --- InputWindowHandle ---
+
+InputWindowHandle::InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle) :
+ inputApplicationHandle(inputApplicationHandle), mInfo(NULL) {
+}
+
+InputWindowHandle::~InputWindowHandle() {
+ delete mInfo;
+}
+
+void InputWindowHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
} // namespace android
diff --git a/services/input/InputWindow.h b/services/input/InputWindow.h
index 272081c..8861bee 100644
--- a/services/input/InputWindow.h
+++ b/services/input/InputWindow.h
@@ -30,15 +30,9 @@
namespace android {
/*
- * A handle to a window that can receive input.
- *
- * Used by the native input dispatcher to indirectly refer to the window manager objects
- * that describe a window.
+ * Describes the properties of a window that can receive input.
*/
-class InputWindowHandle : public RefBase {
-public:
- const sp<InputApplicationHandle> inputApplicationHandle;
-
+struct InputWindowInfo {
// Window flags from WindowManager.LayoutParams
enum {
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
@@ -116,7 +110,6 @@
INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES = 0x00000001,
};
- sp<InputWindowHandle> inputWindowHandle;
sp<InputChannel> inputChannel;
String8 name;
int32_t layoutParamsFlags;
@@ -149,6 +142,34 @@
bool isTrustedOverlay() const;
bool supportsSplitTouch() const;
+};
+
+
+/*
+ * Handle for a window that can receive input.
+ *
+ * Used by the native input dispatcher to indirectly refer to the window manager objects
+ * that describe a window.
+ */
+class InputWindowHandle : public RefBase {
+public:
+ const sp<InputApplicationHandle> inputApplicationHandle;
+
+ inline const InputWindowInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline sp<InputChannel> getInputChannel() const {
+ return mInfo ? mInfo->inputChannel : NULL;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
/**
* Requests that the state of this object be updated to reflect
@@ -159,12 +180,19 @@
*
* Returns true on success, or false if the handle is no longer valid.
*/
- virtual bool update() = 0;
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
protected:
- InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle) :
- inputApplicationHandle(inputApplicationHandle) { }
- virtual ~InputWindowHandle() { }
+ InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle);
+ virtual ~InputWindowHandle();
+
+ InputWindowInfo* mInfo;
};
} // namespace android
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 32f948b..a086208 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -852,8 +852,8 @@
mNextDevice = device;
}
- InputDevice* newDevice(int32_t deviceId, const String8& name) {
- return new InputDevice(&mContext, deviceId, name);
+ InputDevice* newDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+ return new InputDevice(&mContext, deviceId, name, classes);
}
protected:
@@ -912,7 +912,7 @@
FakeInputMapper* addDeviceWithFakeInputMapper(int32_t deviceId,
const String8& name, uint32_t classes, uint32_t sources,
const PropertyMap* configuration) {
- InputDevice* device = mReader->newDevice(deviceId, name);
+ InputDevice* device = mReader->newDevice(deviceId, name, classes);
FakeInputMapper* mapper = new FakeInputMapper(device, sources);
device->addMapper(mapper);
mReader->setNextDevice(device);
@@ -1211,6 +1211,7 @@
protected:
static const char* DEVICE_NAME;
static const int32_t DEVICE_ID;
+ static const uint32_t DEVICE_CLASSES;
sp<FakeEventHub> mFakeEventHub;
sp<FakeInputReaderPolicy> mFakePolicy;
@@ -1226,7 +1227,7 @@
mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener);
mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
- mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME), DEVICE_CLASSES);
}
virtual void TearDown() {
@@ -1241,10 +1242,13 @@
const char* InputDeviceTest::DEVICE_NAME = "device";
const int32_t InputDeviceTest::DEVICE_ID = 1;
+const uint32_t InputDeviceTest::DEVICE_CLASSES = INPUT_DEVICE_CLASS_KEYBOARD
+ | INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_JOYSTICK;
TEST_F(InputDeviceTest, ImmutableProperties) {
ASSERT_EQ(DEVICE_ID, mDevice->getId());
ASSERT_STREQ(DEVICE_NAME, mDevice->getName());
+ ASSERT_EQ(DEVICE_CLASSES, mDevice->getClasses());
}
TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
@@ -1390,6 +1394,7 @@
protected:
static const char* DEVICE_NAME;
static const int32_t DEVICE_ID;
+ static const uint32_t DEVICE_CLASSES;
sp<FakeEventHub> mFakeEventHub;
sp<FakeInputReaderPolicy> mFakePolicy;
@@ -1402,7 +1407,7 @@
mFakePolicy = new FakeInputReaderPolicy();
mFakeListener = new FakeInputListener();
mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener);
- mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME), DEVICE_CLASSES);
mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
}
@@ -1483,6 +1488,7 @@
const char* InputMapperTest::DEVICE_NAME = "device";
const int32_t InputMapperTest::DEVICE_ID = 1;
+const uint32_t InputMapperTest::DEVICE_CLASSES = 0; // not needed for current tests
// --- SwitchInputMapperTest ---
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index b3309e5..997318a 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -197,14 +197,14 @@
= new SparseArray<HashSet<ApplicationInfo>>();
// set of backup services that have pending changes
class BackupRequest {
- public ApplicationInfo appInfo;
+ public String packageName;
- BackupRequest(ApplicationInfo app) {
- appInfo = app;
+ BackupRequest(String pkgName) {
+ packageName = pkgName;
}
public String toString() {
- return "BackupRequest{app=" + appInfo + "}";
+ return "BackupRequest{pkg=" + packageName + "}";
}
}
// Backups that we haven't started yet. Keys are package names.
@@ -877,6 +877,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addDataScheme("package");
mContext.registerReceiver(mBroadcastReceiver, filter);
// Register for events related to sdcard installation.
@@ -1174,7 +1175,8 @@
Bundle extras = intent.getExtras();
String pkgList[] = null;
if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
+ Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
Uri uri = intent.getData();
if (uri == null) {
return;
@@ -1183,8 +1185,14 @@
if (pkgName != null) {
pkgList = new String[] { pkgName };
}
- added = Intent.ACTION_PACKAGE_ADDED.equals(action);
- replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
+ if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+ // use the existing "add with replacement" logic
+ if (MORE_DEBUG) Slog.d(TAG, "PACKAGE_REPLACED, updating package " + pkgName);
+ added = replacing = true;
+ } else {
+ added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+ replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
+ }
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
added = true;
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
@@ -1192,6 +1200,7 @@
added = false;
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
}
+
if (pkgList == null || pkgList.length == 0) {
return;
}
@@ -1665,9 +1674,7 @@
if (status == BackupConstants.TRANSPORT_OK) {
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
mPackageManager, allAgentPackages());
- BackupRequest pmRequest = new BackupRequest(new ApplicationInfo());
- pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL;
- status = processOneBackup(pmRequest,
+ status = processOneBackup(PACKAGE_MANAGER_SENTINEL,
IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
}
@@ -1716,7 +1723,7 @@
if (status != BackupConstants.TRANSPORT_OK) {
Slog.w(TAG, "Backup pass unsuccessful, restaging");
for (BackupRequest req : mQueue) {
- dataChangedImpl(req.appInfo.packageName);
+ dataChangedImpl(req.packageName);
}
// We also want to reset the backup schedule based on whatever
@@ -1750,9 +1757,11 @@
// Verify that the requested app exists; it might be something that
// requested a backup but was then uninstalled. The request was
// journalled and rather than tamper with the journal it's safer
- // to sanity-check here.
+ // to sanity-check here. This also gives us the classname of the
+ // package's backup agent.
+ PackageInfo pkg;
try {
- mPackageManager.getPackageInfo(request.appInfo.packageName, 0);
+ pkg = mPackageManager.getPackageInfo(request.packageName, 0);
} catch (NameNotFoundException e) {
Slog.d(TAG, "Package does not exist; skipping");
continue;
@@ -1760,11 +1769,11 @@
IBackupAgent agent = null;
try {
- mWakelock.setWorkSource(new WorkSource(request.appInfo.uid));
- agent = bindToAgentSynchronous(request.appInfo,
+ mWakelock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
+ agent = bindToAgentSynchronous(pkg.applicationInfo,
IApplicationThread.BACKUP_MODE_INCREMENTAL);
if (agent != null) {
- int result = processOneBackup(request, agent, transport);
+ int result = processOneBackup(request.packageName, agent, transport);
if (result != BackupConstants.TRANSPORT_OK) return result;
}
} catch (SecurityException ex) {
@@ -1772,7 +1781,7 @@
Slog.d(TAG, "error in bind/backup", ex);
} finally {
try { // unbind even on timeout, just in case
- mActivityManager.unbindBackupAgent(request.appInfo);
+ mActivityManager.unbindBackupAgent(pkg.applicationInfo);
} catch (RemoteException e) {}
}
}
@@ -1782,9 +1791,8 @@
return BackupConstants.TRANSPORT_OK;
}
- private int processOneBackup(BackupRequest request, IBackupAgent agent,
+ private int processOneBackup(String packageName, IBackupAgent agent,
IBackupTransport transport) {
- final String packageName = request.appInfo.packageName;
if (DEBUG) Slog.d(TAG, "processOneBackup doBackup() on " + packageName);
File savedStateName = new File(mStateDir, packageName);
@@ -4207,7 +4215,7 @@
if (app.packageName.equals(packageName)) {
// Add the caller to the set of pending backups. If there is
// one already there, then overwrite it, but no harm done.
- BackupRequest req = new BackupRequest(app);
+ BackupRequest req = new BackupRequest(packageName);
if (mPendingBackups.put(app.packageName, req) == null) {
// Journal this request in case of crash. The put()
// operation returned null when this package was not already
@@ -4218,7 +4226,7 @@
int numKeys = mPendingBackups.size();
Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
for (BackupRequest b : mPendingBackups.values()) {
- Slog.d(TAG, " + " + b + " agent=" + b.appInfo.backupAgentName);
+ Slog.d(TAG, " + " + b);
}
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 3815c3b..2348d76 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -703,6 +703,12 @@
return result.toArray(new NetworkInfo[result.size()]);
}
+ @Override
+ public boolean isNetworkSupported(int networkType) {
+ enforceAccessPermission();
+ return (isNetworkTypeValid(networkType) && (mNetTrackers[networkType] != null));
+ }
+
/**
* Return LinkProperties for the active (i.e., connected) default
* network interface. It is assumed that at most one default network
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 5429c0c..f0b5958 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -137,3 +137,10 @@
# [ 8- 3] Detailed state ordinal (as defined by NetworkInfo.DetailedState)
# [ 2- 0] Network type (as defined by ConnectivityManager)
50020 connectivity_state_changed (custom|1|5)
+
+
+# ---------------------------
+# NetworkStatsService.java
+# ---------------------------
+51100 netstats_mobile_sample (iface_rx|2|2),(iface_tx|2|2),(uid_rx|2|2),(uid_tx|2|2)
+51101 netstats_wifi_sample (iface_rx|2|2),(iface_tx|2|2),(uid_rx|2|2),(uid_tx|2|2)
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 38bcebc..0e1a1e3 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -655,7 +655,7 @@
List<InputMethodSubtype> enabledSubtypes =
mSettings.getEnabledInputMethodSubtypeListLocked(imi);
if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
- enabledSubtypes = getApplicableSubtypesLocked(mRes, getSubtypes(imi));
+ enabledSubtypes = getImplicitlyApplicableSubtypesLocked(mRes, imi);
}
return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes);
}
@@ -1668,13 +1668,13 @@
}
@Override
- public boolean setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
// By this IPC call, only a process which shares the same uid with the IME can add
// additional input method subtypes to the IME.
- if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return false;
+ if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
synchronized (mMethodMap) {
final InputMethodInfo imi = mMethodMap.get(imiId);
- if (imi == null) return false;
+ if (imi == null) return;
final PackageManager pm = mContext.getPackageManager();
final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid());
if (packageInfos != null) {
@@ -1682,13 +1682,18 @@
for (int i = 0; i < packageNum; ++i) {
if (packageInfos[i].equals(imi.getPackageName())) {
mFileManager.addInputMethodSubtypes(imi, subtypes);
- buildInputMethodListLocked(mMethodList, mMethodMap);
- return true;
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ buildInputMethodListLocked(mMethodList, mMethodMap);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return;
}
}
}
}
- return false;
+ return;
}
private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
@@ -1707,7 +1712,7 @@
return;
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
setInputMethodLocked(id, subtypeId);
} finally {
@@ -1898,6 +1903,20 @@
return subtypes;
}
+
+ private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
+ InputMethodInfo imi, String mode) {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
+ subtypes.add(subtype);
+ }
+ }
+ return subtypes;
+ }
+
private boolean chooseNewDefaultIMELocked() {
List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
if (enabled != null && enabled.size() > 0) {
@@ -2352,8 +2371,9 @@
return NOT_A_SUBTYPE_ID;
}
- private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked(
- Resources res, List<InputMethodSubtype> subtypes) {
+ private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+ Resources res, InputMethodInfo imi) {
+ final List<InputMethodSubtype> subtypes = getSubtypes(imi);
final String systemLocale = res.getConfiguration().locale.toString();
if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
@@ -2361,6 +2381,19 @@
final int N = subtypes.size();
boolean containsKeyboardSubtype = false;
for (int i = 0; i < N; ++i) {
+ // scan overriding implicitly enabled subtypes.
+ InputMethodSubtype subtype = subtypes.get(i);
+ if (subtype.overridesImplicitlyEnabledSubtype()) {
+ final String mode = subtype.getMode();
+ if (!applicableModeAndSubtypesMap.containsKey(mode)) {
+ applicableModeAndSubtypesMap.put(mode, subtype);
+ }
+ }
+ }
+ if (applicableModeAndSubtypesMap.size() > 0) {
+ return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
+ }
+ for (int i = 0; i < N; ++i) {
InputMethodSubtype subtype = subtypes.get(i);
final String locale = subtype.getLocale();
final String mode = subtype.getMode();
@@ -2484,16 +2517,21 @@
subtype = findLastResortApplicableSubtypeLocked(
mRes, enabledSubtypes, mode, null, true);
}
+ final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
+ getOverridingImplicitlyEnabledSubtypes(imi, mode);
+ final ArrayList<InputMethodSubtype> subtypesForSearch =
+ overridingImplicitlyEnabledSubtypes.isEmpty()
+ ? getSubtypes(imi) : overridingImplicitlyEnabledSubtypes;
// 4. Search by the current subtype's locale from all subtypes.
if (subtype == null && mCurrentSubtype != null) {
subtype = findLastResortApplicableSubtypeLocked(
- mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false);
+ mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
}
// 5. Search by the system locale from all subtypes.
// 6. Search the first enabled subtype matched with mode from all subtypes.
if (subtype == null) {
subtype = findLastResortApplicableSubtypeLocked(
- mRes, getSubtypes(imi), mode, null, true);
+ mRes, subtypesForSearch, mode, null, true);
}
if (subtype != null) {
if (imiId.equals(mCurMethodId)) {
@@ -2940,12 +2978,12 @@
if (explicitlyEnabledSubtypes.size() == 0) {
// If there are no explicitly enabled subtypes, applicable subtypes are
// enabled implicitly.
- InputMethodInfo ime = mMethodMap.get(imeId);
+ InputMethodInfo imi = mMethodMap.get(imeId);
// If IME is enabled and no subtypes are enabled, applicable subtypes
// are enabled implicitly, so needs to treat them to be enabled.
- if (ime != null && ime.getSubtypeCount() > 0) {
+ if (imi != null && imi.getSubtypeCount() > 0) {
List<InputMethodSubtype> implicitlySelectedSubtypes =
- getApplicableSubtypesLocked(mRes, getSubtypes(ime));
+ getImplicitlyApplicableSubtypesLocked(mRes, imi);
if (implicitlySelectedSubtypes != null) {
final int N = implicitlySelectedSubtypes.size();
for (int i = 0; i < N; ++i) {
@@ -3065,17 +3103,11 @@
public void addInputMethodSubtypes(
InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
synchronized (mMethodMap) {
- final HashSet<InputMethodSubtype> existingSubtypes =
- new HashSet<InputMethodSubtype>();
- for (int i = 0; i < imi.getSubtypeCount(); ++i) {
- existingSubtypes.add(imi.getSubtypeAt(i));
- }
-
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
final int N = additionalSubtypes.length;
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = additionalSubtypes[i];
- if (!subtypes.contains(subtype) && !existingSubtypes.contains(subtype)) {
+ if (!subtypes.contains(subtype)) {
subtypes.add(subtype);
}
}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 85d8cece..1497511 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -1033,6 +1033,38 @@
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final HashSet<String> knownIfaces = Sets.newHashSet();
+ final HashSet<String> activeIfaces = Sets.newHashSet();
+
+ // collect any historical stats and active state
+ // TODO: migrate to reading from single file
+ if (mBandwidthControlEnabled) {
+ for (String iface : fileListWithoutNull(mStatsXtIface)) {
+ final File ifacePath = new File(mStatsXtIface, iface);
+
+ final long active = readSingleLongFromFile(new File(ifacePath, "active"));
+ if (active == 1) {
+ knownIfaces.add(iface);
+ activeIfaces.add(iface);
+ } else if (active == 0) {
+ knownIfaces.add(iface);
+ } else {
+ continue;
+ }
+
+ entry.iface = iface;
+ entry.uid = UID_ALL;
+ entry.set = SET_DEFAULT;
+ entry.tag = TAG_NONE;
+ entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes"));
+ entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets"));
+ entry.txBytes = readSingleLongFromFile(new File(ifacePath, "tx_bytes"));
+ entry.txPackets = readSingleLongFromFile(new File(ifacePath, "tx_packets"));
+
+ stats.addValues(entry);
+ }
+ }
+
final ArrayList<String> values = Lists.newArrayList();
BufferedReader reader = null;
@@ -1058,7 +1090,13 @@
entry.txBytes = Long.parseLong(values.get(9));
entry.txPackets = Long.parseLong(values.get(10));
- stats.addValues(entry);
+ if (activeIfaces.contains(entry.iface)) {
+ // combine stats when iface is active
+ stats.combineValues(entry);
+ } else if (!knownIfaces.contains(entry.iface)) {
+ // add stats when iface is unknown
+ stats.addValues(entry);
+ }
} catch (NumberFormatException e) {
Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
}
@@ -1073,24 +1111,6 @@
IoUtils.closeQuietly(reader);
}
- // splice in historical stats not reflected in mStatsIface
- if (mBandwidthControlEnabled) {
- for (String iface : fileListWithoutNull(mStatsXtIface)) {
- final File ifacePath = new File(mStatsXtIface, iface);
-
- entry.iface = iface;
- entry.uid = UID_ALL;
- entry.set = SET_DEFAULT;
- entry.tag = TAG_NONE;
- entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes"));
- entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets"));
- entry.txBytes = readSingleLongFromFile(new File(ifacePath, "tx_bytes"));
- entry.txPackets = readSingleLongFromFile(new File(ifacePath, "tx_packets"));
-
- stats.combineValues(entry);
- }
- }
-
return stats;
}
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index bd9df20..69cac48 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -856,6 +856,14 @@
if ((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) {
int oldWakeLockState = mWakeLockState;
mWakeLockState = mLocks.reactivateScreenLocksLocked();
+
+ // Disable proximity sensor if if user presses power key while we are in the
+ // "waiting for proximity sensor to go negative" state.
+ if ((mWakeLockState & SCREEN_ON_BIT) != 0
+ && mProximitySensorActive && mProximityWakeLockCount == 0) {
+ mProximitySensorActive = false;
+ }
+
if (mSpew) {
Slog.d(TAG, "wakeup here mUserState=0x" + Integer.toHexString(mUserState)
+ " mWakeLockState=0x"
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7a1bd0a..c6941dd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -352,7 +352,6 @@
Slog.i(TAG, "Wi-Fi Service");
wifi = new WifiService(context);
ServiceManager.addService(Context.WIFI_SERVICE, wifi);
- wifi.checkAndStartWifi();
} catch (Throwable e) {
reportWtf("starting Wi-Fi Service", e);
}
@@ -363,6 +362,7 @@
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
networkStats.bindConnectivityManager(connectivity);
networkPolicy.bindConnectivityManager(connectivity);
+ wifi.checkAndStartWifi();
wifiP2p.connectivityServiceReady();
} catch (Throwable e) {
reportWtf("starting Connectivity Service", e);
diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java
index f6c369e..c792b33 100644
--- a/services/java/com/android/server/TextServicesManagerService.java
+++ b/services/java/com/android/server/TextServicesManagerService.java
@@ -201,7 +201,7 @@
Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE);
if (DBG) {
- Slog.w(TAG, "getCurrentSpellChecker: " + subtypeHashCodeStr);
+ Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCodeStr);
}
final SpellCheckerInfo sci = getCurrentSpellChecker(null);
if (sci == null || sci.getSubtypeCount() == 0) {
@@ -509,7 +509,8 @@
listener.mScLocale, listener.mScListener, listener.mBundle);
listener.mTsListener.onServiceConnected(session);
} catch (RemoteException e) {
- Slog.e(TAG, "Exception in getting the spell checker session: " + e);
+ Slog.e(TAG, "Exception in getting the spell checker session."
+ + "Reconnect to the spellchecker. ", e);
removeAll();
return;
}
@@ -579,8 +580,12 @@
Slog.d(TAG, "cleanLocked");
}
if (mListeners.isEmpty()) {
- if (mSpellCheckerBindGroups.containsKey(this)) {
- mSpellCheckerBindGroups.remove(this);
+ final String sciId = mInternalConnection.mSciId;
+ if (mSpellCheckerBindGroups.containsKey(sciId)) {
+ if (DBG) {
+ Slog.d(TAG, "Remove bind group.");
+ }
+ mSpellCheckerBindGroups.remove(sciId);
}
// Unbind service when there is no active clients.
mContext.unbindService(mInternalConnection);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5aee39a..8589ac5 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1903,7 +1903,12 @@
}
boolean startHomeActivityLocked() {
- if (mHeadless) return false;
+ if (mHeadless) {
+ // Added because none of the other calls to ensureBootCompleted seem to fire
+ // when running headless.
+ ensureBootCompleted();
+ return false;
+ }
if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
@@ -3815,7 +3820,7 @@
synchronized (this) {
booting = mBooting;
mBooting = false;
- enableScreen = !mBooted;
+ enableScreen = !mBooted && !mHeadless;
mBooted = true;
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index e0dc96f..4d54fd4 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -31,6 +31,8 @@
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.UID_REMOVED;
import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
@@ -76,6 +78,7 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.util.EventLog;
import android.util.NtpTrustedTime;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -83,6 +86,7 @@
import com.android.internal.os.AtomicFile;
import com.android.internal.util.Objects;
+import com.android.server.EventLogTags;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
@@ -387,7 +391,9 @@
entry.uid = UID_ALL;
entry.tag = TAG_NONE;
entry.rxBytes = historyEntry.rxBytes;
+ entry.rxPackets = historyEntry.rxPackets;
entry.txBytes = historyEntry.txBytes;
+ entry.txPackets = historyEntry.txPackets;
stats.combineValues(entry);
}
@@ -716,6 +722,11 @@
Slog.v(TAG, "performPollLocked() took " + duration + "ms");
}
+ // sample stats after detailed poll
+ if (detailedPoll) {
+ performSample();
+ }
+
// finally, dispatch updated event to any listeners
final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -809,6 +820,33 @@
}
/**
+ * Sample recent statistics summary into {@link EventLog}.
+ */
+ private void performSample() {
+ // take sample as total over last 4 hours
+ final long end = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
+ final long start = end - (4 * HOUR_IN_MILLIS);
+
+ NetworkTemplate template = null;
+ NetworkStats.Entry ifaceTotal = null;
+ NetworkStats.Entry uidTotal = null;
+
+ // collect mobile sample
+ template = buildTemplateMobileAll(getActiveSubscriberId(mContext));
+ ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
+ uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
+ EventLogTags.writeNetstatsMobileSample(
+ ifaceTotal.rxBytes, ifaceTotal.txBytes, uidTotal.rxBytes, uidTotal.txBytes);
+
+ // collect wifi sample
+ template = buildTemplateWifi();
+ ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
+ uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
+ EventLogTags.writeNetstatsWifiSample(
+ ifaceTotal.rxBytes, ifaceTotal.txBytes, uidTotal.rxBytes, uidTotal.txBytes);
+ }
+
+ /**
* Clean up {@link #mUidStats} after UID is removed.
*/
private void removeUidLocked(int uid) {
@@ -1249,6 +1287,12 @@
}
};
+ private static String getActiveSubscriberId(Context context) {
+ final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ return telephony.getSubscriberId();
+ }
+
/**
* Key uniquely identifying a {@link NetworkStatsHistory} for a UID.
*/
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index b8797d1..bfb244b 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -78,8 +78,6 @@
import android.os.Environment;
import android.os.FileObserver;
import android.os.FileUtils;
-import android.os.FileUtils.FileStatus;
-import android.os.Debug;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -706,6 +704,7 @@
Runtime.getRuntime().gc();
}
if (msg.obj != null) {
+ @SuppressWarnings("unchecked")
Set<SdInstallArgs> args = (Set<SdInstallArgs>) msg.obj;
if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading all containers");
// Unload containers
@@ -3039,10 +3038,6 @@
return null;
}
mScanningPath = scanFile;
- if (pkg == null) {
- mLastScanError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
- return null;
- }
if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java
index 36f5dcb..10e294b 100644
--- a/services/java/com/android/server/wm/BlackFrame.java
+++ b/services/java/com/android/server/wm/BlackFrame.java
@@ -51,8 +51,8 @@
mTmpMatrix.setTranslate(left, top);
mTmpMatrix.postConcat(matrix);
mTmpMatrix.getValues(mTmpFloats);
- surface.setPosition((int)mTmpFloats[Matrix.MTRANS_X],
- (int)mTmpFloats[Matrix.MTRANS_Y]);
+ surface.setPosition(mTmpFloats[Matrix.MTRANS_X],
+ mTmpFloats[Matrix.MTRANS_Y]);
surface.setMatrix(
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java
index b37d1c2..dd440bf 100644
--- a/services/java/com/android/server/wm/DragState.java
+++ b/services/java/com/android/server/wm/DragState.java
@@ -139,6 +139,9 @@
mServerChannel.dispose();
mClientChannel = null;
mServerChannel = null;
+
+ mDragWindowHandle = null;
+ mDragApplicationHandle = null;
}
}
@@ -270,7 +273,7 @@
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw");
Surface.openTransaction();
try {
- mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY));
+ mSurface.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DRAG "
+ mSurface + ": pos=(" +
(int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
diff --git a/services/java/com/android/server/wm/InputApplicationHandle.java b/services/java/com/android/server/wm/InputApplicationHandle.java
index d78b1d9..1812f11 100644
--- a/services/java/com/android/server/wm/InputApplicationHandle.java
+++ b/services/java/com/android/server/wm/InputApplicationHandle.java
@@ -46,7 +46,10 @@
@Override
protected void finalize() throws Throwable {
- nativeDispose();
- super.finalize();
+ try {
+ nativeDispose();
+ } finally {
+ super.finalize();
+ }
}
}
diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java
index 12ef238..573a7d4 100644
--- a/services/java/com/android/server/wm/InputMonitor.java
+++ b/services/java/com/android/server/wm/InputMonitor.java
@@ -17,10 +17,10 @@
package com.android.server.wm;
import android.graphics.Rect;
-import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
+import android.view.InputChannel;
import android.view.KeyEvent;
import android.view.WindowManager;
@@ -160,13 +160,21 @@
if (WindowManagerService.DEBUG_DRAG) {
Log.d(WindowManagerService.TAG, "Inserting drag window");
}
- addInputWindowHandleLw(mService.mDragState.mDragWindowHandle);
+ final InputWindowHandle dragWindowHandle = mService.mDragState.mDragWindowHandle;
+ if (dragWindowHandle != null) {
+ addInputWindowHandleLw(dragWindowHandle);
+ } else {
+ Slog.w(WindowManagerService.TAG, "Drag is in progress but there is no "
+ + "drag window handle.");
+ }
}
final int N = windows.size();
for (int i = N - 1; i >= 0; i--) {
final WindowState child = windows.get(i);
- if (child.mInputChannel == null || child.mRemoved) {
+ final InputChannel inputChannel = child.mInputChannel;
+ final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;
+ if (inputChannel == null || inputWindowHandle == null || child.mRemoved) {
// Skip this window because it cannot possibly receive input.
continue;
}
@@ -186,8 +194,6 @@
}
// Add a window to our list of input windows.
- final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;
- inputWindowHandle.inputChannel = child.mInputChannel;
inputWindowHandle.name = child.toString();
inputWindowHandle.layoutParamsFlags = flags;
inputWindowHandle.layoutParamsType = type;
diff --git a/services/java/com/android/server/wm/InputWindowHandle.java b/services/java/com/android/server/wm/InputWindowHandle.java
index abf68d9..264877c 100644
--- a/services/java/com/android/server/wm/InputWindowHandle.java
+++ b/services/java/com/android/server/wm/InputWindowHandle.java
@@ -98,7 +98,10 @@
@Override
protected void finalize() throws Throwable {
- nativeDispose();
- super.finalize();
+ try {
+ nativeDispose();
+ } finally {
+ super.finalize();
+ }
}
}
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index 16af151..3c475a0 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -148,8 +148,8 @@
void setSnapshotTransform(Matrix matrix, float alpha) {
if (mSurface != null) {
matrix.getValues(mTmpFloats);
- mSurface.setPosition((int)mTmpFloats[Matrix.MTRANS_X],
- (int)mTmpFloats[Matrix.MTRANS_Y]);
+ mSurface.setPosition(mTmpFloats[Matrix.MTRANS_X],
+ mTmpFloats[Matrix.MTRANS_Y]);
mSurface.setMatrix(
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java
index 50b251f..2e0c9ab 100644
--- a/services/java/com/android/server/wm/Session.java
+++ b/services/java/com/android/server/wm/Session.java
@@ -281,8 +281,8 @@
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION performDrag");
Surface.openTransaction();
try {
- surface.setPosition((int)(touchX - thumbCenterX),
- (int)(touchY - thumbCenterY));
+ surface.setPosition(touchX - thumbCenterX,
+ touchY - thumbCenterY);
surface.setAlpha(.7071f);
surface.setLayer(mService.mDragState.getDragLayerLw());
surface.show();
diff --git a/services/java/com/android/server/wm/ViewServer.java b/services/java/com/android/server/wm/ViewServer.java
index 70cb26a..a763e2c 100644
--- a/services/java/com/android/server/wm/ViewServer.java
+++ b/services/java/com/android/server/wm/ViewServer.java
@@ -319,7 +319,7 @@
}
}
} catch (Exception e) {
- Slog.w(LOG_TAG, "Connection error: ", e);
+ // Ignore
} finally {
if (out != null) {
try {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index a148924..54caaa9 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -2055,10 +2055,11 @@
return res;
}
- if (outInputChannel != null) {
+ if (outInputChannel != null && (attrs.inputFeatures
+ & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
String name = win.makeInputChannelName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
- win.mInputChannel = inputChannels[0];
+ win.setInputChannel(inputChannels[0]);
inputChannels[1].transferTo(outInputChannel);
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
@@ -2501,6 +2502,10 @@
int attrChanges = 0;
int flagChanges = 0;
if (attrs != null) {
+ if (win.mAttrs.type != attrs.type) {
+ throw new IllegalArgumentException(
+ "Window type can not be changed after the window is added.");
+ }
flagChanges = win.mAttrs.flags ^= attrs.flags;
attrChanges = win.mAttrs.copyFrom(attrs);
}
@@ -4706,6 +4711,8 @@
mH.sendMessageDelayed(msg, 30*1000);
}
+ mPolicy.systemBooted();
+
performEnableScreen();
}
@@ -7926,13 +7933,13 @@
if (mWindowDetachedWallpaper != windowDetachedWallpaper) {
if (DEBUG_WALLPAPER) Slog.v(TAG,
"Detached wallpaper changed from " + mWindowDetachedWallpaper
- + windowDetachedWallpaper);
+ + " to " + windowDetachedWallpaper);
mWindowDetachedWallpaper = windowDetachedWallpaper;
wallpaperMayChange = true;
}
if (windowAnimationBackgroundColor != 0) {
- // If this window that wants black is the current wallpaper
+ // If the window that wants black is the current wallpaper
// target, then the black goes *below* the wallpaper so we
// don't cause the wallpaper to suddenly disappear.
WindowState target = windowAnimationBackground;
@@ -8460,8 +8467,8 @@
+ Integer.toHexString(diff));
}
win.mConfiguration = mCurConfiguration;
- win.mClient.resized(win.mSurfaceW, win.mSurfaceH, win.mLastContentInsets,
- win.mLastVisibleInsets, win.mDrawPending,
+ win.mClient.resized((int)win.mSurfaceW, (int)win.mSurfaceH,
+ win.mLastContentInsets, win.mLastVisibleInsets, win.mDrawPending,
configChanged ? win.mConfiguration : null);
win.mContentInsetsChanged = false;
win.mVisibleInsetsChanged = false;
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index cdd0047..bb36d3a 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -30,6 +30,7 @@
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.os.IBinder;
import android.os.RemoteException;
@@ -110,7 +111,7 @@
* are in the screen's coordinate space (WITH the compatibility scale
* applied).
*/
- final Rect mShownFrame = new Rect();
+ final RectF mShownFrame = new RectF();
/**
* Set when we have changed the size of the surface, to know that
@@ -267,7 +268,7 @@
// For debugging, this is the last information given to the surface flinger.
boolean mSurfaceShown;
- int mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH;
+ float mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH;
int mSurfaceLayer;
float mSurfaceAlpha;
@@ -518,7 +519,7 @@
return mFrame;
}
- public Rect getShownFrameLw() {
+ public RectF getShownFrameLw() {
return mShownFrame;
}
@@ -1128,8 +1129,8 @@
mDtDx = tmpFloats[Matrix.MSKEW_Y];
mDsDy = tmpFloats[Matrix.MSKEW_X];
mDtDy = tmpFloats[Matrix.MSCALE_Y];
- int x = (int)tmpFloats[Matrix.MTRANS_X];
- int y = (int)tmpFloats[Matrix.MTRANS_Y];
+ float x = tmpFloats[Matrix.MTRANS_X];
+ float y = tmpFloats[Matrix.MTRANS_Y];
int w = frame.width();
int h = frame.height();
mShownFrame.set(x, y, x+w, y+h);
@@ -1236,7 +1237,8 @@
* Input Manager uses when discarding windows from input consideration.
*/
boolean isPotentialDragTarget() {
- return isVisibleNow() && (mInputChannel != null) && !mRemoved;
+ return isVisibleNow() && !mRemoved
+ && mInputChannel != null && mInputWindowHandle != null;
}
/**
@@ -1372,7 +1374,16 @@
// we are doing this as part of processing a death note.)
}
}
-
+
+ void setInputChannel(InputChannel inputChannel) {
+ if (mInputChannel != null) {
+ throw new IllegalStateException("Window already has an input channel.");
+ }
+
+ mInputChannel = inputChannel;
+ mInputWindowHandle.inputChannel = inputChannel;
+ }
+
void disposeInputChannel() {
if (mInputChannel != null) {
mService.mInputManager.unregisterInputChannel(mInputChannel);
@@ -1380,6 +1391,8 @@
mInputChannel.dispose();
mInputChannel = null;
}
+
+ mInputWindowHandle.inputChannel = null;
}
private class DeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/jni/com_android_server_InputApplicationHandle.cpp b/services/jni/com_android_server_InputApplicationHandle.cpp
index 7de67d9..c76ab53 100644
--- a/services/jni/com_android_server_InputApplicationHandle.cpp
+++ b/services/jni/com_android_server_InputApplicationHandle.cpp
@@ -49,25 +49,30 @@
return env->NewLocalRef(mObjWeak);
}
-bool NativeInputApplicationHandle::update() {
+bool NativeInputApplicationHandle::updateInfo() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jobject obj = env->NewLocalRef(mObjWeak);
if (!obj) {
+ releaseInfo();
return false;
}
+ if (!mInfo) {
+ mInfo = new InputApplicationInfo();
+ }
+
jstring nameObj = jstring(env->GetObjectField(obj,
gInputApplicationHandleClassInfo.name));
if (nameObj) {
const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
- name.setTo(nameStr);
+ mInfo->name.setTo(nameStr);
env->ReleaseStringUTFChars(nameObj, nameStr);
env->DeleteLocalRef(nameObj);
} else {
- name.setTo("<null>");
+ mInfo->name.setTo("<null>");
}
- dispatchingTimeout = env->GetLongField(obj,
+ mInfo->dispatchingTimeout = env->GetLongField(obj,
gInputApplicationHandleClassInfo.dispatchingTimeoutNanos);
env->DeleteLocalRef(obj);
diff --git a/services/jni/com_android_server_InputApplicationHandle.h b/services/jni/com_android_server_InputApplicationHandle.h
index 04cd9d6..89d48c6 100644
--- a/services/jni/com_android_server_InputApplicationHandle.h
+++ b/services/jni/com_android_server_InputApplicationHandle.h
@@ -31,7 +31,7 @@
jobject getInputApplicationHandleObjLocalRef(JNIEnv* env);
- virtual bool update();
+ virtual bool updateInfo();
private:
jweak mObjWeak;
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 0a723e8..f976301 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -618,8 +618,9 @@
size_t numWindows = windowHandles.size();
for (size_t i = 0; i < numWindows; i++) {
const sp<InputWindowHandle>& windowHandle = windowHandles.itemAt(i);
- if (windowHandle->hasFocus && (windowHandle->inputFeatures
- & InputWindowHandle::INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES)) {
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ if (windowInfo && windowInfo->hasFocus && (windowInfo->inputFeatures
+ & InputWindowInfo::INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES)) {
newPointerGesturesEnabled = false;
}
}
@@ -1086,8 +1087,9 @@
status_t status = gNativeInputManager->registerInputChannel(
env, inputChannel, inputWindowHandle, monitor);
if (status) {
- jniThrowRuntimeException(env, "Failed to register input channel. "
- "Check logs for details.");
+ String8 message;
+ message.appendFormat("Failed to register input channel. status=%d", status);
+ jniThrowRuntimeException(env, message.string());
return;
}
@@ -1113,9 +1115,10 @@
android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
status_t status = gNativeInputManager->unregisterInputChannel(env, inputChannel);
- if (status) {
- jniThrowRuntimeException(env, "Failed to unregister input channel. "
- "Check logs for details.");
+ if (status && status != BAD_VALUE) { // ignore already unregistered channel
+ String8 message;
+ message.appendFormat("Failed to unregister input channel. status=%d", status);
+ jniThrowRuntimeException(env, message.string());
}
}
diff --git a/services/jni/com_android_server_InputWindowHandle.cpp b/services/jni/com_android_server_InputWindowHandle.cpp
index 09be881..0607eee 100644
--- a/services/jni/com_android_server_InputWindowHandle.cpp
+++ b/services/jni/com_android_server_InputWindowHandle.cpp
@@ -74,77 +74,82 @@
return env->NewLocalRef(mObjWeak);
}
-bool NativeInputWindowHandle::update() {
+bool NativeInputWindowHandle::updateInfo() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jobject obj = env->NewLocalRef(mObjWeak);
if (!obj) {
+ releaseInfo();
return false;
}
+ if (!mInfo) {
+ mInfo = new InputWindowInfo();
+ }
+
jobject inputChannelObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.inputChannel);
if (inputChannelObj) {
- inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj);
+ mInfo->inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj);
env->DeleteLocalRef(inputChannelObj);
} else {
- inputChannel = NULL;
+ mInfo->inputChannel.clear();
}
jstring nameObj = jstring(env->GetObjectField(obj,
gInputWindowHandleClassInfo.name));
if (nameObj) {
const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
- name.setTo(nameStr);
+ mInfo->name.setTo(nameStr);
env->ReleaseStringUTFChars(nameObj, nameStr);
env->DeleteLocalRef(nameObj);
} else {
- name.setTo("<null>");
+ mInfo->name.setTo("<null>");
}
- layoutParamsFlags = env->GetIntField(obj,
+ mInfo->layoutParamsFlags = env->GetIntField(obj,
gInputWindowHandleClassInfo.layoutParamsFlags);
- layoutParamsType = env->GetIntField(obj,
+ mInfo->layoutParamsType = env->GetIntField(obj,
gInputWindowHandleClassInfo.layoutParamsType);
- dispatchingTimeout = env->GetLongField(obj,
+ mInfo->dispatchingTimeout = env->GetLongField(obj,
gInputWindowHandleClassInfo.dispatchingTimeoutNanos);
- frameLeft = env->GetIntField(obj,
+ mInfo->frameLeft = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameLeft);
- frameTop = env->GetIntField(obj,
+ mInfo->frameTop = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameTop);
- frameRight = env->GetIntField(obj,
+ mInfo->frameRight = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameRight);
- frameBottom = env->GetIntField(obj,
+ mInfo->frameBottom = env->GetIntField(obj,
gInputWindowHandleClassInfo.frameBottom);
- scaleFactor = env->GetFloatField(obj,
+ mInfo->scaleFactor = env->GetFloatField(obj,
gInputWindowHandleClassInfo.scaleFactor);
jobject regionObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.touchableRegion);
if (regionObj) {
SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
- touchableRegion.set(*region);
+ mInfo->touchableRegion.set(*region);
env->DeleteLocalRef(regionObj);
} else {
- touchableRegion.setEmpty();
+ mInfo->touchableRegion.setEmpty();
}
- visible = env->GetBooleanField(obj,
+ mInfo->visible = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.visible);
- canReceiveKeys = env->GetBooleanField(obj,
+ mInfo->canReceiveKeys = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.canReceiveKeys);
- hasFocus = env->GetBooleanField(obj,
+ mInfo->hasFocus = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.hasFocus);
- hasWallpaper = env->GetBooleanField(obj,
+ mInfo->hasWallpaper = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.hasWallpaper);
- paused = env->GetBooleanField(obj,
+ mInfo->paused = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.paused);
- layer = env->GetIntField(obj,
+ mInfo->layer = env->GetIntField(obj,
gInputWindowHandleClassInfo.layer);
- ownerPid = env->GetIntField(obj,
+ mInfo->ownerPid = env->GetIntField(obj,
gInputWindowHandleClassInfo.ownerPid);
- ownerUid = env->GetIntField(obj,
+ mInfo->ownerUid = env->GetIntField(obj,
gInputWindowHandleClassInfo.ownerUid);
- inputFeatures = env->GetIntField(obj,
+ mInfo->inputFeatures = env->GetIntField(obj,
gInputWindowHandleClassInfo.inputFeatures);
env->DeleteLocalRef(obj);
diff --git a/services/jni/com_android_server_InputWindowHandle.h b/services/jni/com_android_server_InputWindowHandle.h
index 913c3b1..2cfa17d3 100644
--- a/services/jni/com_android_server_InputWindowHandle.h
+++ b/services/jni/com_android_server_InputWindowHandle.h
@@ -32,7 +32,7 @@
jobject getInputWindowHandleObjLocalRef(JNIEnv* env);
- virtual bool update();
+ virtual bool updateInfo();
private:
jweak mObjWeak;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index f8925b8..edbc7b0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -212,6 +212,24 @@
} else {
hwcl->transform = finalTransform;
}
+
+ if (isCropped()) {
+ hwcl->sourceCrop.left = mCurrentCrop.left;
+ hwcl->sourceCrop.top = mCurrentCrop.top;
+ hwcl->sourceCrop.right = mCurrentCrop.right;
+ hwcl->sourceCrop.bottom = mCurrentCrop.bottom;
+ } else {
+ const sp<GraphicBuffer>& buffer(mActiveBuffer);
+ hwcl->sourceCrop.left = 0;
+ hwcl->sourceCrop.top = 0;
+ if (buffer != NULL) {
+ hwcl->sourceCrop.right = buffer->width;
+ hwcl->sourceCrop.bottom = buffer->height;
+ } else {
+ hwcl->sourceCrop.right = mTransformedBounds.width();
+ hwcl->sourceCrop.bottom = mTransformedBounds.height();
+ }
+ }
}
void Layer::setPerFrameData(hwc_layer_t* hwcl) {
@@ -225,23 +243,6 @@
} else {
hwcl->handle = buffer->handle;
}
-
- if (isCropped()) {
- hwcl->sourceCrop.left = mCurrentCrop.left;
- hwcl->sourceCrop.top = mCurrentCrop.top;
- hwcl->sourceCrop.right = mCurrentCrop.right;
- hwcl->sourceCrop.bottom = mCurrentCrop.bottom;
- } else {
- hwcl->sourceCrop.left = 0;
- hwcl->sourceCrop.top = 0;
- if (buffer != NULL) {
- hwcl->sourceCrop.right = buffer->width;
- hwcl->sourceCrop.bottom = buffer->height;
- } else {
- hwcl->sourceCrop.right = mTransformedBounds.width();
- hwcl->sourceCrop.bottom = mTransformedBounds.height();
- }
- }
}
void Layer::onDraw(const Region& clip) const
@@ -416,8 +417,7 @@
return;
}
- mActiveBuffer = mSurfaceTexture->getCurrentBuffer();
- mSurfaceTexture->getTransformMatrix(mTextureMatrix);
+ sp<GraphicBuffer> newFrontBuffer(mSurfaceTexture->getCurrentBuffer());
const Rect crop(mSurfaceTexture->getCurrentCrop());
const uint32_t transform(mSurfaceTexture->getCurrentTransform());
@@ -432,7 +432,23 @@
mFlinger->invalidateHwcGeometry();
}
- mCurrentOpacity = getOpacityForFormat(mActiveBuffer->format);
+ GLfloat textureMatrix[16];
+ mSurfaceTexture->getTransformMatrix(textureMatrix);
+ if (memcmp(textureMatrix, mTextureMatrix, sizeof(textureMatrix))) {
+ memcpy(mTextureMatrix, textureMatrix, sizeof(textureMatrix));
+ mFlinger->invalidateHwcGeometry();
+ }
+
+ uint32_t bufWidth = newFrontBuffer->getWidth();
+ uint32_t bufHeight = newFrontBuffer->getHeight();
+ if (mActiveBuffer != NULL) {
+ if (bufWidth != uint32_t(mActiveBuffer->width) ||
+ bufHeight != uint32_t(mActiveBuffer->height)) {
+ mFlinger->invalidateHwcGeometry();
+ }
+ }
+
+ mCurrentOpacity = getOpacityForFormat(newFrontBuffer->format);
if (oldOpacity != isOpaque()) {
recomputeVisibleRegions = true;
}
@@ -446,15 +462,14 @@
// FIXME: mPostedDirtyRegion = dirty & bounds
mPostedDirtyRegion.set(front.w, front.h);
+ // update active buffer
+ mActiveBuffer = newFrontBuffer;
if ((front.w != front.requested_w) ||
(front.h != front.requested_h))
{
// check that we received a buffer of the right size
// (Take the buffer's orientation into account)
- sp<GraphicBuffer> newFrontBuffer(mActiveBuffer);
- uint32_t bufWidth = newFrontBuffer->getWidth();
- uint32_t bufHeight = newFrontBuffer->getHeight();
if (mCurrentTransform & Transform::ROT_90) {
swap(bufWidth, bufHeight);
}
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 4cc245a..603fb60 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -45,7 +45,6 @@
mFlinger(flinger), mFiltering(false),
mNeedsFiltering(false),
mOrientation(0),
- mLeft(0), mTop(0),
mTransactionFlags(0),
mPremultipliedAlpha(true), mName("unnamed"), mDebug(false),
mInvalidate(0)
@@ -119,7 +118,7 @@
return android_atomic_or(flags, &mTransactionFlags);
}
-bool LayerBase::setPosition(int32_t x, int32_t y) {
+bool LayerBase::setPosition(float x, float y) {
if (mCurrentState.transform.tx() == x && mCurrentState.transform.ty() == y)
return false;
mCurrentState.sequence++;
@@ -259,8 +258,6 @@
mOrientation = tr.getOrientation();
mTransform = tr;
mTransformedBounds = tr.makeBounds(w, h);
- mLeft = tr.tx();
- mTop = tr.ty();
}
void LayerBase::lockPageFlip(bool& recomputeVisibleRegions)
@@ -335,15 +332,16 @@
reinterpret_cast<hwc_rect_t const *>(
visibleRegionScreen.getArray(
&hwcl->visibleRegionScreen.numRects));
+
+ hwcl->sourceCrop.left = 0;
+ hwcl->sourceCrop.top = 0;
+ hwcl->sourceCrop.right = mTransformedBounds.width();
+ hwcl->sourceCrop.bottom = mTransformedBounds.height();
}
void LayerBase::setPerFrameData(hwc_layer_t* hwcl) {
hwcl->compositionType = HWC_FRAMEBUFFER;
hwcl->handle = NULL;
- hwcl->sourceCrop.left = 0;
- hwcl->sourceCrop.top = 0;
- hwcl->sourceCrop.right = mTransformedBounds.width();
- hwcl->sourceCrop.bottom = mTransformedBounds.height();
}
void LayerBase::setFiltering(bool filtering)
@@ -476,10 +474,10 @@
snprintf(buffer, SIZE,
"+ %s %p\n"
" "
- "z=%9d, pos=(%4d,%4d), size=(%4d,%4d), "
+ "z=%9d, pos=(%g,%g), size=(%4d,%4d), "
"isOpaque=%1d, needsDithering=%1d, invalidate=%1d, "
"alpha=0x%02x, flags=0x%08x, tr=[%.2f, %.2f][%.2f, %.2f]\n",
- getTypeId(), this, s.z, tx(), ty(), s.w, s.h,
+ getTypeId(), this, s.z, s.transform.tx(), s.transform.ty(), s.w, s.h,
isOpaque(), needsDithering(), contentDirty,
s.alpha, s.flags,
s.transform[0][0], s.transform[0][1],
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index 2cd3a94..d20f06a 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -84,7 +84,7 @@
String8 getName() const;
// modify current state
- bool setPosition(int32_t x, int32_t y);
+ bool setPosition(float x, float y);
bool setLayer(uint32_t z);
bool setSize(uint32_t w, uint32_t h);
bool setAlpha(uint8_t alpha);
@@ -217,8 +217,6 @@
inline State& currentState() { return mCurrentState; }
int32_t getOrientation() const { return mOrientation; }
- int tx() const { return mLeft; }
- int ty() const { return mTop; }
protected:
const GraphicPlane& graphicPlane(int dpy) const;
@@ -250,8 +248,6 @@
Transform mTransform;
GLfloat mVertices[4][2];
Rect mTransformedBounds;
- int mLeft;
- int mTop;
// these are protected by an external lock
State mCurrentState;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 598220f..b4c5dec 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -817,6 +817,20 @@
mHwWorkListDirty = false;
HWComposer& hwc(graphicPlane(0).displayHardware().getHwComposer());
if (hwc.initCheck() == NO_ERROR) {
+
+ const DisplayHardware& hw(graphicPlane(0).displayHardware());
+ uint32_t flags = hw.getFlags();
+ if ((flags & DisplayHardware::SWAP_RECTANGLE) ||
+ (flags & DisplayHardware::BUFFER_PRESERVED))
+ {
+ // we need to redraw everything (the whole screen)
+ // NOTE: we could be more subtle here and redraw only
+ // the area which will end-up in an overlay. But since this
+ // shouldn't happen often, we invalidate everything.
+ mDirtyRegion.set(hw.bounds());
+ mInvalidRegion = mDirtyRegion;
+ }
+
const Vector< sp<LayerBase> >& currentLayers(mVisibleLayersSortedByZ);
const size_t count = currentLayers.size();
hwc.createWorkList(count);
diff --git a/services/surfaceflinger/Transform.cpp b/services/surfaceflinger/Transform.cpp
index 05b7527..ba345ce 100644
--- a/services/surfaceflinger/Transform.cpp
+++ b/services/surfaceflinger/Transform.cpp
@@ -91,12 +91,12 @@
return type() > TRANSLATE;
}
-int Transform::tx() const {
- return floorf(mMatrix[2][0] + 0.5f);
+float Transform::tx() const {
+ return mMatrix[2][0];
}
-int Transform::ty() const {
- return floorf(mMatrix[2][1] + 0.5f);
+float Transform::ty() const {
+ return mMatrix[2][1];
}
void Transform::reset() {
@@ -239,7 +239,9 @@
out.set(transform(reg.bounds()));
}
} else {
- out = reg.translate(tx(), ty());
+ int xpos = floorf(tx() + 0.5f);
+ int ypos = floorf(ty() + 0.5f);
+ out = reg.translate(xpos, ypos);
}
return out;
}
diff --git a/services/surfaceflinger/Transform.h b/services/surfaceflinger/Transform.h
index 8fa5b86..ec74243 100644
--- a/services/surfaceflinger/Transform.h
+++ b/services/surfaceflinger/Transform.h
@@ -64,8 +64,8 @@
uint32_t getOrientation() const;
float const* operator [] (int i) const; // returns column i
- int tx() const;
- int ty() const;
+ float tx() const;
+ float ty() const;
// modify the transform
void reset();
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
index ecf78d9..2a25866 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -106,6 +106,7 @@
public void testNetworkStatsSummaryDown() throws Exception {
stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
+ stageLong(1L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/active"));
stageLong(1024L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/rx_bytes"));
stageLong(128L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/rx_packets"));
stageLong(2048L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/tx_bytes"));
@@ -119,6 +120,7 @@
public void testNetworkStatsCombined() throws Exception {
stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
+ stageLong(1L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/active"));
stageLong(10L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_bytes"));
stageLong(20L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_packets"));
stageLong(30L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_bytes"));
@@ -129,6 +131,18 @@
2205L + 20L, 489339L + 30L, 2237L + 40L);
}
+ public void testNetworkStatsCombinedInactive() throws Exception {
+ stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
+ stageLong(0L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/active"));
+ stageLong(10L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_bytes"));
+ stageLong(20L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/rx_packets"));
+ stageLong(30L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_bytes"));
+ stageLong(40L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_packets"));
+
+ final NetworkStats stats = mService.getNetworkStatsSummary();
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 10L, 20L, 30L, 40L);
+ }
+
public void testKernelTags() throws Exception {
assertEquals("0", tagToKernel(0x0));
assertEquals("214748364800", tagToKernel(0x32));
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1954172..c59dd3c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -60,12 +60,13 @@
/** @hide */
public TelephonyManager(Context context) {
+ context = context.getApplicationContext();
if (sContext == null) {
- sContext = context.getApplicationContext();
+ sContext = context;
sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
- } else {
+ } else if (sContext != context) {
Log.e(TAG, "Hidden constructor called more than once per process!");
Log.e(TAG, "Original: " + sContext.getPackageName() + ", new: " +
context.getPackageName());
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 3e13a86..bd35058 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -29,6 +29,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.ConnectivityManager;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.AsyncResult;
@@ -230,8 +231,6 @@
Object mLastNITZTimeInfo;
- private static final String WIFI_ONLY_CARRIER = "wifi-only";
-
//***** Events
static final int EVENT_SEND = 1;
@@ -626,10 +625,9 @@
Looper looper = mSenderThread.getLooper();
mSender = new RILSender(looper);
- // TODO: Provide a common API for determining if a
- // device is wifi-only. bug: 3480713
- String carrier = SystemProperties.get("ro.carrier");
- if (WIFI_ONLY_CARRIER.equals(carrier)) {
+ ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
riljLog("Not starting RILReceiver: wifi-only");
} else {
riljLog("Starting RILReceiver");
diff --git a/tests/BandwidthTests/Android.mk b/tests/BandwidthTests/Android.mk
index 2cc2009..7bc5f857 100644
--- a/tests/BandwidthTests/Android.mk
+++ b/tests/BandwidthTests/Android.mk
@@ -16,7 +16,9 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+
LOCAL_PACKAGE_NAME := BandwidthEnforcementTest
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-include $(BUILD_PACKAGE)
\ No newline at end of file
+include $(BUILD_PACKAGE)
diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp
index 50a0ba4..78404cb 100644
--- a/tools/aidl/aidl.cpp
+++ b/tools/aidl/aidl.cpp
@@ -593,12 +593,19 @@
// ==========================================================
void
-generate_dep_file(const Options& options)
+generate_dep_file(const Options& options, const document_item_type* items)
{
- /* we open the file in binary mode to ensure that the same output is
- * generated on all platforms !!
- */
- FILE* to = fopen(options.depFileName.c_str(), "wb");
+ /* we open the file in binary mode to ensure that the same output is
+ * generated on all platforms !!
+ */
+ FILE* to = NULL;
+ if (options.autoDepFile) {
+ string fileName = options.outputFileName + ".d";
+ to = fopen(fileName.c_str(), "wb");
+ } else {
+ to = fopen(options.depFileName.c_str(), "wb");
+ }
+
if (to == NULL) {
return;
}
@@ -609,7 +616,12 @@
slash = "";
}
- fprintf(to, "%s: \\\n", options.outputFileName.c_str());
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
+ fprintf(to, "%s: \\\n", options.outputFileName.c_str());
+ } else {
+ // parcelable: there's no output file.
+ fprintf(to, " : \\\n");
+ }
fprintf(to, " %s %s\n", options.inputFileName.c_str(), slash);
while (import) {
@@ -629,44 +641,60 @@
// ==========================================================
static string
-generate_outputFileName(const Options& options, const document_item_type* items)
+generate_outputFileName2(const Options& options, const buffer_type& name, const char* package)
{
string result;
- // items has already been checked to have only one interface.
- if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
- interface_type* type = (interface_type*)items;
+ // create the path to the destination folder based on the
+ // interface package name
+ result = options.outputBaseFolder;
+ result += OS_PATH_SEPARATOR;
- // create the path to the destination folder based on the
- // interface package name
- result = options.outputBaseFolder;
- result += OS_PATH_SEPARATOR;
-
- string package = type->package;
- size_t len = package.length();
- for (size_t i=0; i<len; i++) {
- if (package[i] == '.') {
- package[i] = OS_PATH_SEPARATOR;
- }
+ string packageStr = package;
+ size_t len = packageStr.length();
+ for (size_t i=0; i<len; i++) {
+ if (packageStr[i] == '.') {
+ packageStr[i] = OS_PATH_SEPARATOR;
}
-
- result += package;
-
- // add the filename by replacing the .aidl extension to .java
- const char* p = strchr(type->name.data, '.');
- len = p ? p-type->name.data : strlen(type->name.data);
-
- result += OS_PATH_SEPARATOR;
- result.append(type->name.data, len);
- result += ".java";
}
+ result += packageStr;
+
+ // add the filename by replacing the .aidl extension to .java
+ const char* p = strchr(name.data, '.');
+ len = p ? p-name.data : strlen(name.data);
+
+ result += OS_PATH_SEPARATOR;
+ result.append(name.data, len);
+ result += ".java";
+
return result;
}
// ==========================================================
+static string
+generate_outputFileName(const Options& options, const document_item_type* items)
+{
+ // items has already been checked to have only one interface.
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
+ interface_type* type = (interface_type*)items;
+
+ return generate_outputFileName2(options, type->name, type->package);
+ } else if (items->item_type == PARCELABLE_TYPE) {
+ parcelable_type* type = (parcelable_type*)items;
+ return generate_outputFileName2(options, type->name, type->package);
+ }
+
+ // I don't think we can come here, but safer than returning NULL.
+ string result;
+ return result;
+}
+
+
+
+// ==========================================================
static void
-check_outputFileName(const string& path) {
+check_outputFilePath(const string& path) {
size_t len = path.length();
for (size_t i=0; i<len ; i++) {
if (path[i] == OS_PATH_SEPARATOR) {
@@ -774,7 +802,7 @@
// ==========================================================
static int
-compile_aidl(const Options& options)
+compile_aidl(Options& options)
{
int err = 0, N;
@@ -868,27 +896,30 @@
return 1;
}
+ // if needed, generate the outputFileName from the outputBaseFolder
+ if (options.outputFileName.length() == 0 &&
+ options.outputBaseFolder.length() > 0) {
+ options.outputFileName = generate_outputFileName(options, mainDoc);
+ }
+
+ // if we were asked to, generate a make dependency file
+ // unless it's a parcelable *and* it's supposed to fail on parcelable
+ if ((options.autoDepFile || options.depFileName != "") &&
+ !(onlyParcelable && options.failOnParcelable)) {
+ // make sure the folders of the output file all exists
+ check_outputFilePath(options.outputFileName);
+ generate_dep_file(options, mainDoc);
+ }
+
// they didn't ask to fail on parcelables, so just exit quietly.
if (onlyParcelable && !options.failOnParcelable) {
return 0;
}
- // if we were asked to, generate a make dependency file
- if (options.depFileName != "") {
- generate_dep_file(options);
- }
-
- // if needed, generate the outputFileName from the outputBaseFolder
- string outputFileName = options.outputFileName;
- if (outputFileName.length() == 0 &&
- options.outputBaseFolder.length() > 0) {
- outputFileName = generate_outputFileName(options, mainDoc);
- }
-
// make sure the folders of the output file all exists
- check_outputFileName(outputFileName);
+ check_outputFilePath(options.outputFileName);
- err = generate_java(outputFileName, options.inputFileName.c_str(),
+ err = generate_java(options.outputFileName, options.inputFileName.c_str(),
(interface_type*)mainDoc);
return err;
@@ -982,5 +1013,3 @@
fprintf(stderr, "aidl: internal error\n");
return 1;
}
-
-
diff --git a/tools/aidl/options.cpp b/tools/aidl/options.cpp
index 0aa7db2..7b2daeb 100644
--- a/tools/aidl/options.cpp
+++ b/tools/aidl/options.cpp
@@ -15,6 +15,7 @@
"OPTIONS:\n"
" -I<DIR> search path for import statements.\n"
" -d<FILE> generate dependency file.\n"
+ " -a generate dependency file next to the output file with the name based on the input file.\n"
" -p<FILE> file created by --preprocess to import.\n"
" -o<FOLDER> base output folder for generated files.\n"
" -b fail when trying to compile a parcelable.\n"
@@ -49,6 +50,7 @@
options->task = COMPILE_AIDL;
options->failOnParcelable = false;
+ options->autoDepFile = false;
// OPTIONS
while (i < argc) {
@@ -73,6 +75,9 @@
return usage();
}
}
+ else if (s[1] == 'a') {
+ options->autoDepFile = true;
+ }
else if (s[1] == 'p') {
if (len > 2) {
options->preprocessedFiles.push_back(s+2);
diff --git a/tools/aidl/options.h b/tools/aidl/options.h
index d88d988..387e37d 100644
--- a/tools/aidl/options.h
+++ b/tools/aidl/options.h
@@ -23,6 +23,7 @@
string outputFileName;
string outputBaseFolder;
string depFileName;
+ bool autoDepFile;
vector<string> filesToPreprocess;
};
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 23e0ca1..2a52888 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -107,10 +107,9 @@
}
- public boolean setAdditionalInputMethodSubtypes(String arg0, InputMethodSubtype[] arg1)
+ public void setAdditionalInputMethodSubtypes(String arg0, InputMethodSubtype[] arg1)
throws RemoteException {
// TODO Auto-generated method stub
- return false;
}
public boolean setCurrentInputMethodSubtype(InputMethodSubtype arg0) throws RemoteException {
@@ -187,11 +186,4 @@
// TODO Auto-generated method stub
return null;
}
-
- public boolean setAdditionalInputMethodSubtypes(IBinder arg0, InputMethodSubtype[] arg1)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
}
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 2cd54d9..450f816 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -284,8 +284,11 @@
break;
}
+ //TODO: Add persist behavior once the supplicant interaction is fixed for both
+ // group and client scenarios
/* Persist unless there is an explicit request to not do so*/
- if (config.persist != WifiP2pConfig.Persist.NO) args.add("persistent");
+ //if (config.persist != WifiP2pConfig.Persist.NO) args.add("persistent");
+
if (joinExistingGroup) args.add("join");
int groupOwnerIntent = config.groupOwnerIntent;
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 4fc5e08..d116e5b 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1911,6 +1911,12 @@
transitionTo(mDriverUnloadingState);
break;
case CMD_START_SUPPLICANT:
+ try {
+ mNwService.wifiFirmwareReload(mInterfaceName, "STA");
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to reload STA firmware " + e);
+ // continue
+ }
//A runtime crash can leave the interface up and
//this affects connectivity when supplicant starts up.
//Ensure interface is down before a supplicant start.
diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
index 274edae..c52142d 100644
--- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
@@ -36,6 +36,7 @@
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Slog;
+import android.util.Log;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
@@ -174,7 +175,7 @@
* It triggers a disableNetwork call if a DNS check fails.
*/
public boolean mDisableAPNextFailure = false;
- private ConnectivityManager mConnectivityManager;
+ private static boolean sWifiOnly = false;
private boolean mNotificationShown;
public boolean mHasConnectedWifiManager = false;
@@ -219,9 +220,14 @@
public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
ContentResolver contentResolver = context.getContentResolver();
+
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
+
// Disable for wifi only devices.
if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null &&
- "wifi-only".equals(SystemProperties.get("ro.carrier"))) {
+ sWifiOnly) {
putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false);
}
WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
@@ -508,22 +514,6 @@
}
}
- /**
- * @return true if there is definitely no mobile data (we'll be less aggressive)
- */
- private boolean hasNoMobileData() {
- if (mConnectivityManager == null) {
- mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- }
- NetworkInfo mobileNetInfo = mConnectivityManager.getNetworkInfo(
- ConnectivityManager.TYPE_MOBILE);
- if (mobileNetInfo == null || !mobileNetInfo.isAvailable()) {
- return true;
- }
- return false;
- }
-
class DefaultState extends State {
@Override
public boolean processMessage(Message msg) {
@@ -941,7 +931,7 @@
if (mDisableAPNextFailure || mNumCheckFailures >= mBssids.size()
|| mNumCheckFailures >= mMaxSsidBlacklists) {
- if (hasNoMobileData()) {
+ if (sWifiOnly) {
Slog.w(WWSM_TAG, "Would disable bad network, but device has no mobile data!" +
" Going idle...");
// This state should be called idle -- will be changing flow.
diff --git a/wifi/java/android/net/wifi/WpsConfiguration.java b/wifi/java/android/net/wifi/WpsConfiguration.java
index 2e7689a..0c2adfd 100644
--- a/wifi/java/android/net/wifi/WpsConfiguration.java
+++ b/wifi/java/android/net/wifi/WpsConfiguration.java
@@ -46,16 +46,21 @@
public Setup setup;
+ /** @hide */
public String BSSID;
public String pin;
+ /** @hide */
public IpAssignment ipAssignment;
+ /** @hide */
public ProxySettings proxySettings;
+ /** @hide */
public LinkProperties linkProperties;
+ /** @hide */
public WpsConfiguration() {
setup = Setup.INVALID;
BSSID = null;
@@ -65,6 +70,7 @@
linkProperties = new LinkProperties();
}
+ /** @hide */
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append(" setup: ").append(setup.toString());
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 2d57363..686d698 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -28,11 +28,6 @@
public class WifiP2pConfig implements Parcelable {
/**
- * Device name
- */
- public String deviceName;
-
- /**
* Device address
*/
public String deviceAddress;
@@ -53,6 +48,7 @@
/**
* Indicates whether the configuration is saved
+ * @hide
*/
public enum Persist {
SYSTEM_DEFAULT,
@@ -60,6 +56,7 @@
NO
}
+ /** @hide */
public Persist persist = Persist.SYSTEM_DEFAULT;
public WifiP2pConfig() {
@@ -110,7 +107,6 @@
public String toString() {
StringBuffer sbuf = new StringBuffer();
- sbuf.append("Device: ").append(deviceName);
sbuf.append("\n address: ").append(deviceAddress);
sbuf.append("\n wps: ").append(wpsConfig);
sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent);
@@ -132,7 +128,6 @@
/** Implement the Parcelable interface {@hide} */
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(deviceName);
dest.writeString(deviceAddress);
dest.writeParcelable(wpsConfig, flags);
dest.writeInt(groupOwnerIntent);
@@ -144,7 +139,6 @@
new Creator<WifiP2pConfig>() {
public WifiP2pConfig createFromParcel(Parcel in) {
WifiP2pConfig config = new WifiP2pConfig();
- config.deviceName = in.readString();
config.deviceAddress = in.readString();
config.wpsConfig = (WpsConfiguration) in.readParcelable(null);
config.groupOwnerIntent = in.readInt();
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index ca6e4d5..14246b4 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -69,7 +69,7 @@
}
/**
- * @param string formats supported include
+ * @param supplicantEvent formats supported include
*
* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
* [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
index 9dc2fbf..a02175e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pInfo.java
@@ -34,9 +34,11 @@
public InetAddress groupOwnerAddress;
- public WifiP2pInfo() {
+ /** @hide */
+ WifiP2pInfo() {
}
+ /** @hide */
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("groupFormed: ").append(groupFormed)
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 25daf1c..0bdd269 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -35,25 +35,64 @@
import com.android.internal.util.Protocol;
/**
- * This class provides the API for managing Wi-Fi p2p
- * connectivity. Get an instance of this class by calling
- * {@link android.content.Context#getSystemService(String)
+ * This class provides the API for managing Wi-Fi peer-to-peer connectivity. This lets an
+ * application discover available peers, setup connection to peers and query for the list of peers.
+ * When a p2p connection is formed over wifi, the device continues to maintain the uplink
+ * connection over mobile or any other available network for internet connectivity on the device.
+ *
+ * <p> The API is asynchronous and response to a request from an application is sent in the form
+ * of a {@link android.os.Message} on a {@link android.os.Handler} that needs to be initialized
+ * by the application right at the beginning before any p2p operations are performed via
+ * {@link #initialize}.
+ *
+ * <p> An application can request for the current list of peers using {@link #requestPeers}. The
+ * {@link #RESPONSE_PEERS} message on the handler indicates that the peer list is available.
+ * Use {@link #peersInResponse} to extract the peer device list upon the receiving the
+ * {@link #RESPONSE_PEERS} message.
+ *
+ * <p> If an application needs to initiate a discovery, use {@link #discoverPeers} and listen
+ * to {@link #WIFI_P2P_PEERS_CHANGED_ACTION} intent action to initiate a request to fetch
+ * list of peers with {@link #requestPeers}. An initiated discovery request from an application
+ * stays active until the device starts connecting to a peer or forms a p2p group.
+ *
+ * <p> An application can initiate a connection request to a peer through {@link #connect}. See
+ * {@link WifiP2pConfig} for details on setting up the configuration. For communication with legacy
+ * Wi-Fi devices that do not support p2p, an app can create a group using {@link #createGroup}
+ * which creates an access point whose details can be fetched with {@link #requestGroupInfo}.
+ *
+ * <p> After a successful group formation through {@link #createGroup} or through {@link #connect},
+ * use {@link #requestConnectionInfo} to fetch the connection details. Connection information
+ * can be obtained with {@link #connectionInfoInResponse} on a {@link #RESPONSE_CONNECTION_INFO}
+ * message. The connection info {@link WifiP2pInfo} contains the address of the group owner
+ * {@link WifiP2pInfo#groupOwnerAddress} and a flag {@link #WifiP2pInfo#isGroupOwner} to indicate
+ * if the current device is a p2p group owner. A p2p client can thus communicate with
+ * the p2p group owner through a socket connection.
+ *
+ * <p> Android has no platform support for service discovery yet, so applications could
+ * run a service discovery protocol to discover services on the peer-to-peer netework.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Registering an application handler with {@link #initialize} requires the permissions
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE} to perform any further peer-to-peer
+ * operations.
+ *
+ * Get an instance of this class by calling {@link android.content.Context#getSystemService(String)
* Context.getSystemService(Context.WIFI_P2P_SERVICE)}.
*
- * It deals with the following:
- * <ul>
- * <li>Wi-Fi peer discovery and connection setup. Allows applications to initiate a discovery to
- * find available peers and then setup a connection </li>
- * <li>Configuration and status query. Allows applications to fetch the current list
- * of available and connected peers and query connection status </li>
- * <li>Intent actions that are broadcast to track operations
- * on a p2p connection</li>
- * </ul>
+ * {@see WifiP2pConfig}
+ * {@see WifiP2pInfo}
+ * {@see WifiP2pGroup}
+ * {@see WifiP2pDevice}
+ * {@see WifiP2pDeviceList}
* @hide
*/
public class WifiP2pManager {
/**
- * Broadcast intent action to indicate whether Wi-Fi p2p is enabled or disabled.
+ * Broadcast intent action to indicate whether Wi-Fi p2p is enabled or disabled. An
+ * extra {@link #EXTRA_WIFI_STATE} provides the state information as int.
+ *
+ * @see #EXTRA_WIFI_STATE
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String WIFI_P2P_STATE_CHANGED_ACTION =
@@ -72,7 +111,6 @@
* Wi-Fi p2p is disabled.
*
* @see #WIFI_P2P_STATE_CHANGED_ACTION
- * @see #getWifiP2pState()
*/
public static final int WIFI_P2P_STATE_DISABLED = 1;
@@ -80,14 +118,16 @@
* Wi-Fi p2p is enabled.
*
* @see #WIFI_P2P_STATE_CHANGED_ACTION
- * @see #getWifiP2pState()
*/
public static final int WIFI_P2P_STATE_ENABLED = 2;
/**
* Broadcast intent action indicating that the state of Wi-Fi p2p connectivity
- * has changed. One extra provides the new state
- * in the form of a {@link android.net.NetworkInfo} object.
+ * has changed. One extra {@link #EXTRA_WIFI_P2P_INFO} provides the p2p connection info in
+ * the form of a {@link WifiP2pInfo} object. Another extra {@link #EXTRA_NETWORK_INFO} provides
+ * the network info in the form of a {@link android.net.NetworkInfo}.
+ *
+ * @see #EXTRA_WIFI_P2P_INFO
* @see #EXTRA_NETWORK_INFO
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -124,7 +164,8 @@
public static final String EXTRA_LINK_CAPABILITIES = "linkCapabilities";
/**
- * Broadcast intent action indicating that the available peer list has changed
+ * Broadcast intent action indicating that the available peer list has changed. Fetch
+ * the changed list of peers with {@link #requestPeers}
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String WIFI_P2P_PEERS_CHANGED_ACTION =
@@ -134,6 +175,7 @@
* Activity Action: Pick a Wi-Fi p2p network to connect to.
* <p>Input: Nothing.
* <p>Output: Nothing.
+ * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PICK_WIFI_P2P_NETWORK =
@@ -141,47 +183,169 @@
IWifiP2pManager mService;
- /* AsyncChannel notifications to apps */
- public static final int HANDLER_CONNECTION = AsyncChannel.CMD_CHANNEL_HALF_CONNECTED;
+ /**
+ * Message {@link android.os.Message#what} sent on the application handler specified
+ * at {@link #initialize} indicating the asynchronous channel has disconnected. An
+ * application could choose to reconnect with {@link #initialize}
+ */
public static final int HANDLER_DISCONNECTION = AsyncChannel.CMD_CHANNEL_DISCONNECTED;
private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER;
+ /** @hide */
public static final int ENABLE_P2P = BASE + 1;
+ /** @hide */
public static final int ENABLE_P2P_FAILED = BASE + 2;
+ /** @hide */
public static final int ENABLE_P2P_SUCCEEDED = BASE + 3;
+ /** @hide */
public static final int DISABLE_P2P = BASE + 4;
+ /** @hide */
public static final int DISABLE_P2P_FAILED = BASE + 5;
+ /** @hide */
public static final int DISABLE_P2P_SUCCEEDED = BASE + 6;
+ /** @hide */
public static final int DISCOVER_PEERS = BASE + 7;
+
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #discoverPeers}
+ * operation failed.
+ * <p> The reason for failure could be one of {@link #P2P_UNSUPPORTED}, {@link #P2P_DISABLED}
+ * or {@link #ALREADY_IN_EFFECT}
+ */
public static final int DISCOVER_PEERS_FAILED = BASE + 8;
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #discoverPeers}
+ * operation succeeded.
+ * <p> The application can register for {@link #WIFI_P2P_PEERS_CHANGED_ACTION} intent
+ * to listen for changes in the peer list as a result of the discovery process.
+ */
public static final int DISCOVER_PEERS_SUCCEEDED = BASE + 9;
+ /** @hide */
public static final int CONNECT = BASE + 10;
+
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #connect}
+ * operation failed.
+ * <p> The reason for failure could be one of {@link #P2P_UNSUPPORTED}, {@link #P2P_DISABLED}
+ * or {@link #ALREADY_IN_EFFECT}
+ */
public static final int CONNECT_FAILED = BASE + 11;
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #connect}
+ * operation succeeded.
+ * <p> The application can register for {@link #WIFI_P2P_CONNECTION_CHANGED_ACTION} intent
+ * to listen for connectivity change as a result of the connect operation
+ */
public static final int CONNECT_SUCCEEDED = BASE + 12;
+ /** @hide */
public static final int CREATE_GROUP = BASE + 13;
+
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #createGroup}
+ * operation failed.
+ * <p> The reason for failure could be one of {@link #P2P_UNSUPPORTED}, {@link #P2P_DISABLED}
+ * or {@link #ALREADY_IN_EFFECT}
+ */
public static final int CREATE_GROUP_FAILED = BASE + 14;
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #createGroup}
+ * operation succeeded.
+ * <p> The application can request the group details with {@link #requestGroupInfo}
+ */
public static final int CREATE_GROUP_SUCCEEDED = BASE + 15;
+ /** @hide */
public static final int REMOVE_GROUP = BASE + 16;
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #removeGroup}
+ * operation failed.
+ * <p> The reason for failure could be one of {@link #P2P_UNSUPPORTED}, {@link #P2P_DISABLED}
+ * or {@link #ALREADY_IN_EFFECT}
+ */
public static final int REMOVE_GROUP_FAILED = BASE + 17;
+ /**
+ * Message {@link android.os.Message#what} value indicating that the {@link #removeGroup}
+ * operation succeeded.
+ */
public static final int REMOVE_GROUP_SUCCEEDED = BASE + 18;
- public static final int REQUEST_PEERS = BASE + 19;
- public static final int RESPONSE_PEERS = BASE + 20;
-
- public static final int REQUEST_CONNECTION_INFO = BASE + 21;
- public static final int RESPONSE_CONNECTION_INFO = BASE + 22;
-
- /* arg1 values on response messages from the framework */
+ /**
+ * Supported {@link android.os.Message#arg1} value on the following response messages:
+ * {@link #DISCOVER_PEERS_FAILED}, {@link #CONNECT_FAILED}, {@link #CREATE_GROUP_FAILED}
+ * and {@link #REMOVE_GROUP_FAILED}
+ *
+ * <p> This indicates that the reason for failure is because p2p is unsupported on the
+ * device
+ */
public static final int P2P_UNSUPPORTED = 1;
+ /**
+ * Supported {@link android.os.Message#arg1} value on the following response messages:
+ * {@link #DISCOVER_PEERS_FAILED}, {@link #CONNECT_FAILED}, {@link #CREATE_GROUP_FAILED}
+ * and {@link #REMOVE_GROUP_FAILED}
+ *
+ * <p> This indicates that the reason for failure is because p2p is currently disabled
+ * by the user
+ */
+ public static final int P2P_DISABLED = 2;
+
+ /**
+ * Supported {@link android.os.Message#arg1} value on the following response messages:
+ * {@link #DISCOVER_PEERS_FAILED}, {@link #CONNECT_FAILED}, {@link #CREATE_GROUP_FAILED}
+ * and {@link #REMOVE_GROUP_FAILED}
+ *
+ * <p> This indicates that the reason for failure is because the operation is already in
+ * effect
+ */
+ public static final int ALREADY_IN_EFFECT = 3;
+
+
+ /** @hide */
+ public static final int REQUEST_PEERS = BASE + 19;
+ /**
+ * Message {@link android.os.Message#what} delivered on the application hander
+ * in response to a {@link #requestPeers} call from the application.
+ *
+ * <p> Extract a {@link WifiP2pDeviceList} object by calling {@link #peersInResponse}
+ * on the message object
+ */
+ public static final int RESPONSE_PEERS = BASE + 20;
+
+ /** @hide */
+ public static final int REQUEST_CONNECTION_INFO = BASE + 21;
+
+ /**
+ * Message {@link android.os.Message#what} delivered on the application hander
+ * in response to a {@link #requestConnectionInfo} call from the application.
+ *
+ * <p> Extract a {@link WifiP2pInfo} object by calling {@link #connectionInfoInResponse}
+ * on the message object
+ */
+ public static final int RESPONSE_CONNECTION_INFO = BASE + 22;
+
+ /** @hide */
+ public static final int REQUEST_GROUP_INFO = BASE + 23;
+
+ /**
+ * Message {@link android.os.Message#what} delivered on the application hander
+ * in response to a {@link #requestGroupInfo} call from the application.
+ *
+ * <p> Extract a {@link WifiP2pGroup} object by calling {@link #groupInfoInResponse}
+ * on the message object
+ */
+
+ public static final int RESPONSE_GROUP_INFO = BASE + 24;
+
+ /** @hide */
public static final int WPS_PBC = BASE + 23;
+ /** @hide */
public static final int WPS_PIN = BASE + 24;
+ /** @hide */
public static final int WPS_PIN_AVAILABLE = BASE + 25;
/**
@@ -199,7 +363,8 @@
/**
* A channel that connects the application handler to the Wifi framework.
- * All p2p operations are performed on a channel.
+ * Most p2p operations require a Channel as an argument. An instance of Channel is obtained
+ * by doing a call on {@link #initialize}
*/
public class Channel {
Channel(AsyncChannel c) {
@@ -210,10 +375,17 @@
/**
* Registers the application handler with the Wi-Fi framework. This function
- * must be the first to be called before any p2p control or query operations can be performed.
+ * must be the first to be called before any p2p operations are performed.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * The handler registered with the framework should only handle messages
+ * with {@link android.os.Message#what} values defined in this file. Adding application
+ * specific private {@link android.os.Message#what} types should be done on a seperate handler
+ *
* @param srcContext is the context of the source
- * @param srcHandler is the handler on which the source receives messages
- * @return Channel instance that is necessary for performing p2p operations
+ * @param srcHandler is the handler on which the source will receive message responses
+ * asynchronously
+ * @return Channel instance that is necessary for performing any further p2p operations
*/
public Channel initialize(Context srcContext, Handler srcHandler) {
Messenger messenger = getMessenger();
@@ -229,6 +401,7 @@
}
}
+ /** @hide */
public boolean isP2pSupported() {
try {
return mService.isP2pSupported();
@@ -240,6 +413,7 @@
/**
* Sends in a request to the system to enable p2p. This will pop up a dialog
* to the user and upon authorization will enable p2p.
+ * @hide
*/
public void enableP2p(Channel c) {
if (c == null) return;
@@ -249,6 +423,7 @@
/**
* Sends in a request to the system to disable p2p. This will pop up a dialog
* to the user and upon authorization will enable p2p.
+ * @hide
*/
public void disableP2p(Channel c) {
if (c == null) return;
@@ -256,7 +431,22 @@
}
/**
- * Initiates peer discovery
+ * Initiate peer discovery. A discovery process involves scanning for available Wi-Fi peers
+ * for the purpose of establishing a connection.
+ *
+ * <p> The function call immediately returns after sending a discovery request
+ * to the framework. The application handler is notified of a success or failure to initiate
+ * discovery with {@link #DISCOVER_PEERS_SUCCEEDED} or {@link #DISCOVER_PEERS_FAILED}.
+ *
+ * <p> The discovery remains active until a connection is initiated or
+ * a p2p group is formed. Register for {@link #WIFI_P2P_PEERS_CHANGED_ACTION} intent to
+ * determine when the framework notifies of a change as peers are discovered.
+ *
+ * <p> Upon receiving a {@link #WIFI_P2P_PEERS_CHANGED_ACTION} intent, an application
+ * can request for the list of peers using {@link #requestPeers} which will deliver a
+ * {@link #RESPONSE_PEERS} message on the application handler. The application can then
+ * extract a {@link WifiP2pDeviceList} object by calling {@link #peersInResponse}
+ * on the message.
*/
public void discoverPeers(Channel c) {
if (c == null) return;
@@ -264,9 +454,23 @@
}
/**
- * Start a p2p connection
+ * Start a p2p connection to a device with the specified configuration.
*
- * @param peer Configuration described in a {@link WifiP2pConfig} object.
+ * <p> The function call immediately returns after sending a connection request
+ * to the framework. The application handler is notified of a success or failure to initiate
+ * connectivity with {@link #CONNECT_SUCCEEDED} or {@link #CONNECT_FAILED}.
+ *
+ * <p> Register for {@link #WIFI_P2P_CONNECTION_CHANGED_ACTION} intent to
+ * determine when the framework notifies of a change in connectivity.
+ *
+ * <p> If the current device is not part of a p2p group, a connect request initiates
+ * a group negotiation with the peer.
+ *
+ * <p> If the current device is part of an existing p2p group or has created
+ * a p2p group with {@link #createGroup}, an invitation to join the group is sent to
+ * the peer device.
+ *
+ * @param config options as described in {@link WifiP2pConfig} class.
*/
public void connect(Channel c, WifiP2pConfig config) {
if (c == null) return;
@@ -274,8 +478,20 @@
}
/**
- * Create a p2p group. This is essentially an access point that can accept
- * client connections.
+ * Create a p2p group with the current device as the group owner. This essentially creates
+ * an access point that can accept connections from legacy clients as well as other p2p
+ * devices.
+ * <p> For p2p operation, this would normally not be used unless the current device needs
+ * to form a p2p connection with a legacy client
+ *
+ * <p> The function call immediately returns after sending a group creation request
+ * to the framework. The application handler is notified of a success or failure to create
+ * group with {@link #CREATE_GROUP_SUCCEEDED} or {@link #CREATE_GROUP_FAILED}.
+ *
+ * <p> Application can request for the group details with {@link #requestGroupInfo} which will
+ * deliver a {@link #RESPONSE_GROUP_INFO} message on the application handler. The application
+ * can then extract a {@link WifiP2pGroup} object by calling {@link #groupInfoInResponse}
+ * on the message.
*/
public void createGroup(Channel c) {
if (c == null) return;
@@ -283,8 +499,11 @@
}
/**
- * Remove the current group. This also removes the p2p interface created
- * during group formation.
+ * Remove the current p2p group.
+ *
+ * <p> The function call immediately returns after sending a group removal request
+ * to the framework. The application handler is notified of a success or failure to remove
+ * a group with {@link #REMOVE_GROUP_SUCCEEDED} or {@link #REMOVE_GROUP_FAILED}.
*/
public void removeGroup(Channel c) {
if (c == null) return;
@@ -292,8 +511,9 @@
}
/**
- * Request the list of peers. This returns a RESPONSE_PEERS on the source
- * handler.
+ * Request the current list of peers. This returns a {@link #RESPONSE_PEERS} on the application
+ * handler. The {@link #RESPONSE_PEERS} message on the handler indicates that the peer list is
+ * available. Use {@link #peersInResponse} to extract {@link WifiP2pDeviceList} from the message
*/
public void requestPeers(Channel c) {
if (c == null) return;
@@ -301,15 +521,18 @@
}
/**
- * Fetch device list from a RESPONSE_PEERS message
+ * Upon receiving a {@link #RESPONSE_PEERS} on the application handler, an application
+ * can extract the peer device list using this function.
*/
public WifiP2pDeviceList peersInResponse(Message msg) {
return (WifiP2pDeviceList) msg.obj;
}
/**
- * Request device connection info. This returns a RESPONSE_CONNECTION_INFO on
- * the source handler.
+ * Request device connection info. This returns a {@link #RESPONSE_CONNECTION_INFO} on
+ * the application handler. The {@link #RESPONSE_CONNECTION_INFO} message on the handler
+ * indicates that connection info is available. Use {@link #connectionInfoInResponse} to
+ * extract {@link WifiP2pInfo} from the message.
*/
public void requestConnectionInfo(Channel c) {
if (c == null) return;
@@ -317,12 +540,31 @@
}
/**
- * Fetch p2p connection status from a RESPONSE_CONNECTION_INFO message
+ * Upon receiving a {@link #RESPONSE_CONNECTION_INFO} on the application handler, an application
+ * can extract the connection info using this function.
*/
public WifiP2pInfo connectionInfoInResponse(Message msg) {
return (WifiP2pInfo) msg.obj;
}
+ /**
+ * Request p2p group info. This returns a {@link #RESPONSE_GROUP_INFO} on
+ * the application handler. The {@link #RESPONSE_GROUP_INFO} message on the handler
+ * indicates that group info is available. Use {@link #groupInfoInResponse} to
+ * extract {@link WifiP2pGroup} from the message.
+ */
+ public void requestGroupInfo(Channel c) {
+ if (c == null) return;
+ c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO);
+ }
+
+ /**
+ * Upon receiving a {@link #RESPONSE_GROUP_INFO} on the application handler, an application
+ * can extract the group info using this function.
+ */
+ public WifiP2pGroup groupInfoInResponse(Message msg) {
+ return (WifiP2pGroup) msg.obj;
+ }
/**
* Get a reference to WifiP2pService handler. This is used to establish
@@ -339,7 +581,6 @@
}
}
-
/**
* Setup DNS connectivity on the current process to the connected Wi-Fi p2p peers
*