Merge "Fix simulator build, part 1/n"
diff --git a/api/current.xml b/api/current.xml
index 9da64f8..953c9f3 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2103,7 +2103,7 @@
type="int"
transient="false"
volatile="false"
- value="16843563"
+ value="16843567"
static="true"
final="true"
deprecated="not deprecated"
@@ -2125,7 +2125,7 @@
type="int"
transient="false"
volatile="false"
- value="16843572"
+ value="16843576"
static="true"
final="true"
deprecated="not deprecated"
@@ -2136,7 +2136,7 @@
type="int"
transient="false"
volatile="false"
- value="16843571"
+ value="16843575"
static="true"
final="true"
deprecated="not deprecated"
@@ -2147,7 +2147,7 @@
type="int"
transient="false"
volatile="false"
- value="16843573"
+ value="16843577"
static="true"
final="true"
deprecated="not deprecated"
@@ -2191,7 +2191,7 @@
type="int"
transient="false"
volatile="false"
- value="16843580"
+ value="16843584"
static="true"
final="true"
deprecated="not deprecated"
@@ -2213,7 +2213,7 @@
type="int"
transient="false"
volatile="false"
- value="16843576"
+ value="16843580"
static="true"
final="true"
deprecated="not deprecated"
@@ -2235,7 +2235,7 @@
type="int"
transient="false"
volatile="false"
- value="16843574"
+ value="16843578"
static="true"
final="true"
deprecated="not deprecated"
@@ -2246,7 +2246,7 @@
type="int"
transient="false"
volatile="false"
- value="16843581"
+ value="16843585"
static="true"
final="true"
deprecated="not deprecated"
@@ -2257,7 +2257,7 @@
type="int"
transient="false"
volatile="false"
- value="16843582"
+ value="16843586"
static="true"
final="true"
deprecated="not deprecated"
@@ -2466,7 +2466,7 @@
type="int"
transient="false"
volatile="false"
- value="16843570"
+ value="16843574"
static="true"
final="true"
deprecated="not deprecated"
@@ -2807,7 +2807,7 @@
type="int"
transient="false"
volatile="false"
- value="16843589"
+ value="16843593"
static="true"
final="true"
deprecated="not deprecated"
@@ -2818,7 +2818,7 @@
type="int"
transient="false"
volatile="false"
- value="16843588"
+ value="16843592"
static="true"
final="true"
deprecated="not deprecated"
@@ -4497,6 +4497,28 @@
visibility="public"
>
</field>
+<field name="fragmentNextEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843563"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentNextExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843564"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="fragmentOpenEnterAnimation"
type="int"
transient="false"
@@ -4519,6 +4541,28 @@
visibility="public"
>
</field>
+<field name="fragmentPrevEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843565"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentPrevExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843566"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="freezesText"
type="int"
transient="false"
@@ -4996,7 +5040,7 @@
type="int"
transient="false"
volatile="false"
- value="16843579"
+ value="16843583"
static="true"
final="true"
deprecated="not deprecated"
@@ -5117,7 +5161,7 @@
type="int"
transient="false"
volatile="false"
- value="16843566"
+ value="16843570"
static="true"
final="true"
deprecated="not deprecated"
@@ -5128,7 +5172,7 @@
type="int"
transient="false"
volatile="false"
- value="16843564"
+ value="16843568"
static="true"
final="true"
deprecated="not deprecated"
@@ -5139,7 +5183,7 @@
type="int"
transient="false"
volatile="false"
- value="16843565"
+ value="16843569"
static="true"
final="true"
deprecated="not deprecated"
@@ -5502,7 +5546,7 @@
type="int"
transient="false"
volatile="false"
- value="16843575"
+ value="16843579"
static="true"
final="true"
deprecated="not deprecated"
@@ -6778,7 +6822,7 @@
type="int"
transient="false"
volatile="false"
- value="16843568"
+ value="16843572"
static="true"
final="true"
deprecated="not deprecated"
@@ -6822,7 +6866,7 @@
type="int"
transient="false"
volatile="false"
- value="16843590"
+ value="16843594"
static="true"
final="true"
deprecated="not deprecated"
@@ -6833,7 +6877,7 @@
type="int"
transient="false"
volatile="false"
- value="16843584"
+ value="16843588"
static="true"
final="true"
deprecated="not deprecated"
@@ -6921,7 +6965,7 @@
type="int"
transient="false"
volatile="false"
- value="16843592"
+ value="16843596"
static="true"
final="true"
deprecated="not deprecated"
@@ -7735,7 +7779,7 @@
type="int"
transient="false"
volatile="false"
- value="16843585"
+ value="16843589"
static="true"
final="true"
deprecated="not deprecated"
@@ -9044,7 +9088,7 @@
type="int"
transient="false"
volatile="false"
- value="16843569"
+ value="16843573"
static="true"
final="true"
deprecated="not deprecated"
@@ -9066,7 +9110,7 @@
type="int"
transient="false"
volatile="false"
- value="16843567"
+ value="16843571"
static="true"
final="true"
deprecated="not deprecated"
@@ -9165,7 +9209,7 @@
type="int"
transient="false"
volatile="false"
- value="16843583"
+ value="16843587"
static="true"
final="true"
deprecated="not deprecated"
@@ -9396,7 +9440,7 @@
type="int"
transient="false"
volatile="false"
- value="16843578"
+ value="16843582"
static="true"
final="true"
deprecated="not deprecated"
@@ -9781,7 +9825,7 @@
type="int"
transient="false"
volatile="false"
- value="16843586"
+ value="16843590"
static="true"
final="true"
deprecated="not deprecated"
@@ -9858,7 +9902,7 @@
type="int"
transient="false"
volatile="false"
- value="16843587"
+ value="16843591"
static="true"
final="true"
deprecated="not deprecated"
@@ -9902,7 +9946,7 @@
type="int"
transient="false"
volatile="false"
- value="16843591"
+ value="16843595"
static="true"
final="true"
deprecated="not deprecated"
@@ -10320,7 +10364,7 @@
type="int"
transient="false"
volatile="false"
- value="16843577"
+ value="16843581"
static="true"
final="true"
deprecated="not deprecated"
@@ -30872,6 +30916,17 @@
visibility="public"
>
</field>
+<field name="TRANSIT_FRAGMENT_NEXT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4099"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TRANSIT_FRAGMENT_OPEN"
type="int"
transient="false"
@@ -30883,6 +30938,17 @@
visibility="public"
>
</field>
+<field name="TRANSIT_FRAGMENT_PREV"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8196"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TRANSIT_NONE"
type="int"
transient="false"
@@ -55076,6 +55142,17 @@
visibility="public"
>
</field>
+<field name="SCREEN_ORIENTATION_FULL_SENSOR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SCREEN_ORIENTATION_LANDSCAPE"
type="int"
transient="false"
@@ -55109,6 +55186,28 @@
visibility="public"
>
</field>
+<field name="SCREEN_ORIENTATION_REVERSE_LANDSCAPE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SCREEN_ORIENTATION_REVERSE_PORTRAIT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SCREEN_ORIENTATION_SENSOR"
type="int"
transient="false"
@@ -55120,6 +55219,28 @@
visibility="public"
>
</field>
+<field name="SCREEN_ORIENTATION_SENSOR_LANDSCAPE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SCREEN_ORIENTATION_SENSOR_PORTRAIT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SCREEN_ORIENTATION_UNSPECIFIED"
type="int"
transient="false"
@@ -143207,6 +143328,8 @@
</parameter>
<parameter name="args" type="android.os.Bundle">
</parameter>
+<parameter name="next" type="boolean">
+</parameter>
</method>
<field name="EXTRA_NO_HEADERS"
type="java.lang.String"
@@ -203420,7 +203543,7 @@
<class name="View.DragThumbnailBuilder"
extends="java.lang.Object"
abstract="false"
- static="false"
+ static="true"
final="false"
deprecated="not deprecated"
visibility="public"
@@ -223783,7 +223906,7 @@
>
</method>
<method name="getInAnimation"
- return="android.view.animation.Animation"
+ return="android.animation.ObjectAnimator<?>"
abstract="false"
native="false"
synchronized="false"
@@ -223794,7 +223917,7 @@
>
</method>
<method name="getOutAnimation"
- return="android.view.animation.Animation"
+ return="android.animation.ObjectAnimator<?>"
abstract="false"
native="false"
synchronized="false"
@@ -223910,7 +224033,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="inAnimation" type="android.view.animation.Animation">
+<parameter name="inAnimation" type="android.animation.ObjectAnimator<?>">
</parameter>
</method>
<method name="setInAnimation"
@@ -223938,7 +224061,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="outAnimation" type="android.view.animation.Animation">
+<parameter name="outAnimation" type="android.animation.ObjectAnimator<?>">
</parameter>
</method>
<method name="setOutAnimation"
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b558318..b34c243 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1341,6 +1341,18 @@
return true;
}
+ case CHECK_GRANT_URI_PERMISSION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int callingUid = data.readInt();
+ String targetPkg = data.readString();
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ int modeFlags = data.readInt();
+ int res = checkGrantUriPermission(callingUid, targetPkg, uri, modeFlags);
+ reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
+
case DUMP_HEAP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String process = data.readString();
@@ -2998,6 +3010,23 @@
reply.recycle();
}
+ public int checkGrantUriPermission(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(callingUid);
+ data.writeString(targetPkg);
+ uri.writeToParcel(data, 0);
+ data.writeInt(modeFlags);
+ mRemote.transact(CHECK_GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
public boolean dumpHeap(String process, boolean managed,
String path, ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index df18ce7..f3f7ee7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,7 +77,6 @@
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
-import dalvik.system.VMDebug;
import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
import java.io.File;
@@ -710,14 +709,14 @@
long dalvikAllocated = dalvikMax - dalvikFree;
long viewInstanceCount = ViewDebug.getViewInstanceCount();
long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
- long appContextInstanceCount = VMDebug.countInstancesOfClass(ContextImpl.class);
- long activityInstanceCount = VMDebug.countInstancesOfClass(Activity.class);
+ long appContextInstanceCount = Debug.countInstancesOfClass(ContextImpl.class);
+ long activityInstanceCount = Debug.countInstancesOfClass(Activity.class);
int globalAssetCount = AssetManager.getGlobalAssetCount();
int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
- int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
+ long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class);
long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index da7ba6f..37e7253 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1380,6 +1380,12 @@
case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+ rev = FragmentTransaction.TRANSIT_FRAGMENT_PREV;
+ break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+ rev = FragmentTransaction.TRANSIT_FRAGMENT_NEXT;
+ break;
}
return rev;
@@ -1398,6 +1404,16 @@
? com.android.internal.R.styleable.FragmentAnimation_fragmentCloseEnterAnimation
: com.android.internal.R.styleable.FragmentAnimation_fragmentCloseExitAnimation;
break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+ animAttr = enter
+ ? com.android.internal.R.styleable.FragmentAnimation_fragmentNextEnterAnimation
+ : com.android.internal.R.styleable.FragmentAnimation_fragmentNextExitAnimation;
+ break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+ animAttr = enter
+ ? com.android.internal.R.styleable.FragmentAnimation_fragmentPrevEnterAnimation
+ : com.android.internal.R.styleable.FragmentAnimation_fragmentPrevExitAnimation;
+ break;
}
return animAttr;
}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 09d8d26..b00476bb 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -106,10 +106,14 @@
public final int TRANSIT_UNSET = -1;
/** No animation for transition. */
public final int TRANSIT_NONE = 0;
- /** Fragment is being added */
+ /** Fragment is being added onto the stack */
public final int TRANSIT_FRAGMENT_OPEN = 1 | TRANSIT_ENTER_MASK;
- /** Fragment is being removed */
+ /** Fragment is being removed from the stack */
public final int TRANSIT_FRAGMENT_CLOSE = 2 | TRANSIT_EXIT_MASK;
+ /** Fragment is being added in a 'next' operation*/
+ public final int TRANSIT_FRAGMENT_NEXT = 3 | TRANSIT_ENTER_MASK;
+ /** Fragment is being removed in a 'previous' operation */
+ public final int TRANSIT_FRAGMENT_PREV = 4 | TRANSIT_EXIT_MASK;
/**
* Set specific animation resources to run for the fragments that are
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 4d73817..cd229e3 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -326,6 +326,9 @@
public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
int mode) throws RemoteException;
+ public int checkGrantUriPermission(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) throws RemoteException;
+
// Cause the specified process to dump the specified heap.
public boolean dumpHeap(String process, boolean managed, String path,
ParcelFileDescriptor fd) throws RemoteException;
@@ -540,5 +543,6 @@
int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115;
int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116;
int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117;
- int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
+ int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
+ int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119;
}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 0ea0648..85a6765 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -108,7 +108,7 @@
*/
public ClipData getPrimaryClip() {
try {
- return getService().getPrimaryClip();
+ return getService().getPrimaryClip(mContext.getPackageName());
} catch (RemoteException e) {
return null;
}
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index 3e1fe55..254901b 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -27,7 +27,7 @@
*/
interface IClipboard {
void setPrimaryClip(in ClipData clip);
- ClipData getPrimaryClip();
+ ClipData getPrimaryClip(String pkg);
ClipDescription getPrimaryClipDescription();
boolean hasPrimaryClip();
void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 950d339..c9115c5 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,6 +16,18 @@
package android.content;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.android.internal.R;
@@ -36,17 +48,6 @@
import android.content.pm.RegisteredServicesCacheListener;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
@@ -58,11 +59,13 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Random;
-import java.util.Collection;
import java.util.concurrent.CountDownLatch;
/**
@@ -81,30 +84,17 @@
private static final long MAX_TIME_PER_SYNC;
static {
- String localSyncDelayString = SystemProperties.get("sync.local_sync_delay");
- long localSyncDelay = 30 * 1000; // 30 seconds
- if (localSyncDelayString != null) {
- try {
- localSyncDelay = Long.parseLong(localSyncDelayString);
- } catch (NumberFormatException nfe) {
- // ignore, use default
- }
- }
- LOCAL_SYNC_DELAY = localSyncDelay;
-
- String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync");
- long maxTimePerSync = 5 * 60 * 1000; // 5 minutes
- if (maxTimePerSyncString != null) {
- try {
- maxTimePerSync = Long.parseLong(maxTimePerSyncString);
- } catch (NumberFormatException nfe) {
- // ignore, use default
- }
- }
- MAX_TIME_PER_SYNC = maxTimePerSync;
+ MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = SystemProperties.getInt("sync.max_init_syncs", 5);
+ MAX_SIMULTANEOUS_REGULAR_SYNCS = SystemProperties.getInt("sync.max_regular_syncs", 2);
+ LOCAL_SYNC_DELAY =
+ SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
+ MAX_TIME_PER_SYNC =
+ SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
+ SYNC_NOTIFICATION_DELAY =
+ SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
}
- private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
+ private static final long SYNC_NOTIFICATION_DELAY;
/**
* When retrying a sync for the first time use this delay. After that
@@ -123,21 +113,21 @@
*/
private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
- /**
- * An error notification is sent if sync of any of the providers has been failing for this long.
- */
- private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
-
private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
+ private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
+
+ private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
+ private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
private Context mContext;
private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+ volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
@@ -147,10 +137,8 @@
private final SyncStorageEngine mSyncStorageEngine;
public final SyncQueue mSyncQueue;
- private ActiveSyncContext mActiveSyncContext = null;
+ private final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
- // set if the sync error indicator should be reported.
- private boolean mNeedSyncErrorNotification = false;
// set if the sync active indicator should be reported
private boolean mNeedSyncActiveNotification = false;
@@ -200,6 +188,9 @@
private final PowerManager mPowerManager;
+ 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
+
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;
@@ -207,11 +198,10 @@
// if a sync is in progress yet it is no longer in the accounts list,
// cancel it
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (activeSyncContext != null) {
- if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) {
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ if (!ArrayUtils.contains(accounts, currentSyncContext.mSyncOperation.account)) {
Log.d(TAG, "canceling sync since the account has been removed");
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
+ sendSyncFinishedOrCanceledMessage(currentSyncContext,
null /* no result since this is a cancel */);
}
}
@@ -238,6 +228,7 @@
// If this was the bootup case then don't sync everything, instead only
// sync those that have an unknown syncable state, which will give them
// a chance to set their syncable state.
+
boolean onlyThoseWithUnkownSyncableState = justBootedUp;
scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
}
@@ -371,6 +362,15 @@
HANDLE_SYNC_ALARM_WAKE_LOCK);
mHandleAlarmWakeLock.setReferenceCounted(false);
+ // This WakeLock is used to ensure that we stay awake while running the sync loop
+ // message handler. Normally we will hold a sync adapter wake lock while it is being
+ // synced but during the execution of the sync loop it might finish a sync for
+ // one sync adapter before starting the sync for the other sync adapter and we
+ // don't want the device to go to sleep during that window.
+ mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ SYNC_LOOP_WAKE_LOCK);
+ mSyncManagerWakeLock.setReferenceCounted(false);
+
mSyncStorageEngine.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
public void onStatusChanged(int which) {
@@ -427,8 +427,8 @@
Intent intent = new Intent();
intent.setAction("android.content.SyncAdapter");
intent.setComponent(syncAdapterInfo.componentName);
- if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext,
- mMainHandler),
+ if (!mContext.bindService(intent,
+ new InitializerServiceConnection(account, authority, mContext, mMainHandler),
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
}
@@ -508,8 +508,8 @@
* @param requestedAccount the account to sync, may be null to signify all accounts
* @param requestedAuthority the authority to sync, may be null to indicate all authorities
* @param extras a Map of SyncAdapter-specific information to control
-* syncs of a specific provider. Can be null. Is ignored
-* if the url is null.
+ * syncs of a specific provider. Can be null. Is ignored
+ * if the url is null.
* @param delay how many milliseconds in the future to wait before performing this
* @param onlyThoseWithUnkownSyncableState
*/
@@ -613,16 +613,29 @@
continue;
}
- if (isLoggable) {
- Log.v(TAG, "scheduleSync:"
- + " delay " + delay
- + ", source " + source
- + ", account " + account
- + ", authority " + authority
- + ", extras " + extras);
+ Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(account, authority);
+ long delayUntil = mSyncStorageEngine.getDelayUntilTime(account, authority);
+ final long backoffTime = backoff != null ? backoff.first : 0;
+ if (isSyncable < 0) {
+ Bundle newExtras = new Bundle();
+ newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ scheduleSyncOperation(
+ new SyncOperation(account, source, authority, newExtras, 0,
+ backoffTime, delayUntil));
}
- scheduleSyncOperation(
- new SyncOperation(account, source, authority, extras, delay));
+ if (!onlyThoseWithUnkownSyncableState) {
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync:"
+ + " delay " + delay
+ + ", source " + source
+ + ", account " + account
+ + ", authority " + authority
+ + ", extras " + extras);
+ }
+ scheduleSyncOperation(
+ new SyncOperation(account, source, authority, extras, delay,
+ backoffTime, delayUntil));
+ }
}
}
}
@@ -636,7 +649,8 @@
}
public SyncAdapterType[] getSyncAdapterTypes() {
- final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
+ final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>
+ serviceInfos =
mSyncAdapters.getAllServices();
SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
int i = 0;
@@ -666,6 +680,14 @@
mSyncHandler.sendMessage(msg);
}
+ private void sendCancelSyncsMessage(final Account account, final String authority) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_CANCEL;
+ msg.obj = Pair.create(account, authority);
+ mSyncHandler.sendMessage(msg);
+ }
+
class SyncHandlerMessagePayload {
public final ActiveSyncContext activeSyncContext;
public final SyncResult syncResult;
@@ -683,11 +705,6 @@
}
}
- private void clearBackoffSetting(SyncOperation op) {
- mSyncStorageEngine.setBackoff(op.account, op.authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
- }
-
private void increaseBackoffSetting(SyncOperation op) {
final long now = SystemClock.elapsedRealtime();
@@ -713,6 +730,9 @@
mSyncStorageEngine.setBackoff(op.account, op.authority,
now + newDelayInMs, newDelayInMs);
+ synchronized (mSyncQueue) {
+ mSyncQueue.onBackoffChanged(op.account, op.authority, now + newDelayInMs);
+ }
}
private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
@@ -725,6 +745,9 @@
newDelayUntilTime = 0;
}
mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
+ synchronized (mSyncQueue) {
+ mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+ }
}
/**
@@ -733,23 +756,7 @@
* @param authority limit the cancelations to syncs with this authority, if non-null
*/
public void cancelActiveSync(Account account, String authority) {
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (activeSyncContext != null) {
- // if an authority was specified then only cancel the sync if it matches
- if (account != null) {
- if (!account.equals(activeSyncContext.mSyncOperation.account)) {
- return;
- }
- }
- // if an account was specified then only cancel the sync if it matches
- if (authority != null) {
- if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
- return;
- }
- }
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
- null /* no result since this is a cancel */);
- }
+ sendCancelSyncsMessage(account, authority);
}
/**
@@ -758,22 +765,6 @@
* @param syncOperation the SyncOperation to schedule
*/
public void scheduleSyncOperation(SyncOperation syncOperation) {
- // If this operation is expedited and there is a sync in progress then
- // reschedule the current operation and send a cancel for it.
- final ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (syncOperation.expedited && activeSyncContext != null) {
- final boolean hasSameKey =
- activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
- // This request is expedited and there is a sync in progress.
- // Interrupt the current sync only if it is not expedited and if it has a different
- // key than the one we are scheduling.
- if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
- scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
- null /* no result since this is a cancel */);
- }
- }
-
boolean queueChanged;
synchronized (mSyncQueue) {
queueChanged = mSyncQueue.add(syncOperation);
@@ -798,11 +789,11 @@
* @param authority limit the removals to operations with this authority, if non-null
*/
public void clearScheduledSyncOperations(Account account, String authority) {
- mSyncStorageEngine.setBackoff(account, authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
synchronized (mSyncQueue) {
mSyncQueue.remove(account, authority);
}
+ mSyncStorageEngine.setBackoff(account, authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
}
void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
@@ -829,7 +820,8 @@
if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
+ operation);
- } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+ } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
+ && !syncResult.syncAlreadyInProgress) {
operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ "encountered an error: " + operation);
@@ -850,7 +842,8 @@
}
scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
operation.authority, operation.extras,
- DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000));
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
+ operation.backoff, operation.delayUntil));
} else if (syncResult.hasSoftError()) {
if (isLoggable) {
Log.d(TAG, "retrying sync operation because it encountered a soft error: "
@@ -873,15 +866,33 @@
final long mStartTime;
long mTimeoutStartTime;
boolean mBound;
+ final PowerManager.WakeLock mSyncWakeLock;
+ final int mSyncAdapterUid;
+ SyncInfo mSyncInfo;
- public ActiveSyncContext(SyncOperation syncOperation,
- long historyRowId) {
+ /**
+ * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
+ * sync adapter. Since this grabs the wakelock you need to be sure to call
+ * close() when you are done with this ActiveSyncContext, whether the sync succeeded
+ * or not.
+ * @param syncOperation the SyncOperation we are about to sync
+ * @param historyRowId the row in which to record the history info for this sync
+ * @param syncAdapterUid the UID of the application that contains the sync adapter
+ * for this sync. This is used to attribute the wakelock hold to that application.
+ */
+ public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
+ int syncAdapterUid) {
super();
+ mSyncAdapterUid = syncAdapterUid;
mSyncOperation = syncOperation;
mHistoryRowId = historyRowId;
mSyncAdapter = null;
mStartTime = SystemClock.elapsedRealtime();
mTimeoutStartTime = mStartTime;
+ mSyncWakeLock = mSyncHandler.getSyncWakeLock(
+ mSyncOperation.account.type, mSyncOperation.authority);
+ mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
+ mSyncWakeLock.acquire();
}
public void sendHeartbeat() {
@@ -889,6 +900,7 @@
}
public void onFinished(SyncResult result) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
// include "this" in the message so that the handler can ignore it if this
// ActiveSyncContext is no longer the mActiveSyncContext at message handling
// time
@@ -936,6 +948,10 @@
return bindResult;
}
+ /**
+ * Performs the required cleanup, which is the releasing of the wakelock and
+ * unbinding from the sync adapter (if actually bound).
+ */
protected void close() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
@@ -944,6 +960,8 @@
mBound = false;
mContext.unbindService(this);
}
+ mSyncWakeLock.setWorkSource(null);
+ mSyncWakeLock.release();
}
@Override
@@ -1003,62 +1021,28 @@
pw.println("no alarm is scheduled (there had better not be any pending syncs)");
}
- final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
- pw.print("active sync: "); pw.println(activeSyncContext);
-
pw.print("notification info: ");
sb.setLength(0);
mSyncHandler.mSyncNotificationInfo.toString(sb);
pw.println(sb.toString());
+ pw.println();
+ pw.println("Active Syncs: " + mActiveSyncContexts.size());
+ for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
+ pw.print(" ");
+ pw.print(DateUtils.formatElapsedTime(durationInSeconds));
+ pw.print(" - ");
+ pw.print(activeSyncContext.mSyncOperation.dump(false));
+ pw.println();
+ }
+
synchronized (mSyncQueue) {
- pw.print("sync queue: ");
sb.setLength(0);
mSyncQueue.dump(sb);
- pw.println(sb.toString());
}
-
- SyncInfo active = mSyncStorageEngine.getCurrentSync();
- if (active != null) {
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(active.authorityId);
- final long durationInSeconds = (now - active.startTime) / 1000;
- pw.print("Active sync: ");
- pw.print(authority != null ? authority.account : "<no account>");
- pw.print(" ");
- pw.print(authority != null ? authority.authority : "<no account>");
- if (activeSyncContext != null) {
- pw.print(" ");
- pw.print(SyncStorageEngine.SOURCES[
- activeSyncContext.mSyncOperation.syncSource]);
- }
- pw.print(", duration is ");
- pw.println(DateUtils.formatElapsedTime(durationInSeconds));
- } else {
- pw.println("No sync is in progress.");
- }
-
- ArrayList<SyncStorageEngine.PendingOperation> ops
- = mSyncStorageEngine.getPendingOperations();
- if (ops != null && ops.size() > 0) {
- pw.println();
- pw.println("Pending Syncs");
- final int N = ops.size();
- for (int i=0; i<N; i++) {
- SyncStorageEngine.PendingOperation op = ops.get(i);
- pw.print(" #"); pw.print(i); pw.print(": account=");
- pw.print(op.account.name); pw.print(":");
- pw.print(op.account.type); pw.print(" authority=");
- pw.print(op.authority); pw.print(" expedited=");
- pw.println(op.expedited);
- if (op.extras != null && op.extras.size() > 0) {
- sb.setLength(0);
- SyncOperation.extrasToStringBuilder(op.extras, sb, false /* asKey */);
- pw.print(" extras: "); pw.println(sb.toString());
- }
- }
- }
+ pw.println();
+ pw.print(sb.toString());
// join the installed sync adapter with the accounts list and emit for everything
pw.println();
@@ -1261,7 +1245,7 @@
/** Call to let the tracker know that the sync state may have changed */
public synchronized void update() {
- final boolean isSyncInProgress = mActiveSyncContext != null;
+ final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
if (isSyncInProgress == mLastWasSyncing) return;
final long now = SystemClock.elapsedRealtime();
if (isSyncInProgress) {
@@ -1301,17 +1285,14 @@
private static final int MESSAGE_CHECK_ALARMS = 3;
private static final int MESSAGE_SERVICE_CONNECTED = 4;
private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
+ private static final int MESSAGE_CANCEL = 6;
public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
private Long mAlarmScheduleTime = null;
public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
- private PowerManager.WakeLock mSyncWakeLock;
- private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks =
+ private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks =
Maps.newHashMap();
- // used to track if we have installed the error notification so that we don't reinstall
- // it if sync is still failing
- private boolean mErrorNotificationInstalled = false;
private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
public void onBootCompleted() {
mBootCompleted = true;
@@ -1351,12 +1332,6 @@
* Used to keep track of whether a sync notification is active and who it is for.
*/
class SyncNotificationInfo {
- // only valid if isActive is true
- public Account account;
-
- // only valid if isActive is true
- public String authority;
-
// true iff the notification manager has been asked to send the notification
public boolean isActive = false;
@@ -1365,10 +1340,7 @@
public Long startTime = null;
public void toString(StringBuilder sb) {
- sb.append("account ").append(account)
- .append(", authority ").append(authority)
- .append(", isActive ").append(isActive)
- .append(", startTime ").append(startTime);
+ sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
}
@Override
@@ -1384,60 +1356,72 @@
}
public void handleMessage(Message msg) {
- Long earliestFuturePollTime = null;
+ long earliestFuturePollTime = Long.MAX_VALUE;
+ long nextPendingSyncTime = Long.MAX_VALUE;
try {
waitUntilReadyToRun();
+ mSyncManagerWakeLock.acquire();
// 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
// to also take into account the periodic syncs.
earliestFuturePollTime = scheduleReadyPeriodicSyncs();
switch (msg.what) {
+ case SyncHandler.MESSAGE_CANCEL: {
+ Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
+ + payload.first + ", " + payload.second);
+ }
+ cancelActiveSyncLocked(payload.first, payload.second);
+ nextPendingSyncTime = maybeStartNextSyncLocked();
+ break;
+ }
+
case SyncHandler.MESSAGE_SYNC_FINISHED:
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
}
SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
- if (mActiveSyncContext != payload.activeSyncContext) {
- Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
- + "dropping: mActiveSyncContext " + mActiveSyncContext
- + " != " + payload.activeSyncContext);
- return;
+ if (!isSyncStillActive(payload.activeSyncContext)) {
+ Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+ + "sync is no longer active: "
+ + payload.activeSyncContext);
+ break;
}
- runSyncFinishedOrCanceled(payload.syncResult);
+ runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
- // since we are no longer syncing, check if it is time to start a new sync
- runStateIdle();
+ // since a sync just finished check if it is time to start a new sync
+ nextPendingSyncTime = maybeStartNextSyncLocked();
break;
case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
- + msgData.activeSyncContext
- + " active is " + mActiveSyncContext);
+ + msgData.activeSyncContext);
}
// check that this isn't an old message
- if (mActiveSyncContext == msgData.activeSyncContext) {
- runBoundToSyncAdapter(msgData.syncAdapter);
+ if (isSyncStillActive(msgData.activeSyncContext)) {
+ runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
}
break;
}
case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
- ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+ final ActiveSyncContext currentSyncContext =
+ ((ServiceConnectionData)msg.obj).activeSyncContext;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
- + msgData.activeSyncContext
- + " active is " + mActiveSyncContext);
+ + currentSyncContext);
}
// check that this isn't an old message
- if (mActiveSyncContext == msgData.activeSyncContext) {
+ if (isSyncStillActive(currentSyncContext)) {
// cancel the sync if we have a syncadapter, which means one is
// outstanding
- if (mActiveSyncContext.mSyncAdapter != null) {
+ if (currentSyncContext.mSyncAdapter != null) {
try {
- mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext);
+ currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
} catch (RemoteException e) {
// we don't need to retry this in this case
}
@@ -1447,11 +1431,10 @@
// which is a soft error
SyncResult syncResult = new SyncResult();
syncResult.stats.numIoExceptions++;
- runSyncFinishedOrCanceled(syncResult);
+ runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
- // since we are no longer syncing, check if it is time to start a new
- // sync
- runStateIdle();
+ // since a sync just finished check if it is time to start a new sync
+ nextPendingSyncTime = maybeStartNextSyncLocked();
}
break;
@@ -1464,22 +1447,7 @@
}
mAlarmScheduleTime = null;
try {
- if (mActiveSyncContext != null) {
- if (isLoggable) {
- Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
- }
- runStateSyncing();
- }
-
- // if the above call to runStateSyncing() resulted in the end of a sync,
- // check if it is time to start a new sync
- if (mActiveSyncContext == null) {
- if (isLoggable) {
- Log.v(TAG, "handleSyncHandlerMessage: "
- + "sync context is not active");
- }
- runStateIdle();
- }
+ nextPendingSyncTime = maybeStartNextSyncLocked();
} finally {
mHandleAlarmWakeLock.release();
}
@@ -1490,19 +1458,14 @@
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
}
- // we do all the work for this case in the finally block
+ nextPendingSyncTime = maybeStartNextSyncLocked();
break;
}
} finally {
- final boolean isSyncInProgress = mActiveSyncContext != null;
- if (!isSyncInProgress && mSyncWakeLock != null) {
- mSyncWakeLock.release();
- mSyncWakeLock = null;
- }
- manageSyncNotification();
- manageErrorNotification();
- manageSyncAlarm(earliestFuturePollTime);
+ manageSyncNotificationLocked();
+ manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
mSyncTimeTracker.update();
+ mSyncManagerWakeLock.release();
}
}
@@ -1511,10 +1474,10 @@
* @return the desired start time of the earliest future periodic sync operation,
* in milliseconds since boot
*/
- private Long scheduleReadyPeriodicSyncs() {
+ private long scheduleReadyPeriodicSyncs() {
final boolean backgroundDataUsageAllowed =
getConnectivityManager().getBackgroundDataSetting();
- Long earliestFuturePollTime = null;
+ long earliestFuturePollTime = Long.MAX_VALUE;
if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
return earliestFuturePollTime;
}
@@ -1544,23 +1507,27 @@
long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
// if it is ready to run then schedule it and mark it as having been scheduled
if (nextPollTimeAbsolute <= nowAbsolute) {
+ final Pair<Long, Long> backoff =
+ mSyncStorageEngine.getBackoff(info.account, info.authority);
scheduleSyncOperation(
new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
- info.authority, extras, 0 /* delay */));
+ info.authority, extras, 0 /* delay */,
+ backoff != null ? backoff.first : 0,
+ mSyncStorageEngine.getDelayUntilTime(
+ info.account, info.authority)));
status.setPeriodicSyncTime(i, nowAbsolute);
} else {
// it isn't ready to run, remember this time if it is earlier than
// earliestFuturePollTime
- if (earliestFuturePollTime == null
- || nextPollTimeAbsolute < earliestFuturePollTime) {
+ if (nextPollTimeAbsolute < earliestFuturePollTime) {
earliestFuturePollTime = nextPollTimeAbsolute;
}
}
}
}
- if (earliestFuturePollTime == null) {
- return null;
+ if (earliestFuturePollTime == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
}
// convert absolute time to elapsed time
@@ -1570,47 +1537,23 @@
: (earliestFuturePollTime - nowAbsolute));
}
- private void runStateSyncing() {
- // if the sync timeout has been reached then cancel it
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
- final long now = SystemClock.elapsedRealtime();
- if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
- Pair<SyncOperation, Long> nextOpAndRunTime;
- synchronized (mSyncQueue) {
- nextOpAndRunTime = mSyncQueue.nextOperation();
- }
- if (nextOpAndRunTime != null && nextOpAndRunTime.second <= now) {
- Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
- + activeSyncContext.mSyncOperation);
- scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
- null /* no result since this is a cancel */);
- } else {
- activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
- }
- }
-
- // no need to schedule an alarm, as that will be done by our caller.
- }
-
- private void runStateIdle() {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) Log.v(TAG, "runStateIdle");
+ private long maybeStartNextSyncLocked() {
+ final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) Log.v(TAG, "maybeStartNextSync");
// If we aren't ready to run (e.g. the data connection is down), get out.
if (!mDataConnectionIsConnected) {
if (isLoggable) {
- Log.v(TAG, "runStateIdle: no data connection, skipping");
+ Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
}
- return;
+ return Long.MAX_VALUE;
}
if (mStorageIsLow) {
if (isLoggable) {
- Log.v(TAG, "runStateIdle: memory low, skipping");
+ Log.v(TAG, "maybeStartNextSync: memory low, skipping");
}
- return;
+ return Long.MAX_VALUE;
}
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
@@ -1618,46 +1561,56 @@
Account[] accounts = mAccounts;
if (accounts == INITIAL_ACCOUNTS_ARRAY) {
if (isLoggable) {
- Log.v(TAG, "runStateIdle: accounts not known, skipping");
+ Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
}
- return;
+ return Long.MAX_VALUE;
}
// Otherwise consume SyncOperations from the head of the SyncQueue until one is
// found that is runnable (not disabled, etc). If that one is ready to run then
// start it, otherwise just get out.
- SyncOperation op;
- int syncableState;
final boolean backgroundDataUsageAllowed =
getConnectivityManager().getBackgroundDataSetting();
final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
- synchronized (mSyncQueue) {
- final long now = SystemClock.elapsedRealtime();
- while (true) {
- Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
- if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: no more ready sync operations, returning");
- }
- return;
- }
- op = nextOpAndRunTime.first;
+ final long now = SystemClock.elapsedRealtime();
- // we are either going to run this sync or drop it so go ahead and
- // remove it from the queue now
- mSyncQueue.remove(op);
+ // will be set to the next time that a sync should be considered for running
+ long nextReadyToRunTime = Long.MAX_VALUE;
+
+ // order the sync queue, dropping syncs that are not allowed
+ ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
+ synchronized (mSyncQueue) {
+ if (isLoggable) {
+ Log.v(TAG, "build the operation array, syncQueue size is "
+ + mSyncQueue.mOperationsMap.size());
+ }
+ Iterator<SyncOperation> operationIterator =
+ mSyncQueue.mOperationsMap.values().iterator();
+ while (operationIterator.hasNext()) {
+ final SyncOperation op = operationIterator.next();
// drop the sync if the account of this operation no longer exists
if (!ArrayUtils.contains(mAccounts, op.account)) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
continue;
}
-
- // drop this sync request if it isn't syncable, intializing the sync adapter
- // if the syncable state is set to "unknown"
- syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
+ // drop this sync request if it isn't syncable
+ int syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
if (syncableState == 0) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ continue;
+ }
+
+ // if the next run time is in the future, meaning there are no syncs ready
+ // to run, return the time
+ if (op.effectiveRunTime > now) {
+ if (nextReadyToRunTime > op.effectiveRunTime) {
+ nextReadyToRunTime = op.effectiveRunTime;
+ }
continue;
}
@@ -1669,30 +1622,139 @@
|| !backgroundDataUsageAllowed
|| !mSyncStorageEngine.getSyncAutomatically(
op.account, op.authority))) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
continue;
}
- // go ahead and try to sync this syncOperation
- break;
- }
-
- // We will do this sync. Run it outside of the synchronized block.
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: we are going to sync " + op);
+ operations.add(op);
}
}
- // convert the op into an initialization sync if the syncable state is "unknown" and
- // op isn't already an initialization sync. If it is marked syncable then convert
- // this into a regular sync
- final boolean initializeIsSet =
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- if (syncableState < 0 && !initializeIsSet) {
- op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- op = new SyncOperation(op);
- } else if (syncableState > 0 && initializeIsSet) {
- op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- op = new SyncOperation(op);
+ // find the next operation to dispatch, if one is ready
+ // iterate from the top, keep issuing (while potentially cancelling existing syncs)
+ // until the quotas are filled.
+ // once the quotas are filled iterate once more to find when the next one would be
+ // (also considering pre-emption reasons).
+ if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
+ Collections.sort(operations);
+ if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
+ for (int i = 0, N = operations.size(); i < N; i++) {
+ final SyncOperation candidate = operations.get(i);
+ final boolean candidateIsInitialization = candidate.isInitialization();
+
+ int numInit = 0;
+ int numRegular = 0;
+ ActiveSyncContext conflict = null;
+ ActiveSyncContext longRunning = null;
+ ActiveSyncContext toReschedule = null;
+
+ for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final SyncOperation activeOp = activeSyncContext.mSyncOperation;
+ if (activeOp.isInitialization()) {
+ numInit++;
+ } else {
+ numRegular++;
+ }
+ if (activeOp.account.type.equals(candidate.account.type)
+ && activeOp.authority.equals(candidate.authority)) {
+ conflict = activeSyncContext;
+ // don't break out since we want to do a full count of the varieties
+ } else {
+ if (candidateIsInitialization == activeOp.isInitialization()
+ && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
+ longRunning = activeSyncContext;
+ // don't break out since we want to do a full count of the varieties
+ }
+ }
+ }
+
+ if (isLoggable) {
+ Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
+ Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
+ Log.v(TAG, " longRunning: " + longRunning);
+ Log.v(TAG, " conflict: " + conflict);
+ }
+
+ if (conflict != null) {
+ if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
+ && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
+ toReschedule = conflict;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since an initialization "
+ + "takes higher priority, " + conflict);
+ }
+ } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+ && (candidateIsInitialization
+ == conflict.mSyncOperation.isInitialization())) {
+ toReschedule = conflict;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since an expedited "
+ + "takes higher priority, " + conflict);
+ }
+ } else {
+ continue;
+ }
+ } else {
+ final boolean roomAvailable = candidateIsInitialization
+ ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
+ : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
+ if (roomAvailable) {
+ // dispatch candidate
+ } else if (longRunning != null
+ && (candidateIsInitialization
+ == longRunning.mSyncOperation.isInitialization())) {
+ toReschedule = longRunning;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
+ + longRunning);
+ }
+ } else {
+ continue;
+ }
+ }
+
+ if (toReschedule != null) {
+ runSyncFinishedOrCanceledLocked(null, toReschedule);
+ scheduleSyncOperation(toReschedule.mSyncOperation);
+ }
+
+ synchronized (mSyncQueue){
+ mSyncQueue.remove(candidate);
+ }
+ dispatchSyncOperation(candidate);
+ }
+
+ return nextReadyToRunTime;
+ }
+
+ private boolean dispatchSyncOperation(SyncOperation op) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "maybeStartNextSync: we are going to sync " + op);
+ Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
+ for (ActiveSyncContext syncContext : mActiveSyncContexts) {
+ Log.v(TAG, syncContext.toString());
+ }
+ }
+
+ // if this is an initialization sync and there is already a sync running with
+ // the same account type and authority cancel that sync before starting this one
+ // since otherwise the syncadapter will likely reject this request
+ if (op.isInitialization()) {
+ Iterator<ActiveSyncContext> iterator = mActiveSyncContexts.iterator();
+ while (iterator.hasNext()) {
+ ActiveSyncContext syncContext = iterator.next();
+ if (!syncContext.mSyncOperation.isInitialization()
+ && syncContext.mSyncOperation.account.type.equals(op.account.type)
+ && syncContext.mSyncOperation.authority.equals(op.authority)) {
+ Log.d(TAG, "canceling and rescheduling " + syncContext.mSyncOperation
+ + " since we are about to start a sync that used the "
+ + "same sync adapter, " + op);
+ iterator.remove();
+ runSyncFinishedOrCanceledLocked(null, syncContext);
+ scheduleSyncOperation(syncContext.mSyncOperation);
+ }
+ }
}
// connect to the sync adapter
@@ -1703,79 +1765,70 @@
Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+ ", removing settings for it");
mSyncStorageEngine.removeAuthority(op.account, op.authority);
- runStateIdle();
- return;
+ return false;
}
ActiveSyncContext activeSyncContext =
- new ActiveSyncContext(op, insertStartSyncEvent(op));
- mActiveSyncContext = activeSyncContext;
+ new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+ activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
+ mActiveSyncContexts.add(activeSyncContext);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
+ Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
}
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
- mActiveSyncContext.close();
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
- runStateIdle();
- return;
+ closeActiveSyncContext(activeSyncContext);
+ return false;
}
- // Find the wakelock for this account and authority and store it in mSyncWakeLock.
- // Be sure to release the previous wakelock so that we don't end up with it being
- // held until it is used again.
- // There are a couple tricky things about this code:
- // - make sure that we acquire the new wakelock before releasing the old one,
- // otherwise the device might go to sleep as soon as we release it.
- // - since we use non-reference counted wakelocks we have to be sure not to do
- // the release if the wakelock didn't change. Othewise we would do an
- // acquire followed by a release on the same lock, resulting in no lock
- // being held.
- PowerManager.WakeLock oldWakeLock = mSyncWakeLock;
- try {
- mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority);
- mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid));
- mSyncWakeLock.acquire();
- } finally {
- if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) {
- oldWakeLock.release();
- }
- }
-
- // no need to schedule an alarm, as that will be done by our caller.
-
- // the next step will occur when we get either a timeout or a
- // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
+ return true;
}
- private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
- mActiveSyncContext.mSyncAdapter = syncAdapter;
- final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+ private void runBoundToSyncAdapter(ActiveSyncContext activeSyncContext,
+ ISyncAdapter syncAdapter) {
+ activeSyncContext.mSyncAdapter = syncAdapter;
+ final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
try {
- syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
+ syncAdapter.startSync(activeSyncContext, syncOperation.authority,
syncOperation.account, syncOperation.extras);
} catch (RemoteException remoteExc) {
- Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
- mActiveSyncContext.close();
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
+ closeActiveSyncContext(activeSyncContext);
increaseBackoffSetting(syncOperation);
scheduleSyncOperation(new SyncOperation(syncOperation));
} catch (RuntimeException exc) {
- mActiveSyncContext.close();
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ closeActiveSyncContext(activeSyncContext);
Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
}
}
- private void runSyncFinishedOrCanceled(SyncResult syncResult) {
+ private void cancelActiveSyncLocked(Account account, String authority) {
+ ArrayList<ActiveSyncContext> activeSyncs =
+ new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
+ for (ActiveSyncContext activeSyncContext : activeSyncs) {
+ if (activeSyncContext != null) {
+ // if an authority was specified then only cancel the sync if it matches
+ if (account != null) {
+ if (!account.equals(activeSyncContext.mSyncOperation.account)) {
+ return;
+ }
+ }
+ // if an account was specified then only cancel the sync if it matches
+ if (authority != null) {
+ if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
+ return;
+ }
+ }
+ runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
+ activeSyncContext);
+ }
+ }
+ }
+
+ private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
+ ActiveSyncContext activeSyncContext) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- final ActiveSyncContext activeSyncContext = mActiveSyncContext;
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ closeActiveSyncContext(activeSyncContext);
final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
@@ -1795,16 +1848,6 @@
// TODO: set these correctly when the SyncResult is extended to include it
downstreamActivity = 0;
upstreamActivity = 0;
- clearBackoffSetting(syncOperation);
- // if this was an initialization sync and the sync adapter is now
- // marked syncable then reschedule the sync. The next time it runs it
- // will be made into a regular sync.
- if (syncOperation.extras.getBoolean(
- ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
- && mSyncStorageEngine.getIsSyncable(
- syncOperation.account, syncOperation.authority) > 0) {
- scheduleSyncOperation(new SyncOperation(syncOperation));
- }
} else {
Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
// the operation failed so increase the backoff time
@@ -1839,8 +1882,6 @@
stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
upstreamActivity, downstreamActivity, elapsedTime);
- activeSyncContext.close();
-
if (syncResult != null && syncResult.tooManyDeletions) {
installHandleTooManyDeletesNotification(syncOperation.account,
syncOperation.authority, syncResult.stats.numDeletes);
@@ -1851,11 +1892,18 @@
if (syncResult != null && syncResult.fullSyncRequested) {
scheduleSyncOperation(new SyncOperation(syncOperation.account,
- syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
+ syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
+ syncOperation.backoff, syncOperation.delayUntil));
}
// no need to schedule an alarm, as that will be done by our caller.
}
+ private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
+ activeSyncContext.close();
+ mActiveSyncContexts.remove(activeSyncContext);
+ mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo);
+ }
+
/**
* Convert the error-containing SyncResult into the Sync.History error number. Since
* the SyncResult may indicate multiple errors at once, this method just returns the
@@ -1885,11 +1933,11 @@
throw new IllegalStateException("we are not in an error state, " + syncResult);
}
- private void manageSyncNotification() {
+ private void manageSyncNotificationLocked() {
boolean shouldCancel;
boolean shouldInstall;
- if (mActiveSyncContext == null) {
+ if (mActiveSyncContexts.isEmpty()) {
mSyncNotificationInfo.startTime = null;
// we aren't syncing. if the notification is active then remember that we need
@@ -1898,34 +1946,38 @@
shouldInstall = false;
} else {
// we are syncing
- final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
-
final long now = SystemClock.elapsedRealtime();
if (mSyncNotificationInfo.startTime == null) {
mSyncNotificationInfo.startTime = now;
}
- // cancel the notification if it is up and the authority or account is wrong
- shouldCancel = mSyncNotificationInfo.isActive &&
- (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
- || !syncOperation.account.equals(mSyncNotificationInfo.account));
-
- // there are four cases:
- // - the notification is up and there is no change: do nothing
- // - the notification is up but we should cancel since it is stale:
- // need to install
+ // there are three cases:
+ // - the notification is up: do nothing
// - the notification is not up but it isn't time yet: don't install
// - the notification is not up and it is time: need to install
if (mSyncNotificationInfo.isActive) {
- shouldInstall = shouldCancel;
+ shouldInstall = shouldCancel = false;
} else {
+ // it isn't currently up, so there is nothing to cancel
+ shouldCancel = false;
+
final boolean timeToShowNotification =
now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
- // show the notification immediately if this is a manual sync
- final boolean manualSync = syncOperation.extras
- .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- shouldInstall = timeToShowNotification || manualSync;
+ if (timeToShowNotification) {
+ shouldInstall = true;
+ } else {
+ // show the notification immediately if this is a manual sync
+ shouldInstall = false;
+ for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final boolean manualSync = activeSyncContext.mSyncOperation.extras
+ .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+ if (manualSync) {
+ shouldInstall = true;
+ break;
+ }
+ }
+ }
}
}
@@ -1936,94 +1988,82 @@
}
if (shouldInstall) {
- SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
mNeedSyncActiveNotification = true;
sendSyncStateIntent();
mSyncNotificationInfo.isActive = true;
- mSyncNotificationInfo.account = syncOperation.account;
- mSyncNotificationInfo.authority = syncOperation.authority;
}
}
- /**
- * Check if there were any long-lasting errors, if so install the error notification,
- * otherwise cancel the error notification.
- */
- private void manageErrorNotification() {
- //
- long when = mSyncStorageEngine.getInitialSyncFailureTime();
- if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
- if (!mErrorNotificationInstalled) {
- mNeedSyncErrorNotification = true;
- sendSyncStateIntent();
- }
- mErrorNotificationInstalled = true;
- } else {
- if (mErrorNotificationInstalled) {
- mNeedSyncErrorNotification = false;
- sendSyncStateIntent();
- }
- mErrorNotificationInstalled = false;
- }
- }
-
- private void manageSyncAlarm(Long earliestFuturePollElapsedTime) {
+ private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
+ long nextPendingEventElapsedTime) {
// in each of these cases the sync loop will be kicked, which will cause this
// method to be called again
if (!mDataConnectionIsConnected) return;
if (mStorageIsLow) return;
- final long now = SystemClock.elapsedRealtime();
+ // When the status bar notification should be raised
+ final long notificationTime =
+ (!mSyncHandler.mSyncNotificationInfo.isActive
+ && mSyncHandler.mSyncNotificationInfo.startTime != null)
+ ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
+ : Long.MAX_VALUE;
- // Compute the alarm fire time:
- // - not syncing: time of the next sync operation
- // - syncing, no notification: time from sync start to notification create time
- // - syncing, with notification: time till timeout of the active sync operation
- Long alarmTime;
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (activeSyncContext == null) {
- synchronized (mSyncQueue) {
- final Pair<SyncOperation, Long> candidate = mSyncQueue.nextOperation();
- if (earliestFuturePollElapsedTime == null && candidate == null) {
- alarmTime = null;
- } else if (earliestFuturePollElapsedTime == null) {
- alarmTime = candidate.second;
- } else if (candidate == null) {
- alarmTime = earliestFuturePollElapsedTime;
- } else {
- alarmTime = Math.min(earliestFuturePollElapsedTime, candidate.second);
- }
+ // When we should consider canceling an active sync
+ long earliestTimeoutTime = Long.MAX_VALUE;
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ final long currentSyncTimeoutTime =
+ currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
+ + currentSyncTimeoutTime);
}
- } else {
- final long notificationTime =
- mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
- final long timeoutTime =
- mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
- if (mSyncHandler.mSyncNotificationInfo.isActive) {
- alarmTime = timeoutTime;
- } else {
- alarmTime = Math.min(notificationTime, timeoutTime);
+ if (earliestTimeoutTime > currentSyncTimeoutTime) {
+ earliestTimeoutTime = currentSyncTimeoutTime;
}
}
- // adjust the alarmTime so that we will wake up when it is time to
- // install the error notification
- if (!mErrorNotificationInstalled) {
- long when = mSyncStorageEngine.getInitialSyncFailureTime();
- if (when > 0) {
- when += ERROR_NOTIFICATION_DELAY_MS;
- // convert when fron absolute time to elapsed run time
- long delay = when - System.currentTimeMillis();
- when = now + delay;
- alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
+ + nextPeriodicEventElapsedTime);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
+ + nextPendingEventElapsedTime);
+ }
+
+ long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
+ alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
+ alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
+
+ // Bound the alarm time.
+ final long now = SystemClock.elapsedRealtime();
+ if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
+ + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
}
+ alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
+ } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
+ + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+ }
+ alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
}
// determine if we need to set or cancel the alarm
boolean shouldSet = false;
boolean shouldCancel = false;
final boolean alarmIsActive = mAlarmScheduleTime != null;
- final boolean needAlarm = alarmTime != null;
+ final boolean needAlarm = alarmTime != Long.MAX_VALUE;
if (needAlarm) {
if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
shouldSet = true;
@@ -2035,6 +2075,11 @@
// set or cancel the alarm as directed
ensureAlarmService();
if (shouldSet) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
+ + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
+ + " secs from now");
+ }
mAlarmScheduleTime = alarmTime;
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
mSyncAlarmIntent);
@@ -2048,7 +2093,7 @@
Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
- syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
+ syncStateIntent.putExtra("failing", false);
mContext.sendBroadcast(syncStateIntent);
}
@@ -2137,4 +2182,13 @@
resultMessage, downstreamActivity, upstreamActivity);
}
}
+
+ private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
+ for (ActiveSyncContext sync : mActiveSyncContexts) {
+ if (sync == activeSyncContext) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index b0160885..3d7f3fbf 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -33,9 +33,12 @@
public long earliestRunTime;
public boolean expedited;
public SyncStorageEngine.PendingOperation pendingOperation;
+ public Long backoff;
+ public long delayUntil;
+ public long effectiveRunTime;
public SyncOperation(Account account, int source, String authority, Bundle extras,
- long delayInMs) {
+ long delayInMs, long backoff, long delayUntil) {
this.account = account;
this.syncSource = source;
this.authority = authority;
@@ -48,6 +51,8 @@
removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+ this.delayUntil = delayUntil;
+ this.backoff = backoff;
final long now = SystemClock.elapsedRealtime();
if (delayInMs < 0) {
this.expedited = true;
@@ -56,6 +61,7 @@
this.expedited = false;
this.earliestRunTime = now + delayInMs;
}
+ updateEffectiveRunTime();
this.key = toKey();
}
@@ -72,49 +78,78 @@
this.extras = new Bundle(other.extras);
this.expedited = other.expedited;
this.earliestRunTime = SystemClock.elapsedRealtime();
+ this.backoff = other.backoff;
+ this.delayUntil = other.delayUntil;
+ this.updateEffectiveRunTime();
this.key = toKey();
}
public String toString() {
+ return dump(true);
+ }
+
+ public String dump(boolean useOneLine) {
StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account: ").append(account);
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb, false /* asKey */);
- sb.append(" syncSource: ").append(syncSource);
- sb.append(" when: ").append(earliestRunTime);
- sb.append(" expedited: ").append(expedited);
+ sb.append(account.name);
+ sb.append(" (" + account.type + ")");
+ sb.append(", " + authority);
+ sb.append(", ");
+ sb.append(SyncStorageEngine.SOURCES[syncSource]);
+ sb.append(", earliestRunTime " + earliestRunTime);
+ if (expedited) {
+ sb.append(", EXPEDITED");
+ }
+ if (!useOneLine && !extras.keySet().isEmpty()) {
+ sb.append("\n ");
+ extrasToStringBuilder(extras, sb);
+ }
return sb.toString();
}
+ public boolean isInitialization() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+ }
+
+ public boolean ignoreBackoff() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+ }
+
private String toKey() {
StringBuilder sb = new StringBuilder();
sb.append("authority: ").append(authority);
- sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
+ sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
sb.append(" extras: ");
- extrasToStringBuilder(extras, sb, true /* asKey */);
+ extrasToStringBuilder(extras, sb);
return sb.toString();
}
- public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
+ public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
sb.append("[");
for (String key : bundle.keySet()) {
- // if we are writing this as a key don't consider whether this
- // is an initialization sync or not when computing the key since
- // we set this flag appropriately when dispatching the sync request.
- if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
- continue;
- }
sb.append(key).append("=").append(bundle.get(key)).append(" ");
}
sb.append("]");
}
+ public void updateEffectiveRunTime() {
+ effectiveRunTime = ignoreBackoff()
+ ? earliestRunTime
+ : Math.max(
+ Math.max(earliestRunTime, delayUntil),
+ backoff);
+ }
+
public int compareTo(Object o) {
SyncOperation other = (SyncOperation)o;
- if (earliestRunTime == other.earliestRunTime) {
+
+ if (expedited != other.expedited) {
+ return expedited ? -1 : 1;
+ }
+
+ if (effectiveRunTime == other.effectiveRunTime) {
return 0;
}
- return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+
+ return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
}
}
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index 28baa0d..f826147 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -16,17 +16,18 @@
package android.content;
-import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
import android.util.Pair;
import android.util.Log;
import android.accounts.Account;
-import java.util.HashMap;
import java.util.ArrayList;
-import java.util.Map;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
/**
*
@@ -38,7 +39,7 @@
// A Map of SyncOperations operationKey -> SyncOperation that is designed for
// quick lookup of an enqueued SyncOperation.
- private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+ public final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
public SyncQueue(SyncStorageEngine syncStorageEngine) {
mSyncStorageEngine = syncStorageEngine;
@@ -47,8 +48,11 @@
final int N = ops.size();
for (int i=0; i<N; i++) {
SyncStorageEngine.PendingOperation op = ops.get(i);
+ final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority);
SyncOperation syncOperation = new SyncOperation(
- op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+ op.account, op.syncSource, op.authority, op.extras, 0 /* delay */,
+ backoff != null ? backoff.first : 0,
+ syncStorageEngine.getDelayUntilTime(op.account, op.authority));
syncOperation.expedited = op.expedited;
syncOperation.pendingOperation = op;
add(syncOperation, op);
@@ -119,65 +123,26 @@
}
}
- /**
- * Find the operation that should run next. Operations are sorted by their earliestRunTime,
- * prioritizing first those with a syncable state of "unknown" that aren't retries then
- * expedited operations.
- * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
- * @return the operation that should run next and when it should run. The time may be in
- * the future. It is expressed in milliseconds since boot.
- */
- public Pair<SyncOperation, Long> nextOperation() {
- SyncOperation best = null;
- long bestRunTime = 0;
- boolean bestSyncableIsUnknownAndNotARetry = false;
+ public void onBackoffChanged(Account account, String providerName, long backoff) {
+ // for each op that matches the account and provider update its
+ // backoff and effectiveStartTime
for (SyncOperation op : mOperationsMap.values()) {
- long opRunTime = op.earliestRunTime;
- if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
- Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
- long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
- opRunTime = Math.max(
- Math.max(opRunTime, delayUntil),
- backoff != null ? backoff.first : 0);
- }
- // we know a sync is a retry if the intialization flag is set, since that will only
- // be set by the sync dispatching code, thus if it is set it must have already been
- // dispatched
- final boolean syncableIsUnknownAndNotARetry =
- !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
- && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
- // if the unsyncable state differs, make the current the best if it is unsyncable
- // else, if the expedited state differs, make the current the best if it is expedited
- // else, make the current the best if it is earlier than the best
- if (best == null
- || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
- ? (best.expedited == op.expedited
- ? opRunTime < bestRunTime
- : op.expedited)
- : syncableIsUnknownAndNotARetry)) {
- best = op;
- bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
- bestRunTime = opRunTime;
+ if (op.account.equals(account) && op.authority.equals(providerName)) {
+ op.backoff = backoff;
+ op.updateEffectiveRunTime();
}
}
- if (best == null) {
- return null;
- }
- return Pair.create(best, bestRunTime);
}
- /**
- * Find and return the SyncOperation that should be run next and is ready to run.
- * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
- * decide if the sync operation is ready to run
- * @return the SyncOperation that should be run next and is ready to run.
- */
- public Pair<SyncOperation, Long> nextReadyToRun(long now) {
- Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
- if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
- return null;
+ public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
+ // for each op that matches the account and provider update its
+ // delayUntilTime and effectiveStartTime
+ for (SyncOperation op : mOperationsMap.values()) {
+ if (op.account.equals(account) && op.authority.equals(providerName)) {
+ op.delayUntil = delayUntil;
+ op.updateEffectiveRunTime();
+ }
}
- return nextOpAndRunTime;
}
public void remove(Account account, String authority) {
@@ -200,9 +165,17 @@
}
public void dump(StringBuilder sb) {
+ final long now = SystemClock.elapsedRealtime();
sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
for (SyncOperation operation : mOperationsMap.values()) {
- sb.append(operation).append("\n");
+ sb.append(" ");
+ if (operation.effectiveRunTime <= now) {
+ sb.append("READY");
+ } else {
+ sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
+ }
+ sb.append(" - ");
+ sb.append(operation.dump(false)).append("\n");
}
}
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 6413cec..487f6ce 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -229,7 +229,7 @@
private final ArrayList<PendingOperation> mPendingOperations =
new ArrayList<PendingOperation>();
- private SyncInfo mCurrentSync;
+ private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>();
private final SparseArray<SyncStatusInfo> mSyncStatus =
new SparseArray<SyncStatusInfo>();
@@ -690,23 +690,12 @@
/**
* Returns true if there is currently a sync operation for the given
- * account or authority in the pending list, or actively being processed.
+ * account or authority actively being processed.
*/
public boolean isSyncActive(Account account, String authority) {
synchronized (mAuthorities) {
- int i = mPendingOperations.size();
- while (i > 0) {
- i--;
- // TODO(fredq): this probably shouldn't be considering
- // pending operations.
- PendingOperation op = mPendingOperations.get(i);
- if (op.account.equals(account) && op.authority.equals(authority)) {
- return true;
- }
- }
-
- if (mCurrentSync != null) {
- AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
+ for (SyncInfo syncInfo : mCurrentSyncs) {
+ AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
if (ainfo != null && ainfo.account.equals(account)
&& ainfo.authority.equals(authority)) {
return true;
@@ -887,40 +876,47 @@
}
/**
- * Called when the currently active sync is changing (there can only be
- * one at a time). Either supply a valid ActiveSyncContext with information
- * about the sync, or null to stop the currently active sync.
+ * Called when a sync is starting. Supply a valid ActiveSyncContext with information
+ * about the sync.
*/
- public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+ public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+ final SyncInfo syncInfo;
synchronized (mAuthorities) {
- if (activeSyncContext != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setActiveSync: account="
- + activeSyncContext.mSyncOperation.account
- + " auth=" + activeSyncContext.mSyncOperation.authority
- + " src=" + activeSyncContext.mSyncOperation.syncSource
- + " extras=" + activeSyncContext.mSyncOperation.extras);
- }
- if (mCurrentSync != null) {
- Log.w(TAG, "setActiveSync called with existing active sync!");
- }
- AuthorityInfo authority = getAuthorityLocked(
- activeSyncContext.mSyncOperation.account,
- activeSyncContext.mSyncOperation.authority,
- "setActiveSync");
- if (authority == null) {
- return;
- }
- mCurrentSync = new SyncInfo(authority.ident,
- authority.account, authority.authority,
- activeSyncContext.mStartTime);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
- mCurrentSync = null;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setActiveSync: account="
+ + activeSyncContext.mSyncOperation.account
+ + " auth=" + activeSyncContext.mSyncOperation.authority
+ + " src=" + activeSyncContext.mSyncOperation.syncSource
+ + " extras=" + activeSyncContext.mSyncOperation.extras);
}
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ activeSyncContext.mSyncOperation.account,
+ activeSyncContext.mSyncOperation.authority,
+ -1 /* assign a new identifier if creating a new authority */,
+ true /* write to storage if this results in a change */);
+ syncInfo = new SyncInfo(authority.ident,
+ authority.account, authority.authority,
+ activeSyncContext.mStartTime);
+ mCurrentSyncs.add(syncInfo);
}
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
+ reportActiveChange();
+ return syncInfo;
+ }
+
+ /**
+ * Called to indicate that a previously active sync is no longer active.
+ */
+ public void removeActiveSync(SyncInfo syncInfo) {
+ synchronized (mAuthorities) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "removeActiveSync: account="
+ + syncInfo.account + " auth=" + syncInfo.authority);
+ }
+ mCurrentSyncs.remove(syncInfo);
+ }
+
+ reportActiveChange();
}
/**
@@ -1095,10 +1091,26 @@
* Return the currently active sync information, or null if there is no
* active sync. Note that the returned object is the real, live active
* sync object, so be careful what you do with it.
+ * <p>
+ * Since multiple concurrent syncs are now supported you should use
+ * {@link #getCurrentSyncs()} to get the accurate list of current syncs.
+ * This method returns the first item from the list of current syncs
+ * or null if there are none.
+ * @deprecated use {@link #getCurrentSyncs()}
*/
public SyncInfo getCurrentSync() {
synchronized (mAuthorities) {
- return mCurrentSync;
+ return !mCurrentSyncs.isEmpty() ? mCurrentSyncs.get(0) : null;
+ }
+ }
+
+ /**
+ * Return a list of the currently active syncs. Note that the returned items are the
+ * real, live active sync objects, so be careful what you do with it.
+ */
+ public List<SyncInfo> getCurrentSyncs() {
+ synchronized (mAuthorities) {
+ return new ArrayList<SyncInfo>(mCurrentSyncs);
}
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index e21cb97..e688c86 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -216,10 +216,41 @@
public static final int SCREEN_ORIENTATION_SENSOR = 4;
/**
- * Constant corresponding to <code>sensor</code> in
+ * Constant corresponding to <code>nosensor</code> in
* the {@link android.R.attr#screenOrientation} attribute.
*/
public static final int SCREEN_ORIENTATION_NOSENSOR = 5;
+
+ /**
+ * Constant corresponding to <code>sensorLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
+
+ /**
+ * Constant corresponding to <code>sensorPortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7;
+
+ /**
+ * Constant corresponding to <code>reverseLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
+
+ /**
+ * Constant corresponding to <code>reversePortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
+
+ /**
+ * Constant corresponding to <code>fullSensor</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_FULL_SENSOR = 10;
+
/**
* The preferred screen orientation this activity would like to run in.
* From the {@link android.R.attr#screenOrientation} attribute, one of
@@ -229,7 +260,12 @@
* {@link #SCREEN_ORIENTATION_USER},
* {@link #SCREEN_ORIENTATION_BEHIND},
* {@link #SCREEN_ORIENTATION_SENSOR},
- * {@link #SCREEN_ORIENTATION_NOSENSOR}.
+ * {@link #SCREEN_ORIENTATION_NOSENSOR},
+ * {@link #SCREEN_ORIENTATION_SENSOR_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_SENSOR_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_REVERSE_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_FULL_SENSOR}.
*/
public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index f0d0fb4..a98a305 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -410,8 +410,16 @@
* @see #unlock()
*/
/* package */ void lock() {
+ lock(false);
+ }
+ private void lock(boolean forced) {
+ // make sure this method is NOT being called from a 'synchronized' method
+ if (Thread.holdsLock(this)) {
+ // STOPSHIP change the following line to Log.w()
+ throw new IllegalStateException("don't lock() while in a synchronized method");
+ }
verifyDbIsOpen();
- if (!mLockingEnabled) return;
+ if (!forced && !mLockingEnabled) return;
mLock.lock();
if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
if (mLock.getHoldCount() == 1) {
@@ -421,7 +429,6 @@
}
}
}
-
/**
* Locks the database for exclusive access. The database lock must be held when
* touch the native sqlite3* object since it is single threaded and uses
@@ -431,15 +438,7 @@
* @see #unlockForced()
*/
private void lockForced() {
- verifyDbIsOpen();
- mLock.lock();
- if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
- if (mLock.getHoldCount() == 1) {
- // Use elapsed real-time since the CPU may sleep when waiting for IO
- mLockAcquiredWallTime = SystemClock.elapsedRealtime();
- mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
- }
- }
+ lock(true);
}
/**
diff --git a/core/java/android/net/LinkAddress.aidl b/core/java/android/net/LinkAddress.aidl
new file mode 100644
index 0000000..e7d8646
--- /dev/null
+++ b/core/java/android/net/LinkAddress.aidl
@@ -0,0 +1,21 @@
+/**
+ *
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable LinkAddress;
+
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
new file mode 100644
index 0000000..cb302da
--- /dev/null
+++ b/core/java/android/net/LinkAddress.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Identifies an address of a network link
+ * @hide
+ */
+public class LinkAddress implements Parcelable {
+ /**
+ * IPv4 or IPv6 address.
+ */
+ private final InetAddress address;
+
+ /**
+ * Network prefix
+ */
+ private final int prefix;
+
+ public LinkAddress(InetAddress address, InetAddress mask) {
+ this.address = address;
+ this.prefix = computeprefix(mask);
+ }
+
+ public LinkAddress(InetAddress address, int prefix) {
+ this.address = address;
+ this.prefix = prefix;
+ }
+
+ public LinkAddress(InterfaceAddress interfaceAddress) {
+ this.address = interfaceAddress.getAddress();
+ this.prefix = interfaceAddress.getNetworkPrefixLength();
+ }
+
+ private static int computeprefix(InetAddress mask) {
+ int count = 0;
+ for (byte b : mask.getAddress()) {
+ for (int i = 0; i < 8; ++i) {
+ if ((b & (1 << i)) != 0) {
+ ++count;
+ }
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public String toString() {
+ return (address == null ? "" : (address.getHostAddress() + "/" + prefix));
+ }
+
+ /**
+ * Compares this {@code LinkAddress} instance against the specified address
+ * in {@code obj}. Two addresses are equal if their InetAddress and prefix
+ * are equal
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LinkAddress)) {
+ return false;
+ }
+ LinkAddress linkAddress = (LinkAddress) obj;
+ return this.address.equals(linkAddress.address) &&
+ this.prefix == linkAddress.prefix;
+ }
+
+ /**
+ * Returns the InetAddress for this address.
+ */
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Get network prefix length
+ */
+ public int getNetworkPrefix() {
+ return prefix;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (address != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(address.getAddress());
+ dest.writeInt(prefix);
+ } else {
+ dest.writeByte((byte)0);
+ }
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public static final Creator<LinkAddress> CREATOR =
+ new Creator<LinkAddress>() {
+ public LinkAddress createFromParcel(Parcel in) {
+ InetAddress address = null;
+ int prefix = 0;
+ if (in.readByte() == 1) {
+ try {
+ address = InetAddress.getByAddress(in.createByteArray());
+ prefix = in.readInt();
+ } catch (UnknownHostException e) { }
+ }
+ return new LinkAddress(address, prefix);
+ }
+
+ public LinkAddress[] newArray(int size) {
+ return new LinkAddress[size];
+ }
+ };
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index f411eac..f1545ea 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -36,8 +36,8 @@
*/
public class LinkProperties implements Parcelable {
- private NetworkInterface mIface;
- private Collection<InetAddress> mAddresses;
+ String mIfaceName;
+ private Collection<LinkAddress> mLinkAddresses;
private Collection<InetAddress> mDnses;
private InetAddress mGateway;
private ProxyProperties mHttpProxy;
@@ -49,34 +49,42 @@
// copy constructor instead of clone
public LinkProperties(LinkProperties source) {
if (source != null) {
- mIface = source.getInterface();
- mAddresses = source.getAddresses();
+ mIfaceName = source.getInterfaceName();
+ mLinkAddresses = source.getLinkAddresses();
mDnses = source.getDnses();
mGateway = source.getGateway();
mHttpProxy = new ProxyProperties(source.getHttpProxy());
}
}
- public void setInterface(NetworkInterface iface) {
- mIface = iface;
- }
- public NetworkInterface getInterface() {
- return mIface;
- }
- public String getInterfaceName() {
- return (mIface == null ? null : mIface.getName());
+ public void setInterfaceName(String iface) {
+ mIfaceName = iface;
}
- public void addAddress(InetAddress address) {
- mAddresses.add(address);
+ public String getInterfaceName() {
+ return mIfaceName;
}
+
public Collection<InetAddress> getAddresses() {
- return Collections.unmodifiableCollection(mAddresses);
+ Collection<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ addresses.add(linkAddress.getAddress());
+ }
+ return Collections.unmodifiableCollection(addresses);
+ }
+
+ public void addLinkAddress(LinkAddress address) {
+ mLinkAddresses.add(address);
+ }
+
+ public Collection<LinkAddress> getLinkAddresses() {
+ return Collections.unmodifiableCollection(mLinkAddresses);
}
public void addDns(InetAddress dns) {
mDnses.add(dns);
}
+
public Collection<InetAddress> getDnses() {
return Collections.unmodifiableCollection(mDnses);
}
@@ -96,8 +104,8 @@
}
public void clear() {
- mIface = null;
- mAddresses = new ArrayList<InetAddress>();
+ mIfaceName = null;
+ mLinkAddresses = new ArrayList<LinkAddress>();
mDnses = new ArrayList<InetAddress>();
mGateway = null;
mHttpProxy = null;
@@ -113,11 +121,11 @@
@Override
public String toString() {
- String ifaceName = (mIface == null ? "" : "InterfaceName: " + mIface.getName() + " ");
+ String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
- String ip = "IpAddresses: [";
- for (InetAddress addr : mAddresses) ip += addr.getHostAddress() + ",";
- ip += "] ";
+ String linkAddresses = "LinkAddresses: [";
+ for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString();
+ linkAddresses += "] ";
String dns = "DnsAddresses: [";
for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
@@ -126,7 +134,7 @@
String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
String gateway = (mGateway == null ? "" : "Gateway: " + mGateway.getHostAddress() + " ");
- return ifaceName + ip + gateway + dns + proxy;
+ return ifaceName + linkAddresses + gateway + dns + proxy;
}
/**
@@ -135,12 +143,11 @@
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getInterfaceName());
- dest.writeInt(mAddresses.size());
- //TODO: explore an easy alternative to preserve hostname
- // without doing a lookup
- for(InetAddress a : mAddresses) {
- dest.writeByteArray(a.getAddress());
+ dest.writeInt(mLinkAddresses.size());
+ for(LinkAddress linkAddress : mLinkAddresses) {
+ dest.writeParcelable(linkAddress, flags);
}
+
dest.writeInt(mDnses.size());
for(InetAddress d : mDnses) {
dest.writeByteArray(d.getAddress());
@@ -170,16 +177,14 @@
String iface = in.readString();
if (iface != null) {
try {
- netProp.setInterface(NetworkInterface.getByName(iface));
+ netProp.setInterfaceName(iface);
} catch (Exception e) {
return null;
}
}
int addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
- try {
- netProp.addAddress(InetAddress.getByAddress(in.createByteArray()));
- } catch (UnknownHostException e) { }
+ netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
}
addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index ad7289d..3df8ec0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -45,7 +45,7 @@
public class MobileDataStateTracker implements NetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private Phone.DataState mMobileDataState;
private ITelephony mPhoneService;
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index a58e70b..2b4f39a 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -775,6 +775,15 @@
public static native void dumpNativeHeap(FileDescriptor fd);
/**
+ * Returns a count of the extant instances of a class.
+ *
+ * @hide
+ */
+ public static long countInstancesOfClass(Class cls) {
+ return VMDebug.countInstancesOfClass(cls);
+ }
+
+ /**
* Returns the number of sent transactions from this process.
* @return The number of sent transactions or -1 if it could not read t.
*/
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index a6c7d9e..a59b2f8 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -893,11 +893,15 @@
}
}
- public void switchToHeaderInner(String fragmentName, Bundle args) {
+ public void switchToHeaderInner(String fragmentName, Bundle args, boolean next) {
getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
Fragment f = Fragment.instantiate(this, fragmentName, args);
- getFragmentManager().openTransaction().replace(
- com.android.internal.R.id.prefs, f).commit();
+ FragmentTransaction transaction = getFragmentManager().openTransaction();
+ transaction.setTransition(next ?
+ FragmentTransaction.TRANSIT_FRAGMENT_NEXT :
+ FragmentTransaction.TRANSIT_FRAGMENT_PREV);
+ transaction.replace(com.android.internal.R.id.prefs, f);
+ transaction.commit();
}
/**
@@ -909,7 +913,7 @@
*/
public void switchToHeader(String fragmentName, Bundle args) {
setSelectedHeader(null);
- switchToHeaderInner(fragmentName, args);
+ switchToHeaderInner(fragmentName, args, true);
}
/**
@@ -919,7 +923,8 @@
* @param header The new header to display.
*/
public void switchToHeader(Header header) {
- switchToHeaderInner(header.fragment, header.fragmentArguments);
+ switchToHeaderInner(header.fragment, header.fragmentArguments,
+ mHeaders.indexOf(header) > mHeaders.indexOf(mCurHeader));
setSelectedHeader(header);
}
@@ -979,7 +984,10 @@
FragmentTransaction transaction = getFragmentManager().openTransaction();
startPreferenceFragment(fragment, transaction);
if (push) {
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
+ } else {
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_NEXT);
}
transaction.commit();
}
@@ -1001,6 +1009,7 @@
FragmentTransaction transaction = getFragmentManager().openTransaction();
startPreferenceFragment(f, transaction);
transaction.setBreadCrumbTitle(pref.getTitle());
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
transaction.commit();
return true;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 63450de..35b806a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -66,6 +66,7 @@
import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
+import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -8542,7 +8543,7 @@
if ((privateFlags & PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
- if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
+ if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
@@ -9842,15 +9843,15 @@
* The base class implementation makes the thumbnail the same size and appearance
* as the view itself, and positions it with its center at the touch point.
*/
- public class DragThumbnailBuilder {
- private View mView;
+ public static class DragThumbnailBuilder {
+ private final WeakReference<View> mView;
/**
* Construct a thumbnail builder object for use with the given view.
* @param view
*/
public DragThumbnailBuilder(View view) {
- mView = view;
+ mView = new WeakReference<View>(view);
}
/**
@@ -9870,8 +9871,13 @@
* the touch point on the screen during a drag.
*/
public void onProvideThumbnailMetrics(Point thumbnailSize, Point thumbnailTouchPoint) {
- thumbnailSize.set(mView.getWidth(), mView.getHeight());
- thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2);
+ final View view = mView.get();
+ if (view != null) {
+ thumbnailSize.set(view.getWidth(), view.getHeight());
+ thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2);
+ } else {
+ Log.e(View.VIEW_LOG_TAG, "Asked for drag thumb metrics but no view");
+ }
}
/**
@@ -9882,7 +9888,12 @@
* @param canvas
*/
public void onDrawThumbnail(Canvas canvas) {
- mView.draw(canvas);
+ final View view = mView.get();
+ if (view != null) {
+ view.draw(canvas);
+ } else {
+ Log.e(View.VIEW_LOG_TAG, "Asked to draw drag thumb but no view");
+ }
}
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 727cf17..11e68c5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -27,7 +27,6 @@
import android.os.Environment;
import android.os.Debug;
import android.os.RemoteException;
-import dalvik.system.VMDebug;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -431,7 +430,7 @@
* @hide
*/
public static long getViewInstanceCount() {
- return VMDebug.countInstancesOfClass(View.class);
+ return Debug.countInstancesOfClass(View.class);
}
/**
@@ -442,7 +441,7 @@
* @hide
*/
public static long getViewRootInstanceCount() {
- return VMDebug.countInstancesOfClass(ViewRoot.class);
+ return Debug.countInstancesOfClass(ViewRoot.class);
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e71a4d6..5ebc981 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,8 +19,6 @@
import android.animation.LayoutTransition;
import com.android.internal.R;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -963,7 +961,7 @@
final View[] children = mChildren;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
- if (child.mCanAcceptDrop == false) {
+ if (!child.mCanAcceptDrop) {
continue;
}
@@ -1179,16 +1177,20 @@
return handled;
}
- /* Resets all touch state in preparation for a new cycle. */
- private final void resetTouchState() {
+ /**
+ * Resets all touch state in preparation for a new cycle.
+ */
+ private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
- /* Resets the cancel next up flag.
- * Returns true if the flag was previously set. */
- private final boolean resetCancelNextUpFlag(View view) {
+ /**
+ * Resets the cancel next up flag.
+ * Returns true if the flag was previously set.
+ */
+ private boolean resetCancelNextUpFlag(View view) {
if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
return true;
@@ -1196,8 +1198,10 @@
return false;
}
- /* Clears all touch targets. */
- private final void clearTouchTargets() {
+ /**
+ * Clears all touch targets.
+ */
+ private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
@@ -1209,8 +1213,10 @@
}
}
- /* Cancels and clears all touch targets. */
- private final void cancelAndClearTouchTargets(MotionEvent event) {
+ /**
+ * Cancels and clears all touch targets.
+ */
+ private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
@@ -1232,9 +1238,11 @@
}
}
- /* Gets the touch target for specified child view.
- * Returns null if not found. */
- private final TouchTarget getTouchTarget(View child) {
+ /**
+ * Gets the touch target for specified child view.
+ * Returns null if not found.
+ */
+ private TouchTarget getTouchTarget(View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
@@ -1243,17 +1251,21 @@
return null;
}
- /* Adds a touch target for specified child to the beginning of the list.
- * Assumes the target child is not already present. */
- private final TouchTarget addTouchTarget(View child, int pointerIdBits) {
+ /**
+ * Adds a touch target for specified child to the beginning of the list.
+ * Assumes the target child is not already present.
+ */
+ private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
- /* Removes the pointer ids from consideration. */
- private final void removePointersFromTouchTargets(int pointerIdBits) {
+ /**
+ * Removes the pointer ids from consideration.
+ */
+ private void removePointersFromTouchTargets(int pointerIdBits) {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
@@ -1276,10 +1288,12 @@
}
}
- /* Returns true if a child view contains the specified point when transformed
+ /**
+ * Returns true if a child view contains the specified point when transformed
* into its coordinate space.
- * Child must not be null. */
- private final boolean isTransformedTouchPointInView(float x, float y, View child,
+ * Child must not be null.
+ */
+ private boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
float localX = x + mScrollX - child.mLeft;
float localY = y + mScrollY - child.mTop;
@@ -1298,10 +1312,12 @@
return isInView;
}
- /* Transforms a motion event into the coordinate space of a particular child view,
+ /**
+ * Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
- * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */
- private final boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+ * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
+ */
+ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
@@ -1476,9 +1492,11 @@
return handled;
}
- /* Enlarge the temporary pointer arrays for splitting pointers.
- * May discard contents (but keeps PointerCoords objects to avoid reallocating them). */
- private final void growTmpPointerArrays(int desiredCapacity) {
+ /**
+ * Enlarge the temporary pointer arrays for splitting pointers.
+ * May discard contents (but keeps PointerCoords objects to avoid reallocating them).
+ */
+ private void growTmpPointerArrays(int desiredCapacity) {
final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords;
int capacity;
if (oldTmpPointerCoords != null) {
@@ -2174,7 +2192,8 @@
if (!canvas.isHardwareAccelerated()) {
cache = child.getDrawingCache(true);
} else {
- displayList = child.getDisplayList();
+ // TODO: bring back
+ // displayList = child.getDisplayList();
}
}
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 8ffa158..3e2e92b 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -28,6 +28,11 @@
* A special helper class used by the WindowManager
* for receiving notifications from the SensorManager when
* the orientation of the device has changed.
+ *
+ * NOTE: If changing anything here, please run the API demo
+ * "App/Activity/Screen Orientation" to ensure that all orientation
+ * modes still work correctly.
+ *
* @hide
*/
public abstract class WindowOrientationListener {
@@ -103,6 +108,10 @@
}
}
+ public void setAllow180Rotation(boolean allowed) {
+ mSensorEventListener.setAllow180Rotation(allowed);
+ }
+
public int getCurrentRotation(int lastRotation) {
if (mEnabled) {
return mSensorEventListener.getCurrentRotation(lastRotation);
@@ -151,19 +160,20 @@
private static final int ROTATION_0 = 0;
private static final int ROTATION_90 = 1;
private static final int ROTATION_270 = 2;
+ private static final int ROTATION_180 = 3;
// Mapping our internal aliases into actual Surface rotation values
private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] {
- Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270};
+ Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270,
+ Surface.ROTATION_180};
// Mapping Surface rotation values to internal aliases.
- // We have no constant for Surface.ROTATION_180. That should never happen, but if it
- // does, we'll arbitrarily choose a mapping.
private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] {
- ROTATION_0, ROTATION_90, ROTATION_90, ROTATION_270};
+ ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270};
// Threshold ranges of orientation angle to transition into other orientation states.
- // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
+ // The first list is for transitions from ROTATION_0, ROTATION_90, ROTATION_270,
+ // and then ROTATION_180.
// ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
// in sync with this.
// We generally transition about the halfway point between two states with a swing of 30
@@ -172,13 +182,32 @@
{{60, 180}, {180, 300}},
{{0, 30}, {195, 315}, {315, 360}},
{{0, 45}, {45, 165}, {330, 360}},
- };
+ // Handle situation where we are currently doing 180 rotation
+ // but that is no longer allowed.
+ {{0, 45}, {45, 135}, {135, 225}, {225, 315}, {315, 360}},
+ };
// See THRESHOLDS
private static final int[][] ROTATE_TO = new int[][] {
{ROTATION_90, ROTATION_270},
{ROTATION_0, ROTATION_270, ROTATION_0},
{ROTATION_0, ROTATION_90, ROTATION_0},
+ {ROTATION_0, ROTATION_90, ROTATION_0, ROTATION_270, ROTATION_0},
+ };
+
+ // Thresholds that allow all 4 orientations.
+ private static final int[][][] THRESHOLDS_WITH_180 = new int[][][] {
+ {{60, 165}, {165, 195}, {195, 300}},
+ {{0, 30}, {165, 195}, {195, 315}, {315, 360}},
+ {{0, 45}, {45, 165}, {165, 195}, {330, 360}},
+ {{0, 45}, {45, 135}, {225, 315}, {315, 360}},
+ };
+ // See THRESHOLDS_WITH_180
+ private static final int[][] ROTATE_TO_WITH_180 = new int[][] {
+ {ROTATION_90, ROTATION_180, ROTATION_270},
+ {ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0},
+ {ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0},
+ {ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_0},
};
// Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
@@ -188,7 +217,7 @@
// Additional limits on tilt angle to transition to each new orientation. We ignore all
// data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
// particular orientation here.
- private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65};
+ private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65, 40};
// Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
// with a higher time constant, making us less sensitive to change. This primarily helps
@@ -228,6 +257,8 @@
private static final float ACCELERATING_LOWPASS_ALPHA =
computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
+ private boolean mAllow180Rotation = false;
+
private WindowOrientationListener mOrientationListener;
private int mRotation = ROTATION_0; // Current orientation state
private float mTiltAngle = 0; // low-pass filtered
@@ -249,6 +280,10 @@
return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
}
+ void setAllow180Rotation(boolean allowed) {
+ mAllow180Rotation = allowed;
+ }
+
int getCurrentRotation(int lastRotation) {
if (mTiltDistrust > 0) {
// we really don't know the current orientation, so trust what's currently displayed
@@ -259,7 +294,9 @@
private void calculateNewRotation(float orientation, float tiltAngle) {
if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
- int thresholdRanges[][] = THRESHOLDS[mRotation];
+ final boolean allow180Rotation = mAllow180Rotation;
+ int thresholdRanges[][] = allow180Rotation
+ ? THRESHOLDS_WITH_180[mRotation] : THRESHOLDS[mRotation];
int row = -1;
for (int i = 0; i < thresholdRanges.length; i++) {
if (orientation >= thresholdRanges[i][0] && orientation < thresholdRanges[i][1]) {
@@ -269,13 +306,15 @@
}
if (row == -1) return; // no matching transition
- int rotation = ROTATE_TO[mRotation][row];
+ int rotation = allow180Rotation
+ ? ROTATE_TO_WITH_180[mRotation][row] : ROTATE_TO[mRotation][row];
if (tiltAngle > MAX_TRANSITION_TILT[rotation]) {
// tilted too far flat to go to this rotation
return;
}
- if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
+ if (localLOGV) Log.i(TAG, "orientation " + orientation + " gives new rotation = "
+ + rotation);
mRotation = rotation;
mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]);
}
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index 4ff849e..9b866d3 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -41,6 +41,7 @@
private Double mAlpha;
private Double mBeta;
private Double mGamma;
+ private boolean mHaveSentErrorEvent;
private static final double DELTA_DEGRESS = 1.0;
@@ -75,11 +76,16 @@
private void sendErrorEvent() {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+ // The spec requires that each listener receives the error event only once.
+ if (mHaveSentErrorEvent)
+ return;
+ mHaveSentErrorEvent = true;
mHandler.post(new Runnable() {
@Override
public void run() {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
if (mIsRunning) {
+ // The special case of all nulls is used to signify a failure to get data.
mManager.onOrientationChange(null, null, null);
}
}
@@ -169,6 +175,8 @@
mBeta = beta;
mGamma = gamma;
mManager.onOrientationChange(mAlpha, mBeta, mGamma);
+ // Now that we have successfully sent some data, reset whether we've sent an error.
+ mHaveSentErrorEvent = false;
}
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 7462668..7c089d8 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.media.MediaFile;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -252,6 +253,13 @@
return mSettings;
}
+ /*
+ * Given mimeType, check whether it's supported in Android media framework.
+ * mimeType could be such as "audio/ogg" and "video/mp4".
+ */
+ /* package */ static boolean supportsMimeType(String mimeType) {
+ return MediaFile.getFileTypeForMimeType(mimeType) > 0;
+ }
/**
* Add an error message to the client's console.
* @param message The message to add
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 9983d54..b7b1a23 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import android.animation.AnimatorInflater;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
@@ -135,12 +136,15 @@
int mReferenceChildHeight = -1;
/**
- * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
+ * In and out animations.
*/
- Animation mInAnimation;
- Animation mOutAnimation;
+ ObjectAnimator<?> mInAnimation;
+ ObjectAnimator<?> mOutAnimation;
+
private ArrayList<View> mViewsToBringToFront;
+ private static final int DEFAULT_ANIMATION_DURATION = 200;
+
public AdapterViewAnimator(Context context) {
super(context);
initViewAnimator();
@@ -155,11 +159,15 @@
com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
setInAnimation(context, resource);
+ } else {
+ setInAnimation(getDefaultInAnimation());
}
resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
if (resource > 0) {
setOutAnimation(context, resource);
+ } else {
+ setOutAnimation(getDefaultOutAnimation());
}
boolean flag = a.getBoolean(
@@ -229,17 +237,23 @@
* @param view The view that is being animated
*/
void animateViewForTransition(int fromIndex, int toIndex, View view) {
- ObjectAnimator pa;
if (fromIndex == -1) {
- view.setAlpha(0.0f);
- pa = new ObjectAnimator(400, view, "alpha", 0.0f, 1.0f);
- pa.start();
+ mInAnimation.setTarget(view);
+ mInAnimation.start();
} else if (toIndex == -1) {
- pa = new ObjectAnimator(400, view, "alpha", 1.0f, 0.0f);
- pa.start();
+ mOutAnimation.setTarget(view);
+ mOutAnimation.start();
}
}
+ ObjectAnimator<?> getDefaultInAnimation() {
+ return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 0.0f, 1.0f);
+ }
+
+ ObjectAnimator<?> getDefaultOutAnimation() {
+ return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 1.0f, 0.0f);
+ }
+
/**
* Sets which child view will be displayed.
*
@@ -265,20 +279,6 @@
}
/**
- * Return default inAnimation. To be overriden by subclasses.
- */
- Animation getDefaultInAnimation() {
- return null;
- }
-
- /**
- * Return default outAnimation. To be overridden by subclasses.
- */
- Animation getDefaultOutAnimation() {
- return null;
- }
-
- /**
* To be overridden by subclasses. This method applies a view / index specific
* transform to the child view.
*
@@ -324,7 +324,11 @@
}
private int modulo(int pos, int size) {
- return (size + (pos % size)) % size;
+ if (size > 0) {
+ return (size + (pos % size)) % size;
+ } else {
+ return 0;
+ }
}
/**
@@ -383,6 +387,8 @@
void showOnly(int childIndex, boolean animate, boolean onLayout) {
if (mAdapter == null) return;
+ final int adapterCount = mAdapter.getCount();
+ if (adapterCount == 0) return;
for (int i = 0; i < mPreviousViews.size(); i++) {
View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
@@ -399,7 +405,6 @@
removeViewInLayout(viewToRemove);
}
mPreviousViews.clear();
- int adapterCount = mAdapter.getCount();
int newWindowStartUnbounded = childIndex - mActiveOffset;
int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
int newWindowStart = Math.max(0, newWindowStartUnbounded);
@@ -690,7 +695,7 @@
* @see #setInAnimation(android.view.animation.Animation)
* @see #setInAnimation(android.content.Context, int)
*/
- public Animation getInAnimation() {
+ public ObjectAnimator<?> getInAnimation() {
return mInAnimation;
}
@@ -702,7 +707,7 @@
* @see #getInAnimation()
* @see #setInAnimation(android.content.Context, int)
*/
- public void setInAnimation(Animation inAnimation) {
+ public void setInAnimation(ObjectAnimator<?> inAnimation) {
mInAnimation = inAnimation;
}
@@ -714,7 +719,7 @@
* @see #setOutAnimation(android.view.animation.Animation)
* @see #setOutAnimation(android.content.Context, int)
*/
- public Animation getOutAnimation() {
+ public ObjectAnimator<?> getOutAnimation() {
return mOutAnimation;
}
@@ -726,7 +731,7 @@
* @see #getOutAnimation()
* @see #setOutAnimation(android.content.Context, int)
*/
- public void setOutAnimation(Animation outAnimation) {
+ public void setOutAnimation(ObjectAnimator<?> outAnimation) {
mOutAnimation = outAnimation;
}
@@ -740,7 +745,7 @@
* @see #setInAnimation(android.view.animation.Animation)
*/
public void setInAnimation(Context context, int resourceID) {
- setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
+ setInAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
}
/**
@@ -753,7 +758,7 @@
* @see #setOutAnimation(android.view.animation.Animation)
*/
public void setOutAnimation(Context context, int resourceID) {
- setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
+ setOutAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
}
/**
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 95ebdd3..b09ade7 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.animation.ObjectAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,10 +44,8 @@
private static final boolean LOGD = false;
private static final int DEFAULT_INTERVAL = 10000;
- private static final int DEFAULT_ANIMATION_DURATION = 200;
private int mFlipInterval = DEFAULT_INTERVAL;
- private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
private boolean mAutoStart = false;
private boolean mRunning = false;
@@ -56,7 +55,6 @@
public AdapterViewFlipper(Context context) {
super(context);
- initDefaultAnimations();
}
public AdapterViewFlipper(Context context, AttributeSet attrs) {
@@ -74,19 +72,6 @@
com.android.internal.R.styleable.AdapterViewAnimator_loopViews, true);
a.recycle();
- initDefaultAnimations();
- }
-
- private void initDefaultAnimations() {
- // Set the default animations to be fade in/out
- if (mInAnimation == null) {
- mInAnimation = new AlphaAnimation(0.0f, 1.0f);
- mInAnimation.setDuration(mAnimationDuration);
- }
- if (mOutAnimation == null) {
- mOutAnimation = new AlphaAnimation(1.0f, 0.0f);
- mOutAnimation.setDuration(mAnimationDuration);
- }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 839de7d..f0954e2 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -749,7 +749,9 @@
if (mAdapter != null && mWhichChild == -1) {
mWhichChild = mAdapter.getCount() - 1;
}
- setDisplayedChild(mWhichChild);
+ if (mWhichChild >= 0) {
+ setDisplayedChild(mWhichChild);
+ }
}
LayoutParams createOrReuseLayoutParams(View v) {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 4a0617c..fefdcea 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -17,6 +17,9 @@
package com.android.internal.app;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import com.android.internal.R;
+
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -32,10 +35,11 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
-import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckedTextView;
@@ -48,9 +52,6 @@
import android.widget.ScrollView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-
-import com.android.internal.R;
import java.lang.ref.WeakReference;
@@ -403,12 +404,10 @@
mIconView = (ImageView) mWindow.findViewById(R.id.icon);
if (hasTextTitle) {
-
/* Display the title if a title is supplied, else hide it */
mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
mTitleView.setText(mTitle);
- mIconView.setImageResource(R.drawable.ic_dialog_menu_generic);
/* Do this last so that if the user has supplied any
* icons we use them instead of the default ones. If the
@@ -689,7 +688,7 @@
public final Context mContext;
public final LayoutInflater mInflater;
- public int mIconId = -1;
+ public int mIconId = 0;
public Drawable mIcon;
public CharSequence mTitle;
public View mCustomTitleView;
diff --git a/core/res/res/anim/fragment_close_enter.xml b/core/res/res/anim/fragment_close_enter.xml
index 53afa2a..7a9a3b9 100644
--- a/core/res/res/anim/fragment_close_enter.xml
+++ b/core/res/res/anim/fragment_close_enter.xml
@@ -19,20 +19,6 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_close_exit.xml b/core/res/res/anim/fragment_close_exit.xml
index 1554a4e..0743577 100644
--- a/core/res/res/anim/fragment_close_exit.xml
+++ b/core/res/res/anim/fragment_close_exit.xml
@@ -20,20 +20,6 @@
<objectAnimator
android:interpolator="@anim/accelerate_interpolator"
android:valueFrom="1"
- android:valueTo=".5"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
- android:valueTo=".5"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="alpha"
diff --git a/core/res/res/anim/fragment_next_enter.xml b/core/res/res/anim/fragment_next_enter.xml
new file mode 100644
index 0000000..d2d6ec9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_longAnimTime"/>
+ <objectAnimator
+ android:valueFrom="50"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_longAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_next_exit.xml b/core/res/res/anim/fragment_next_exit.xml
new file mode 100644
index 0000000..fbb82d9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_exit.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="-50"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_open_enter.xml b/core/res/res/anim/fragment_open_enter.xml
index 142f60c..ac60494 100644
--- a/core/res/res/anim/fragment_open_enter.xml
+++ b/core/res/res/anim/fragment_open_enter.xml
@@ -18,20 +18,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_open_exit.xml b/core/res/res/anim/fragment_open_exit.xml
index 21260b9..3bf1ad4 100644
--- a/core/res/res/anim/fragment_open_exit.xml
+++ b/core/res/res/anim/fragment_open_exit.xml
@@ -18,20 +18,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
- android:valueTo="2"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
- android:valueTo="2"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_prev_enter.xml b/core/res/res/anim/fragment_prev_enter.xml
new file mode 100644
index 0000000..d37afd0
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_longAnimTime"/>
+ <objectAnimator
+ android:valueFrom="-50"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_longAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/fragment_prev_exit.xml b/core/res/res/anim/fragment_prev_exit.xml
new file mode 100644
index 0000000..a445a4d
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_exit.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="50"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
\ No newline at end of file
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_100.png b/core/res/res/drawable-hdpi/stat_sys_battery_100.png
index e85f524..e49448d 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_100.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_100.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_40.png b/core/res/res/drawable-hdpi/stat_sys_battery_40.png
index ca70784..441bbfb 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_40.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_40.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_60.png b/core/res/res/drawable-hdpi/stat_sys_battery_60.png
index fee28db..d9467ed 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_60.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_60.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_80.png b/core/res/res/drawable-hdpi/stat_sys_battery_80.png
index 7f4a38f..e3f4805 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_80.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_80.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png
index 2277096..426a66b 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png
index 7a3b19e..21582ca 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png
index 5c23c13..8a94763 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png
index 321545f..fad0d65 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim1.png b/core/res/res/drawable-hdpi/stat_sys_download_anim1.png
index 4f47db0..851eff9 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim1.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim2.png b/core/res/res/drawable-hdpi/stat_sys_download_anim2.png
index e50d3c3..2d06bb1 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim3.png b/core/res/res/drawable-hdpi/stat_sys_download_anim3.png
index 6c1029c..5c0ae7a 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim3.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim4.png b/core/res/res/drawable-hdpi/stat_sys_download_anim4.png
index 693f085..f8d5c8a 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim4.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim5.png b/core/res/res/drawable-hdpi/stat_sys_download_anim5.png
index eabe0ef..32b1425 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim5.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim5.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png
index d72afbe..32b5077 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
index 5de2873..8387b45 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
index dd0b49f..c9a56a0 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png
index 8ac29b3..972808a 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png
index 26c3e714..d47c761 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png
index 431ac6b..7aeafbe 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png
Binary files differ
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8e2b762..33d3eeb 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1088,6 +1088,10 @@
<attr name="fragmentOpenExitAnimation" format="reference" />
<attr name="fragmentCloseEnterAnimation" format="reference" />
<attr name="fragmentCloseExitAnimation" format="reference" />
+ <attr name="fragmentNextEnterAnimation" format="reference" />
+ <attr name="fragmentNextExitAnimation" format="reference" />
+ <attr name="fragmentPrevEnterAnimation" format="reference" />
+ <attr name="fragmentPrevExitAnimation" format="reference" />
</declare-styleable>
<!-- Window animation class attributes. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8cf65ae..949d960 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -497,10 +497,10 @@
orientation will changed based on how the user rotates the device -->
<enum name="unspecified" value="-1" />
<!-- Would like to have the screen in a landscape orientation: that
- is, with the display wider than it is tall. -->
+ is, with the display wider than it is tall, ignoring sensor data. -->
<enum name="landscape" value="0" />
<!-- Would like to have the screen in a portrait orientation: that
- is, with the display taller than it is wide. -->
+ is, with the display taller than it is wide, ignoring sensor data. -->
<enum name="portrait" value="1" />
<!-- Use the user's current preferred orientation of the handset. -->
<enum name="user" value="2" />
@@ -513,6 +513,24 @@
<!-- Always ignore orientation determined by orientation sensor:
the display will not rotate when the user moves the device. -->
<enum name="nosensor" value="5" />
+ <!-- Would like to have the screen in landscape orientation, but can
+ use the sensor to change which direction the screen is facing. -->
+ <enum name="sensorLandscape" value="6" />
+ <!-- Would like to have the screen in portrait orientation, but can
+ use the sensor to change which direction the screen is facing. -->
+ <enum name="sensorPortait" value="7" />
+ <!-- Would like to have the screen in landscape orientation, turned in
+ the opposite direction from normal landscape. -->
+ <enum name="reverseLandscape" value="8" />
+ <!-- Would like to have the screen in portrait orientation, turned in
+ the opposite direction from normal portrait. -->
+ <enum name="reversePortait" value="9" />
+ <!-- Orientation is determined by a physical orientation sensor:
+ the display will rotate based on how the user moves the device.
+ This allows any of the 4 possible rotations, regardless of what
+ the device will normally do (for example some devices won't
+ normally use 180 degree rotation). -->
+ <enum name="fullSensor" value="10" />
</attr>
<!-- Specify one or more configuration changes that the activity will
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 35f8df5..2c3c4fc 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1332,6 +1332,10 @@
<public type="attr" name="fragmentOpenExitAnimation" />
<public type="attr" name="fragmentCloseEnterAnimation" />
<public type="attr" name="fragmentCloseExitAnimation" />
+ <public type="attr" name="fragmentNextEnterAnimation" />
+ <public type="attr" name="fragmentNextExitAnimation" />
+ <public type="attr" name="fragmentPrevEnterAnimation" />
+ <public type="attr" name="fragmentPrevExitAnimation" />
<public type="attr" name="actionBarSize" />
<public type="attr" name="imeSubtypeLocale" />
<public type="attr" name="imeSubtypeMode" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 3dfaf7f..4b5047e 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -78,6 +78,10 @@
<item name="fragmentOpenExitAnimation">@anim/fragment_open_exit</item>
<item name="fragmentCloseEnterAnimation">@anim/fragment_close_enter</item>
<item name="fragmentCloseExitAnimation">@anim/fragment_close_exit</item>
+ <item name="fragmentNextEnterAnimation">@anim/fragment_next_enter</item>
+ <item name="fragmentNextExitAnimation">@anim/fragment_next_exit</item>
+ <item name="fragmentPrevEnterAnimation">@anim/fragment_prev_enter</item>
+ <item name="fragmentPrevExitAnimation">@anim/fragment_prev_exit</item>
</style>
<!-- Standard animations for a non-full-screen window or activity. -->
diff --git a/core/tests/coretests/src/android/content/SyncQueueTest.java b/core/tests/coretests/src/android/content/SyncQueueTest.java
deleted file mode 100644
index 1da59d1..0000000
--- a/core/tests/coretests/src/android/content/SyncQueueTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2007 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.content;
-
-import android.test.AndroidTestCase;
-import android.test.RenamingDelegatingContext;
-import android.test.mock.MockContext;
-import android.test.mock.MockContentResolver;
-import android.accounts.Account;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-public class SyncQueueTest extends AndroidTestCase {
- private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
- private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
- private static final String AUTHORITY1 = "test.authority1";
- private static final String AUTHORITY2 = "test.authority2";
- private static final String AUTHORITY3 = "test.authority3";
-
- private SyncStorageEngine mSettings;
- private SyncQueue mSyncQueue;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MockContentResolver mockResolver = new MockContentResolver();
- mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
- mSyncQueue = new SyncQueue(mSettings);
- }
-
- public void testSyncQueueOrder() throws Exception {
- final SyncOperation op1 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
- final SyncOperation op2 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
- final SyncOperation op3 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
- final SyncOperation op4 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
- final SyncOperation op5 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
- final SyncOperation op6 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
- op6.expedited = true;
-
- mSyncQueue.add(op1);
- mSyncQueue.add(op2);
- mSyncQueue.add(op3);
- mSyncQueue.add(op4);
- mSyncQueue.add(op5);
- mSyncQueue.add(op6);
-
- long now = SystemClock.elapsedRealtime() + 200;
-
- assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op6);
-
- assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op1);
-
- assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op4);
-
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op5);
-
- assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op2);
-
- assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op3);
- }
-
- public void testOrderWithBackoff() throws Exception {
- final SyncOperation op1 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
- final SyncOperation op2 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
- final SyncOperation op3 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
- final SyncOperation op4 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
- final SyncOperation op5 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
- final SyncOperation op6 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
- op6.expedited = true;
-
- mSyncQueue.add(op1);
- mSyncQueue.add(op2);
- mSyncQueue.add(op3);
- mSyncQueue.add(op4);
- mSyncQueue.add(op5);
- mSyncQueue.add(op6);
-
- long now = SystemClock.elapsedRealtime() + 200;
-
- assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op6);
-
- assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op1);
-
- mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
- mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
- assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-
- mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
- mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0);
- assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op4);
-
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op5);
-
- assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op2);
-
- assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op3);
- }
-
- Bundle newTestBundle(String val) {
- Bundle bundle = new Bundle();
- bundle.putString("test", val);
- return bundle;
- }
-
- static class TestContext extends ContextWrapper {
- ContentResolver mResolver;
-
- public TestContext(ContentResolver resolver, Context realContext) {
- super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
- mResolver = resolver;
- }
-
- @Override
- public void enforceCallingOrSelfPermission(String permission, String message) {
- }
-
- @Override
- public ContentResolver getContentResolver() {
- return mResolver;
- }
- }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
index bbffe70..f6b1d04 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.database.Cursor;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -97,7 +98,10 @@
assertTrue(db.isOpen());
}
- @SmallTest
+ /**
+ * this test could take a while to execute. so, designate it as LargetTest
+ */
+ @LargeTest
public void testFillWindow() {
// create schema
final String testTable = "testV";
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
index 217545f..955336a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
@@ -128,7 +128,7 @@
* pre-compiled SQL statement id except in during the period of binding the arguments
* and executing the SQL statement.
*/
- @SmallTest
+ @LargeTest
public void testReferenceToPrecompiledStatementId() {
mDatabase.execSQL("create table t (i int, j text);");
verifyReferenceToPrecompiledStatementId(false);
diff --git a/docs/html/guide/practices/design/responsiveness.jd b/docs/html/guide/practices/design/responsiveness.jd
index 2c7633d..b811d1b 100644
--- a/docs/html/guide/practices/design/responsiveness.jd
+++ b/docs/html/guide/practices/design/responsiveness.jd
@@ -19,19 +19,6 @@
<p><strong>Figure 1.</strong> An ANR dialog displayed to the user.</p>
</div>
-<p>It's possible to write code that wins every performance test in the world, but still sends users
-in a fiery rage when they try to use the application. These are the applications that aren't
-<em>responsive</em> enough — the ones that feel
-sluggish, hang or freeze for significant periods, or take too long to process
-input. </p>
-
-<p>In Android, the system guards against applications that are insufficiently responsive for a
-period of time by displaying a dialog to the user, called the Application Not Responding (ANR)
-dialog. The user can choose to let the application continue, but the user won't appreciate having to
-act on this dialog every time he or she uses your application. So it's important to design
-responsiveness into your application, so that the system never has cause to display an ANR to the
-user. </p>
-
<p>It's possible to write code that wins every performance test in the world,
but still sends users in a fiery rage when they try to use the application.
These are the applications that aren't <em>responsive</em> enough — the
diff --git a/docs/html/guide/topics/fundamentals/activities.jd b/docs/html/guide/topics/fundamentals/activities.jd
new file mode 100644
index 0000000..b616ab8
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/activities.jd
@@ -0,0 +1,762 @@
+page.title=Activities
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+ <li>An activity provides a user interface for a single screen in your application</li>
+ <li>Activities can move into the background and then be resumed with their state restored</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#Creating">Creating an Activity</a>
+ <ol>
+ <li><a href="#UI">Implementing a user interface</a></li>
+ <li><a href="#Declaring">Declaring the activity in the manifest</a></li>
+ </ol>
+ </li>
+ <li><a href="#StartingAnActivity">Starting an Activity</a>
+ <ol>
+ <li><a href="#StartingAnActivityForResult">Starting an Activity for a Result</a></li>
+ </ol>
+ </li>
+ <li><a href="#Lifecycle">Managing the Activity Lifecycle</a>
+ <ol>
+ <li><a href="#ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</a></li>
+ <li><a href="#SavingActivityState">Saving activity state</a></li>
+ <li><a href="#ConfigurationChanges">Handling configuration changes</a></li>
+ <li><a href="#CoordinatingActivities">Coordinating activities</a></li>
+ </ol>
+ </li>
+</ol>
+
+<h2>Key classes</h2>
+<ol>
+ <li>{@link android.app.Activity}</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+ <li><a href="{@docRoot}resources/tutorials/hello-world.html">Hello World Tutorial</a></li>
+ <li><a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack">Tasks and Back
+Stack</a></li>
+</ol>
+
+</div>
+</div>
+
+
+
+<p>An {@link android.app.Activity} is an application component that provides a screen with which
+users can interact in order to do something, such as dial the phone, take a photo, send an email, or
+view a map. Each activity is given a window in which to draw its user interface. The window
+typically fills the screen, but may be smaller than the screen and float on top of other
+windows.</p>
+
+<p> An application usually consists of multiple activities that are loosely bound
+to each other. Typically, one activity in an application is specified as the "main" activity, which
+is presented to the user when launching the application for the first time. Each
+activity can then start another activity in order to perform different actions. Each time a new
+activity starts, the previous activity is stopped, but the system preserves the activity
+in a stack (the "back stack"). When a new activity starts, it is pushed onto the back stack and
+takes user focus. The back stack abides to the basic "last in, first out" queue mechanism,
+so, when the user is done with the current activity and presses the BACK key, it
+is popped from the stack (and destroyed) and the previous activity resumes. (The back stack is
+discussed more in the <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks
+and Back Stack</a> document.)</p>
+
+<p>When an activity is stopped because a new activity starts, it is notified of this change in state
+through the activity's lifecycle callback methods.
+There are several callback methods that an activity might receive, due to a change in its
+state—whether the system is creating it, stopping it, resuming it, or destroying it—and
+each callback provides you the opportunity to perform specific work that's
+appropriate to that state change. For instance, when stopped, your activity should release any
+large objects, such as network or database connections. When the activity resumes, you can
+reacquire the necessary resources and resume actions that were interrupted. These state transitions
+are all part of the activity lifecycle.</p>
+
+<p>The rest of this document discusses the basics of how to build and use an activity,
+including a complete discussion of how the activity lifecycle works, so you can properly manage
+the transition between various activity states.</p>
+
+
+
+<h2 id="Creating">Creating an Activity</h2>
+
+<p>To create an activity, you must create a subclass of {@link android.app.Activity} (or
+an existing subclass of it). In your subclass, you need to implement callback methods that the
+system calls when the activity transitions between various states of its lifecycle, such as when
+the activity is being created, stopped, resumed, or destroyed. The two most important callback
+methods are:</p>
+
+<dl>
+ <dt>{@link android.app.Activity#onCreate onCreate()}</dt>
+ <dd>You must implement this method. The system calls this when creating your
+ activity. Within your implementation, you should initialize the essential components of your
+activity.
+ Most importantly, this is where you must call {@link android.app.Activity#setContentView
+ setContentView()} to define the layout for the activity's user interface.</dd>
+ <dt>{@link android.app.Activity#onPause onPause()}</dt>
+ <dd>The system calls this method as the first indication that the user is leaving your
+activity (though it does not always mean the activity is being destroyed). This is usually where you
+should commit any changes that should be persisted beyond the current user session (because
+the user might not come back).</dd>
+</dl>
+
+<p>There are several other lifecycle callback methods that you should use in order to provide a
+fluid user experience between activities and handle unexpected interuptions that cause your activity
+to be stopped and even destroyed. All of the lifecycle callback methods are discussed later, in
+the section about <a href="#Lifecycle">Managing the Activity Lifecycle</a>.</p>
+
+
+
+<h3 id="UI">Implementing a user interface</h3>
+
+<p> The user interface for an activity is provided by a hierarchy of views—objects derived
+from the {@link android.view.View} class. Each view controls a particular rectangular space
+within the activity's window and can respond to user interaction. For example, a view might be a
+button that initiates an action when the user touches it.</p>
+
+<p>Android provides a number of ready-made views that you can use to design and organize your
+layout. "Widgets" are views that provide a visual (and interactive) elements for the screen, such
+as a button, text field, checkbox, or just an image. "Layouts" are views derived from {@link
+android.view.ViewGroup} that provide a unique layout model for its child views, such as a linear
+layout, a grid layout, or relative layout. You can also subclass the {@link android.view.View} and
+{@link android.view.ViewGroup} classes (or existing subclasses) to create your own widgets and
+layouts and apply them to your activity layout.</p>
+
+<p>The most common way to define a layout using views is with an XML layout file saved in your
+application resources. This way, you can maintain the design of your user interface separately from
+the source code that defines the activity's behavior. You can set the layout as the UI for your
+activity with {@link android.app.Activity#setContentView(int) setContentView()}, passing the
+resource ID for the layout. However, you can also create new {@link android.view.View}s in your
+activity code and build a view hierarchy by inserting new {@link
+android.view.View}s into a {@link android.view.ViewGroup}, then use that layout by passing the root
+{@link android.view.ViewGroup} to {@link android.app.Activity#setContentView(View)
+setContentView()}.</p>
+
+<p>For information about creating a user interface, see the <a
+href="{@docRoot}guide/topics/ui/index.html">User Interface</a> documentation.</p>
+
+
+
+<h3 id="Declaring">Declaring the activity in the manifest</h3>
+
+<p>You must declare your activity in the manifest file in order for it to
+be accessible to the system. To decalare your activity, open your manifest file and add an <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element
+as a child of the <a
+href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
+element. For example:</p>
+
+<pre>
+<manifest ... >
+ <application ... >
+ <activity android:name=".ExampleActivity" />
+ ...
+ </application ... >
+ ...
+</manifest >
+</pre>
+
+<p>There are several other attributes that you can include in this element, to define properties
+such as the label for the activity, an icon for the activity, or a theme to style the activity's
+UI. See the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element
+reference for more information about available attributes.</p>
+
+
+<h4>Using intent filters</h4>
+
+<p>An <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+<activity>}</a> element can also specify various intent filters—using the <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+<intent-filter>}</a> element—in order to declare how other application components may
+activate it.</p>
+
+<p>When you create a new application using the Android SDK tools, the stub activity
+that's created for you automatically includes an intent filter that declares the activity
+responds to the "main" action and should be placed in the "launcher" category. The intent filter
+looks like this:</p>
+
+<pre>
+<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+</activity>
+</pre>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+<action>}</a> element specifies that this is the "main" entry point to the application. The <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+<category>}</a> element specifies that this activity should be listed in the
+system's application launcher (to allow users to launch this activity).</p>
+
+<p>If you intend for your application to be self-contained and not allow other applications to
+activate its activities, then you don't need any other intent filters. Only one activity should
+have the "main" action and "launcher" category, as in the previous example. Activities that
+you don't want to make available to other applications should have no intent filters and you can
+start them yourself using explicit intents (as discussed in the following section).</p>
+
+<p>However, if you want your activity to respond to implicit intents that are delivered from
+other applications (and your own), then you must define additional intent filters for your
+activity. For each type of intent to which you want to respond, you must include an <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+<intent-filter>}</a> that includes an
+<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+<action>}</a> element and, optionally, a <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+<category>}</a> element and/or a <a
+href="{@docRoot}guide/topics/manifest/data-element.html">{@code
+<data>}</a> element. These elements specify the type of intent to which your activity can
+respond.</p>
+
+<p>For more information about how your activities can respond to intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+document.</p>
+
+
+
+<h2 id="StartingAnActivity">Starting an Activity</h2>
+
+<p>You can start another activity by calling {@link android.app.Activity#startActivity
+ startActivity()}, passing it an {@link android.content.Intent} that describes the activity you
+ want to start. The intent specifies either the exact activity you want to start or describes the
+ type of action you want to perform (and the system selects the appropriate activity for you,
+which
+ can even be from a different application). An intent can also carry small amounts of data to be
+ used by the activity that is started.</p>
+
+<p>When working within your own application, you'll often need to simply launch a known activity.
+ You can do so by creating an intent that explicitly defines the activity you want to start,
+using the class name. For example, here's how one activity starts another activity named {@code
+SignInActivity}:</p>
+
+<pre>
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+</pre>
+
+<p>However, your application might also want to perform some action, such as send an email, text
+ message, or status update, using data from your activity. In this case, your application might
+ not have its own activities to perform such actions, so you can instead leverage the activities
+ provided by other applications on the device, which can perform the actions for you. This is where
+intents are really valuable—you can create an intent that describes an action you want to
+perform and the system
+ launches the appropriate activity from another application. If there are
+ multiple activities that can handle the intent, then the user can select which one to use. For
+ example, if you want to allow the user to send an email message, you can create the
+ following intent:</p>
+
+<pre>
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+</pre>
+
+<p>The {@link android.content.Intent#EXTRA_EMAIL} extra added to the intent is a string array of
+ email addresses to which the email should be sent. When an email application responds to this
+ intent, it reads the string array provided in the extra and places them in the "to" field of the
+ email composition form. In this situation, the email application's activity starts and when the
+ user is done, your activity resumes.</p>
+
+
+
+
+<h3 id="StartingAnActivityForResult">Starting an activity for a result</h3>
+
+<p>Sometimes, you might want to receive a result from the activity that you start. In that case,
+ start the activity by calling {@link android.app.Activity#startActivityForResult
+ startActivityForResult()} (instead of {@link android.app.Activity#startActivity
+ startActivity()}). To then receive the result from the subsequent
+activity, implement the {@link android.app.Activity#onActivityResult onActivityResult()} callback
+ method. When the subsequent activity is done, it returns a result in an {@link
+android.content.Intent} to your {@link android.app.Activity#onActivityResult onActivityResult()}
+method.</p>
+
+<p>For example, perhaps you want the user to pick one of their contacts, so your activity can
+do something with the information in that contact. Here's how you can create such an intent and
+handle the result:</p>
+
+<pre>
+private void pickContact() {
+ // Create an intent to "pick" a contact, as defined by the content provider URI
+ Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+ startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+ if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
+ // Perform a query to the contact's content provider for the contact's name
+ Cursor cursor = getContentResolver().query(data.getData(),
+ new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+ if (cursor.moveToFirst()) { // True if the cursor is not empty
+ int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+ String name = cursor.getString(columnIndex);
+ // Do something with the selected contact's name...
+ }
+ }
+}
+</pre>
+
+<p>This example shows the basic logic you should use in your {@link
+android.app.Activity#onActivityResult onActivityResult()} method in order to handle an
+activity result. The first condition checks whether the request was successful—if it was, then
+the {@code resultCode} will be {@link android.app.Activity#RESULT_OK}—and whether the request
+to which this result is responding is known—in this case, the {@code requestCode} matches the
+second parameter sent with {@link android.app.Activity#startActivityForResult
+startActivityForResult()}. From there, the code handles the activity result by querying the
+data returned in an {@link android.content.Intent} (the {@code data} parameter).</p>
+
+<p>What happens is, a {@link
+android.content.ContentResolver} performs a query against a content provider, which returns a
+{@link android.database.Cursor} that allows the queried data to be read. For more information, see
+the <a
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> document.</p>
+
+<p>For more information about using intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a> document.</p>
+
+
+<h2 id="ShuttingDown">Shutting Down an Activity</h2>
+
+<p>You can shut down an activity by calling its {@link android.app.Activity#finish
+finish()} method. You can also shut down a separate activity that you previously started by calling
+{@link android.app.Activity#finishActivity finishActivity()}.</p>
+
+<p class="note"><strong>Note:</strong> In most cases, you should not explicitly finish an activity
+using these methods. As discussed in the following section about the activity lifecycle, the
+Android system manages the life of an activity for you, so you do not need to finish your own
+activities. Calling these methods could adversely affect the expected user
+experience and should only be used when you absolutely do not want the user to return to this
+instance of the activity.</p>
+
+
+<h2 id="Lifecycle">Managing the Activity Lifecycle</h2>
+
+<p>Managing the lifecycle of your activities by implementing callback methods is
+crucial to developing a strong
+and flexible application. The lifecycle of an activity is directly affected by its association with
+other activities, its task and back stack.</p>
+
+<p>An activity can exist in essentially three states:</p>
+
+<dl>
+ <dt><i>Resumed</i></dt>
+ <dd>The activity is in the foreground of the screen and has user focus. (This state is
+also sometimes referred to as "running".)</dd>
+
+ <dt><i>Paused</i></dt>
+ <dd>Another activity is in the foreground and has focus, but this one is still visible. That is,
+another activity is visible on top of this one and that activity is partially transparent or doesn't
+cover the entire screen. A paused activity is completely alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, and remains attached to
+the window manager), but can be killed by the system in extremely low memory situations.</dd>
+
+ <dt><i>Stopped</i></dt>
+ <dd>The activity is completely obscured by another activity (the activity is now in the
+"background"). A stopped activity is also still alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, but is <em>not</em>
+attached to the window manager). However, it is no longer visible to the user and it
+can be killed by the system when memory is needed elsewhere.</dd>
+</dl>
+
+<p>If an activity is paused or stopped, the system can drop it from memory either by asking it to
+finish (calling its {@link android.app.Activity#finish finish()} method), or simply killing its
+process. When the activity is opened again (after being finished or killed), it must be created all
+over.</p>
+
+
+
+<h3 id="ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</h3>
+
+<p>When an activity transitions into and out of the different states described above, it is notified
+through various callback methods. All of the callback methods are hooks that you
+can override to do appropriate work when the state of your activity changes. The following skeleton
+activity includes each of the fundamental lifecycle methods:</p>
+
+
+<pre>
+public class ExampleActivity extends Activity {
+ @Override
+ public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // The activity is being created.
+ }
+ @Override
+ protected void {@link android.app.Activity#onStart onStart()} {
+ super.onStart();
+ // The activity is about to become visible.
+ }
+ @Override
+ protected void {@link android.app.Activity#onResume onResume()} {
+ super.onResume();
+ // The activity has become visible (it is now "resumed").
+ }
+ @Override
+ protected void {@link android.app.Activity#onPause onPause()} {
+ super.onPause();
+ // Another activity is taking focus (this activity is about to be "paused").
+ }
+ @Override
+ protected void {@link android.app.Activity#onStop onStop()} {
+ super.onStop();
+ // The activity is no longer visible (it is now "stopped")
+ }
+ @Override
+ protected void {@link android.app.Activity#onDestroy onDestroy()} {
+ super.onDestroy();
+ // The activity is about to be destroyed.
+ }
+}
+</pre>
+
+<p class="note"><strong>Note:</strong> Your implementation of these lifecycle methods must
+always call the superclass implementation before doing any work, as shown in the examples above.</p>
+
+<p>Taken together, these methods define the entire lifecycle of an activity. By implementing these
+methods, you can monitor three nested loops in the activity lifecycle: </p>
+
+<ul>
+<li>The <b>entire lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onCreate onCreate()} and the call to {@link
+android.app.Activity#onDestroy}. Your activity should perform setup of
+"global" state (such as defining layout) in {@link android.app.Activity#onCreate onCreate()}, and
+release all remaining resources in {@link android.app.Activity#onDestroy}. For example, if your
+activity has a thread running in the background to download data from the network, it might create
+that thread in {@link android.app.Activity#onCreate onCreate()} and then stop the thread in {@link
+android.app.Activity#onDestroy}.</li>
+
+<li><p>The <b>visible lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onStart onStart()} and the call to {@link
+android.app.Activity#onStop onStop()}. During this time, the user can see the activity
+on-screen and interact with it. For example, {@link android.app.Activity#onStop onStop()} is called
+when a new activity starts and this one is no longer visible. Between these two methods, you can
+maintain resources that are needed to show the activity to the user. For example, you can register a
+{@link android.content.BroadcastReceiver} in {@link
+android.app.Activity#onStart onStart()} to monitor changes that impact your UI, and unregister
+it in {@link android.app.Activity#onStop onStop()} when the user can no longer see what you are
+displaying. The system might call {@link android.app.Activity#onStart onStart()} and {@link
+android.app.Activity#onStop onStop()} multiple times during the entire lifetime of the activity, as
+the activity alternates between being visible and hidden to the user.</p></li>
+
+<li><p>The <b>foreground lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onResume onResume()} and the call to {@link android.app.Activity#onPause
+onPause()}. During this time, the activity is in front of all other activities on screen and has
+user input focus. An activity can frequently transition in and out of the foreground—for
+example, {@link android.app.Activity#onPause onPause()} is called when the device goes to sleep or
+when a dialog appears. Because this state can transition often, the code in these two methods should
+be fairly lightweight in order to avoid slow transitions that make the user wait.</p></li>
+</ul>
+
+<p>Figure 1 illustrates these loops and the paths an activity might take between states.
+The rectangles represent the callback methods you can implement to perform operations when
+the activity transitions between states. <p>
+
+<img src="{@docRoot}images/activity_lifecycle.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> The activity lifecycle.</p>
+
+<p>The same lifecycle callback methods are listed in table 1, which describes each of the callback
+methods in more detail and locates each one within the
+activity's overall lifecycle, including whether the system can kill the activity after the
+callback method completes.</p>
+
+<p class="table-caption"><strong>Table 1.</strong> A summary of the activity lifecycle's
+callback methods.</p>
+
+<table border="2" width="85%" frame="hsides" rules="rows">
+<colgroup align="left" span="3"></colgroup>
+<colgroup align="left"></colgroup>
+<colgroup align="center"></colgroup>
+<colgroup align="center"></colgroup>
+
+<thead>
+<tr><th colspan="3">Method</th> <th>Description</th> <th>Killable after?</th> <th>Next</th></tr>
+</thead>
+
+<tbody>
+<tr>
+ <td colspan="3" align="left"><code>{@link android.app.Activity#onCreate onCreate()}</code></td>
+ <td>Called when the activity is first created.
+ This is where you should do all of your normal static set up —
+ create views, bind data to lists, and so on. This method is passed
+ a Bundle object containing the activity's previous state, if that
+ state was captured (see <a href="#actstate">Saving Activity State</a>,
+ later).
+ <p>Always followed by {@code onStart()}.</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+ <td rowspan="5" style="border-left: none; border-right: none;"> </td>
+ <td colspan="2" align="left"><code>{@link android.app.Activity#onRestart
+onRestart()}</code></td>
+ <td>Called after the activity has been stopped, just prior to it being
+ started again.
+ <p>Always followed by {@code onStart()}</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+ <td colspan="2" align="left"><code>{@link android.app.Activity#onStart onStart()}</code></td>
+ <td>Called just before the activity becomes visible to the user.
+ <p>Followed by {@code onResume()} if the activity comes
+ to the foreground, or {@code onStop()} if it becomes hidden.</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+ <td rowspan="2" style="border-left: none;"> </td>
+ <td align="left"><code>{@link android.app.Activity#onResume onResume()}</code></td>
+ <td>Called just before the activity starts
+ interacting with the user. At this point the activity is at
+ the top of the activity stack, with user input going to it.
+ <p>Always followed by {@code onPause()}.</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onPause()}</td>
+</tr>
+
+<tr>
+ <td align="left"><code>{@link android.app.Activity#onPause onPause()}</code></td>
+ <td>Called when the system is about to start resuming another
+ activity. This method is typically used to commit unsaved changes to
+ persistent data, stop animations and other things that may be consuming
+ CPU, and so on. It should do whatever it does very quickly, because
+ the next activity will not be resumed until it returns.
+ <p>Followed either by {@code onResume()} if the activity
+ returns back to the front, or by {@code onStop()} if it becomes
+ invisible to the user.</td>
+ <td align="center"><strong style="color:#800000">Yes</strong></td>
+ <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+ <td colspan="2" align="left"><code>{@link android.app.Activity#onStop onStop()}</code></td>
+ <td>Called when the activity is no longer visible to the user. This
+ may happen because it is being destroyed, or because another activity
+ (either an existing one or a new one) has been resumed and is covering it.
+ <p>Followed either by {@code onRestart()} if
+ the activity is coming back to interact with the user, or by
+ {@code onDestroy()} if this activity is going away.</p></td>
+ <td align="center"><strong style="color:#800000">Yes</strong></td>
+ <td align="center">{@code onRestart()} <br/>or<br/> {@code onDestroy()}</td>
+</tr>
+
+<tr>
+ <td colspan="3" align="left"><code>{@link android.app.Activity#onDestroy
+onDestroy()}</code></td>
+ <td>Called before the activity is destroyed. This is the final call
+ that the activity will receive. It could be called either because the
+ activity is finishing (someone called <code>{@link android.app.Activity#finish
+ finish()}</code> on it), or because the system is temporarily destroying this
+ instance of the activity to save space. You can distinguish
+ between these two scenarios with the <code>{@link
+ android.app.Activity#isFinishing isFinishing()}</code> method.</td>
+ <td align="center"><strong style="color:#800000">Yes</strong></td>
+ <td align="center"><em>nothing</em></td>
+</tr>
+</tbody>
+</table>
+
+<p>The column labeled "Killable after?" indicates whether or not the system can
+kill the process hosting the activity at any time <em>after the method returns</em>, without
+executing another line of the activity's code. Three methods are marked "yes": ({@link
+android.app.Activity#onPause
+onPause()}, {@link android.app.Activity#onStop onStop()}, and {@link android.app.Activity#onDestroy
+onDestroy()}). Because {@link android.app.Activity#onPause onPause()} is the first
+of the three, once the activity is created, {@link android.app.Activity#onPause onPause()} is the
+last method that's guaranteed to be called before the process <em>can</em> be killed—if
+the system must recover memory in an emergency, then {@link
+android.app.Activity#onStop onStop()} and {@link android.app.Activity#onDestroy onDestroy()} might
+not be called. Therefore, you should use {@link android.app.Activity#onPause onPause()} to write
+crucial persistent data (such as user edits) to storage. However, you should be selective about
+what information must be retained during {@link android.app.Activity#onPause onPause()}, because any
+blocking procedures in this method block the transition to the next activity and slow the user
+experience.</p>
+
+<p> Methods that are marked "No" in the <b>Killable</b> column protect the process hosting the
+activity from being killed from the moment they are called. Thus, an activity is killable
+from the time {@link android.app.Activity#onPause onPause()} returns to the time
+{@link android.app.Activity#onResume onResume()} is called. It will not again be killable until
+{@link android.app.Activity#onPause onPause()} is again called and returns. </p>
+
+<p class="note"><strong>Note:</strong> An activity that's not technically "killable" by this
+definition in table 1 might still be killed by the system—but that would happen only in
+extreme circumstances when there is no other recourse. When an activity might be killed is
+discussed more in the <a
+href="{@docRoot}guide/topics/fundamentals/processes-and-threading.html">Processes and
+Threading</a> document.</p>
+
+
+<h3 id="SavingActivityState">Saving activity state</h3>
+
+<p>The introduction to <a href="Lifecycle">Managing the Activity Lifecycle</a> briefly mentions that
+when an activity is paused or stopped, the state of the activity is retained. This is true because
+the {@link android.app.Activity} object is still held in memory when it is paused or
+stopped—all information about its members and current state is still alive. Thus, any changes
+the user made within the activity are retained in memory, so that when the activity returns to the
+foreground (when it "resumes"), those changes are still there.</p>
+
+<div class="figure" style="width:615px">
+<img src="{@docRoot}images/fundamentals/restore_instance.png" alt="" />
+<p class="img-caption"><strong>Figure 2.</strong> The two ways in which an activity returns to user
+focus with its state intact: either the activity is stopped, then resumed and the activity state
+remains intact (left), or the activity is destroyed, then recreated and the activity must restore
+the previous activity state (right).</p>
+</div>
+
+<p>However, when the system destroys an activity in order to recover memory, the {@link
+android.app.Activity} object is destroyed, so the system cannot simply resume it with its state
+intact. Instead, the system must recreate the {@link android.app.Activity} object if the user
+navigates back to it. Yet, the user is unaware
+that the system destroyed the activity and recreated it and, thus, probably
+expects the activity to be exactly as it was. In this situation, you can ensure that
+important information about the activity state is preserved by implementing an additional
+callback method that allows you to save information about the state of your activity and then
+restore it when the the system recreates the activity.</p>
+
+<p>The callback method in which you can save information about the current state of your activity is
+{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. The system calls this method
+before making the activity vulnerable to being destroyed and passes it
+a {@link android.os.Bundle} object. The {@link android.os.Bundle} is where you can store
+state information about the activity as name-value pairs, using methods such as {@link
+android.os.Bundle#putString putString()}. Then, if the system kills your activity's
+process and the user navigates back to your activity, the system passes the {@link
+android.os.Bundle} to {@link android.app.Activity#onCreate onCreate()} so you can restore the
+activity state you saved during {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()}. If there is no state information to restore, then the {@link
+android.os.Bundle} passed to {@link android.app.Activity#onCreate onCreate()} is null.</p>
+
+<p class="note"><strong>Note:</strong> There's no guarantee that {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} will be called before your
+activity is destroyed, because there are cases in which it won't be necessary to save the state
+(such as when the user leaves your activity using the BACK key, because the user is explicitly
+closing the activity). If the method is called, it is always called before {@link
+android.app.Activity#onStop onStop()} and possibly before {@link android.app.Activity#onPause
+onPause()}.</p>
+
+<p>However, even if you do nothing and do not implement {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}, some of the activity state is
+restored by the {@link android.app.Activity} class's default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Specifically, the default
+implementation calls {@link
+android.view.View#onSaveInstanceState onSaveInstanceState()} for every {@link android.view.View}
+in the layout, which allows each view to provide information about itself
+that should be saved. Almost every widget in the Android framework implements this method as
+appropriate, such that any visible changes to the UI are automatically saved and restored when your
+activity is recreated. For example, the {@link android.widget.EditText} widget saves any text
+entered by the user and the {@link android.widget.CheckBox} widget saves whether it's checked or
+not. The only work required by you is to provide a unique ID (with the <a
+href="{@docRoot}guide/topics/resources/layout-resource.html#idvalue">{@code android:id}</a>
+attribute) for each widget you want to save its state. If a widget does not have an ID, then it
+cannot save its state.</p>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<p>You can also explicitly stop a view in your layout from saving its state by setting the
+{@link android.R.attr#saveEnabled android:saveEnabled} attribute to {@code "false"} or by calling
+the {@link android.view.View#setSaveEnabled setSaveEnabled()} method. Usually, you should not
+disable this, but you might if you want to restore the state of the activity UI differently.</p>
+</div>
+</div>
+
+<p>Although the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} saves useful information about
+your activity's UI, you still might need to override it to save additional information.
+For example, you might need to save member values that changed during the activity's life (which
+might correlate to values restored in the UI, but the members that hold those UI values are not
+restored, by default).</p>
+
+<p>Because the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} helps save the state of the UI, if
+you override the method in order to save additional state information, you should always call the
+superclass implementation of {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}
+before doing any work.</p>
+
+<p class="note"><strong>Note:</strong> Because {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()} is not guaranteed
+to be called, you should use it only to record the transient state of the activity (the state of
+the UI)—you should never use it to store persistent data. Instead, you should use {@link
+android.app.Activity#onPause onPause()} to store persistent data (such as data that should be saved
+to a database) when the user leaves the activity.</p>
+
+<p>A good way to test your application's ability to restore its state is to simply rotate the
+device so that the screen orientation changes. When the screen orientation changes, the system
+destroys and recreates the activity in order to apply alternative resources that might be available
+for the new orientation. For this reason alone, it's very important that your activity
+completely restores its state when it is recreated, because users regularly rotate the screen while
+using applications.</p>
+
+
+<h3 id="ConfigurationChanges">Handling configuration changes</h3>
+
+<p>Some device configurations can change during runtime (such as screen orientation, keyboard
+availability, and language). When such a change occurs, Android restarts the running Activity
+({@link android.app.Activity#onDestroy} is called, followed immediately by {@link
+android.app.Activity#onCreate onCreate()}). The restart behavior is
+designed to help your application adapt to new configurations by automatically reloading your
+application with alternative resources that you've provided. If you design your activity to
+properly handle this event, it will be more resilient to unexpected events in the activity
+lifecycle.</p>
+
+<p>The best way to handle a configuration change, such as a change in the screen orientation, is
+ to simply preserve the state of your application using {@link
+ android.app.Activity#onSaveInstanceState onSaveInstanceState()} and {@link
+android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (or {@link
+android.app.Activity#onCreate onCreate()}), as discussed in the previous section.</p>
+
+<p>For a detailed discussion about configuration changes that happen at runtime and how you should
+handle them, read <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling
+Runtime Changes</a>.</p>
+
+
+
+<h3 id="CoordinatingActivities">Coordinating activities</h3>
+
+ <p>When one activity starts another, they both experience lifecycle transitions. The first activity
+pauses and stops (though, it won't stop if it's still visible in the background), while the other
+activity is created. In case these activities share data saved to disc or elsewhere, it's important
+to understand that the first activity is not completely stopped before the second one is created.
+Rather, the process of starting the second one overlaps with the process of stopping the first
+one.</p>
+
+<p>The order of lifecycle callbacks is well defined, particularly when the two activities are in the
+same process and one is starting the other. Here's the order of operations that occur when Activity
+A starts Acivity B: </p>
+
+<ol>
+<li>Activity A's {@link android.app.Activity#onPause onPause()} method executes.</li>
+
+<li>Activity B's {@link android.app.Activity#onCreate onCreate()}, {@link
+android.app.Activity#onStart onStart()}, and {@link android.app.Activity#onResume onResume()}
+methods execute in sequence. (Activity B now has user focus.)</li>
+
+<li>Then, if Activity A is no longer visible on screen, its {@link
+android.app.Activity#onStop onStop()} method executes.</li>
+</ol>
+
+ <p>This predictable sequence of lifecycle callbacks allows you to manage the transition of
+information from one activity to another. For example, if you must write to a database when the
+first activity stops so that the following activity can read it, then you should write to the
+database during {@link android.app.Activity#onPause onPause()} instead of during {@link
+android.app.Activity#onStop onStop()}.</p>
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how Android maintains a history of activities and
+enables user multitasking, continue with the <b><a
+href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+Stack</a></b> document.</p>
diff --git a/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
new file mode 100644
index 0000000..47dc5471
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
@@ -0,0 +1,568 @@
+page.title=Tasks and Back Stack
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+ <li>All activities belong to a task</li>
+ <li>A task contains a collection of activities in the order in which the user interacts with
+them</li>
+ <li>Tasks can move to the background and retain the state of each activity in order for the user
+to perform other tasks without loosing their work</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+<li><a href="#ActivityState">Saving Activity State</a></li></li>
+<li><a href="#ManagingTasks">Managing Tasks</a>
+ <ol>
+ <li><a href="#TaskLaunchModes">Defining launch modes</a></li>
+ <li><a href="#Affinities">Handling affinities</a></li>
+ <li><a href="#Clearing">Clearing the back stack</a></li>
+ <li><a href="#Starting">Starting a task</a></li>
+ </ol>
+</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+ <li><a><a href="{@docRoot}videos/index.html#v=fL6gSd4ugSI">Application Lifecycle video</a></li>
+ <li><a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>} manifest
+element</a></li>
+</ol>
+</div>
+</div>
+
+
+<p>An application usually contains multiple <a
+href="{@docRoot}guide/topics/fundamentals/activities.html">activities</a>. Each activity
+should be designed around a specific kind of action the user can perform and can start other
+activities. For example, an email application might have one activity to show a list of new email.
+When the user selects an email, a new activity opens to view that email.</p>
+
+<p>An activity can even start activities that exist in other applications on the device. For
+example, if your application wants to send an email, you can define an intent to perform a "send"
+action and include some data, such as an email address and a message. An activity from another
+application that declares itself to handle this kind of intent then opens. In this case, the intent
+is to send an email, so an email application's "compose" activity starts (if multiple activities
+support the same intent, then the system lets the user select which one to use). When the email is
+sent, your activity resumes and it seems as if the email activity was part of your application. Even
+though the activities may be from different applications, Android maintains this seamless user
+experience by keeping both activities in the same <em>task</em>.</p>
+
+<p>A task is a collection of activities that users interact with
+when performing a certain job. The activities are arranged in a stack (the "back stack"), in the
+order in which each activity is opened.</p>
+
+<!-- SAVE FOR WHEN THE FRAGMENT DOC IS ADDED
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h3>Adding fragments to a task's back stack</h3>
+
+<p>Your activity can also include {@link android.app.Fragment}s to the back stack. For example,
+suppose you have a two-pane layout using fragments, one of which is a list view (fragment A) and the
+other being a layout to display an item from the list (fragment B). When the user selects an item
+from the list, fragment B is replaced by a new fragment (fragment C). In this case, it might be
+desireable for the user to navigate back to reveal fragment B, using the BACK key.</p>
+<p>In order to add fragment B to the back stack so that this is possible, you must call {@link
+android.app.FragmentTransaction#addToBackStack addToBackStack()} before you {@link
+android.app.FragmentTransaction#commit()} the transaction that replaces fragment B with fragment
+C.</p>
+<p>For more information about using fragments and adding them to the back stack, see the {@link
+android.app.Fragment} class documentation.</p>
+
+</div>
+</div>
+-->
+
+<p>The device Home screen is the starting place for most tasks. When the user touches an icon in the
+application
+launcher (or a shortcut on the Home screen), that application's task comes to the foreground. If no
+task exists for the application (the application has not been used recently), then a new task
+is created and the "main" activity for that application opens as the root activity in the stack.</p>
+
+<p>When the current activity starts another, the new activity is pushed on the top of the stack and
+takes focus. The previous activity remains in the stack, but is stopped. When an activity
+stops, the system retains the current state of its user interface. When the user presses the BACK
+key, the current activity is popped from the top of the stack (the activity is destroyed) and the
+previous activity resumes (the previous state of its UI is restored). Activities in the stack are
+never rearranged, only pushed and popped from the stack—pushed onto the stack when started by
+the current activity and popped off when the user leaves it using the BACK key. As such, the back
+stack operates as a "last in, first out" object structure. Figure 1 visualizes
+this behavior with a timeline showing the progress between activities along with the current back
+stack at each point in time.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> A representation of how each new activity in a
+task adds an item to the back stack. When the user presses the BACK key, the current activity is
+destroyed and the previous activity resumes.</p>
+
+
+<p>If the user continues to press BACK, then each activity in the stack is popped off to reveal the
+previous one, until the user returns to the Home screen (or to whichever activity was running when
+the task began). When all activities are removed from the stack, the task no longer exists.</p>
+
+<div class="figure" style="width:369px">
+<img src="{@docRoot}images/fundamentals/diagram_multitasking.png" alt="" /> <p
+class="img-caption"><strong>Figure 2.</strong> Two tasks: Task A is in the background, waiting
+to be resumed, while Task B receives user interaction in the foreground.</p>
+</div>
+<div class="figure" style="width:178px">
+ <img src="{@docRoot}images/fundamentals/diagram_multiple_instances.png" alt="" /> <p
+class="img-caption"><strong>Figure 3.</strong> A single activity is instantiated multiple times.</p>
+</div>
+
+<p>A task is a cohesive unit that can move to the "background" when users begin a new task or go
+to the Home screen, via the HOME key. While in the background, all the activities in the task are
+stopped, but the back stack for the task remains intact—the task has simply lost focus while
+another task takes place, as shown in figure 2. A task can then return to the "foreground" so users
+can pick up where they left off. Suppose, for example, that the current task (Task A) has three
+activities in its stack—two under the current activity. The user presses the HOME key, then
+starts a new application from the application launcher. When the Home screen appears, Task A goes
+into the background. When the new application starts, the system starts a task for that application
+(Task B) with its own stack of activities. After interacting with
+that application, the user returns Home again and selects the application that originally
+started Task A. Now, Task A comes to the
+foreground—all three activities in its stack are intact and the activity at the top of the
+stack resumes. At
+this point, the user can also switch back to Task B by going Home and selecting the application icon
+that started that task (or by touching and holding the HOME key to reveal recent tasks and selecting
+one). This is an example of multitasking on Android.</p>
+
+<p class="note"><strong>Note:</strong> Multiple tasks can be held in the background at once.
+However, if the user is running many background tasks at the same time, the system might begin
+destroying background activities in order to recover memory, causing the activity states to be lost.
+See the following section about <a href="#ActivityState">Activity state</a>.</p>
+
+<p>Because the activities in the back stack are never rearranged, if your application allows
+users to start a particular activity from more than one activity, a new instance of
+that activity is created and popped onto the stack (rather than bringing any previous instance of
+the activity to the top). As such, one activity in your application might be instantiated multiple
+times (even from different tasks), as shown in figure 3. As such, if the user navigates backward
+using the BACK key, each instance of the activity is revealed in the order they were opened (each
+with their own UI state). However, you can modify this behavior if you do not want an activity to be
+instantiated more than once. How to do so is discussed in the later section about <a
+href="#ManagingTasks">Managing Tasks</a>.</p>
+
+
+<p>To summarize the default behavior for activities and tasks:</p>
+
+<ul>
+ <li>When Activity A starts Activity B, Activity A is stopped, but the system retains its state
+(such as scroll position and text entered into forms).
+If the user presses the BACK key while in Activity B, Activity A resumes with its state
+restored.</li>
+ <li>When the user leaves a task by pressing the HOME key, the current activity is stopped and
+its task goes into the background. The system retains the state of every activity in the task. If
+the user later resumes the task by selecting the launcher icon that began the task, the task comes
+to the foreground and resumes the activity at the top of the stack.</li>
+ <li>If the user presses the BACK key, the current activity is popped from the stack and
+destroyed. The previous activity in the stack is resumed. When an activity is destroyed, the system
+<em>does not</em> retain the activity's state.</li>
+ <li>Activities can be instantiated multiple times, even from other tasks.</li>
+</ul>
+
+
+<h2 id="ActivityState">Saving Activity State</h2>
+
+<p>As discussed above, the system's default behavior preserves the state of an activity when it is
+stopped. This way, when users navigate back to a previous activity, its user interface appears
+the way they left it. However, you can—and <strong>should</strong>—proactively retain
+the state of your activities using callback methods, in case the activity is destroyed and must
+be recreated.</p>
+
+<p>When the system stops one of your activities (such as when a new activity starts or the task
+moves to the background), the system might destroy that activity completely if it needs to recover
+system memory. When this happens, information about the activity state is lost. If this happens, the
+system still
+knows that the activity has a place in the back stack, but when the activity is brought to the
+top of the stack the system must recreate it (rather than resume it). In order to
+avoid loosing the user's work, you should proactively retain it by implementing the {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} callback
+methods in your activity.</p>
+
+<p>For more information about how to save your activity state, see the <a
+href="{@docRoot}guide/topics/fundamentals/activities.html#SavingActivityState">Activities</a>
+document.</p>
+
+
+
+<h2 id="ManagingTasks">Managing Tasks</h2>
+
+<p>The way Android manages tasks and the back stack, as described above—by placing all
+activities started in succession in the same task and in a "last in, first out" stack—works
+great for most applications and you shouldn't have to worry about how your activities are associated
+with tasks or how they exist in the back stack. However, you might decide that you want to interrupt
+the normal behavior. Perhaps you want an activity in your application to begin a new task when it is
+started (instead of being placed within the current task); or, when you start an activity, you want
+to bring forward an existing instance of it (instead of creating a new
+instance on top of the back stack); or, you want your back stack to be cleared of all
+activitiesstart an activity except for the root activity when the user leaves the task.</p>
+
+<p>You can do these things and more, with attributes in the
+<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+<activity>}</a> manifest element and with flags in the intent that you pass to {@link
+android.app.Activity#startActivity startActivity()}.</p>
+
+<p>In this regard, the the principal <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>
+attributes you can use are:</p>
+
+<ul class="nolist">
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code
+taskAffinity}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code
+clearTaskOnLaunch}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a></li>
+</ul>
+
+<p>And the principal intent flags you can use are:</p>
+
+<ul class="nolist">
+ <li>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</li>
+ <li>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</li>
+ <li>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</li>
+</ul>
+
+<p>In the following sections, you'll see how you can use these manifest attributes and intent
+flags to define how activities are associated with tasks and how the behave in the back stack.</p>
+
+
+<p class="caution"><strong>Caution:</strong> Most applications should not interrupt the default
+behavior for activities and tasks. If you determine that it's necessary for your activity to modify
+the default behaviors, use caution and be sure to test the usability of the activity during
+launch and when navigating back to it from other activities and tasks with the BACK key. Be sure
+to test for navigation behaviors that might conflict with the user's expected behavior.</p>
+
+
+<h3 id="TaskLaunchModes">Defining launch modes</h3>
+
+<p>Launch modes allow you to define how a new instance of an activity is associated with the
+current task. You can define different launch modes in two ways:</p>
+<ul class="nolist">
+ <li><a href="#ManifestForTasks">Using the manifest file</a>
+ <p>When you declare an activity in your manifest file, you can specify how the activity
+should associate with tasks when it starts.</li>
+ <li><a href="#IntentFlagsForTasks">Using Intent flags</a>
+ <p>When you call {@link android.app.Activity#startActivity startActivity()},
+you can include a flag in the {@link android.content.Intent} that declares how (or
+whether) the new activity should associate with the current task.</p></li>
+</ul>
+
+<p>As such, if Activity A starts Activity B, Activity B can define in its manifest how it
+should associate with the current task (if at all) and Activity A can also request how Activity
+B should associate with current task. If both activities define how Activity B
+should associate with a task, then Activity A's request (as defined in the intent) is honored
+over Activity B's request (as defined in its manifest).</p>
+
+<p class="note"><strong>Note:</strong> Some the launch modes available in the manifest
+are not available as flags for an intent and, likewise, some launch modes available as flags
+for an intent cannot be defined in the manifest.</p>
+
+
+<h4 id="ManifestForTasks">Using the manifest file</h4>
+
+<p>When declaring an activity in your manifest file, you can specify how the activity should
+associate with a task using the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>
+element's <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute.</p>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute specifies an instruction on how the activity should be launched into a
+task. There are four different launch modes you can assign to the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">launchMode</a></code>
+attribute:</p>
+
+<dl>
+<dt>{@code "standard"} (the default mode)</dt>
+ <dd>Default. The system creates a new instance of the activity in the task from
+which it was started and routes the intent to it. The activity can be instantiated multiple times,
+each instance can belong to different tasks, and one task can have multiple instances.</dd>
+<dt>{@code "singleTop"}</dt>
+ <dd>If an instance of the activity already exists at the top of the current task, the system
+routes the intent to that instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance of the
+activity. The activity can be instantiated multiple times, each instance can
+belong to different tasks, and one task can have multiple instances (but only if the the
+activity at the top of the back stack is <em>not</em> an existing instance of the activity).
+ <p>For example, suppose a task's back stack consists of root activity A with activities B, C,
+and D on top (the stack is A-B-C-D; D is on top). An intent arrives for an activity of type D.
+If D has the default {@code "standard"} launch mode, a new instance of the class is launched and the
+stack becomes A-B-C-D-D. However, if D's launch mode is {@code "singleTop"}, the existing instance
+of D is deliverd the intent through {@link
+android.app.Activity#onNewIntent onNewIntent()}, because it's at the top of the stack—the
+stack remains A-B-C-D. However, if an intent arrives for an activity of type B, then a new
+instance of B is added to the stack, even if its launch mode is {@code "singleTop"}.</p>
+ <p class="note"><strong>Note:</strong> When a new instance of an activity is created,
+the user can press the BACK key to return to the previous activity. But when an existing instance of
+an activity handles a new intent, the user cannot press the BACK key to return to the state of
+the activity before the new intent arrived in {@link android.app.Activity#onNewIntent
+onNewIntent()}.</p>
+</dd>
+
+<dt>{@code "singleTask"}</dt>
+ <dd>The system creates a new task and instantiates the activity at the root of the new task.
+However, if an instance of the activity already exists in a separate task, the system routes the
+intent to the existing instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance. Only
+one instance of the activity can exist at a time.
+ <p class="note"><strong>Note:</strong> Although the activity starts in a new task, the
+BACK key still returns the user to the previous activity.</p></dd>
+<dt>{@code "singleInstance"}.</dt>
+ <dd>Same as {@code "singleTask"}, except that the system doesn't launch any other activities into
+the task holding the instance. The activity is always the single and only member of its task;
+any activities started by this one open in a separate task.</dd>
+</dl>
+
+
+<p>As another example, the Android Browser application declares that the web browser activity should
+always open in its own task—by specifying the {@code singleTask} launch mode in the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element.
+This means that if your application issues an
+intent to open the Android Browser, its activity is <em>not</em> placed in the same
+task as your application. Instead, either a new task starts for the Browser or, if the Browser
+already has a task running in the background, that task is brought forward to handle the new
+intent.</p>
+
+<p>Regardless of whether an activity starts in a new task or in the same task as the activity that
+started it, the BACK key always takes the user to the previous activity. However, if you
+start an activity from your task (Task A) that specifies the {@code singleTask} launch mode, then
+that activity might have an instance in the background that belongs to a task with its own back
+stack (Task B). In this
+case, when Task B is brought forward to handle a new intent, the BACK key first navigates
+backward through the activities in Task B before returning to
+the top-most activity in Task A. Figure 4 visualizes this type of scenario.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack_singletask_multiactivity.png" alt="" />
+<p class="img-caption"><strong>Figure 4.</strong> A representation of how an activity with
+launch mode "singleTask" is added to the back stack. If the activity is already a part of a
+background task with its own back stack (Task B), then the entire back stack also comes
+forward, on top of the current task (Task A).</p>
+
+<p>For more information about using launch modes in the manifest file, see the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>
+element documentation, where the {@code launchMode} attribute and the accepted values are
+discussed more.</p>
+
+<p class="note"><strong>Note:</strong> The behaviors that you specify for your activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> attribute
+can be overriden by flags included with the intent that start your activity, as discussed in the
+next section.</p>
+
+
+
+<h4 id="#IntentFlagsForTasks">Using Intent flags</h4>
+
+<p>When starting an activity, you can modify the default association of an activity to its task
+by including flags in the intent that you deliver to {@link
+android.app.Activity#startActivity startActivity()}. The flags you can use to modify the
+default behavior are:</p>
+
+<p>
+ <dt>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</dt>
+ <dd>Start the activity in a new task. If a task is already running for the activity you are now
+starting, that task is brought to the foreground with its last state restored and the activity
+receives the new intent in {@link android.app.Activity#onNewIntent onNewIntent()}.
+ <p>This produces the same behavior as the {@code "singleTask"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+ <dt>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</dt>
+ <dd>If the activity being started is the current activity (at the top of the back stack), then
+the existing instance receives a call to {@link android.app.Activity#onNewIntent onNewIntent()},
+instead of creating a new instance of the activity.
+ <p>This produces the same behavior as the {@code "singleTop"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+ <dt>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</dt>
+ <dd>If the activity being started is already running in the current task, then instead
+of launching a new instance of that activity, all of the other activities on top of it are
+destroyed and this intent is delivered to the resumed instance of the activity (now on top),
+through {@link android.app.Activity#onNewIntent onNewIntent()}).
+ <p>There is no value for the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a>
+attribute that produces this behavior.</p>
+ <p>{@code FLAG_ACTIVITY_CLEAR_TOP} is most often used in conjunction with {@code
+FLAG_ACTIVITY_NEW_TASK}. When used together, these flags are a way of locating an existing activity
+in another task and putting it in a position where it can respond to the intent. </p>
+ <p class="note"><strong>Note:</strong> If the launch mode of the designated activity is {@code
+"standard"}, it too is removed from the stack and a new instance is launched in its place to handle
+the incoming intent. That's because a new instance is always created for a new intent when the
+launch mode is {@code "standard"}. </p>
+</dd>
+</dl>
+
+
+
+
+
+<h3 id="Affinities">Handling affinities</h3>
+
+<p>The <em>affinity</em> indicates which task an activity prefers to belong to. By default, all the
+activities from the same application have an affinity for each other. So, by default, all
+activities in the same application prefer to be in the same task. However, you can modify
+the default affinity for an activity. Activities defined in
+different applications can share an affinity, or activities defined in the same application can be
+assigned different task affinities.</p>
+
+<p>You can modify the affinity for any given activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> attribute
+of the <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>
+element.</p>
+
+<p>The <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute takes a string value, which must be unique from the default package name
+declared in the <a href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code
+<manifest>}</a> element, because the system uses that name to identify the default task
+affinity for the application.</p>
+
+<p>The affinity comes into play in two circumstances:</p>
+<ul>
+ <li>When the intent that launches an activity contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag.
+
+<p>A new activity is, by default, launched into the task of the activity
+that called {@link android.app.Activity#startActivity startActivity()}. It's pushed onto the same
+back stack as the caller. However, if the intent passed to {@link
+android.app.Activity#startActivity startActivity()} contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+flag, the system looks for a different task to house the new activity. Often, it's a new task.
+However, it doesn't have to be. If there's already an existing task with the same affinity as the
+new activity, the activity is launched into that task. If not, it begins a new task.</p>
+
+<p>If this flag causes an activity to begin a new task and the user presses the HOME key to leave
+it, there must be some way for the user to navigate back to the task. Some entities (such as the
+notification manager) always start activities in an external task, never as part of their own, so
+they always put {@code FLAG_ACTIVITY_NEW_TASK} in the intents they pass to {@link
+android.app.Activity#startActivity startActivity()}. If you have an activity that can be invoked by
+an external entity that might use this flag, take care that the user has a independent way to get
+back to the task that's started, such as with a launcher icon (the root activity of the task
+has a {@link android.content.Intent#CATEGORY_LAUNCHER} intent filter; see the <a
+href="#Starting">Starting a task</a> section below).</p>
+</li>
+
+ <li>When an activity has its <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a> attribute set to {@code "true"}.
+ <p>In this case, the activity can move from the task it starts to the task it has an affinity
+for, when that task comes to the foreground.</p>
+ <p>For example, suppose that an activity that reports weather conditions in selected cities is
+defined as part of a travel application. It has the same affinity as other activities in the same
+application (the default application affinity) and it allows re-parenting with this attribute.
+When one of your activities starts the weather reporter activity, it initially belongs to the same
+task as your activity. However, when the travel application's task comes to the foreground, the
+weather reporter activity is reassigned to that task and displayed within it.</p>
+</li>
+</ul>
+
+<p class="note"><strong>Tip:</strong> If an {@code .apk} file contains more than one "application"
+from the user's point of view, you probably want to use the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute to assign different affinities to the activities associated with each "application".</p>
+
+
+
+<h3 id="Clearing">Clearing the back stack</h3>
+
+<p>If the user leaves a task for a long time, the system clears the task of all activities except
+the root activity. When the user returns to the task again, only the root activity is restored.
+The system behaves this way, because, after an extended amount of time, users likely have abandoned
+what they were doing before and are returning to the task to begin something new. </p>
+
+<p>There are some activity attributes that you can use to modify this behavior: </p>
+
+<dl>
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">alwaysRetainTaskState</a></code>
+</dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the default behavior just described does not happen.
+The task retains all activities in its stack even after a long period.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">clearTaskOnLaunch</a></code></dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the stack is cleared down to the root activity whenever the user leaves the task
+and returns to it. In other words, it's the opposite of <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a>. The user always returns to the task in its
+initial state, even after a leaving the task for only a moment.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#finish">finishOnTaskLaunch</a></code>
+</dt>
+<dd>This attribute is like <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code clearTaskOnLaunch}</a>,
+but it operates on a
+single activity, not an entire task. It can also cause any activity to go
+away, including the root activity. When it's set to {@code "true"}, the
+activity remains part of the task only for the current session. If the user
+leaves and then returns to the task, it is no longer present.</dd>
+</dl>
+
+
+
+
+<h3 id="Starting">Starting a task</h3>
+
+<p>You can set up an activity as the entry point for a task by giving it an intent filter with
+{@code "android.intent.action.MAIN"} as the specified action and {@code
+"android.intent.category.LAUNCHER"} as the specified category. For example:</p>
+
+<pre>
+<activity ... >
+ <intent-filter ... >
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ ...
+</activity>
+</pre>
+
+<p>An intent filter of this kind causes an icon and label for the
+activity to be displayed in the application launcher, giving users a way to launch the activity and
+to return to the task that it creates any time after it has been launched.
+</p>
+
+<p>This second ability is important: Users must be able to leave a task and then come back to it
+later using this activity launcher. For this reason, the two <a href="#LaunchModes">launch
+modes</a> that mark activities as always initiating a task, {@code "singleTask"} and "{@code
+"singleInstance"}, should be used only when the activity has an {@link
+android.content.Intent#ACTION_MAIN}
+and a {@link android.content.Intent#CATEGORY_LAUNCHER}
+filter. Imagine, for example, what could happen if the filter is missing: An intent launches a
+{@code "singleTask"} activity, initiating a new task, and the user spends some time working in
+that task. The user then presses the HOME key. The task is now sent to the background and not
+visible. Because it is not represented in the application launcher, the user has no way to return to
+the task.
+</p>
+
+<p>For those cases where you don't want the user to be able to return to an activity, set the
+ <code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> element's
+<a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a> to {@code "true"} (see <a
+href="#Clearing">Clearing the stack</a>).</p>
+
+
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how to use intents to
+activate other application components and publish the intents to which your components
+respond, continue with the <b><a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a></b> document.</p>
diff --git a/docs/html/images/fundamentals/diagram_backstack.png b/docs/html/images/fundamentals/diagram_backstack.png
new file mode 100644
index 0000000..2c6e33f
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
new file mode 100644
index 0000000..d6a21d7
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multiple_instances.png b/docs/html/images/fundamentals/diagram_multiple_instances.png
new file mode 100644
index 0000000..380e7788
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multiple_instances.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multitasking.png b/docs/html/images/fundamentals/diagram_multitasking.png
new file mode 100644
index 0000000..b8c7b45
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multitasking.png
Binary files differ
diff --git a/docs/html/images/fundamentals/restore_instance.png b/docs/html/images/fundamentals/restore_instance.png
new file mode 100644
index 0000000..fa428a7
--- /dev/null
+++ b/docs/html/images/fundamentals/restore_instance.png
Binary files differ
diff --git a/docs/html/images/home/market-intl.png b/docs/html/images/home/market-intl.png
new file mode 100644
index 0000000..2bb22f1
--- /dev/null
+++ b/docs/html/images/home/market-intl.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index f37a122..049df62 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -11,12 +11,14 @@
</div><!-- end homeTitle -->
<div id="announcement-block">
<!-- total max width is 520px -->
- <img src="{@docRoot}images/home/io-logo.png" alt="Google IO
-2010" width="200" height="41" style="padding:22px 12px;"/>
+ <img src="{@docRoot}images/home/market-intl.png" alt="Android
+Market" width="104" height="120" style="padding:10px 60px 5px" />
<div id="announcement" style="width:295px">
-<p>Thanks to everyone who visited us at Google I/O in San Francisco! Stay tuned for
-videos and slides from the Android sessions, which will be posted at the Google I/O web site.</p><p><a
-href="http://code.google.com/events/io/2010/sessions.html#Android">Learn more »</a></p>
+<p>We're pleased to announce that paid apps are available in more locations of the world! Developers
+from 20 more locations can now sell paid apps on Android Market. Users in more locations will also
+soon be able to purchase apps.</p><p><a
+href="http://android-developers.blogspot.com/2010/09/more-countries-more-sellers-more-buyers.html">
+Learn more »</a></p>
</div> <!-- end annoucement -->
</div> <!-- end annoucement-block -->
</div><!-- end topAnnouncement -->
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 985d700..2c076b3 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -182,6 +182,24 @@
mRS.nAllocationRead(mID, d);
}
+ public void resize(int dimX) {
+ if ((mType.getY() > 0)|| (mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+ throw new IllegalStateException("Resize only support for 1D allocations at this time.");
+ }
+ mRS.nAllocationResize1D(mID, dimX);
+ }
+
+ /*
+ public void resize(int dimX, int dimY) {
+ if ((mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+ throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+ }
+ if (mType.getY() == 0) {
+ throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+ }
+ mRS.nAllocationResize2D(mID, dimX, dimY);
+ }
+ */
public class Adapter1D extends BaseObj {
Adapter1D(int id, RenderScript rs) {
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 3c0b4e5..0088373 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -254,6 +254,15 @@
return rsnAllocationGetType(mContext, id);
}
+ native void rsnAllocationResize1D(int con, int id, int dimX);
+ synchronized void nAllocationResize1D(int id, int dimX) {
+ rsnAllocationResize1D(mContext, id, dimX);
+ }
+ native void rsnAllocationResize2D(int con, int id, int dimX, int dimY);
+ synchronized void nAllocationResize2D(int id, int dimX, int dimY) {
+ rsnAllocationResize2D(mContext, id, dimX, dimY);
+ }
+
native int rsnFileA3DCreateFromAssetStream(int con, int assetStream);
synchronized int nFileA3DCreateFromAssetStream(int assetStream) {
return rsnFileA3DCreateFromAssetStream(mContext, assetStream);
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 67a2b63..8f1e93c 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -587,6 +587,20 @@
return (jint) rsAllocationGetType(con, (RsAllocation)a);
}
+static void
+nAllocationResize1D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX)
+{
+ LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i)", con, (RsAllocation)alloc, dimX);
+ rsAllocationResize1D(con, (RsAllocation)alloc, dimX);
+}
+
+static void
+nAllocationResize2D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX, jint dimY)
+{
+ LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i), sizeY(%i)", con, (RsAllocation)alloc, dimX, dimY);
+ rsAllocationResize2D(con, (RsAllocation)alloc, dimX, dimY);
+}
+
// -----------------------------------
static int
@@ -1252,6 +1266,8 @@
{"rsnAllocationRead", "(II[I)V", (void*)nAllocationRead_i },
{"rsnAllocationRead", "(II[F)V", (void*)nAllocationRead_f },
{"rsnAllocationGetType", "(II)I", (void*)nAllocationGetType},
+{"rsnAllocationResize1D", "(III)V", (void*)nAllocationResize1D },
+{"rsnAllocationResize2D", "(IIII)V", (void*)nAllocationResize2D },
{"rsnAdapter1DBindAllocation", "(III)V", (void*)nAdapter1DBindAllocation },
{"rsnAdapter1DSetConstraint", "(IIII)V", (void*)nAdapter1DSetConstraint },
diff --git a/include/utils/ZipFileRO.h b/include/utils/ZipFileRO.h
index e1ff780..3c1f3ca 100644
--- a/include/utils/ZipFileRO.h
+++ b/include/utils/ZipFileRO.h
@@ -14,13 +14,19 @@
* limitations under the License.
*/
-//
-// Read-only access to Zip archives, with minimal heap allocation.
-//
-// This is similar to the more-complete ZipFile class, but no attempt
-// has been made to make them interchangeable. This class operates under
-// a very different set of assumptions and constraints.
-//
+/*
+ * Read-only access to Zip archives, with minimal heap allocation.
+ *
+ * This is similar to the more-complete ZipFile class, but no attempt
+ * has been made to make them interchangeable. This class operates under
+ * a very different set of assumptions and constraints.
+ *
+ * One such assumption is that if you're getting file descriptors for
+ * use with this class as a child of a fork() operation, you must be on
+ * a pread() to guarantee correct operation. This is because pread() can
+ * atomically read at a file offset without worrying about a lock around an
+ * lseek() + read() pair.
+ */
#ifndef __LIBS_ZIPFILERO_H
#define __LIBS_ZIPFILERO_H
@@ -55,6 +61,10 @@
* the record structure. However, this requires a private mapping of
* every page that the Central Directory touches. Easier to tuck a copy
* of the string length into the hash table entry.
+ *
+ * NOTE: If this is used on file descriptors inherited from a fork() operation,
+ * you must be on a platform that implements pread() to guarantee correctness
+ * on the shared file descriptors.
*/
class ZipFileRO {
public:
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
index 20b27c9..bdd4dd6 100644
--- a/libs/binder/CursorWindow.cpp
+++ b/libs/binder/CursorWindow.cpp
@@ -141,10 +141,12 @@
size = requestedSize + padding;
if (size > freeSpace()) {
- LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows);
+ LOGV("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size,
+ freeSpace(), mHeader->numRows);
// Only grow the window if the first row doesn't fit
if (mHeader->numRows > 1) {
-LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize);
+ LOGV("not growing since there are already %d row(s), max size %d", mHeader->numRows,
+ mMaxSize);
return 0;
}
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
index 77fbda2..2ef71c2 100644
--- a/libs/hwui/FboCache.cpp
+++ b/libs/hwui/FboCache.cpp
@@ -16,6 +16,8 @@
#define LOG_TAG "OpenGLRenderer"
+#include <stdlib.h>
+
#include "FboCache.h"
#include "Properties.h"
@@ -57,14 +59,31 @@
///////////////////////////////////////////////////////////////////////////////
void FboCache::clear() {
-
+ for (size_t i = 0; i < mCache.size(); i++) {
+ const GLuint fbo = mCache.itemAt(i);
+ glDeleteFramebuffers(1, &fbo);
+ }
+ mCache.clear();
}
GLuint FboCache::get() {
- return 0;
+ GLuint fbo;
+ if (mCache.size() > 0) {
+ fbo = mCache.itemAt(mCache.size() - 1);
+ mCache.removeAt(mCache.size() - 1);
+ } else {
+ glGenFramebuffers(1, &fbo);
+ }
+ return fbo;
}
bool FboCache::put(GLuint fbo) {
+ if (mCache.size() < mMaxSize) {
+ mCache.add(fbo);
+ return true;
+ }
+
+ glDeleteFramebuffers(1, &fbo);
return false;
}
diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h
index 66f66ea..ec4afe9 100644
--- a/libs/hwui/FboCache.h
+++ b/libs/hwui/FboCache.h
@@ -21,8 +21,6 @@
#include <utils/SortedVector.h>
-#include "GenerationCache.h"
-
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index 070e33f..35c6bea 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -65,6 +65,7 @@
void put(K key, V value);
V remove(K key);
V removeOldest();
+ V getValueAt(uint32_t index) const;
uint32_t size() const;
@@ -128,6 +129,11 @@
}
template<typename K, typename V>
+V GenerationCache<K, V>::getValueAt(uint32_t index) const {
+ return mCache.valueAt(index);
+}
+
+template<typename K, typename V>
V GenerationCache<K, V>::get(K key) {
ssize_t index = mCache.indexOfKey(key);
if (index >= 0) {
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index a0cc5d6..6024765 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -32,21 +32,14 @@
* Dimensions of a layer.
*/
struct LayerSize {
- LayerSize(): width(0), height(0), id(0) { }
- LayerSize(const uint32_t width, const uint32_t height): width(width), height(height), id(0) { }
- LayerSize(const LayerSize& size): width(size.width), height(size.height), id(size.id) { }
+ LayerSize(): width(0), height(0) { }
+ LayerSize(const uint32_t width, const uint32_t height): width(width), height(height) { }
+ LayerSize(const LayerSize& size): width(size.width), height(size.height) { }
uint32_t width;
uint32_t height;
- // Incremental id used by the layer cache to store multiple
- // LayerSize with the same dimensions
- uint32_t id;
-
bool operator<(const LayerSize& rhs) const {
- if (id != 0 && rhs.id != 0 && id != rhs.id) {
- return id < rhs.id;
- }
if (width == rhs.width) {
return height < rhs.height;
}
@@ -54,12 +47,12 @@
}
bool operator==(const LayerSize& rhs) const {
- return id == rhs.id && width == rhs.width && height == rhs.height;
+ return width == rhs.width && height == rhs.height;
}
}; // struct LayerSize
/**
- * A layer has dimensions and is backed by an OpenGL texture.
+ * A layer has dimensions and is backed by an OpenGL texture or FBO.
*/
struct Layer {
/**
@@ -71,6 +64,11 @@
*/
GLuint texture;
/**
+ * Name of the FBO used to render the layer. If the name is 0
+ * this layer is not backed by an FBO, but a simple texture.
+ */
+ GLuint fbo;
+ /**
* Opacity of the layer.
*/
int alpha;
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 8c70cf9..2183718 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -32,7 +32,7 @@
LayerCache::LayerCache():
mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
- mIdGenerator(1), mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+ mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
char property[PROPERTY_VALUE_MAX];
if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
LOGD(" Setting layer cache size to %sMB", property);
@@ -44,7 +44,7 @@
LayerCache::LayerCache(uint32_t maxByteSize):
mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
- mIdGenerator(1), mSize(0), mMaxSize(maxByteSize) {
+ mSize(0), mMaxSize(maxByteSize) {
}
LayerCache::~LayerCache() {
@@ -110,6 +110,7 @@
layer = new Layer;
layer->blend = true;
layer->empty = true;
+ layer->fbo = 0;
glGenTextures(1, &layer->texture);
glBindTexture(GL_TEXTURE_2D, layer->texture);
@@ -121,6 +122,14 @@
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+#if DEBUG_LAYERS
+ uint32_t size = mCache.size();
+ for (uint32_t i = 0; i < size; i++) {
+ LayerSize ls = mCache.getKeyAt(i);
+ LAYER_LOGD(" Layer size %dx%d", ls.width, ls.height);
+ }
+#endif
}
return layer;
@@ -133,9 +142,10 @@
while (mSize + size > mMaxSize) {
Layer* oldest = mCache.removeOldest();
deleteLayer(oldest);
+ LAYER_LOGD(" Deleting layer %.2fx%.2f", oldest->layer.getWidth(),
+ oldest->layer.getHeight());
}
- layerSize.id = mIdGenerator++;
mCache.put(layerSize, layer);
mSize += size;
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index c0c7542..cbb7ae2 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -64,6 +64,7 @@
* @param size The dimensions of the desired layer
*/
Layer* get(LayerSize& size);
+
/**
* Adds the layer to the cache. The layer will not be added if there is
* not enough space available.
@@ -96,7 +97,6 @@
void deleteLayer(Layer* layer);
GenerationCache<LayerSize, Layer*> mCache;
- uint32_t mIdGenerator;
uint32_t mSize;
uint32_t mMaxSize;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e3790f5..ee5fe22 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -145,6 +145,9 @@
mWidth = width;
mHeight = height;
+
+ mFirstSnapshot->height = height;
+ mFirstSnapshot->viewport.set(0, 0, width, height);
}
void OpenGLRenderer::prepare() {
@@ -185,7 +188,7 @@
}
void OpenGLRenderer::releaseContext() {
- glViewport(0, 0, mWidth, mHeight);
+ glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
glEnable(GL_SCISSOR_TEST);
setScissorFromClip();
@@ -237,10 +240,17 @@
bool OpenGLRenderer::restoreSnapshot() {
bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
+ bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
sp<Snapshot> current = mSnapshot;
sp<Snapshot> previous = mSnapshot->previous;
+ if (restoreOrtho) {
+ Rect& r = previous->viewport;
+ glViewport(r.left, r.top, r.right, r.bottom);
+ mOrthoMatrix.load(current->orthoMatrix);
+ }
+
mSaveCount--;
mSnapshot = previous;
@@ -261,7 +271,8 @@
int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
const SkPaint* p, int flags) {
- int count = saveSnapshot(flags);
+ const GLuint previousFbo = mSnapshot->fbo;
+ const int count = saveSnapshot(flags);
int alpha = 255;
SkXfermode::Mode mode;
@@ -281,7 +292,7 @@
mode = SkXfermode::kSrcOver_Mode;
}
- createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
+ createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo);
return count;
}
@@ -346,17 +357,21 @@
* something actually gets drawn are the layers regions cleared.
*/
bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
- float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) {
- LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top);
+ float right, float bottom, int alpha, SkXfermode::Mode mode,
+ int flags, GLuint previousFbo) {
+ LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
+ const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
+
// Window coordinates of the layer
Rect bounds(left, top, right, bottom);
- mSnapshot->transform->mapRect(bounds);
-
- // Layers only make sense if they are in the framebuffer's bounds
- bounds.intersect(*mSnapshot->clipRect);
- bounds.snapToPixelBoundaries();
+ if (!fboLayer) {
+ mSnapshot->transform->mapRect(bounds);
+ // Layers only make sense if they are in the framebuffer's bounds
+ bounds.intersect(*mSnapshot->clipRect);
+ bounds.snapToPixelBoundaries();
+ }
if (bounds.isEmpty() || bounds.getWidth() > mMaxTextureSize ||
bounds.getHeight() > mMaxTextureSize) {
@@ -379,29 +394,77 @@
snapshot->flags |= Snapshot::kFlagIsLayer;
snapshot->layer = layer;
- // Copy the framebuffer into the layer
- glBindTexture(GL_TEXTURE_2D, layer->texture);
+ if (fboLayer) {
+ layer->fbo = mCaches.fboCache.get();
- // TODO: Workaround for b/3054204
- glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
- bounds.getWidth(), bounds.getHeight(), 0);
+ snapshot->flags |= Snapshot::kFlagIsFboLayer;
+ snapshot->fbo = layer->fbo;
+ snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+ snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ snapshot->height = bounds.getHeight();
+ snapshot->flags |= Snapshot::kFlagDirtyOrtho;
+ snapshot->orthoMatrix.load(mOrthoMatrix);
- // TODO: Waiting for b/3054204 to be fixed
-// if (layer->empty) {
-// glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-// bounds.getWidth(), bounds.getHeight(), 0);
-// layer->empty = false;
-// } else {
-// glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
-// bounds.getWidth(), bounds.getHeight());
-// }
-
- if (flags & SkCanvas::kClipToLayer_SaveFlag && mSnapshot->clipTransformed(bounds)) {
setScissorFromClip();
- }
- // Enqueue the buffer coordinates to clear the corresponding region later
- mLayers.push(new Rect(bounds));
+ // Bind texture to FBO
+ glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+ // Initialize the texture if needed
+ if (layer->empty) {
+ layer->empty = false;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ }
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ layer->texture, 0);
+
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ glDeleteTextures(1, &layer->texture);
+ mCaches.fboCache.put(layer->fbo);
+
+ delete layer;
+
+ return false;
+ }
+
+ // Clear the FBO
+ glDisable(GL_SCISSOR_TEST);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glEnable(GL_SCISSOR_TEST);
+
+ // Change the ortho projection
+ glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
+ mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
+ } else {
+ // Copy the framebuffer into the layer
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+ // TODO: Workaround for b/3054204
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ bounds.getWidth(), bounds.getHeight(), 0);
+
+ // TODO: Waiting for b/3054204 to be fixed
+ // if (layer->empty) {
+ // glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ // bounds.getWidth(), bounds.getHeight(), 0);
+ // layer->empty = false;
+ // } else {
+ // glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+ // bounds.getWidth(), bounds.getHeight());
+ // }
+
+ // Enqueue the buffer coordinates to clear the corresponding region later
+ mLayers.push(new Rect(bounds));
+ }
return true;
}
@@ -415,14 +478,21 @@
return;
}
+ const bool fboLayer = current->flags & SkCanvas::kClipToLayer_SaveFlag;
+
+ if (fboLayer) {
+ // Unbind current FBO and restore previous one
+ glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+ }
+
// Restore the clip from the previous snapshot
const Rect& clip = *previous->clipRect;
- glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
Layer* layer = current->layer;
const Rect& rect = layer->layer;
- if (layer->alpha < 255) {
+ if (!fboLayer && layer->alpha < 255) {
drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
}
@@ -430,20 +500,32 @@
// Layers are already drawn with a top-left origin, don't flip the texture
resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
- drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
- 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
- &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+ if (fboLayer) {
+ drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+ layer->texture, layer->alpha / 255.0f, layer->mode, layer->blend);
+ } else {
+ drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+ 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+ &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+ }
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+ if (fboLayer) {
+ // Detach the texture from the FBO
+ glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+
+ // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
+ mCaches.fboCache.put(current->fbo);
+ }
+
LayerSize size(rect.getWidth(), rect.getHeight());
- // Failing to add the layer to the cache should happen only if the
- // layer is too large
+ // Failing to add the layer to the cache should happen only if the layer is too large
if (!mCaches.layerCache.put(size, layer)) {
LAYER_LOGD("Deleting layer");
-
glDeleteTextures(1, &layer->texture);
-
delete layer;
}
}
@@ -503,7 +585,7 @@
void OpenGLRenderer::setScissorFromClip() {
const Rect& clip = *mSnapshot->clipRect;
- glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
}
const Rect& OpenGLRenderer::getClipBounds() {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1974cf0..e3d4653 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -167,11 +167,12 @@
* @param alpha The translucency of the layer
* @param mode The blending mode of the layer
* @param flags The layer save flags
+ * @param previousFbo The name of the current framebuffer
*
* @return True if the layer was successfully created, false otherwise
*/
bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags);
+ int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
/**
* Clears all the regions corresponding to the current list of layers.
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d573805..3012824 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -45,7 +45,7 @@
#define MB(s) s * 1024 * 1024
#define DEFAULT_TEXTURE_CACHE_SIZE 18.0f
-#define DEFAULT_LAYER_CACHE_SIZE 4.0f
+#define DEFAULT_LAYER_CACHE_SIZE 8.0f
#define DEFAULT_PATH_CACHE_SIZE 5.0f
#define DEFAULT_PATCH_CACHE_SIZE 100
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index c736a1c..3d74b4c 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -43,7 +43,7 @@
*/
class Snapshot: public LightRefBase<Snapshot> {
public:
- Snapshot(): flags(0), previous(NULL), layer(NULL) {
+ Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) {
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
}
@@ -53,7 +53,8 @@
* the previous snapshot.
*/
Snapshot(const sp<Snapshot>& s, int saveFlags):
- flags(0), previous(s), layer(NULL) {
+ flags(0), previous(s), layer(NULL),
+ fbo(s->fbo), viewport(s->viewport), height(s->height) {
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
mTransformRoot.load(*s->transform);
transform = &mTransformRoot;
@@ -91,9 +92,19 @@
*/
kFlagIsLayer = 0x2,
/**
+ * Indicates that this snapshot is a special type of layer
+ * backed by an FBO. This flag only makes sense when the
+ * flag kFlagIsLayer is also set.
+ */
+ kFlagIsFboLayer = 0x4,
+ /**
* Indicates that the local clip should be recomputed.
*/
- kFlagDirtyLocalClip = 0x4,
+ kFlagDirtyLocalClip = 0x8,
+ /**
+ * Indicates that this snapshot has changed the ortho matrix.
+ */
+ kFlagDirtyOrtho = 0x10,
};
/**
@@ -169,6 +180,17 @@
return mLocalClip;
}
+ void resetTransform(float x, float y, float z) {
+ transform = &mTransformRoot;
+ transform->loadTranslate(x, y, z);
+ }
+
+ void resetClip(float left, float top, float right, float bottom) {
+ clipRect = &mClipRectRoot;
+ clipRect->set(left, top, right, bottom);
+ flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+ }
+
/**
* Dirty flags.
*/
@@ -185,6 +207,26 @@
Layer* layer;
/**
+ * Only set when the flag kFlagIsFboLayer is set.
+ */
+ GLuint fbo;
+
+ /**
+ * Current viewport.
+ */
+ Rect viewport;
+
+ /**
+ * Height of the framebuffer the snapshot is rendering into.
+ */
+ int height;
+
+ /**
+ * Contains the previous ortho matrix.
+ */
+ mat4 orthoMatrix;
+
+ /**
* Local transformation. Holds the current translation, scale and
* rotation values.
*/
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index adf6ee2..701df83 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -18,6 +18,8 @@
#include <GLES2/gl2.h>
+#include <SkCanvas.h>
+
#include <utils/threads.h>
#include "TextureCache.h"
@@ -192,8 +194,12 @@
// decoding happened
texture->blend = !bitmap->isOpaque();
break;
+ case SkBitmap::kIndex8_Config:
+ uploadPalettedTexture(resize, bitmap, texture->width, texture->height);
+ texture->blend = false;
+ break;
default:
- LOGW("Unsupported bitmap config");
+ LOGW("Unsupported bitmap config: %d", bitmap->getConfig());
break;
}
@@ -204,6 +210,20 @@
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
+void TextureCache::uploadPalettedTexture(bool resize, SkBitmap* bitmap,
+ uint32_t width, uint32_t height) {
+ SkBitmap rgbaBitmap;
+ rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ rgbaBitmap.allocPixels();
+ rgbaBitmap.eraseColor(0);
+
+ SkCanvas canvas(rgbaBitmap);
+ canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL);
+
+ uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), height,
+ GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
+}
+
void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
GLenum type, const GLvoid * data) {
if (resize) {
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 7cf66d9..34c5455 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -79,6 +79,7 @@
*/
void generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate = false);
+ void uploadPalettedTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height);
void uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
GLenum type, const GLvoid * data);
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 37c418b..05c1a48 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -83,7 +83,6 @@
rsElement.cpp \
rsFileA3D.cpp \
rsFont.cpp \
- rsLight.cpp \
rsLocklessFifo.cpp \
rsObjectBase.cpp \
rsMatrix.cpp \
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index 13ae1fb..66e27f3 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -40,7 +40,6 @@
typedef void * RsScript;
typedef void * RsMesh;
typedef void * RsType;
-typedef void * RsLight;
typedef void * RsObjectBase;
typedef void * RsProgram;
@@ -242,7 +241,6 @@
RS_A3D_CLASS_ID_PROGRAM_STORE,
RS_A3D_CLASS_ID_SAMPLER,
RS_A3D_CLASS_ID_ANIMATION,
- RS_A3D_CLASS_ID_LIGHT,
RS_A3D_CLASS_ID_ADAPTER_1D,
RS_A3D_CLASS_ID_ADAPTER_2D,
RS_A3D_CLASS_ID_SCRIPT_C
diff --git a/libs/rs/RenderScriptEnv.h b/libs/rs/RenderScriptEnv.h
index c83ece4..b82eaf1 100644
--- a/libs/rs/RenderScriptEnv.h
+++ b/libs/rs/RenderScriptEnv.h
@@ -13,8 +13,6 @@
typedef void * RsType;
typedef void * RsProgramFragment;
typedef void * RsProgramStore;
-typedef void * RsLight;
-
typedef struct {
float m[16];
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
index a15c4a1..0990da3 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -84,8 +84,6 @@
private Allocation mTexTransparent;
private Allocation mTexChecker;
- private Allocation mAllocPV;
-
private Mesh mMbyNMesh;
private Mesh mTorus;
@@ -95,7 +93,6 @@
Font mFontSerifItalic;
Font mFontSerifBoldItalic;
Font mFontMono;
-
private Allocation mTextAlloc;
private ScriptC_rsrenderstates mScript;
@@ -267,12 +264,15 @@
mFontSerifBoldItalic = Font.createFromFamily(mRS, mRes, "serif", Font.Style.BOLD_ITALIC, 8);
mFontMono = Font.createFromFamily(mRS, mRes, "mono", Font.Style.NORMAL, 8);
+ mTextAlloc = Allocation.createFromString(mRS, "String from allocation");
+
mScript.set_gFontSans(mFontSans);
mScript.set_gFontSerif(mFontSerif);
mScript.set_gFontSerifBold(mFontSerifBold);
mScript.set_gFontSerifItalic(mFontSerifItalic);
mScript.set_gFontSerifBoldItalic(mFontSerifBoldItalic);
mScript.set_gFontMono(mFontMono);
+ mScript.set_gTextAlloc(mTextAlloc);
}
private void initMesh() {
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
index b471504..77384ef 100644
--- a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -42,6 +42,7 @@
rs_font gFontSerifItalic;
rs_font gFontSerifBoldItalic;
rs_font gFontMono;
+rs_allocation gTextAlloc;
int gDisplayMode;
@@ -70,7 +71,7 @@
#pragma rs export_var(gProgStoreBlendNoneDepth, gProgStoreBlendNone, gProgStoreBlendAlpha, gProgStoreBlendAdd)
#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent, gTexChecker)
#pragma rs export_var(gMbyNMesh, gTorusMesh)
-#pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono)
+#pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono, gTextAlloc)
#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gMipLinearAniso8, gMipLinearAniso15, gNearestClamp)
#pragma rs export_var(gCullBack, gCullFront, gCullNone)
#pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom, gProgFragmentMultitex)
@@ -85,7 +86,7 @@
void displayFontSamples() {
rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
- int yPos = 30;
+ int yPos = 100;
rsgBindFont(gFontSans);
rsgDrawText("Sans font sample", 30, yPos);
yPos += 30;
@@ -107,6 +108,47 @@
yPos += 30;
rsgBindFont(gFontMono);
rsgDrawText("Monospace font sample", 30, yPos);
+ yPos += 50;
+
+ // Now use text metrics to center the text
+ uint width = rsgGetWidth();
+ uint height = rsgGetHeight();
+ int left = 0, right = 0, top = 0, bottom = 0;
+
+ rsgFontColor(0.9f, 0.9f, 0.95f, 1.0f);
+ rsgBindFont(gFontSerifBoldItalic);
+
+ rsgMeasureText(gTextAlloc, &left, &right, &top, &bottom);
+ int centeredPos = width / 2 - (right - left) / 2;
+ rsgDrawText(gTextAlloc, centeredPos, yPos);
+ yPos += 30;
+
+ const char* text = "Centered Text Sample";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ centeredPos = width / 2 - (right - left) / 2;
+ rsgDrawText(text, centeredPos, yPos);
+ yPos += 30;
+
+ rsgBindFont(gFontSans);
+ text = "More Centered Text Samples";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ centeredPos = width / 2 - (right - left) / 2;
+ rsgDrawText(text, centeredPos, yPos);
+ yPos += 30;
+
+ // Now draw bottom and top right aligned text
+ text = "Top-right aligned text";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ rsgDrawText(text, width - right, top);
+
+ text = "Top-left";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ rsgDrawText(text, -left, top);
+
+ text = "Bottom-right aligned text";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ rsgDrawText(text, width - right, height + bottom);
+
}
void bindProgramVertexOrtho() {
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index a4752f4..0da637e 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -280,6 +280,17 @@
ret const void*
}
+AllocationResize1D {
+ param RsAllocation va
+ param uint32_t dimX
+ }
+
+AllocationResize2D {
+ param RsAllocation va
+ param uint32_t dimX
+ param uint32_t dimY
+ }
+
SamplerBegin {
}
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 0356e4d..2e9e0b3 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -488,12 +488,13 @@
}
}
-void Allocation::incRefs(const void *ptr, size_t ct) const
+void Allocation::incRefs(const void *ptr, size_t ct, size_t startOff) const
{
const uint8_t *p = static_cast<const uint8_t *>(ptr);
const Element *e = mType->getElement();
uint32_t stride = e->getSizeBytes();
+ p += stride * startOff;
while (ct > 0) {
e->incRefs(p);
ct --;
@@ -501,12 +502,13 @@
}
}
-void Allocation::decRefs(const void *ptr, size_t ct) const
+void Allocation::decRefs(const void *ptr, size_t ct, size_t startOff) const
{
const uint8_t *p = static_cast<const uint8_t *>(ptr);
const Element *e = mType->getElement();
uint32_t stride = e->getSizeBytes();
+ p += stride * startOff;
while (ct > 0) {
e->decRefs(p);
ct --;
@@ -514,6 +516,37 @@
}
}
+void Allocation::copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len)
+{
+}
+
+void Allocation::resize1D(Context *rsc, uint32_t dimX)
+{
+ Type *t = mType->cloneAndResize1D(rsc, dimX);
+
+ uint32_t oldDimX = mType->getDimX();
+ if (dimX == oldDimX) {
+ return;
+ }
+
+ if (dimX < oldDimX) {
+ decRefs(mPtr, oldDimX - dimX, dimX);
+ }
+ mPtr = realloc(mPtr, t->getSizeBytes());
+
+ if (dimX > oldDimX) {
+ const Element *e = mType->getElement();
+ uint32_t stride = e->getSizeBytes();
+ memset(((uint8_t *)mPtr) + stride * oldDimX, 0, stride * (dimX - oldDimX));
+ }
+ mType.set(t);
+}
+
+void Allocation::resize2D(Context *rsc, uint32_t dimX, uint32_t dimY)
+{
+ LOGE("not implemented");
+}
+
/////////////////
//
@@ -822,6 +855,18 @@
a->read(data);
}
+void rsi_AllocationResize1D(Context *rsc, RsAllocation va, uint32_t dimX)
+{
+ Allocation *a = static_cast<Allocation *>(va);
+ a->resize1D(rsc, dimX);
+}
+
+void rsi_AllocationResize2D(Context *rsc, RsAllocation va, uint32_t dimX, uint32_t dimY)
+{
+ Allocation *a = static_cast<Allocation *>(va);
+ a->resize2D(rsc, dimX, dimY);
+}
+
const void* rsi_AllocationGetType(Context *rsc, RsAllocation va)
{
Allocation *a = static_cast<Allocation *>(va);
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index ce5372f..24e245f 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -55,6 +55,10 @@
void uploadToBufferObject(const Context *rsc);
uint32_t getBufferObjectID() const {return mBufferID;}
+ void copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len);
+
+ void resize1D(Context *rsc, uint32_t dimX);
+ void resize2D(Context *rsc, uint32_t dimX, uint32_t dimY);
void data(Context *rsc, const void *data, uint32_t sizeBytes);
void subData(Context *rsc, uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes);
@@ -86,8 +90,8 @@
bool getIsTexture() const {return mIsTexture;}
bool getIsBufferObject() const {return mIsVertexBuffer;}
- void incRefs(const void *ptr, size_t ct) const;
- void decRefs(const void *ptr, size_t ct) const;
+ void incRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
+ void decRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
void sendDirty() const;
bool getHasGraphicsMipmaps() const {return mTextureGenMipmap;}
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index 6940033..30add62 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -188,6 +188,9 @@
mTimeFrame = mTimeLast;
mTimeLastFrame = mTimeLast;
mTimerActive = RS_TIMER_INTERNAL;
+ mAverageFPSFrameCount = 0;
+ mAverageFPSStartTime = mTimeLast;
+ mAverageFPS = 0;
timerReset();
}
@@ -195,6 +198,16 @@
{
mTimeLastFrame = mTimeFrame;
mTimeFrame = getTime();
+ // Update average fps
+ const uint64_t averageFramerateInterval = 1000 * 1000000;
+ mAverageFPSFrameCount ++;
+ uint64_t inverval = mTimeFrame - mAverageFPSStartTime;
+ if(inverval >= averageFramerateInterval) {
+ inverval = inverval / 1000000;
+ mAverageFPS = (mAverageFPSFrameCount * 1000) / inverval;
+ mAverageFPSFrameCount = 0;
+ mAverageFPSStartTime = mTimeFrame;
+ }
}
void Context::timerSet(Timers tm)
@@ -218,12 +231,13 @@
if (props.mLogTimes) {
- LOGV("RS: Frame (%i), Script %2.1f (%i), Clear & Swap %2.1f (%i), Idle %2.1f (%lli), Internal %2.1f (%lli)",
+ LOGV("RS: Frame (%i), Script %2.1f (%i), Clear & Swap %2.1f (%i), Idle %2.1f (%lli), Internal %2.1f (%lli), Avg fps: %u",
mTimeMSLastFrame,
100.0 * mTimers[RS_TIMER_SCRIPT] / total, mTimeMSLastScript,
100.0 * mTimers[RS_TIMER_CLEAR_SWAP] / total, mTimeMSLastSwap,
100.0 * mTimers[RS_TIMER_IDLE] / total, mTimers[RS_TIMER_IDLE] / 1000000,
- 100.0 * mTimers[RS_TIMER_INTERNAL] / total, mTimers[RS_TIMER_INTERNAL] / 1000000);
+ 100.0 * mTimers[RS_TIMER_INTERNAL] / total, mTimers[RS_TIMER_INTERNAL] / 1000000,
+ mAverageFPS);
}
}
@@ -255,17 +269,17 @@
void Context::displayDebugStats()
{
char buffer[128];
- sprintf(buffer, "Frame %i ms, Script %i ms", mTimeMSLastFrame, mTimeMSLastScript);
+ sprintf(buffer, "Avg fps %u, Frame %i ms, Script %i ms", mAverageFPS, mTimeMSLastFrame, mTimeMSLastScript);
float oldR, oldG, oldB, oldA;
mStateFont.getFontColor(&oldR, &oldG, &oldB, &oldA);
+ uint32_t bufferLen = strlen(buffer);
- float shadowCol = 0.2f;
+ float shadowCol = 0.1f;
mStateFont.setFontColor(shadowCol, shadowCol, shadowCol, 1.0f);
- mStateFont.renderText(buffer, 5, getHeight() - 5);
+ mStateFont.renderText(buffer, bufferLen, 5, getHeight() - 6);
- float textCol = 0.9f;
- mStateFont.setFontColor(textCol, textCol, textCol, 1.0f);
- mStateFont.renderText(buffer, 4, getHeight() - 6);
+ mStateFont.setFontColor(1.0f, 0.7f, 0.0f, 1.0f);
+ mStateFont.renderText(buffer, bufferLen, 4, getHeight() - 7);
mStateFont.setFontColor(oldR, oldG, oldB, oldA);
}
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index dabe196..8a8b8a8 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -30,7 +30,6 @@
#include "rsAllocation.h"
#include "rsAdapter.h"
#include "rsSampler.h"
-#include "rsLight.h"
#include "rsFont.h"
#include "rsProgramFragment.h"
#include "rsProgramStore.h"
@@ -94,7 +93,6 @@
ProgramStoreState mStateFragmentStore;
ProgramRasterState mStateRaster;
ProgramVertexState mStateVertex;
- LightState mStateLight;
VertexArrayState mStateVertexArray;
FontState mStateFont;
@@ -296,6 +294,9 @@
uint32_t mTimeMSLastFrame;
uint32_t mTimeMSLastScript;
uint32_t mTimeMSLastSwap;
+ uint32_t mAverageFPSFrameCount;
+ uint64_t mAverageFPSStartTime;
+ uint32_t mAverageFPS;
};
}
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
index 06298e8..aa0205d 100644
--- a/libs/rs/rsContextHostStub.h
+++ b/libs/rs/rsContextHostStub.h
@@ -30,7 +30,6 @@
#include "rsAllocation.h"
#include "rsAdapter.h"
#include "rsSampler.h"
-#include "rsLight.h"
#include "rsProgramFragment.h"
#include "rsProgramStore.h"
#include "rsProgramRaster.h"
@@ -68,7 +67,6 @@
ProgramStoreState mStateFragmentStore;
//ProgramRasterState mStateRaster;
//ProgramVertexState mStateVertex;
- LightState mStateLight;
VertexArrayState mStateVertexArray;
//ScriptCState mScriptC;
diff --git a/libs/rs/rsFileA3D.cpp b/libs/rs/rsFileA3D.cpp
index 893598f..c90edc2 100644
--- a/libs/rs/rsFileA3D.cpp
+++ b/libs/rs/rsFileA3D.cpp
@@ -278,9 +278,6 @@
case RS_A3D_CLASS_ID_ANIMATION:
entry->mRsObj = Animation::createFromStream(mRSC, mReadStream);
break;
- case RS_A3D_CLASS_ID_LIGHT:
- entry->mRsObj = Light::createFromStream(mRSC, mReadStream);
- break;
case RS_A3D_CLASS_ID_ADAPTER_1D:
entry->mRsObj = Adapter1D::createFromStream(mRSC, mReadStream);
break;
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index c516ea9..b9de7e1 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -84,34 +84,95 @@
}
}
-void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y)
+void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y)
{
FontState *state = &mRSC->mStateFont;
- int nPenX = x + glyph->mBitmapLeft;
- int nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
+ int32_t nPenX = x + glyph->mBitmapLeft;
+ int32_t nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
- state->appendMeshQuad(nPenX, nPenY, 0,
- glyph->mBitmapMinU, glyph->mBitmapMaxV,
+ float u1 = glyph->mBitmapMinU;
+ float u2 = glyph->mBitmapMaxU;
+ float v1 = glyph->mBitmapMinV;
+ float v2 = glyph->mBitmapMaxV;
- nPenX + (int)glyph->mBitmapWidth, nPenY, 0,
- glyph->mBitmapMaxU, glyph->mBitmapMaxV,
+ int32_t width = (int32_t) glyph->mBitmapWidth;
+ int32_t height = (int32_t) glyph->mBitmapHeight;
- nPenX + (int)glyph->mBitmapWidth, nPenY - (int)glyph->mBitmapHeight, 0,
- glyph->mBitmapMaxU, glyph->mBitmapMinV,
-
- nPenX, nPenY - (int)glyph->mBitmapHeight, 0,
- glyph->mBitmapMinU, glyph->mBitmapMinV);
+ state->appendMeshQuad(nPenX, nPenY, 0, u1, v2,
+ nPenX + width, nPenY, 0, u2, v2,
+ nPenX + width, nPenY - height, 0, u2, v1,
+ nPenX, nPenY - height, 0, u1, v1);
}
-void Font::renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y)
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int32_t x, int32_t y,
+ uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+ int32_t nPenX = x + glyph->mBitmapLeft;
+ int32_t nPenY = y + glyph->mBitmapTop;
+
+ uint32_t endX = glyph->mBitmapMinX + glyph->mBitmapWidth;
+ uint32_t endY = glyph->mBitmapMinY + glyph->mBitmapHeight;
+
+ FontState *state = &mRSC->mStateFont;
+ uint32_t cacheWidth = state->getCacheTextureType()->getDimX();
+ const uint8_t* cacheBuffer = state->getTextTextureData();
+
+ uint32_t cacheX = 0, cacheY = 0;
+ int32_t bX = 0, bY = 0;
+ for (cacheX = glyph->mBitmapMinX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
+ for (cacheY = glyph->mBitmapMinY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+ if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
+ LOGE("Skipping invalid index");
+ continue;
+ }
+ uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
+ bitmap[bY * bitmapW + bX] = tempCol;
+ }
+ }
+
+}
+
+void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y, Rect *bounds) {
+ int32_t nPenX = x + glyph->mBitmapLeft;
+ int32_t nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
+
+ int32_t width = (int32_t) glyph->mBitmapWidth;
+ int32_t height = (int32_t) glyph->mBitmapHeight;
+
+ if (bounds->bottom > nPenY) {
+ bounds->bottom = nPenY;
+ }
+ if (bounds->left > nPenX) {
+ bounds->left = nPenX;
+ }
+ if (bounds->right < nPenX + width) {
+ bounds->right = nPenX + width;
+ }
+ if (bounds->top < nPenY + height) {
+ bounds->top = nPenY + height;
+ }
+}
+
+void Font::renderUTF(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t start, int32_t numGlyphs,
+ RenderMode mode, Rect *bounds,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH)
{
if(!mInitialized || numGlyphs == 0 || text == NULL || len == 0) {
return;
}
- int penX = x, penY = y;
- int glyphsLeft = 1;
+ if(mode == Font::MEASURE) {
+ if (bounds == NULL) {
+ LOGE("No return rectangle provided to measure text");
+ return;
+ }
+ // Reset min and max of the bounding box to something large
+ bounds->set(1e6, -1e6, -1e6, 1e6);
+ }
+
+ int32_t penX = x, penY = y;
+ int32_t glyphsLeft = 1;
if(numGlyphs > 0) {
glyphsLeft = numGlyphs;
}
@@ -135,7 +196,17 @@
// If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
if(cachedGlyph->mIsValid) {
- drawCachedGlyph(cachedGlyph, penX, penY);
+ switch(mode) {
+ case FRAMEBUFFER:
+ drawCachedGlyph(cachedGlyph, penX, penY);
+ break;
+ case BITMAP:
+ drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+ break;
+ case MEASURE:
+ measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+ break;
+ }
}
penX += (cachedGlyph->mAdvance.x >> 6);
@@ -283,7 +354,7 @@
}
// Get the black gamma threshold
- int blackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
+ int32_t blackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
if (property_get(PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD, property, NULL) > 0) {
LOGD(" Setting text black gamma threshold to %s", property);
blackThreshold = atoi(property);
@@ -294,7 +365,7 @@
mBlackThreshold = (float)(blackThreshold) / 255.0f;
// Get the white gamma threshold
- int whiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
+ int32_t whiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
if (property_get(PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD, property, NULL) > 0) {
LOGD(" Setting text white gamma threshold to %s", property);
whiteThreshold = atoi(property);
@@ -397,13 +468,13 @@
uint32_t cacheWidth = getCacheTextureType()->getDimX();
- unsigned char *cacheBuffer = (unsigned char*)mTextTexture->getPtr();
- unsigned char *bitmapBuffer = bitmap->buffer;
+ uint8_t *cacheBuffer = (uint8_t*)mTextTexture->getPtr();
+ uint8_t *bitmapBuffer = bitmap->buffer;
uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
for(cacheX = startX, bX = 0; cacheX < endX; cacheX ++, bX ++) {
for(cacheY = startY, bY = 0; cacheY < endY; cacheY ++, bY ++) {
- unsigned char tempCol = bitmapBuffer[bY * bitmap->width + bX];
+ uint8_t tempCol = bitmapBuffer[bY * bitmap->width + bX];
cacheBuffer[cacheY*cacheWidth + cacheX] = tempCol;
}
}
@@ -487,7 +558,7 @@
mTextTexture->deferedUploadToTexture(mRSC, false, 0);
// Split up our cache texture into lines of certain widths
- int nextLine = 0;
+ int32_t nextLine = 0;
mCacheLines.push(new CacheTextureLine(16, texType->getDimX(), nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(24, texType->getDimX(), nextLine, 0));
@@ -519,8 +590,8 @@
// Four verts, two triangles , six indices per quad
for(uint32_t i = 0; i < mMaxNumberOfQuads; i ++) {
- int i6 = i * 6;
- int i4 = i * 4;
+ int32_t i6 = i * 6;
+ int32_t i4 = i * 4;
indexPtr[i6 + 0] = i4 + 0;
indexPtr[i6 + 1] = i4 + 1;
@@ -713,7 +784,11 @@
}
-void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y)
+void FontState::renderText(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t startIndex, int32_t numGlyphs,
+ Font::RenderMode mode,
+ Font::Rect *bounds,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH)
{
checkInit();
@@ -730,7 +805,8 @@
return;
}
- currentFont->renderUTF(text, len, startIndex, numGlyphs, x, y);
+ currentFont->renderUTF(text, len, x, y, startIndex, numGlyphs,
+ mode, bounds, bitmap, bitmapW, bitmapH);
if(mCurrentQuadIndex != 0) {
issueDrawCommand();
@@ -738,32 +814,8 @@
}
}
-void FontState::renderText(const char *text, int x, int y)
-{
- size_t textLen = strlen(text);
- renderText(text, textLen, 0, -1, x, y);
-}
-
-void FontState::renderText(Allocation *alloc, int x, int y)
-{
- if(!alloc) {
- return;
- }
-
- const char *text = (const char *)alloc->getPtr();
- size_t allocSize = alloc->getType()->getSizeBytes();
- renderText(text, allocSize, 0, -1, x, y);
-}
-
-void FontState::renderText(Allocation *alloc, uint32_t start, int len, int x, int y)
-{
- if(!alloc) {
- return;
- }
-
- const char *text = (const char *)alloc->getPtr();
- size_t allocSize = alloc->getType()->getSizeBytes();
- renderText(text, allocSize, start, len, x, y);
+void FontState::measureText(const char *text, uint32_t len, Font::Rect *bounds) {
+ renderText(text, len, 0, 0, 0, -1, Font::MEASURE, bounds);
}
void FontState::setFontColor(float r, float g, float b, float a) {
@@ -773,7 +825,7 @@
mConstants.mFontColor[3] = a;
mConstants.mGamma = 1.0f;
- const int luminance = (r * 2.0f + g * 5.0f + b) / 8.0f;
+ const float luminance = (r * 2.0f + g * 5.0f + b) / 8.0f;
if (luminance <= mBlackThreshold) {
mConstants.mGamma = mBlackGamma;
} else if (luminance >= mWhiteThreshold) {
diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h
index 16009ef..0012b84 100644
--- a/libs/rs/rsFont.h
+++ b/libs/rs/rsFont.h
@@ -45,12 +45,26 @@
class Font : public ObjectBase
{
public:
- ~Font();
+ enum RenderMode {
+ FRAMEBUFFER,
+ BITMAP,
+ MEASURE,
+ };
- // Pointer to the utf data, length of data, where to start, number of glyphs ot read
- // (each glyph may be longer than a char because we are dealing with utf data)
- // Last two variables are the initial pen position
- void renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y);
+ struct Rect {
+ int32_t left;
+ int32_t top;
+ int32_t right;
+ int32_t bottom;
+ void set(int32_t l, int32_t r, int32_t t, int32_t b) {
+ left = l;
+ right = r;
+ top = t;
+ bottom = b;
+ }
+ };
+
+ ~Font();
// Currently files do not get serialized,
// but we need to inherit from ObjectBase for ref tracking
@@ -66,6 +80,14 @@
friend class FontState;
+ // Pointer to the utf data, length of data, where to start, number of glyphs ot read
+ // (each glyph may be longer than a char because we are dealing with utf data)
+ // Last two variables are the initial pen position
+ void renderUTF(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t start, int32_t numGlyphs,
+ RenderMode mode = FRAMEBUFFER, Rect *bounds = NULL,
+ uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+
void invalidateTextureCache();
struct CachedGlyphInfo
{
@@ -106,7 +128,10 @@
CachedGlyphInfo *cacheGlyph(uint32_t glyph);
void updateGlyphCache(CachedGlyphInfo *glyph);
- void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
+ void measureCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y, Rect *bounds);
+ void drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y);
+ void drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
};
class FontState
@@ -121,10 +146,13 @@
ObjectBaseRef<Font> mDefault;
ObjectBaseRef<Font> mLast;
- void renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y);
- void renderText(const char *text, int x, int y);
- void renderText(Allocation *alloc, int x, int y);
- void renderText(Allocation *alloc, uint32_t start, int len, int x, int y);
+ void renderText(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t startIndex = 0, int numGlyphs = -1,
+ Font::RenderMode mode = Font::FRAMEBUFFER,
+ Font::Rect *bounds = NULL,
+ uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+
+ void measureText(const char *text, uint32_t len, Font::Rect *bounds);
void setFontColor(float r, float g, float b, float a);
void getFontColor(float *r, float *g, float *b, float *a) const;
@@ -198,6 +226,9 @@
// Texture to cache glyph bitmaps
ObjectBaseRef<Allocation> mTextTexture;
void initTextTexture();
+ const uint8_t* getTextTextureData() const {
+ return (uint8_t*)mTextTexture->getPtr();
+ }
bool cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY);
const Type* getCacheTextureType() {
diff --git a/libs/rs/rsLight.cpp b/libs/rs/rsLight.cpp
deleted file mode 100644
index eab9a07..0000000
--- a/libs/rs/rsLight.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2009 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 ANDROID_RS_BUILD_FOR_HOST
-#include "rsContext.h"
-#include <GLES/gl.h>
-#else
-#include "rsContextHostStub.h"
-#include <OpenGL/gl.h>
-#endif //ANDROID_RS_BUILD_FOR_HOST
-
-using namespace android;
-using namespace android::renderscript;
-
-
-Light::Light(Context *rsc, bool isLocal, bool isMono) : ObjectBase(rsc)
-{
- mAllocFile = __FILE__;
- mAllocLine = __LINE__;
- mIsLocal = isLocal;
- mIsMono = isMono;
-
- mPosition[0] = 0;
- mPosition[1] = 0;
- mPosition[2] = 1;
- mPosition[3] = 0;
-
- mColor[0] = 1.f;
- mColor[1] = 1.f;
- mColor[2] = 1.f;
- mColor[3] = 1.f;
-}
-
-Light::~Light()
-{
-}
-
-void Light::setPosition(float x, float y, float z)
-{
- mPosition[0] = x;
- mPosition[1] = y;
- mPosition[2] = z;
-}
-
-void Light::setColor(float r, float g, float b)
-{
- mColor[0] = r;
- mColor[1] = g;
- mColor[2] = b;
-}
-
-void Light::setupGL(uint32_t num) const
-{
- glLightfv(GL_LIGHT0 + num, GL_DIFFUSE, mColor);
- glLightfv(GL_LIGHT0 + num, GL_SPECULAR, mColor);
- glLightfv(GL_LIGHT0 + num, GL_POSITION, mPosition);
-}
-
-void Light::serialize(OStream *stream) const
-{
-
-}
-
-Light *Light::createFromStream(Context *rsc, IStream *stream)
-{
- return NULL;
-}
-
-////////////////////////////////////////////
-
-LightState::LightState()
-{
- clear();
-}
-
-LightState::~LightState()
-{
-}
-
-void LightState::clear()
-{
- mIsLocal = false;
- mIsMono = false;
-}
-
-
-////////////////////////////////////////////////////
-//
-
-namespace android {
-namespace renderscript {
-
-void rsi_LightBegin(Context *rsc)
-{
- rsc->mStateLight.clear();
-}
-
-void rsi_LightSetLocal(Context *rsc, bool isLocal)
-{
- rsc->mStateLight.mIsLocal = isLocal;
-}
-
-void rsi_LightSetMonochromatic(Context *rsc, bool isMono)
-{
- rsc->mStateLight.mIsMono = isMono;
-}
-
-RsLight rsi_LightCreate(Context *rsc)
-{
- Light *l = new Light(rsc, rsc->mStateLight.mIsLocal,
- rsc->mStateLight.mIsMono);
- l->incUserRef();
- return l;
-}
-
-void rsi_LightSetColor(Context *rsc, RsLight vl, float r, float g, float b)
-{
- Light *l = static_cast<Light *>(vl);
- l->setColor(r, g, b);
-}
-
-void rsi_LightSetPosition(Context *rsc, RsLight vl, float x, float y, float z)
-{
- Light *l = static_cast<Light *>(vl);
- l->setPosition(x, y, z);
-}
-
-
-
-}
-}
diff --git a/libs/rs/rsLight.h b/libs/rs/rsLight.h
deleted file mode 100644
index bd58979..0000000
--- a/libs/rs/rsLight.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2009 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 ANDROID_LIGHT_H
-#define ANDROID_LIGHT_H
-
-
-#include "rsObjectBase.h"
-
-// ---------------------------------------------------------------------------
-namespace android {
-namespace renderscript {
-
-
-// An element is a group of Components that occupies one cell in a structure.
-class Light : public ObjectBase
-{
-public:
- Light(Context *, bool isLocal, bool isMono);
- virtual ~Light();
-
- // Values, mutable after creation.
- void setPosition(float x, float y, float z);
- void setColor(float r, float g, float b);
-
- void setupGL(uint32_t num) const;
- virtual void serialize(OStream *stream) const;
- virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_LIGHT; }
- static Light *createFromStream(Context *rsc, IStream *stream);
-
-protected:
- float mColor[4];
- float mPosition[4];
- bool mIsLocal;
- bool mIsMono;
-};
-
-
-class LightState {
-public:
- LightState();
- ~LightState();
-
- void clear();
-
- bool mIsMono;
- bool mIsLocal;
-};
-
-
-}
-}
-#endif //ANDROID_LIGHT_H
-
diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp
index 88db761..b991cab 100644
--- a/libs/rs/rsScriptC_LibGL.cpp
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -351,13 +351,59 @@
CHECK_OBJ(va);
GET_TLS();
Allocation *alloc = static_cast<Allocation *>(va);
- rsc->mStateFont.renderText(alloc, x, y);
+ const char *text = (const char *)alloc->getPtr();
+ size_t allocSize = alloc->getType()->getSizeBytes();
+ rsc->mStateFont.renderText(text, allocSize, x, y);
}
static void SC_DrawText(const char *text, int x, int y)
{
GET_TLS();
- rsc->mStateFont.renderText(text, x, y);
+ size_t textLen = strlen(text);
+ rsc->mStateFont.renderText(text, textLen, x, y);
+}
+
+static void SC_setMetrics(Font::Rect *metrics,
+ int32_t *left, int32_t *right,
+ int32_t *top, int32_t *bottom)
+{
+ if(left) {
+ *left = metrics->left;
+ }
+ if(right) {
+ *right = metrics->right;
+ }
+ if(top) {
+ *top = metrics->top;
+ }
+ if(bottom) {
+ *bottom = metrics->bottom;
+ }
+}
+
+static void SC_MeasureTextAlloc(RsAllocation va,
+ int32_t *left, int32_t *right,
+ int32_t *top, int32_t *bottom)
+{
+ CHECK_OBJ(va);
+ GET_TLS();
+ Allocation *alloc = static_cast<Allocation *>(va);
+ const char *text = (const char *)alloc->getPtr();
+ size_t textLen = alloc->getType()->getSizeBytes();
+ Font::Rect metrics;
+ rsc->mStateFont.measureText(text, textLen, &metrics);
+ SC_setMetrics(&metrics, left, right, top, bottom);
+}
+
+static void SC_MeasureText(const char *text,
+ int32_t *left, int32_t *right,
+ int32_t *top, int32_t *bottom)
+{
+ GET_TLS();
+ size_t textLen = strlen(text);
+ Font::Rect metrics;
+ rsc->mStateFont.measureText(text, textLen, &metrics);
+ SC_setMetrics(&metrics, left, right, top, bottom);
}
static void SC_BindFont(RsFont font)
@@ -432,6 +478,8 @@
{ "_Z11rsgDrawTextPKcii", (void *)&SC_DrawText },
{ "_Z11rsgDrawText13rs_allocationii", (void *)&SC_DrawTextAlloc },
+ { "_Z14rsgMeasureTextPKcPiS1_S1_S1_", (void *)&SC_MeasureText },
+ { "_Z14rsgMeasureText13rs_allocationPiS0_S0_S0_", (void *)&SC_MeasureTextAlloc },
{ "_Z11rsgBindFont7rs_font", (void *)&SC_BindFont },
{ "_Z12rsgFontColorffff", (void *)&SC_FontColor },
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index fc037a3..8cdb48a 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -274,6 +274,59 @@
return false;
}
+Type * Type::cloneAndResize1D(Context *rsc, uint32_t dimX) const
+{
+ TypeState * stc = &rsc->mStateType;
+ for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+ Type *t = stc->mTypes[ct];
+ if (t->getElement() != mElement.get()) continue;
+ if (t->getDimX() != dimX) continue;
+ if (t->getDimY() != mDimY) continue;
+ if (t->getDimZ() != mDimZ) continue;
+ if (t->getDimLOD() != mDimLOD) continue;
+ if (t->getDimFaces() != mFaces) continue;
+ t->incUserRef();
+ return t;
+ }
+
+ Type *nt = new Type(rsc);
+ nt->mElement.set(mElement);
+ nt->mDimX = dimX;
+ nt->mDimY = mDimY;
+ nt->mDimZ = mDimZ;
+ nt->mDimLOD = mDimLOD;
+ nt->mFaces = mFaces;
+ nt->compute();
+ return nt;
+}
+
+Type * Type::cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const
+{
+ TypeState * stc = &rsc->mStateType;
+ for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+ Type *t = stc->mTypes[ct];
+ if (t->getElement() != mElement.get()) continue;
+ if (t->getDimX() != dimX) continue;
+ if (t->getDimY() != dimY) continue;
+ if (t->getDimZ() != mDimZ) continue;
+ if (t->getDimLOD() != mDimLOD) continue;
+ if (t->getDimFaces() != mFaces) continue;
+ t->incUserRef();
+ return t;
+ }
+
+ Type *nt = new Type(rsc);
+ nt->mElement.set(mElement);
+ nt->mDimX = dimX;
+ nt->mDimY = dimY;
+ nt->mDimZ = mDimZ;
+ nt->mDimLOD = mDimLOD;
+ nt->mFaces = mFaces;
+ nt->compute();
+ return nt;
+}
+
+
//////////////////////////////////////////////////
//
namespace android {
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index 33faa87..b5548c0 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -79,6 +79,9 @@
bool isEqual(const Type *other) const;
+ Type * cloneAndResize1D(Context *rsc, uint32_t dimX) const;
+ Type * cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const;
+
protected:
struct LOD {
size_t mX;
diff --git a/libs/rs/rsVertexArray.h b/libs/rs/rsVertexArray.h
index bd76d87..dea7d41 100644
--- a/libs/rs/rsVertexArray.h
+++ b/libs/rs/rsVertexArray.h
@@ -62,7 +62,6 @@
}
void add(const Attrib &, uint32_t stride);
- //void addLegacy(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset);
void add(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset, const char *name);
void setupGL2(const Context *rsc, class VertexArrayState *, ShaderCache *) const;
@@ -89,7 +88,7 @@
}
}
-#endif //ANDROID_LIGHT_H
+#endif //ANDROID_VERTEX_ARRAY_H
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index c0b2d2d..ac6f8cc 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -79,6 +79,12 @@
rsgBindFont(rs_font);
extern void __attribute__((overloadable))
rsgFontColor(float, float, float, float);
+// Returns the bounding box of the text relative to (0, 0)
+// Any of left, right, top, bottom could be NULL
+extern void __attribute__((overloadable))
+ rsgMeasureText(const char *, int *left, int *right, int *top, int *bottom);
+extern void __attribute__((overloadable))
+ rsgMeasureText(rs_allocation, int *left, int *right, int *top, int *bottom);
extern void __attribute__((overloadable))
rsgMeshComputeBoundingBox(rs_mesh mesh, float *minX, float *minY, float *minZ,
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index bee86b2..9b1f82f 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -508,6 +508,36 @@
unsigned char lfhBuf[kLFHLen];
+#ifdef HAVE_PREAD
+ /*
+ * This file descriptor might be from zygote's preloaded assets,
+ * so we need to do an pread() instead of a lseek() + read() to
+ * guarantee atomicity across the processes with the shared file
+ * descriptors.
+ */
+ ssize_t actual =
+ TEMP_FAILURE_RETRY(pread(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset));
+
+ if (actual != sizeof(lfhBuf)) {
+ LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
+ return false;
+ }
+
+ if (get4LE(lfhBuf) != kLFHSignature) {
+ LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+ "got: data=0x%08lx\n",
+ localHdrOffset, kLFHSignature, get4LE(lfhBuf));
+ return false;
+ }
+#else /* HAVE_PREAD */
+ /*
+ * For hosts don't have pread() we cannot guarantee atomic reads from
+ * an offset in a file. Android should never run on those platforms.
+ * File descriptors inherited from a fork() share file offsets and
+ * there would be nothing to protect from two different processes
+ * calling lseek() concurrently.
+ */
+
{
AutoMutex _l(mFdLock);
@@ -517,7 +547,7 @@
}
ssize_t actual =
- TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
+ TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
if (actual != sizeof(lfhBuf)) {
LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
return false;
@@ -531,6 +561,7 @@
return false;
}
}
+#endif /* HAVE_PREAD */
off_t dataOffset = localHdrOffset + kLFHLen
+ get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java
index 8da7eaa..3ebad00 100755
--- a/media/java/android/media/videoeditor/AudioTrack.java
+++ b/media/java/android/media/videoeditor/AudioTrack.java
@@ -183,11 +183,13 @@
*/
@SuppressWarnings("unused")
private AudioTrack() throws IOException {
- this(null, null);
+ this(null, null, null);
}
/**
* Constructor
+ *
+ * @param editor The video editor reference
* @param audioTrackId The audio track id
* @param filename The absolute file name
*
@@ -195,7 +197,8 @@
* @throws IllegalArgumentException if file format is not supported or if
* the codec is not supported
*/
- public AudioTrack(String audioTrackId, String filename) throws IOException {
+ public AudioTrack(VideoEditor editor, String audioTrackId, String filename)
+ throws IOException {
mUniqueId = audioTrackId;
mFilename = filename;
mStartTimeMs = 0;
@@ -233,6 +236,7 @@
/**
* Constructor
*
+ * @param editor The video editor reference
* @param audioTrackId The audio track id
* @param filename The audio filename
* @param startTimeMs the start time in milliseconds (relative to the
@@ -248,8 +252,9 @@
*
* @throws IOException if file is not found
*/
- AudioTrack(String audioTrackId, String filename, long startTimeMs, long beginMs, long endMs,
- boolean loop, int volume, boolean muted, String audioWaveformFilename) throws IOException {
+ AudioTrack(VideoEditor editor, String audioTrackId, String filename, long startTimeMs,
+ long beginMs, long endMs, boolean loop, int volume, boolean muted,
+ String audioWaveformFilename) throws IOException {
mUniqueId = audioTrackId;
mFilename = filename;
mStartTimeMs = startTimeMs;
diff --git a/media/java/android/media/videoeditor/Effect.java b/media/java/android/media/videoeditor/Effect.java
index 038bfc2..f5e6a67 100755
--- a/media/java/android/media/videoeditor/Effect.java
+++ b/media/java/android/media/videoeditor/Effect.java
@@ -124,6 +124,23 @@
}
/**
+ * Set the start time and duration
+ *
+ * @param startTimeMs start time in milliseconds
+ * @param durationMs The duration in milliseconds
+ */
+ public void setStartTimeAndDuration(long startTimeMs, long durationMs) {
+ if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Invalid start time or duration");
+ }
+
+ mStartTimeMs = startTimeMs;
+ mDurationMs = durationMs;
+
+ mMediaItem.invalidateTransitions(this);
+ }
+
+ /**
* @return The media item owner
*/
public MediaItem getMediaItem() {
diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java
index ab23119..df3c5fb 100755
--- a/media/java/android/media/videoeditor/MediaImageItem.java
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -17,7 +17,6 @@
package android.media.videoeditor;
import java.io.IOException;
-import java.util.List;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -54,12 +53,13 @@
*/
@SuppressWarnings("unused")
private MediaImageItem() throws IOException {
- this(null, null, 0, RENDERING_MODE_BLACK_BORDER);
+ this(null, null, null, 0, RENDERING_MODE_BLACK_BORDER);
}
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The media item id
* @param filename The image file name
* @param durationMs The duration of the image on the storyboard
@@ -67,9 +67,10 @@
*
* @throws IOException
*/
- public MediaImageItem(String mediaItemId, String filename, long durationMs, int renderingMode)
+ public MediaImageItem(VideoEditor editor, String mediaItemId, String filename, long durationMs,
+ int renderingMode)
throws IOException {
- super(mediaItemId, filename, renderingMode);
+ super(editor, mediaItemId, filename, renderingMode);
// Determine the dimensions of the image
final BitmapFactory.Options dbo = new BitmapFactory.Options();
@@ -153,55 +154,16 @@
}
/**
- * This method will adjust the duration of bounding transitions if the
- * current duration of the transactions become greater than the maximum
- * allowable duration.
+ * This method will adjust the duration of bounding transitions, effects
+ * and overlays if the current duration of the transactions become greater
+ * than the maximum allowable duration.
*
* @param durationMs The duration of the image in the storyboard timeline
*/
public void setDuration(long durationMs) {
mDurationMs = durationMs;
- // Check if the duration of transitions need to be adjusted
- if (mBeginTransition != null) {
- final long maxDurationMs = mBeginTransition.getMaximumDuration();
- if (mBeginTransition.getDuration() > maxDurationMs) {
- mBeginTransition.setDuration(maxDurationMs);
- }
- }
-
- if (mEndTransition != null) {
- final long maxDurationMs = mEndTransition.getMaximumDuration();
- if (mEndTransition.getDuration() > maxDurationMs) {
- mEndTransition.setDuration(maxDurationMs);
- }
- }
-
- final List<Overlay> overlays = getAllOverlays();
- for (Overlay overlay : overlays) {
- // Adjust the start time if necessary
- if (overlay.getStartTime() < getTimelineDuration()) {
- overlay.setStartTime(0);
- }
-
- // Adjust the duration if necessary
- if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
- overlay.setDuration(getTimelineDuration() - overlay.getStartTime());
- }
- }
-
- final List<Effect> effects = getAllEffects();
- for (Effect effect : effects) {
- // Adjust the start time if necessary
- if (effect.getStartTime() < getTimelineDuration()) {
- effect.setStartTime(0);
- }
-
- // Adjust the duration if necessary
- if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
- effect.setDuration(getTimelineDuration() - effect.getStartTime());
- }
- }
+ adjustElementsDuration();
}
/*
diff --git a/media/java/android/media/videoeditor/MediaItem.java b/media/java/android/media/videoeditor/MediaItem.java
index f4651af..d9c38af 100755
--- a/media/java/android/media/videoeditor/MediaItem.java
+++ b/media/java/android/media/videoeditor/MediaItem.java
@@ -70,6 +70,7 @@
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The MediaItem id
* @param filename name of the media file.
* @param renderingMode The rendering mode
@@ -79,7 +80,8 @@
* supported the exception object contains the unsupported
* capability
*/
- protected MediaItem(String mediaItemId, String filename, int renderingMode) throws IOException {
+ protected MediaItem(VideoEditor editor, String mediaItemId, String filename,
+ int renderingMode) throws IOException {
mUniqueId = mediaItemId;
mFilename = filename;
mRenderingMode = renderingMode;
@@ -472,4 +474,73 @@
}
}
}
+
+ /**
+ * Adjust the duration of effects, overlays and transitions.
+ * This method will be called after a media item duration is changed.
+ */
+ protected void adjustElementsDuration() {
+ // Check if the duration of transitions need to be adjusted
+ if (mBeginTransition != null) {
+ final long maxDurationMs = mBeginTransition.getMaximumDuration();
+ if (mBeginTransition.getDuration() > maxDurationMs) {
+ mBeginTransition.setDuration(maxDurationMs);
+ }
+ }
+
+ if (mEndTransition != null) {
+ final long maxDurationMs = mEndTransition.getMaximumDuration();
+ if (mEndTransition.getDuration() > maxDurationMs) {
+ mEndTransition.setDuration(maxDurationMs);
+ }
+ }
+
+ final List<Overlay> overlays = getAllOverlays();
+ for (Overlay overlay : overlays) {
+ // Adjust the start time if necessary
+ final long overlayStartTimeMs;
+ if (overlay.getStartTime() > getTimelineDuration()) {
+ overlayStartTimeMs = 0;
+ } else {
+ overlayStartTimeMs = overlay.getStartTime();
+ }
+
+ // Adjust the duration if necessary
+ final long overlayDurationMs;
+ if (overlayStartTimeMs + overlay.getDuration() > getTimelineDuration()) {
+ overlayDurationMs = getTimelineDuration() - overlayStartTimeMs;
+ } else {
+ overlayDurationMs = overlay.getDuration();
+ }
+
+ if (overlayStartTimeMs != overlay.getStartTime() ||
+ overlayDurationMs != overlay.getDuration()) {
+ overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);
+ }
+ }
+
+ final List<Effect> effects = getAllEffects();
+ for (Effect effect : effects) {
+ // Adjust the start time if necessary
+ final long effectStartTimeMs;
+ if (effect.getStartTime() > getTimelineDuration()) {
+ effectStartTimeMs = 0;
+ } else {
+ effectStartTimeMs = effect.getStartTime();
+ }
+
+ // Adjust the duration if necessary
+ final long effectDurationMs;
+ if (effectStartTimeMs + effect.getDuration() > getTimelineDuration()) {
+ effectDurationMs = getTimelineDuration() - effectStartTimeMs;
+ } else {
+ effectDurationMs = effect.getDuration();
+ }
+
+ if (effectStartTimeMs != effect.getStartTime() ||
+ effectDurationMs != effect.getDuration()) {
+ effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);
+ }
+ }
+ }
}
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
index cc0a155..dd12336 100755
--- a/media/java/android/media/videoeditor/MediaVideoItem.java
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -17,7 +17,6 @@
package android.media.videoeditor;
import java.io.IOException;
-import java.util.List;
import android.graphics.Bitmap;
import android.util.Log;
@@ -193,26 +192,29 @@
*/
@SuppressWarnings("unused")
private MediaVideoItem() throws IOException {
- this(null, null, RENDERING_MODE_BLACK_BORDER);
+ this(null, null, null, RENDERING_MODE_BLACK_BORDER);
}
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The MediaItem id
* @param filename The image file name
* @param renderingMode The rendering mode
*
* @throws IOException if the file cannot be opened for reading
*/
- public MediaVideoItem(String mediaItemId, String filename, int renderingMode)
+ public MediaVideoItem(VideoEditor editor, String mediaItemId, String filename,
+ int renderingMode)
throws IOException {
- this(mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);
+ this(editor, mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);
}
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The MediaItem id
* @param filename The image file name
* @param renderingMode The rendering mode
@@ -227,10 +229,10 @@
*
* @throws IOException if the file cannot be opened for reading
*/
- MediaVideoItem(String mediaItemId, String filename, int renderingMode,
+ MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, int renderingMode,
long beginMs, long endMs, int volumePercent, boolean muted,
String audioWaveformFilename) throws IOException {
- super(mediaItemId, filename, renderingMode);
+ super(editor, mediaItemId, filename, renderingMode);
// TODO: Set these variables correctly
mWidth = 1080;
mHeight = 720;
@@ -257,9 +259,9 @@
/**
* Sets the start and end marks for trimming a video media item.
- * This method will adjust the duration of bounding transitions if the
- * current duration of the transactions become greater than the maximum
- * allowable duration.
+ * This method will adjust the duration of bounding transitions, effects
+ * and overlays if the current duration of the transactions become greater
+ * than the maximum allowable duration.
*
* @param beginMs Start time in milliseconds. Set to 0 to extract from the
* beginning
@@ -293,46 +295,7 @@
mBeginBoundaryTimeMs = beginMs;
mEndBoundaryTimeMs = endMs;
- // Check if the duration of transitions need to be adjusted
- if (mBeginTransition != null) {
- final long maxDurationMs = mBeginTransition.getMaximumDuration();
- if (mBeginTransition.getDuration() > maxDurationMs) {
- mBeginTransition.setDuration(maxDurationMs);
- }
- }
-
- if (mEndTransition != null) {
- final long maxDurationMs = mEndTransition.getMaximumDuration();
- if (mEndTransition.getDuration() > maxDurationMs) {
- mEndTransition.setDuration(maxDurationMs);
- }
- }
-
- final List<Overlay> overlays = getAllOverlays();
- for (Overlay overlay : overlays) {
- // Adjust the start time if necessary
- if (overlay.getStartTime() < mBeginBoundaryTimeMs) {
- overlay.setStartTime(mBeginBoundaryTimeMs);
- }
-
- // Adjust the duration if necessary
- if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
- overlay.setDuration(getTimelineDuration() - overlay.getStartTime());
- }
- }
-
- final List<Effect> effects = getAllEffects();
- for (Effect effect : effects) {
- // Adjust the start time if necessary
- if (effect.getStartTime() < mBeginBoundaryTimeMs) {
- effect.setStartTime(mBeginBoundaryTimeMs);
- }
-
- // Adjust the duration if necessary
- if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
- effect.setDuration(getTimelineDuration() - effect.getStartTime());
- }
- }
+ adjustElementsDuration();
}
/**
diff --git a/media/java/android/media/videoeditor/Overlay.java b/media/java/android/media/videoeditor/Overlay.java
index d9e7f85..c58b5cb 100755
--- a/media/java/android/media/videoeditor/Overlay.java
+++ b/media/java/android/media/videoeditor/Overlay.java
@@ -126,6 +126,23 @@
}
/**
+ * Set the start time and duration
+ *
+ * @param startTimeMs start time in milliseconds
+ * @param durationMs The duration in milliseconds
+ */
+ public void setStartTimeAndDuration(long startTimeMs, long durationMs) {
+ if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Invalid start time or duration");
+ }
+
+ mStartTimeMs = startTimeMs;
+ mDurationMs = durationMs;
+
+ mMediaItem.invalidateTransitions(this);
+ }
+
+ /**
* @return The media item owner
*/
public MediaItem getMediaItem() {
diff --git a/media/java/android/media/videoeditor/VideoEditorTestImpl.java b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
index b39d9d8..c3cb82a 100644
--- a/media/java/android/media/videoeditor/VideoEditorTestImpl.java
+++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
@@ -765,7 +765,7 @@
if (MediaImageItem.class.getSimpleName().equals(type)) {
final long durationMs = Long.parseLong(parser.getAttributeValue("",
ATTR_DURATION));
- currentMediaItem = new MediaImageItem(mediaItemId, filename,
+ currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
durationMs, renderingMode);
} else if (MediaVideoItem.class.getSimpleName().equals(type)) {
final long beginMs = Long.parseLong(parser.getAttributeValue("",
@@ -778,7 +778,7 @@
ATTR_MUTED));
final String audioWaveformFilename = parser.getAttributeValue("",
ATTR_AUDIO_WAVEFORM_FILENAME);
- currentMediaItem = new MediaVideoItem(mediaItemId, filename,
+ currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
renderingMode, beginMs, endMs, volume, muted,
audioWaveformFilename);
@@ -1017,7 +1017,7 @@
final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
try {
- final AudioTrack audioTrack = new AudioTrack(audioTrackId, filename, startTimeMs,
+ final AudioTrack audioTrack = new AudioTrack(this, audioTrackId, filename, startTimeMs,
beginMs, endMs, loop, volume, muted, waveformFilename);
return audioTrack;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index bf0c6e17..7a78185 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -966,6 +966,65 @@
}
}
+/*
+ * Check to see whether the requested video width and height is one
+ * of the supported sizes. It returns true if so; otherwise, it
+ * returns false.
+ */
+bool StagefrightRecorder::isVideoSizeSupported(
+ const Vector<Size>& supportedSizes) const {
+
+ LOGV("isVideoSizeSupported");
+ for (size_t i = 0; i < supportedSizes.size(); ++i) {
+ if (mVideoWidth == supportedSizes[i].width &&
+ mVideoHeight == supportedSizes[i].height) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * If the preview and video output is separate, we only set the
+ * the video size, and applications should set the preview size
+ * to some proper value, and the recording framework will not
+ * change the preview size; otherwise, if the video and preview
+ * output is the same, we need to set the preview to be the same
+ * as the requested video size.
+ *
+ * On return, it also returns whether the setVideoSize() is
+ * supported.
+ */
+status_t StagefrightRecorder::setCameraVideoSize(
+ CameraParameters* params,
+ bool* isSetVideoSizeSupported) {
+ LOGV("setCameraVideoSize: %dx%d", mVideoWidth, mVideoHeight);
+
+ // Check whether the requested video size is supported
+ Vector<Size> sizes;
+ params->getSupportedVideoSizes(sizes);
+ *isSetVideoSizeSupported = true;
+ if (sizes.size() == 0) {
+ LOGD("Camera does not support setVideoSize()");
+ params->getSupportedPreviewSizes(sizes);
+ *isSetVideoSizeSupported = false;
+ }
+ if (!isVideoSizeSupported(sizes)) {
+ LOGE("Camera does not support video size (%dx%d)!",
+ mVideoWidth, mVideoHeight);
+ return BAD_VALUE;
+ }
+
+ // Actually set the video size
+ if (isSetVideoSizeSupported) {
+ params->setVideoSize(mVideoWidth, mVideoHeight);
+ } else {
+ params->setPreviewSize(mVideoWidth, mVideoHeight);
+ }
+
+ return OK;
+}
+
status_t StagefrightRecorder::setupCamera() {
if (!mCaptureTimeLapse) {
// Dont clip for time lapse capture as encoder will have enough
@@ -993,8 +1052,11 @@
// dont change the preview size because time lapse may be using still camera
// as mVideoWidth, mVideoHeight may correspond to HD resolution not
// supported by the video camera.
+ bool isSetVideoSizeSupported = false;
if (!mCaptureTimeLapse) {
- params.setPreviewSize(mVideoWidth, mVideoHeight);
+ if (OK != setCameraVideoSize(¶ms, &isSetVideoSizeSupported)) {
+ return BAD_VALUE;
+ }
}
params.setPreviewFrameRate(mFrameRate);
@@ -1008,7 +1070,11 @@
// Check on video frame size
int frameWidth = 0, frameHeight = 0;
- newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+ if (isSetVideoSizeSupported) {
+ newCameraParams.getVideoSize(&frameWidth, &frameHeight);
+ } else {
+ newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+ }
if (!mCaptureTimeLapse &&
(frameWidth < 0 || frameWidth != mVideoWidth ||
frameHeight < 0 || frameHeight != mVideoHeight)) {
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 02d9a01..f14c704 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -19,6 +19,7 @@
#define STAGEFRIGHT_RECORDER_H_
#include <media/MediaRecorderBase.h>
+#include <camera/CameraParameters.h>
#include <utils/String8.h>
namespace android {
@@ -125,6 +126,9 @@
status_t startRTPRecording();
sp<MediaSource> createAudioSource();
status_t setupCamera();
+ bool isVideoSizeSupported(const Vector<Size>& supportedSizes) const;
+ status_t setCameraVideoSize(CameraParameters* params,
+ bool *isSetVideoSizeSupported);
status_t setupCameraSource(sp<CameraSource> *cameraSource);
status_t setupAudioEncoder(const sp<MediaWriter>& writer);
status_t setupVideoEncoder(
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 546df47..90b1aab 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -40,6 +40,7 @@
static const int64_t kMax32BitFileSize = 0x007fffffffLL;
static const uint8_t kNalUnitTypeSeqParamSet = 0x07;
static const uint8_t kNalUnitTypePicParamSet = 0x08;
+static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 10000000LL; // 10s
class MPEG4Writer::Track {
public:
@@ -148,6 +149,28 @@
int64_t mPreviousTrackTimeUs;
int64_t mTrackEveryTimeDurationUs;
+ // Has the media time adjustment for video started?
+ bool mIsMediaTimeAdjustmentOn;
+ // The time stamp when previous media time adjustment period starts
+ int64_t mPrevMediaTimeAdjustTimestampUs;
+ // Number of vidoe frames whose time stamp may be adjusted
+ int64_t mMediaTimeAdjustNumFrames;
+ // The sample number when previous meida time adjustmnet period starts
+ int64_t mPrevMediaTimeAdjustSample;
+ // The total accumulated drift time within a period of
+ // kVideoMediaTimeAdjustPeriodTimeUs.
+ int64_t mTotalDriftTimeToAdjustUs;
+ // The total accumalated drift time since the start of the recording
+ // excluding the current time adjustment period
+ int64_t mPrevTotalAccumDriftTimeUs;
+
+ // Update the audio track's drift information.
+ void updateDriftTime(const sp<MetaData>& meta);
+
+ // Adjust the time stamp of the video track according to
+ // the drift time information from the audio track.
+ void adjustMediaTime(int64_t *timestampUs);
+
static void *ThreadWrapper(void *me);
status_t threadEntry();
@@ -319,7 +342,7 @@
size = MAX_MOOV_BOX_SIZE;
}
- LOGI("limits: %lld/%lld bytes/us, bit rate: %d bps and the estimated"
+ LOGV("limits: %lld/%lld bytes/us, bit rate: %d bps and the estimated"
" moov size %lld bytes",
mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size);
return factor * size;
@@ -346,7 +369,7 @@
// If file size is set to be larger than the 32 bit file
// size limit, treat it as an error.
if (mMaxFileSizeLimitBytes > kMax32BitFileSize) {
- LOGW("32-bi file size limit (%lld bytes) too big. "
+ LOGW("32-bit file size limit (%lld bytes) too big. "
"It is changed to %lld bytes",
mMaxFileSizeLimitBytes, kMax32BitFileSize);
mMaxFileSizeLimitBytes = kMax32BitFileSize;
@@ -1149,6 +1172,12 @@
mNumStscTableEntries = 0;
mNumSttsTableEntries = 0;
mMdatSizeBytes = 0;
+ mIsMediaTimeAdjustmentOn = false;
+ mPrevMediaTimeAdjustTimestampUs = 0;
+ mMediaTimeAdjustNumFrames = 0;
+ mPrevMediaTimeAdjustSample = 0;
+ mTotalDriftTimeToAdjustUs = 0;
+ mPrevTotalAccumDriftTimeUs = 0;
pthread_create(&mThread, &attr, ThreadWrapper, this);
pthread_attr_destroy(&attr);
@@ -1437,6 +1466,145 @@
return OK;
}
+/*
+* The video track's media time adjustment for real-time applications
+* is described as follows:
+*
+* First, the media time adjustment is done for every period of
+* kVideoMediaTimeAdjustPeriodTimeUs. kVideoMediaTimeAdjustPeriodTimeUs
+* is currently a fixed value chosen heuristically. The value of
+* kVideoMediaTimeAdjustPeriodTimeUs should not be very large or very small
+* for two considerations: on one hand, a relatively large value
+* helps reduce large fluctuation of drift time in the audio encoding
+* path; while on the other hand, a relatively small value helps keep
+* restoring synchronization in audio/video more frequently. Note for the
+* very first period of kVideoMediaTimeAdjustPeriodTimeUs, there is
+* no media time adjustment for the video track.
+*
+* Second, the total accumulated audio track time drift found
+* in a period of kVideoMediaTimeAdjustPeriodTimeUs is distributed
+* over a stream of incoming video frames. The number of video frames
+* affected is determined based on the number of recorded video frames
+* within the past kVideoMediaTimeAdjustPeriodTimeUs period.
+* We choose to distribute the drift time over only a portion
+* (rather than all) of the total number of recorded video frames
+* in order to make sure that the video track media time adjustment is
+* completed for the current period before the next video track media
+* time adjustment period starts. Currently, the portion chosen is a
+* half (0.5).
+*
+* Last, various additional checks are performed to ensure that
+* the actual audio encoding path does not have too much drift.
+* In particular, 1) we want to limit the average incremental time
+* adjustment for each video frame to be less than a threshold
+* for a single period of kVideoMediaTimeAdjustPeriodTimeUs.
+* Currently, the threshold is set to 5 ms. If the average incremental
+* media time adjustment for a video frame is larger than the
+* threshold, the audio encoding path has too much time drift.
+* 2) We also want to limit the total time drift in the audio
+* encoding path to be less than a threshold for a period of
+* kVideoMediaTimeAdjustPeriodTimeUs. Currently, the threshold
+* is 0.5% of kVideoMediaTimeAdjustPeriodTimeUs. If the time drift of
+* the audio encoding path is larger than the threshold, the audio
+* encoding path has too much time drift. We treat the large time
+* drift of the audio encoding path as errors, since there is no
+* way to keep audio/video in synchronization for real-time
+* applications if the time drift is too large unless we drop some
+* video frames, which has its own problems that we don't want
+* to get into for the time being.
+*/
+void MPEG4Writer::Track::adjustMediaTime(int64_t *timestampUs) {
+ if (*timestampUs - mPrevMediaTimeAdjustTimestampUs >=
+ kVideoMediaTimeAdjustPeriodTimeUs) {
+
+ LOGV("New media time adjustment period at %lld us", *timestampUs);
+ mIsMediaTimeAdjustmentOn = true;
+ mMediaTimeAdjustNumFrames =
+ (mNumSamples - mPrevMediaTimeAdjustSample) >> 1;
+
+ mPrevMediaTimeAdjustTimestampUs = *timestampUs;
+ mPrevMediaTimeAdjustSample = mNumSamples;
+ int64_t totalAccumDriftTimeUs = mOwner->getDriftTimeUs();
+ mTotalDriftTimeToAdjustUs =
+ totalAccumDriftTimeUs - mPrevTotalAccumDriftTimeUs;
+
+ mPrevTotalAccumDriftTimeUs = totalAccumDriftTimeUs;
+
+ // Check on incremental adjusted time per frame
+ int64_t adjustTimePerFrameUs =
+ mTotalDriftTimeToAdjustUs / mMediaTimeAdjustNumFrames;
+
+ if (adjustTimePerFrameUs < 0) {
+ adjustTimePerFrameUs = -adjustTimePerFrameUs;
+ }
+ if (adjustTimePerFrameUs >= 5000) {
+ LOGE("Adjusted time per video frame is %lld us",
+ adjustTimePerFrameUs);
+ CHECK(!"Video frame time adjustment is too large!");
+ }
+
+ // Check on total accumulated time drift within a period of
+ // kVideoMediaTimeAdjustPeriodTimeUs.
+ int64_t driftPercentage = (mTotalDriftTimeToAdjustUs * 1000)
+ / kVideoMediaTimeAdjustPeriodTimeUs;
+
+ if (driftPercentage < 0) {
+ driftPercentage = -driftPercentage;
+ }
+ if (driftPercentage > 5) {
+ LOGE("Audio track has time drift %lld us over %lld us",
+ mTotalDriftTimeToAdjustUs,
+ kVideoMediaTimeAdjustPeriodTimeUs);
+
+ CHECK(!"The audio track media time drifts too much!");
+ }
+
+ }
+
+ if (mIsMediaTimeAdjustmentOn) {
+ if (mNumSamples - mPrevMediaTimeAdjustSample <=
+ mMediaTimeAdjustNumFrames) {
+
+ // Do media time incremental adjustment
+ int64_t incrementalAdjustTimeUs =
+ (mTotalDriftTimeToAdjustUs *
+ (mNumSamples - mPrevMediaTimeAdjustSample))
+ / mMediaTimeAdjustNumFrames;
+
+ *timestampUs +=
+ (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs);
+
+ LOGV("Incremental video frame media time adjustment: %lld us",
+ (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs));
+ } else {
+ // Within the remaining adjustment period,
+ // no incremental adjustment is needed.
+ *timestampUs +=
+ (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs);
+
+ LOGV("Fixed video frame media time adjustment: %lld us",
+ (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs));
+ }
+ }
+}
+
+/*
+ * Updates the drift time from the audio track so that
+ * the video track can get the updated drift time information
+ * from the file writer. The fluctuation of the drift time of the audio
+ * encoding path is smoothed out with a simple filter by giving a larger
+ * weight to more recently drift time. The filter coefficients, 0.5 and 0.5,
+ * are heuristically determined.
+ */
+void MPEG4Writer::Track::updateDriftTime(const sp<MetaData>& meta) {
+ int64_t driftTimeUs = 0;
+ if (meta->findInt64(kKeyDriftTime, &driftTimeUs)) {
+ int64_t prevDriftTimeUs = mOwner->getDriftTimeUs();
+ int64_t timeUs = (driftTimeUs + prevDriftTimeUs) >> 1;
+ mOwner->setDriftTimeUs(timeUs);
+ }
+}
+
status_t MPEG4Writer::Track::threadEntry() {
int32_t count = 0;
const int64_t interleaveDurationUs = mOwner->interleaveDuration();
@@ -1587,24 +1755,16 @@
timestampUs -= previousPausedDurationUs;
CHECK(timestampUs >= 0);
- if (mIsRealTimeRecording && !mIsAudio) {
- // The minor adjustment on the timestamp is heuristic/experimental
- // We are adjusting the timestamp to reduce the fluctuation of the duration
- // of neighboring samples. This in turn helps reduce the track header size,
- // especially, the number of entries in the "stts" box.
- if (mNumSamples > 1) {
- int64_t currDriftTimeUs = mOwner->getDriftTimeUs();
- int64_t durationUs = timestampUs + currDriftTimeUs - lastTimestampUs;
- int64_t diffUs = (durationUs > lastDurationUs)
- ? durationUs - lastDurationUs
- : lastDurationUs - durationUs;
- if (diffUs <= 5000) { // XXX: Magic number 5ms
- timestampUs = lastTimestampUs + lastDurationUs;
- } else {
- timestampUs += currDriftTimeUs;
- }
+
+ // Media time adjustment for real-time applications
+ if (mIsRealTimeRecording) {
+ if (mIsAudio) {
+ updateDriftTime(meta_data);
+ } else {
+ adjustMediaTime(×tampUs);
}
}
+
CHECK(timestampUs >= 0);
if (mNumSamples > 1) {
if (timestampUs <= lastTimestampUs) {
@@ -1656,12 +1816,6 @@
lastDurationUs = timestampUs - lastTimestampUs;
lastDurationTicks = currDurationTicks;
lastTimestampUs = timestampUs;
- if (mIsRealTimeRecording && mIsAudio) {
- int64_t driftTimeUs = 0;
- if (meta_data->findInt64(kKeyDriftTime, &driftTimeUs)) {
- mOwner->setDriftTimeUs(driftTimeUs);
- }
- }
if (isSync != 0) {
addOneStssTableEntry(mNumSamples);
@@ -1735,6 +1889,9 @@
mReachedEOS = true;
LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames. - %s",
count, nZeroLengthFrames, mNumSamples, mIsAudio? "audio": "video");
+ if (mIsAudio) {
+ LOGI("Audio track drift time: %lld us", mOwner->getDriftTimeUs());
+ }
if (err == ERROR_END_OF_STREAM) {
return OK;
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png b/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png
index d894f7b..e74c22f 100644
--- a/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png
+++ b/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
index 3e317dd..b16e436 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
index 2f66b1d..b16e436 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
index 72329f8..1b6ed74 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
index 72329f8..43e35d3 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
index 558c49c..498adbb 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
index 558c49c..b7e42a0 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
index 6440bdd..959fc5c 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
index 6440bdd..f905979 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
index fe20423..6e8e73c 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
index fe20423..b5799c8 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c70f5d4..33685ba 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -296,7 +296,8 @@
int mLandscapeRotation = -1; // default landscape rotation
int mSeascapeRotation = -1; // "other" landscape rotation, 180 degrees from mLandscapeRotation
- int mPortraitRotation = -1;
+ int mPortraitRotation = -1; // default portrait rotation
+ int mUpsideDownRotation = -1; // "other" portrait rotation
// Nothing to see here, move along...
int mFancyRotationAnimation;
@@ -364,26 +365,25 @@
boolean useSensorForOrientationLp(int appOrientation) {
// The app says use the sensor.
- if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
+ if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
return true;
}
// The user preference says we can rotate, and the app is willing to rotate.
- // Note we include SCREEN_ORIENTATION_LANDSCAPE since we can use the sensor to choose
- // between the two possible landscape rotations.
if (mAccelerometerDefault != 0 &&
(appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)) {
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)) {
return true;
}
- // We're in a dock that has a rotation affinity, an the app is willing to rotate.
+ // We're in a dock that has a rotation affinity, and the app is willing to rotate.
if ((mCarDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_CAR)
|| (mDeskDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_DESK)) {
// Note we override the nosensor flag here.
if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
|| appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
return true;
}
}
@@ -397,7 +397,10 @@
* screen is switched off.
*/
boolean needSensorRunningLp() {
- if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
+ if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
// If the application has explicitly requested to follow the
// orientation, then we need to turn the sensor or.
return true;
@@ -2133,21 +2136,42 @@
if (d.getWidth() > d.getHeight()) {
mPortraitRotation = Surface.ROTATION_90;
mLandscapeRotation = Surface.ROTATION_0;
+ mUpsideDownRotation = Surface.ROTATION_270;
mSeascapeRotation = Surface.ROTATION_180;
} else {
mPortraitRotation = Surface.ROTATION_0;
mLandscapeRotation = Surface.ROTATION_90;
+ mUpsideDownRotation = Surface.ROTATION_180;
mSeascapeRotation = Surface.ROTATION_270;
}
}
synchronized (mLock) {
- if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
- //always return portrait if orientation set to portrait
- return mPortraitRotation;
- } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
- return getCurrentLandscapeRotation(lastRotation);
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+ //always return portrait if orientation set to portrait
+ return mPortraitRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+ //always return landscape if orientation set to landscape
+ return mLandscapeRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ //always return portrait if orientation set to portrait
+ return mUpsideDownRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ //always return seascape if orientation set to reverse landscape
+ return mSeascapeRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ //return either landscape rotation based on the sensor
+ mOrientationListener.setAllow180Rotation(false);
+ return getCurrentLandscapeRotation(lastRotation);
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ mOrientationListener.setAllow180Rotation(true);
+ return getCurrentPortraitRotation(lastRotation);
}
+
+ mOrientationListener.setAllow180Rotation(
+ orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+
// case for nosensor meaning ignore sensor and consider only lid
// or orientation sensor disabled
//or case.unspecified
@@ -2167,18 +2191,15 @@
}
private int getCurrentLandscapeRotation(int lastRotation) {
- // landscape-only apps can take either landscape rotation
- if (useSensorForOrientationLp(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)) {
- int sensorRotation = mOrientationListener.getCurrentRotation(lastRotation);
- if (isLandscapeOrSeascape(sensorRotation)) {
- return sensorRotation;
- }
+ int sensorRotation = mOrientationListener.getCurrentRotation(lastRotation);
+ if (isLandscapeOrSeascape(sensorRotation)) {
+ return sensorRotation;
}
// try to preserve the old rotation if it was landscape
if (isLandscapeOrSeascape(lastRotation)) {
return lastRotation;
}
- // default to one of the two landscape rotations
+ // default to one of the primary landscape rotation
return mLandscapeRotation;
}
@@ -2186,6 +2207,23 @@
return sensorRotation == mLandscapeRotation || sensorRotation == mSeascapeRotation;
}
+ private int getCurrentPortraitRotation(int lastRotation) {
+ int sensorRotation = mOrientationListener.getCurrentRotation(lastRotation);
+ if (isAnyPortrait(sensorRotation)) {
+ return sensorRotation;
+ }
+ // try to preserve the old rotation if it was portrait
+ if (isAnyPortrait(lastRotation)) {
+ return lastRotation;
+ }
+ // default to one of the primary portrait rotations
+ return mPortraitRotation;
+ }
+
+ private boolean isAnyPortrait(int sensorRotation) {
+ return sensorRotation == mPortraitRotation || sensorRotation == mUpsideDownRotation;
+ }
+
public boolean detectSafeMode() {
try {
int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU);
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 308c9c0..bdf313c 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,32 +16,81 @@
package com.android.server;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.HashSet;
/**
* Implementation of the clipboard for copy and paste.
*/
public class ClipboardService extends IClipboard.Stub {
- private ClipData mPrimaryClip;
+ private final Context mContext;
+ private final IActivityManager mAm;
+ private final PackageManager mPm;
+ private final IBinder mPermissionOwner;
+
private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
= new RemoteCallbackList<IOnPrimaryClipChangedListener>();
+ private ClipData mPrimaryClip;
+
+ private final HashSet<String> mActivePermissionOwners
+ = new HashSet<String>();
+
/**
* Instantiates the clipboard.
*/
- public ClipboardService(Context context) { }
+ public ClipboardService(Context context) {
+ mContext = context;
+ mAm = ActivityManagerNative.getDefault();
+ mPm = context.getPackageManager();
+ IBinder permOwner = null;
+ try {
+ permOwner = mAm.newUriPermissionOwner("clipboard");
+ } catch (RemoteException e) {
+ Slog.w("clipboard", "AM dead", e);
+ }
+ mPermissionOwner = permOwner;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ Slog.w("clipboard", "Exception: ", e);
+ throw e;
+ }
+
+ }
public void setPrimaryClip(ClipData clip) {
synchronized (this) {
if (clip != null && clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
+ checkDataOwnerLocked(clip, Binder.getCallingUid());
+ clearActiveOwnersLocked();
mPrimaryClip = clip;
final int n = mPrimaryClipListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
@@ -57,8 +106,9 @@
}
}
- public ClipData getPrimaryClip() {
+ public ClipData getPrimaryClip(String pkg) {
synchronized (this) {
+ addActiveOwnerLocked(Binder.getCallingUid(), pkg);
return mPrimaryClip;
}
}
@@ -96,4 +146,110 @@
return false;
}
}
+
+ private final void checkUriOwnerLocked(Uri uri, int uid) {
+ if (!"content".equals(uri.getScheme())) {
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ boolean allowed = false;
+ try {
+ // This will throw SecurityException for us.
+ mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
+ if (item.getUri() != null) {
+ checkUriOwnerLocked(item.getUri(), uid);
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ checkUriOwnerLocked(intent.getData(), uid);
+ }
+ }
+
+ private final void checkDataOwnerLocked(ClipData data, int uid) {
+ final int N = data.getItemCount();
+ for (int i=0; i<N; i++) {
+ checkItemOwnerLocked(data.getItem(i), uid);
+ }
+ }
+
+ private final void grantUriLocked(Uri uri, String pkg) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void grantItemLocked(ClipData.Item item, String pkg) {
+ if (item.getUri() != null) {
+ grantUriLocked(item.getUri(), pkg);
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ grantUriLocked(intent.getData(), pkg);
+ }
+ }
+
+ private final void addActiveOwnerLocked(int uid, String pkg) {
+ PackageInfo pi;
+ try {
+ pi = mPm.getPackageInfo(pkg, 0);
+ if (pi.applicationInfo.uid != uid) {
+ throw new SecurityException("Calling uid " + uid
+ + " does not own package " + pkg);
+ }
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown package " + pkg, e);
+ }
+ if (!mActivePermissionOwners.contains(pkg)) {
+ final int N = mPrimaryClip.getItemCount();
+ for (int i=0; i<N; i++) {
+ grantItemLocked(mPrimaryClip.getItem(i), pkg);
+ }
+ mActivePermissionOwners.add(pkg);
+ }
+ }
+
+ private final void revokeUriLocked(Uri uri) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void revokeItemLocked(ClipData.Item item) {
+ if (item.getUri() != null) {
+ revokeUriLocked(item.getUri());
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ revokeUriLocked(intent.getData());
+ }
+ }
+
+ private final void clearActiveOwnersLocked() {
+ mActivePermissionOwners.clear();
+ if (mPrimaryClip == null) {
+ return;
+ }
+ final int N = mPrimaryClip.getItemCount();
+ for (int i=0; i<N; i++) {
+ revokeItemLocked(mPrimaryClip.getItem(i));
+ }
+ }
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 880befd..6095117 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -68,7 +68,7 @@
*/
public class ConnectivityService extends IConnectivityManager.Stub {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "ConnectivityService";
// how long to wait before switching back to a radio's default network
diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index a33b7c294..2b4845b 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -584,9 +584,9 @@
}
if (linkProperties != null) {
intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties);
- NetworkInterface iface = linkProperties.getInterface();
+ String iface = linkProperties.getInterfaceName();
if (iface != null) {
- intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface.getName());
+ intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface);
}
}
if (linkCapabilities != null) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 19f56a8..540c7fe 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -881,7 +881,6 @@
* the already-set timer.
*/
int pluggedType = intent.getIntExtra("plugged", 0);
- Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType);
if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
!shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
long triggerTime = System.currentTimeMillis() + idleMillis;
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index ba3897d..30aed69 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -3387,11 +3387,8 @@
}
// If this application has requested an explicit orientation,
// then use it.
- if (or == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ||
- or == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||
- or == ActivityInfo.SCREEN_ORIENTATION_SENSOR ||
- or == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR ||
- or == ActivityInfo.SCREEN_ORIENTATION_USER) {
+ if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
return or;
}
findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 1eab7fc7..d008c90 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4350,8 +4350,10 @@
return -1;
}
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Checking grant " + targetPkg + " permission to " + uri);
+ if (targetPkg != null) {
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Checking grant " + targetPkg + " permission to " + uri);
+ }
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -4380,23 +4382,45 @@
}
int targetUid;
- try {
- targetUid = pm.getPackageUid(targetPkg);
- if (targetUid < 0) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Can't grant URI permission no uid for: " + targetPkg);
+ if (targetPkg != null) {
+ try {
+ targetUid = pm.getPackageUid(targetPkg);
+ if (targetUid < 0) {
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Can't grant URI permission no uid for: " + targetPkg);
+ return -1;
+ }
+ } catch (RemoteException ex) {
return -1;
}
- } catch (RemoteException ex) {
- return -1;
+ } else {
+ targetUid = -1;
}
- // First... does the target actually need this permission?
- if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
- // No need to grant the target this permission.
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Target " + targetPkg + " already has full permission to " + uri);
- return -1;
+ if (targetUid >= 0) {
+ // First... does the target actually need this permission?
+ if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+ // No need to grant the target this permission.
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Target " + targetPkg + " already has full permission to " + uri);
+ return -1;
+ }
+ } else {
+ // First... there is no target package, so can anyone access it?
+ boolean allowed = pi.exported;
+ if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ if (pi.readPermission != null) {
+ allowed = false;
+ }
+ }
+ if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ if (pi.writePermission != null) {
+ allowed = false;
+ }
+ }
+ if (allowed) {
+ return -1;
+ }
}
// Second... is the provider allowing granting of URI permissions?
@@ -4426,16 +4450,25 @@
// Third... does the caller itself have permission to access
// this uri?
- if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
- if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
- throw new SecurityException("Uid " + callingUid
- + " does not have permission to uri " + uri);
+ if (callingUid != Process.myUid()) {
+ if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+ if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
+ throw new SecurityException("Uid " + callingUid
+ + " does not have permission to uri " + uri);
+ }
}
}
return targetUid;
}
+ public int checkGrantUriPermission(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) {
+ synchronized(this) {
+ return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
+ }
+ }
+
void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg,
Uri uri, int modeFlags, UriPermissionOwner owner) {
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -4478,6 +4511,10 @@
void grantUriPermissionLocked(int callingUid,
String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+ if (targetPkg == null) {
+ throw new NullPointerException("targetPkg");
+ }
+
int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
if (targetUid < 0) {
return;
@@ -4496,6 +4533,10 @@
+ " from " + intent + "; flags=0x"
+ Integer.toHexString(intent != null ? intent.getFlags() : 0));
+ if (targetPkg == null) {
+ throw new NullPointerException("targetPkg");
+ }
+
if (intent == null) {
return -1;
}
diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java
index 99c82e6..68a2e0f 100644
--- a/services/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/java/com/android/server/am/UriPermissionOwner.java
@@ -45,7 +45,7 @@
}
Binder getExternalTokenLocked() {
- if (externalToken != null) {
+ if (externalToken == null) {
externalToken = new ExternalToken();
}
return externalToken;
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 3030481..185d413 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -21,6 +21,7 @@
import com.android.internal.util.HierarchicalState;
import com.android.internal.util.HierarchicalStateMachine;
+import android.net.LinkAddress;
import android.net.LinkCapabilities;
import android.net.LinkProperties;
import android.os.AsyncResult;
@@ -29,10 +30,10 @@
import android.util.EventLog;
import java.net.InetAddress;
+import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
-import java.util.HashMap;
/**
* {@hide}
@@ -68,7 +69,7 @@
* EVENT_GET_LAST_FAIL_DONE,
* EVENT_DEACTIVATE_DONE.
* }
- * ++ # mInactiveState
+ * ++ # mInactiveState
* e(doNotifications)
* x(clearNotifications) {
* EVENT_RESET { notifiyDisconnectCompleted }.
@@ -428,26 +429,25 @@
try {
String prefix = "net." + interfaceName + ".";
- linkProperties.setInterface(NetworkInterface.getByName(interfaceName));
+ NetworkInterface networkInterface = NetworkInterface.getByName(interfaceName);
+ linkProperties.setInterfaceName(interfaceName);
// TODO: Get gateway and dns via RIL interface not property?
String gatewayAddress = SystemProperties.get(prefix + "gw");
linkProperties.setGateway(InetAddress.getByName(gatewayAddress));
- if (response.length > 2) {
- String ipAddress = response[2];
- linkProperties.addAddress(InetAddress.getByName(ipAddress));
-
- // TODO: Get gateway and dns via RIL interface not property?
- String dnsServers[] = new String[2];
- dnsServers[0] = SystemProperties.get(prefix + "dns1");
- dnsServers[1] = SystemProperties.get(prefix + "dns2");
- if (isDnsOk(dnsServers)) {
- linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
- linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
- } else {
- result = SetupResult.ERR_BadDns;
- }
+ for (InterfaceAddress addr : networkInterface.getInterfaceAddresses()) {
+ linkProperties.addLinkAddress(new LinkAddress(addr));
+ }
+ // TODO: Get gateway and dns via RIL interface not property?
+ String dnsServers[] = new String[2];
+ dnsServers[0] = SystemProperties.get(prefix + "dns1");
+ dnsServers[1] = SystemProperties.get(prefix + "dns2");
+ if (isDnsOk(dnsServers)) {
+ linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
+ linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
+ } else {
+ result = SetupResult.ERR_BadDns;
}
} catch (UnknownHostException e1) {
log("onSetupCompleted: UnknowHostException " + e1);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
index 4430533..0dc836d 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -101,6 +101,8 @@
mSmallPaint.setAntiAlias(false);
canvas.drawLine(0.0f, 0.0f, 400.0f, 0.0f, mSmallPaint);
mSmallPaint.setAntiAlias(true);
+ canvas.drawLine(0.0f, 0.0f, 0.0f, 400.0f, mSmallPaint);
+ canvas.drawLine(0.0f, 400.0f, 400.0f, 400.0f, mSmallPaint);
canvas.translate(120.0f, 0.0f);
mAlphaPaint.setShader(mShader);
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 7ea4872..9634157 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -20,19 +20,24 @@
import android.content.Context;
import android.content.Intent;
import android.net.DhcpInfo;
+import android.net.ProxyProperties;
import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.ProxySettings;
import android.net.wifi.WifiConfiguration.Status;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
-import java.io.BufferedWriter;
-import java.io.File;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.FileInputStream;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
@@ -46,8 +51,31 @@
* It deals with the following
* - Add/update/remove a WifiConfiguration
* The configuration contains two types of information.
- * = IP configuration that is handled by WifiConfigStore and
+ * = IP and proxy configuration that is handled by WifiConfigStore and
* is saved to disk on any change.
+ *
+ * The format of configuration file is as follows:
+ * <version>
+ * <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
+ * <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
+ * ..
+ *
+ * (key, value) pairs for a given network are grouped together and can
+ * be in any order. A "EOS" at the end of a set of (key, value) pairs
+ * indicates that the next set of (key, value) pairs are for a new
+ * network. A network is identified by a unique "id". If there is no
+ * "id" key in the (key, value) pairs, the data is discarded. An IP
+ * configuration includes the keys - "ipAssignment", "ipAddress", "gateway",
+ * "netmask", "dns1" and "dns2". A proxy configuration includes "proxySettings",
+ * "proxyHost", "proxyPort" and "exclusionList"
+ *
+ * An invalid version on read would result in discarding the contents of
+ * the file. On the next write, the latest version is written to file.
+ *
+ * Any failures during read or write to the configuration file are ignored
+ * without reporting to the user since the likelihood of these errors are
+ * low and the impact on connectivity is low.
+ *
* = SSID & security details that is pushed to the supplicant.
* supplicant saves these details to the disk on calling
* saveConfigCommand().
@@ -59,10 +87,9 @@
* to the disk. (TODO: deprecate these calls in WifiManager)
* > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
* These calls persist the supplicant config to disk.
+ *
* - Maintain a list of configured networks for quick access
*
- * TODO:
- * - handle proxy per configuration
*/
class WifiConfigStore {
@@ -110,7 +137,7 @@
List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
synchronized (sConfiguredNetworks) {
for(WifiConfiguration config : sConfiguredNetworks.values()) {
- networks.add(config.clone());
+ networks.add(new WifiConfiguration(config));
}
}
return networks;
@@ -229,6 +256,7 @@
synchronized (sConfiguredNetworks) {
sConfiguredNetworks.remove(netId);
}
+ writeIpAndProxyConfigurations();
sendConfigChangeBroadcast();
} else {
Log.e(TAG, "Failed to remove network " + netId);
@@ -353,6 +381,19 @@
}
/**
+ * Fetch the proxy properties for a given network id
+ */
+ static ProxyProperties getProxyProperties(int netId) {
+ synchronized (sConfiguredNetworks) {
+ WifiConfiguration config = sConfiguredNetworks.get(netId);
+ if (config != null && config.proxySettings == ProxySettings.STATIC) {
+ return new ProxyProperties(config.proxyProperties);
+ }
+ }
+ return null;
+ }
+
+ /**
* Return if the specified network is using static IP
*/
static boolean isUsingStaticIp(int netId) {
@@ -411,7 +452,7 @@
sNetworkIds.put(configKey(config), config.networkId);
}
}
- readIpConfigurations();
+ readIpAndProxyConfigurations();
sendConfigChangeBroadcast();
}
@@ -430,38 +471,89 @@
markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
}
- private static void writeIpConfigurations() {
- StringBuilder builder = new StringBuilder();
- BufferedWriter out = null;
+ private static void writeIpAndProxyConfigurations() {
- builder.append(IPCONFIG_FILE_VERSION);
- builder.append("\n");
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(ipConfigFile)));
- synchronized (sConfiguredNetworks) {
- for(WifiConfiguration config : sConfiguredNetworks.values()) {
- if (config.ipAssignment == WifiConfiguration.IpAssignment.STATIC) {
- builder.append("id=" + configKey(config));
- builder.append(":");
- builder.append("ip=" + config.ipConfig.ipAddress);
- builder.append(":");
- builder.append("gateway=" + config.ipConfig.gateway);
- builder.append(":");
- builder.append("netmask=" + config.ipConfig.netmask);
- builder.append(":");
- builder.append("dns1=" + config.ipConfig.dns1);
- builder.append(":");
- builder.append("dns2=" + config.ipConfig.dns2);
- builder.append("\n");
+ out.writeInt(IPCONFIG_FILE_VERSION);
+
+ synchronized (sConfiguredNetworks) {
+ for(WifiConfiguration config : sConfiguredNetworks.values()) {
+ boolean writeToFile = false;
+
+ switch (config.ipAssignment) {
+ case STATIC:
+ out.writeUTF("ipAssignment");
+ out.writeUTF(config.ipAssignment.toString());
+ out.writeUTF("ipAddress");
+ out.writeInt(config.ipConfig.ipAddress);
+ out.writeUTF("gateway");
+ out.writeInt(config.ipConfig.gateway);
+ out.writeUTF("netmask");
+ out.writeInt(config.ipConfig.netmask);
+ out.writeUTF("dns1");
+ out.writeInt(config.ipConfig.dns1);
+ out.writeUTF("dns2");
+ out.writeInt(config.ipConfig.dns2);
+ writeToFile = true;
+ break;
+ case DHCP:
+ out.writeUTF("ipAssignment");
+ out.writeUTF(config.ipAssignment.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid ip assignment while writing");
+ break;
+ }
+
+ switch (config.proxySettings) {
+ case STATIC:
+ out.writeUTF("proxySettings");
+ out.writeUTF(config.proxySettings.toString());
+ InetSocketAddress proxy = config.proxyProperties.getSocketAddress();
+ if (proxy != null) {
+ out.writeUTF("proxyHost");
+ out.writeUTF(proxy.getHostName());
+ out.writeUTF("proxyPort");
+ out.writeInt(proxy.getPort());
+ String exclusionList = config.proxyProperties.getExclusionList();
+ if (exclusionList != null && exclusionList.length() > 0) {
+ out.writeUTF("exclusionList");
+ out.writeUTF(exclusionList);
+ }
+ }
+ writeToFile = true;
+ break;
+ case NONE:
+ out.writeUTF("proxySettings");
+ out.writeUTF(config.proxySettings.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid proxy settings while writing");
+ break;
+ }
+
+ if (writeToFile) {
+ out.writeUTF("id");
+ out.writeInt(configKey(config));
+ out.writeUTF("EOS");
+ }
}
}
- }
- try {
- out = new BufferedWriter(new FileWriter(ipConfigFile), builder.length());
- out.write(builder.toString());
} catch (IOException e) {
Log.e(TAG, "Error writing data file");
- return;
} finally {
if (out != null) {
try {
@@ -471,80 +563,116 @@
}
}
- private static void readIpConfigurations() {
- File f = new File(ipConfigFile);
- byte[] buffer;
- FileInputStream s = null;
- try {
- buffer = new byte[(int)f.length()];
- s = new FileInputStream(f);
- s.read(buffer);
- } catch (IOException e) {
- Log.e(TAG, "Error reading data file");
- return;
- } finally {
- if (s != null) {
- try {
- s.close();
- } catch (Exception e) {}
- }
- }
+ private static void readIpAndProxyConfigurations() {
- String data = new String(buffer);
- if (data == null || data.length() == 0) {
- Log.d(TAG, "IP configuration file empty");
- return;
- }
-
- String[] parsed = data.split("\n");
+ DataInputStream in = null;
try {
- if (Integer.parseInt(parsed[0]) != IPCONFIG_FILE_VERSION) {
+ in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+ ipConfigFile)));
+
+ if (in.readInt() != IPCONFIG_FILE_VERSION) {
Log.e(TAG, "Bad version on IP configuration file, ignore read");
return;
}
- for (String line : parsed) {
- int hashKey = -1;
+ while (true) {
+ int id = -1;
+ IpAssignment ipAssignment = IpAssignment.UNASSIGNED;
DhcpInfo ipConfig = new DhcpInfo();
- String[] keyVals = line.split(":");
+ ProxySettings proxySettings = ProxySettings.UNASSIGNED;
+ String proxyHost = null;
+ int proxyPort = -1;
+ String exclusionList = null;
+ String key;
- for (String keyVal : keyVals) {
- String[] keyValPair = keyVal.split("=");
- if (keyValPair[0].equals("id")) {
- hashKey = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("ip")) {
- ipConfig.ipAddress = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("gateway")) {
- ipConfig.gateway = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("netmask")) {
- ipConfig.netmask = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("dns1")) {
- ipConfig.dns1 = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("dns2")) {
- ipConfig.dns2 = Integer.parseInt(keyValPair[1]);
+ do {
+ key = in.readUTF();
+ if (key.equals("id")) {
+ id = in.readInt();
+ } else if (key.equals("ipAssignment")) {
+ ipAssignment = IpAssignment.valueOf(in.readUTF());
+ } else if (key.equals("ipAddress")) {
+ ipConfig.ipAddress = in.readInt();
+ } else if (key.equals("gateway")) {
+ ipConfig.gateway = in.readInt();
+ } else if (key.equals("netmask")) {
+ ipConfig.netmask = in.readInt();
+ } else if (key.equals("dns1")) {
+ ipConfig.dns1 = in.readInt();
+ } else if (key.equals("dns2")) {
+ ipConfig.dns2 = in.readInt();
+ } else if (key.equals("proxySettings")) {
+ proxySettings = ProxySettings.valueOf(in.readUTF());
+ } else if (key.equals("proxyHost")) {
+ proxyHost = in.readUTF();
+ } else if (key.equals("proxyPort")) {
+ proxyPort = in.readInt();
+ } else if (key.equals("exclusionList")) {
+ exclusionList = in.readUTF();
+ } else if (key.equals("EOS")) {
+ break;
} else {
- Log.w(TAG, "Ignoring " + keyVal);
+ Log.e(TAG, "Ignore unknown key " + key + "while reading");
}
- }
+ } while (true);
- if (hashKey != -1) {
+ if (id != -1) {
synchronized (sConfiguredNetworks) {
WifiConfiguration config = sConfiguredNetworks.get(
- sNetworkIds.get(hashKey));
+ sNetworkIds.get(id));
if (config == null) {
- Log.e(TAG, "IP configuration found for missing network, ignored");
+ Log.e(TAG, "configuration found for missing network, ignored");
} else {
- config.ipAssignment = WifiConfiguration.IpAssignment.STATIC;
- config.ipConfig = ipConfig;
+ switch (ipAssignment) {
+ case STATIC:
+ config.ipAssignment = ipAssignment;
+ config.ipConfig = ipConfig;
+ break;
+ case DHCP:
+ config.ipAssignment = ipAssignment;
+ break;
+ case UNASSIGNED:
+ //Ignore
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid ip assignment while reading");
+ break;
+ }
+
+ switch (proxySettings) {
+ case STATIC:
+ config.proxySettings = proxySettings;
+ ProxyProperties proxyProperties = new ProxyProperties();
+ proxyProperties.setSocketAddress(
+ new InetSocketAddress(proxyHost, proxyPort));
+ proxyProperties.setExclusionList(exclusionList);
+ config.proxyProperties = proxyProperties;
+ break;
+ case NONE:
+ config.proxySettings = proxySettings;
+ break;
+ case UNASSIGNED:
+ //Ignore
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid proxy settings while reading");
+ break;
+ }
}
}
} else {
- Log.e(TAG,"Missing id while parsing configuration" + line);
+ Log.e(TAG,"Missing id while parsing configuration");
}
}
- } catch (NumberFormatException e) {
+ } catch (IOException e) {
Log.e(TAG, "Error parsing configuration");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (Exception e) {}
+ }
}
}
@@ -759,21 +887,68 @@
}
}
readNetworkVariables(sConfig);
+ writeIpAndProxyConfigurationsOnChange(sConfig, config);
+ return netId;
+ }
- if (config.ipAssignment != IpAssignment.UNASSIGNED) {
+ /* Compare current and new configuration and write to file on change */
+ private static void writeIpAndProxyConfigurationsOnChange(WifiConfiguration currentConfig,
+ WifiConfiguration newConfig) {
+ boolean newNetwork = (newConfig.networkId == INVALID_NETWORK_ID);
+ boolean writeConfigToFile = false;
+
+ if (newConfig.ipAssignment != IpAssignment.UNASSIGNED) {
if (newNetwork ||
- (sConfig.ipAssignment != config.ipAssignment) ||
- (sConfig.ipConfig.ipAddress != config.ipConfig.ipAddress) ||
- (sConfig.ipConfig.gateway != config.ipConfig.gateway) ||
- (sConfig.ipConfig.netmask != config.ipConfig.netmask) ||
- (sConfig.ipConfig.dns1 != config.ipConfig.dns1) ||
- (sConfig.ipConfig.dns2 != config.ipConfig.dns2)) {
- sConfig.ipAssignment = config.ipAssignment;
- sConfig.ipConfig = config.ipConfig;
- writeIpConfigurations();
+ (currentConfig.ipAssignment != newConfig.ipAssignment) ||
+ (currentConfig.ipConfig.ipAddress != newConfig.ipConfig.ipAddress) ||
+ (currentConfig.ipConfig.gateway != newConfig.ipConfig.gateway) ||
+ (currentConfig.ipConfig.netmask != newConfig.ipConfig.netmask) ||
+ (currentConfig.ipConfig.dns1 != newConfig.ipConfig.dns1) ||
+ (currentConfig.ipConfig.dns2 != newConfig.ipConfig.dns2)) {
+ currentConfig.ipAssignment = newConfig.ipAssignment;
+ currentConfig.ipConfig = newConfig.ipConfig;
+ writeConfigToFile = true;
}
}
- return netId;
+
+ if (newConfig.proxySettings != ProxySettings.UNASSIGNED) {
+ InetSocketAddress newSockAddr = newConfig.proxyProperties.getSocketAddress();
+ String newExclusionList = newConfig.proxyProperties.getExclusionList();
+
+ InetSocketAddress currentSockAddr = currentConfig.proxyProperties.getSocketAddress();
+ String currentExclusionList = currentConfig.proxyProperties.getExclusionList();
+
+ boolean socketAddressDiffers = false;
+ boolean exclusionListDiffers = false;
+
+ if (newSockAddr != null && currentSockAddr != null ) {
+ socketAddressDiffers = !currentSockAddr.equals(newSockAddr);
+ } else if (newSockAddr != null || currentSockAddr != null) {
+ socketAddressDiffers = true;
+ }
+
+ if (newExclusionList != null && currentExclusionList != null) {
+ exclusionListDiffers = currentExclusionList.equals(newExclusionList);
+ } else if (newExclusionList != null || currentExclusionList != null) {
+ exclusionListDiffers = true;
+ }
+
+ if (newNetwork ||
+ (currentConfig.proxySettings != newConfig.proxySettings) ||
+ socketAddressDiffers ||
+ exclusionListDiffers) {
+ currentConfig.proxySettings = newConfig.proxySettings;
+ currentConfig.proxyProperties = newConfig.proxyProperties;
+ Log.d(TAG, "proxy change SSID = " + currentConfig.SSID + " proxyProperties: " +
+ currentConfig.proxyProperties.toString());
+ writeConfigToFile = true;
+ }
+ }
+
+ if (writeConfigToFile) {
+ writeIpAndProxyConfigurations();
+ sendConfigChangeBroadcast();
+ }
}
/**
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 57e9bad..c4a1310 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -17,6 +17,7 @@
package android.net.wifi;
import android.net.DhcpInfo;
+import android.net.ProxyProperties;
import android.os.Parcelable;
import android.os.Parcel;
@@ -301,8 +302,13 @@
* @hide
*/
public enum IpAssignment {
+ /* Use statically configured IP settings. Configuration can be accessed
+ * with ipConfig */
STATIC,
+ /* Use dynamically configured IP settigns */
DHCP,
+ /* no IP details are assigned, this is used to indicate
+ * that any existing IP settings should be retained */
UNASSIGNED
}
/**
@@ -314,6 +320,29 @@
*/
public DhcpInfo ipConfig;
+ /**
+ * @hide
+ */
+ public enum ProxySettings {
+ /* No proxy is to be used. Any existing proxy settings
+ * should be cleared. */
+ NONE,
+ /* Use statically configured proxy. Configuration can be accessed
+ * with proxyProperties */
+ STATIC,
+ /* no proxy details are assigned, this is used to indicate
+ * that any existing proxy settings should be retained */
+ UNASSIGNED
+ }
+ /**
+ * @hide
+ */
+ public ProxySettings proxySettings;
+ /**
+ * @hide
+ */
+ public ProxyProperties proxyProperties;
+
public WifiConfiguration() {
networkId = INVALID_NETWORK_ID;
SSID = null;
@@ -333,6 +362,8 @@
}
ipAssignment = IpAssignment.UNASSIGNED;
ipConfig = new DhcpInfo();
+ proxySettings = ProxySettings.UNASSIGNED;
+ proxyProperties = new ProxyProperties();
}
public String toString() {
@@ -419,6 +450,12 @@
sbuf.append(" ").append(ipConfig);
}
sbuf.append('\n');
+
+ if (proxySettings == ProxySettings.STATIC) {
+ sbuf.append(" ").append("Proxy configuration:").append('\n');
+ sbuf.append(" ").append(proxyProperties);
+ }
+ sbuf.append('\n');
return sbuf.toString();
}
@@ -458,38 +495,36 @@
return 0;
}
- /**
- * Returns a copy of this WifiConfiguration.
- *
- * @return a copy of this WifiConfiguration.
- * @hide
- */
- public WifiConfiguration clone() {
- WifiConfiguration config = new WifiConfiguration();
- config.networkId = networkId;
- config.status = status;
- config.SSID = SSID;
- config.BSSID = BSSID;
- config.preSharedKey = preSharedKey;
+ /** copy constructor {@hide} */
+ public WifiConfiguration(WifiConfiguration source) {
+ if (source != null) {
+ networkId = source.networkId;
+ status = source.status;
+ SSID = source.SSID;
+ BSSID = source.BSSID;
+ preSharedKey = source.preSharedKey;
- for (int i = 0; i < wepKeys.length; i++)
- config.wepKeys[i] = wepKeys[i];
+ wepKeys = new String[4];
+ for (int i = 0; i < wepKeys.length; i++)
+ wepKeys[i] = source.wepKeys[i];
- config.wepTxKeyIndex = wepTxKeyIndex;
- config.priority = priority;
- config.hiddenSSID = hiddenSSID;
- config.allowedKeyManagement = (BitSet) allowedKeyManagement.clone();
- config.allowedProtocols = (BitSet) allowedProtocols.clone();
- config.allowedAuthAlgorithms = (BitSet) allowedAuthAlgorithms.clone();
- config.allowedPairwiseCiphers = (BitSet) allowedPairwiseCiphers.clone();
- config.allowedGroupCiphers = (BitSet) allowedGroupCiphers.clone();
+ wepTxKeyIndex = source.wepTxKeyIndex;
+ priority = source.priority;
+ hiddenSSID = source.hiddenSSID;
+ allowedKeyManagement = (BitSet) source.allowedKeyManagement.clone();
+ allowedProtocols = (BitSet) source.allowedProtocols.clone();
+ allowedAuthAlgorithms = (BitSet) source.allowedAuthAlgorithms.clone();
+ allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
+ allowedGroupCiphers = (BitSet) source.allowedGroupCiphers.clone();
- for (int i = 0; i < enterpriseFields.length; i++) {
- config.enterpriseFields[i].setValue(enterpriseFields[i].value());
+ for (int i = 0; i < source.enterpriseFields.length; i++) {
+ enterpriseFields[i].setValue(source.enterpriseFields[i].value());
+ }
+ ipAssignment = source.ipAssignment;
+ ipConfig = new DhcpInfo(source.ipConfig);
+ proxySettings = source.proxySettings;
+ proxyProperties = new ProxyProperties(source.proxyProperties);
}
- config.ipAssignment = ipAssignment;
- config.ipConfig = new DhcpInfo(ipConfig);
- return config;
}
/** Implement the Parcelable interface {@hide} */
@@ -522,6 +557,8 @@
dest.writeInt(ipConfig.dns2);
dest.writeInt(ipConfig.serverAddress);
dest.writeInt(ipConfig.leaseDuration);
+ dest.writeString(proxySettings.name());
+ dest.writeParcelable(proxyProperties, flags);
}
/** Implement the Parcelable interface {@hide} */
@@ -557,6 +594,8 @@
config.ipConfig.dns2 = in.readInt();
config.ipConfig.serverAddress = in.readInt();
config.ipConfig.leaseDuration = in.readInt();
+ config.proxySettings = ProxySettings.valueOf(in.readString());
+ config.proxyProperties = in.readParcelable(null);
return config;
}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index e82c003..572abc0 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -38,12 +38,14 @@
import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
import android.app.ActivityManagerNative;
+import android.net.LinkAddress;
import android.net.NetworkInfo;
import android.net.DhcpInfo;
import android.net.NetworkUtils;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.DetailedState;
import android.net.LinkProperties;
+import android.net.ProxyProperties;
import android.net.wifi.WifiConfiguration.Status;
import android.os.Binder;
import android.os.Message;
@@ -1251,24 +1253,25 @@
}
private void configureLinkProperties() {
- try {
- mLinkProperties.setInterface(NetworkInterface.getByName(mInterfaceName));
- } catch (SocketException e) {
- Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName +
- ". e=" + e);
- return;
- } catch (NullPointerException e) {
- Log.e(TAG, "NPE creating NetworkInterface. e=" + e);
- return;
- }
+
+ mLinkProperties.setInterfaceName(mInterfaceName);
+
// TODO - fix this for v6
synchronized (mDhcpInfo) {
- mLinkProperties.addAddress(NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress));
+ mLinkProperties.addLinkAddress(new LinkAddress(
+ NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress),
+ NetworkUtils.intToInetAddress(mDhcpInfo.netmask)));
mLinkProperties.setGateway(NetworkUtils.intToInetAddress(mDhcpInfo.gateway));
mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns1));
mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns2));
}
- // TODO - add proxy info
+
+ ProxyProperties proxyProperties = WifiConfigStore.getProxyProperties(mLastNetworkId);
+ if (proxyProperties != null) {
+ mLinkProperties.setHttpProxy(proxyProperties);
+ Log.d(TAG, "netId=" + mLastNetworkId + " proxy configured: "
+ + proxyProperties.toString());
+ }
}
private int getMaxDhcpRetries() {