OSDN Git Service

Merge "Improve/flesh-out shared library version check." into jb-mr2-dev
authorDianne Hackborn <hackbod@google.com>
Mon, 18 Mar 2013 18:44:51 +0000 (18:44 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Mon, 18 Mar 2013 18:44:52 +0000 (18:44 +0000)
128 files changed:
api/current.txt
cmds/am/src/com/android/commands/am/Am.java
cmds/app_process/app_main.cpp
core/java/android/accounts/AccountManager.java
core/java/android/animation/AnimatorInflater.java
core/java/android/animation/KeyframeSet.java
core/java/android/animation/ValueAnimator.java
core/java/android/app/PendingIntent.java
core/java/android/bluetooth/BluetoothTetheringDataTracker.java
core/java/android/content/AsyncTaskLoader.java
core/java/android/content/CursorLoader.java
core/java/android/content/Loader.java
core/java/android/content/res/Resources.java
core/java/android/hardware/Sensor.java
core/java/android/hardware/SensorEvent.java
core/java/android/hardware/SensorManager.java
core/java/android/hardware/SystemSensorManager.java
core/java/android/hardware/TriggerEvent.java [deleted file]
core/java/android/hardware/TriggerEventListener.java [deleted file]
core/java/android/inputmethodservice/IInputMethodWrapper.java
core/java/android/inputmethodservice/InputMethodService.java
core/java/android/net/BaseNetworkStateTracker.java
core/java/android/net/DhcpStateMachine.java
core/java/android/net/DummyDataStateTracker.java
core/java/android/net/EthernetDataTracker.java
core/java/android/net/LinkProperties.java
core/java/android/net/MobileDataStateTracker.java
core/java/android/net/NetworkStateTracker.java
core/java/android/os/FileUtils.java
core/java/android/provider/Settings.java
core/java/android/text/GraphicsOperations.java
core/java/android/text/MeasuredText.java
core/java/android/text/SpannableStringBuilder.java
core/java/android/text/TextLine.java
core/java/android/text/bidi/BidiFormatter.java
core/java/android/view/GLES20Canvas.java
core/java/android/view/GLES20RecordingCanvas.java
core/java/android/view/Overlay.java [new file with mode: 0644]
core/java/android/view/Surface.java
core/java/android/view/View.java
core/java/android/view/ViewGroup.java
core/java/android/view/ViewOverlay.java [new file with mode: 0644]
core/java/android/view/ViewRootImpl.java
core/java/android/view/WindowManager.java
core/java/android/widget/AbsListView.java
core/java/android/widget/TextView.java
core/java/com/android/internal/util/Protocol.java
core/java/com/android/internal/util/StateMachine.java
core/java/com/android/internal/view/IInputMethod.aidl
core/jni/android/graphics/Canvas.cpp
core/jni/android/graphics/Paint.cpp
core/jni/android/graphics/TextLayout.cpp
core/jni/android/graphics/TextLayout.h
core/jni/android/graphics/TextLayoutCache.cpp
core/jni/android/graphics/TextLayoutCache.h
core/jni/android_hardware_SensorManager.cpp
core/jni/android_net_NetUtils.cpp
core/jni/android_view_GLES20Canvas.cpp
core/jni/android_view_Surface.cpp
core/res/AndroidManifest.xml
core/res/res/values-cs/strings.xml
core/res/res/values-es-rUS/strings.xml
core/res/res/values-pl/strings.xml
core/res/res/values-ro/strings.xml
core/res/res/values/config.xml
core/res/res/values/symbols.xml
core/res/res/xml/apns.xml
core/tests/coretests/src/android/net/LinkPropertiesTest.java
core/tests/coretests/src/android/os/FileUtilsTest.java
docs/downloads/training/BitmapFun.zip
docs/html/index.jd
docs/html/training/displaying-bitmaps/cache-bitmap.jd
docs/html/training/displaying-bitmaps/display-bitmap.jd
docs/html/training/displaying-bitmaps/index.jd
docs/html/training/displaying-bitmaps/load-bitmap.jd
docs/html/training/displaying-bitmaps/manage-memory.jd [new file with mode: 0644]
docs/html/training/displaying-bitmaps/process-bitmap.jd
docs/html/training/training_toc.cs
graphics/java/android/graphics/Canvas.java
graphics/java/android/graphics/Paint.java
libs/hwui/Caches.cpp
libs/hwui/DeferredDisplayList.cpp
libs/hwui/DeferredDisplayList.h
libs/hwui/DisplayList.cpp
libs/hwui/DisplayList.h
libs/hwui/DisplayListOp.h
libs/hwui/DisplayListRenderer.cpp
libs/hwui/DisplayListRenderer.h
libs/hwui/Matrix.h
libs/hwui/OpenGLRenderer.cpp
libs/hwui/OpenGLRenderer.h
libs/hwui/font/Font.cpp
media/java/android/media/IAudioService.aidl
media/java/android/media/MediaMuxer.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
packages/Shell/Android.mk
packages/Shell/AndroidManifest.xml
packages/Shell/res/layout/confirm_repeat.xml [new file with mode: 0644]
packages/Shell/res/values/strings.xml
packages/Shell/res/xml/file_provider_paths.xml [new file with mode: 0644]
packages/Shell/src/com/android/shell/BugreportPrefs.java [new file with mode: 0644]
packages/Shell/src/com/android/shell/BugreportReceiver.java [new file with mode: 0644]
packages/Shell/src/com/android/shell/BugreportWarningActivity.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
services/java/com/android/server/ConnectivityService.java
services/java/com/android/server/InputMethodManagerService.java
services/java/com/android/server/NetworkManagementService.java
services/java/com/android/server/am/ServiceRecord.java
services/java/com/android/server/connectivity/Nat464Xlat.java [new file with mode: 0644]
services/java/com/android/server/connectivity/Tethering.java
services/java/com/android/server/power/ElectronBeam.java
services/java/com/android/server/wifi/README.txt [new file with mode: 0644]
services/java/com/android/server/wifi/WifiController.java [new file with mode: 0644]
services/java/com/android/server/wifi/WifiService.java
services/java/com/android/server/wifi/WifiSettingsStore.java
tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
tests/BiDiTests/res/layout/canvas.xml [new file with mode: 0644]
tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java [new file with mode: 0644]
tests/BiDiTests/src/com/android/bidi/BiDiTestView.java [new file with mode: 0644]
wifi/java/android/net/wifi/IWifiManager.aidl
wifi/java/android/net/wifi/WifiConfigStore.java
wifi/java/android/net/wifi/WifiStateMachine.java
wifi/java/android/net/wifi/WifiStateTracker.java
wifi/java/android/net/wifi/WifiWatchdogStateMachine.java

index be104a0..f2fcbd6 100644 (file)
@@ -6159,6 +6159,7 @@ package android.content {
     ctor public Loader(android.content.Context);
     method public void abandon();
     method public boolean cancelLoad();
+    method public void commitContentChanged();
     method public java.lang.String dataToString(D);
     method public void deliverCancellation();
     method public void deliverResult(D);
@@ -6179,6 +6180,7 @@ package android.content {
     method public void registerListener(int, android.content.Loader.OnLoadCompleteListener<D>);
     method public void registerOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
     method public void reset();
+    method public void rollbackContentChanged();
     method public final void startLoading();
     method public void stopLoading();
     method public boolean takeContentChanged();
@@ -10045,20 +10047,16 @@ package android.hardware {
     field public static final int TYPE_ACCELEROMETER = 1; // 0x1
     field public static final int TYPE_ALL = -1; // 0xffffffff
     field public static final int TYPE_AMBIENT_TEMPERATURE = 13; // 0xd
-    field public static final int TYPE_GAME_ROTATION_VECTOR = 15; // 0xf
     field public static final int TYPE_GRAVITY = 9; // 0x9
     field public static final int TYPE_GYROSCOPE = 4; // 0x4
-    field public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; // 0x10
     field public static final int TYPE_LIGHT = 5; // 0x5
     field public static final int TYPE_LINEAR_ACCELERATION = 10; // 0xa
     field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2
-    field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe
     field public static final deprecated int TYPE_ORIENTATION = 3; // 0x3
     field public static final int TYPE_PRESSURE = 6; // 0x6
     field public static final int TYPE_PROXIMITY = 8; // 0x8
     field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc
     field public static final int TYPE_ROTATION_VECTOR = 11; // 0xb
-    field public static final int TYPE_SIGNIFICANT_MOTION = 17; // 0x11
     field public static final deprecated int TYPE_TEMPERATURE = 7; // 0x7
   }
 
@@ -10080,7 +10078,6 @@ package android.hardware {
   }
 
   public abstract class SensorManager {
-    method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
     method public static float getAltitude(float, float);
     method public static void getAngleChange(float[], float[], float[]);
     method public android.hardware.Sensor getDefaultSensor(int);
@@ -10096,7 +10093,6 @@ package android.hardware {
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int);
     method public boolean registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int, android.os.Handler);
     method public static boolean remapCoordinateSystem(float[], int, int, float[]);
-    method public boolean requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
     method public deprecated void unregisterListener(android.hardware.SensorListener);
     method public deprecated void unregisterListener(android.hardware.SensorListener, int);
     method public void unregisterListener(android.hardware.SensorEventListener, android.hardware.Sensor);
@@ -10160,17 +10156,6 @@ package android.hardware {
     field public static final float STANDARD_GRAVITY = 9.80665f;
   }
 
-  public final class TriggerEvent {
-    field public android.hardware.Sensor sensor;
-    field public long timestamp;
-    field public final float[] values;
-  }
-
-  public abstract class TriggerEventListener {
-    ctor public TriggerEventListener();
-    method public abstract void onTrigger(android.hardware.TriggerEvent);
-  }
-
 }
 
 package android.hardware.display {
@@ -24968,6 +24953,14 @@ package android.view {
     field public static final int ORIENTATION_UNKNOWN = -1; // 0xffffffff
   }
 
+  public abstract interface Overlay {
+    method public abstract void add(android.graphics.drawable.Drawable);
+    method public abstract void add(android.view.View);
+    method public abstract void clear();
+    method public abstract void remove(android.graphics.drawable.Drawable);
+    method public abstract void remove(android.view.View);
+  }
+
   public class ScaleGestureDetector {
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener);
     method public float getCurrentSpan();
@@ -25272,6 +25265,7 @@ package android.view {
     method public int getNextFocusUpId();
     method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
     method public int getOverScrollMode();
+    method public android.view.Overlay getOverlay();
     method public int getPaddingBottom();
     method public int getPaddingEnd();
     method public int getPaddingLeft();
index 9fa7dbb..1c02960 100644 (file)
@@ -63,6 +63,7 @@ public class Am {
 
     private int mRepeat = 0;
     private int mUserId;
+    private String mReceiverPermission;
 
     private String mProfileFile;
 
@@ -332,6 +333,8 @@ public class Am {
                 mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
             } else if (opt.equals("--user")) {
                 mUserId = parseUserArg(nextArgRequired());
+            } else if (opt.equals("--receiver-permission")) {
+                mReceiverPermission = nextArgRequired();
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return null;
@@ -608,7 +611,7 @@ public class Am {
         Intent intent = makeIntent(UserHandle.USER_ALL);
         IntentReceiver receiver = new IntentReceiver();
         System.out.println("Broadcasting: " + intent);
-        mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null,
+        mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, mReceiverPermission,
                 android.app.AppOpsManager.OP_NONE, true, false, mUserId);
         receiver.waitForFinish();
     }
@@ -1408,6 +1411,7 @@ public class Am {
                 "am broadcast: send a broadcast Intent.  Options are:\n" +
                 "    --user <USER_ID> | all | current: Specify which user to send to; if not\n" +
                 "        specified then send to all users.\n" +
+                "    --receiver-permission <PERMISSION>: Require receiver to hold permission.\n" +
                 "\n" +
                 "am instrument: start an Instrumentation.  Typically this target <COMPONENT>\n" +
                 "  is the form <TEST_PACKAGE>/<RUNNER_CLASS>.  Options are:\n" +
index 6fe358c..0668be6 100644 (file)
@@ -13,7 +13,9 @@
 #include <cutils/process_name.h>
 #include <cutils/memory.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <sys/personality.h>
 
+#include <stdlib.h>
 #include <stdio.h>
 #include <unistd.h>
 
@@ -128,8 +130,32 @@ static void setArgv0(const char *argv0, const char *newArgv0)
     strlcpy(const_cast<char *>(argv0), newArgv0, strlen(argv0));
 }
 
-int main(int argc, const char* const argv[])
+int main(int argc, char* const argv[])
 {
+#ifdef __arm__
+    /*
+     * b/7188322 - Temporarily revert to the compat memory layout
+     * to avoid breaking third party apps.
+     *
+     * THIS WILL GO AWAY IN A FUTURE ANDROID RELEASE.
+     *
+     * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=7dbaa466
+     * changes the kernel mapping from bottom up to top-down.
+     * This breaks some programs which improperly embed
+     * an out of date copy of Android's linker.
+     */
+    if (getenv("NO_ADDR_COMPAT_LAYOUT_FIXUP") == NULL) {
+        int current = personality(0xFFFFFFFF);
+        if ((current & ADDR_COMPAT_LAYOUT) == 0) {
+            personality(current | ADDR_COMPAT_LAYOUT);
+            setenv("NO_ADDR_COMPAT_LAYOUT_FIXUP", "1", 1);
+            execv("/system/bin/app_process", argv);
+            return -1;
+        }
+    }
+    unsetenv("NO_ADDR_COMPAT_LAYOUT_FIXUP");
+#endif
+
     // These are global variables in ProcessState.cpp
     mArgC = argc;
     mArgV = argv;
index 6aac723..f8b7a0c 100644 (file)
@@ -18,9 +18,11 @@ package android.accounts;
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
 import android.content.BroadcastReceiver;
+import android.content.res.Resources;
 import android.database.SQLException;
 import android.os.Bundle;
 import android.os.Handler;
@@ -44,6 +46,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.android.internal.R;
 import com.google.android.collect.Maps;
 
 /**
@@ -1777,8 +1780,11 @@ public class AccountManager {
                                     };
                                     // have many accounts, launch the chooser
                                     Intent intent = new Intent();
-                                    intent.setClassName("android",
-                                            "android.accounts.ChooseAccountActivity");
+                                    ComponentName componentName = ComponentName.unflattenFromString(
+                                            Resources.getSystem().getString(
+                                                    R.string.config_chooseAccountActivity));
+                                    intent.setClassName(componentName.getPackageName(),
+                                            componentName.getClassName());
                                     intent.putExtra(KEY_ACCOUNTS, accounts);
                                     intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
                                             new AccountManagerResponse(chooseResponse));
@@ -1934,7 +1940,10 @@ public class AccountManager {
             String[] addAccountRequiredFeatures,
             Bundle addAccountOptions) {
         Intent intent = new Intent();
-        intent.setClassName("android", "android.accounts.ChooseTypeAndAccountActivity");
+        ComponentName componentName = ComponentName.unflattenFromString(
+                Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity));
+        intent.setClassName(componentName.getPackageName(),
+                componentName.getClassName());
         intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST,
                 allowableAccounts);
         intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
index ed4036d..d753e32 100644 (file)
@@ -185,7 +185,7 @@ public class AnimatorInflater {
         TypedArray a =
                 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
 
-        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0);
+        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
 
         long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
 
index 6172aab..4026f7f 100644 (file)
@@ -21,6 +21,7 @@ import java.util.Arrays;
 import android.animation.Keyframe.IntKeyframe;
 import android.animation.Keyframe.FloatKeyframe;
 import android.animation.Keyframe.ObjectKeyframe;
+import android.util.Log;
 
 /**
  * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
@@ -56,24 +57,36 @@ class KeyframeSet {
         } else {
             keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
             for (int i = 1; i < numKeyframes; ++i) {
-                keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+                keyframes[i] =
+                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
             }
         }
         return new IntKeyframeSet(keyframes);
     }
 
     public static KeyframeSet ofFloat(float... values) {
+        boolean badValue = false;
         int numKeyframes = values.length;
         FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
         if (numKeyframes == 1) {
             keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
             keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
+            if (Float.isNaN(values[0])) {
+                badValue = true;
+            }
         } else {
             keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
             for (int i = 1; i < numKeyframes; ++i) {
-                keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+                keyframes[i] =
+                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+                if (Float.isNaN(values[i])) {
+                    badValue = true;
+                }
             }
         }
+        if (badValue) {
+            Log.w("Animator", "Bad value (NaN) in float animator");
+        }
         return new FloatKeyframeSet(keyframes);
     }
 
index f7460c4..4a58072 100644 (file)
@@ -1015,6 +1015,7 @@ public class ValueAnimator extends Animator {
         mRunning = false;
         mStarted = false;
         mStartListenersCalled = false;
+        mPlayingBackwards = false;
     }
 
     /**
index 37804e9..20114cc 100644 (file)
@@ -261,6 +261,7 @@ public final class PendingIntent implements Parcelable {
                 context.getContentResolver()) : null;
         try {
             intent.setAllowFds(false);
+            intent.migrateExtraStreamToClipData();
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
                     ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
@@ -285,6 +286,7 @@ public final class PendingIntent implements Parcelable {
                 context.getContentResolver()) : null;
         try {
             intent.setAllowFds(false);
+            intent.migrateExtraStreamToClipData();
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
                     ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
index 3ba4f26..43c2392 100644 (file)
@@ -373,4 +373,14 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
     public void setDependencyMet(boolean met) {
         // not supported on this network
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
index 188c786..612c67f 100644 (file)
@@ -231,6 +231,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
         onCanceled(data);
         if (mCancellingTask == task) {
             if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
+            rollbackContentChanged();
             mLastLoadCompleteTime = SystemClock.uptimeMillis();
             mCancellingTask = null;
             if (DEBUG) Slog.v(TAG, "Delivering cancellation");
@@ -248,6 +249,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
                 // This cursor has been abandoned; just cancel the new data.
                 onCanceled(data);
             } else {
+                commitContentChanged();
                 mLastLoadCompleteTime = SystemClock.uptimeMillis();
                 mTask = null;
                 if (DEBUG) Slog.v(TAG, "Delivering result");
index 4e89dec..5d7d677 100644 (file)
@@ -68,7 +68,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
                 try {
                     // Ensure the cursor window is filled.
                     cursor.getCount();
-                    registerContentObserver(cursor, mObserver);
+                    cursor.registerContentObserver(mObserver);
                 } catch (RuntimeException ex) {
                     cursor.close();
                     throw ex;
@@ -93,14 +93,6 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
         }
     }
 
-    /**
-     * Registers an observer to get notifications from the content provider
-     * when the cursor needs to be refreshed.
-     */
-    void registerContentObserver(Cursor cursor, ContentObserver observer) {
-        cursor.registerContentObserver(mObserver);
-    }
-
     /* Runs on the UI thread */
     @Override
     public void deliverResult(Cursor cursor) {
index 3052414..911e49c 100644 (file)
@@ -58,6 +58,7 @@ public class Loader<D> {
     boolean mAbandoned = false;
     boolean mReset = true;
     boolean mContentChanged = false;
+    boolean mProcessingChange = false;
 
     /**
      * An implementation of a ContentObserver that takes care of connecting
@@ -439,6 +440,7 @@ public class Loader<D> {
         mStarted = false;
         mAbandoned = false;
         mContentChanged = false;
+        mProcessingChange = false;
     }
 
     /**
@@ -458,9 +460,34 @@ public class Loader<D> {
     public boolean takeContentChanged() {
         boolean res = mContentChanged;
         mContentChanged = false;
+        mProcessingChange |= res;
         return res;
     }
-    
+
+    /**
+     * Commit that you have actually fully processed a content change that
+     * was returned by {@link #takeContentChanged}.  This is for use with
+     * {@link #rollbackContentChanged()} to handle situations where a load
+     * is cancelled.  Call this when you have completely processed a load
+     * without it being cancelled.
+     */
+    public void commitContentChanged() {
+        mProcessingChange = false;
+    }
+
+    /**
+     * Report that you have abandoned the processing of a content change that
+     * was returned by {@link #takeContentChanged()} and would like to rollback
+     * to the state where there is again a pending content change.  This is
+     * to handle the case where a data load due to a content change has been
+     * canceled before its data was delivered back to the loader.
+     */
+    public void rollbackContentChanged() {
+        if (mProcessingChange) {
+            mContentChanged = true;
+        }
+    }
+
     /**
      * Called when {@link ForceLoadContentObserver} detects a change.  The
      * default implementation checks to see if the loader is currently started;
@@ -512,9 +539,14 @@ public class Loader<D> {
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         writer.print(prefix); writer.print("mId="); writer.print(mId);
                 writer.print(" mListener="); writer.println(mListener);
-        writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
-                writer.print(" mContentChanged="); writer.print(mContentChanged);
-                writer.print(" mAbandoned="); writer.print(mAbandoned);
-                writer.print(" mReset="); writer.println(mReset);
+        if (mStarted || mContentChanged || mProcessingChange) {
+            writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
+                    writer.print(" mContentChanged="); writer.print(mContentChanged);
+                    writer.print(" mProcessingChange="); writer.println(mProcessingChange);
+        }
+        if (mAbandoned || mReset) {
+            writer.print(prefix); writer.print("mAbandoned="); writer.print(mAbandoned);
+                    writer.print(" mReset="); writer.println(mReset);
+        }
     }
 }
\ No newline at end of file
index 805b05e..0152615 100644 (file)
@@ -1739,6 +1739,15 @@ public class Resources {
     }
 
     /**
+     * Return true if given resource identifier includes a package.
+     *
+     * @hide
+     */
+    public static boolean resourceHasPackage(int resid) {
+        return (resid >>> 24) != 0;
+    }
+
+    /**
      * Return the full name for a given resource identifier.  This name is
      * a single string of the form "package:type/entry".
      * 
index af4c074..41384d2 100644 (file)
@@ -114,90 +114,11 @@ public final class Sensor {
     /** A constant describing an ambient temperature sensor type */
     public static final int TYPE_AMBIENT_TEMPERATURE = 13;
 
-    /**
-     * A constant describing a magnetic field uncalibrated sensor type. See
-     * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
-     * details.
-     * <p>
-     * No periodic calibration is performed (ie: there are no discontinuities
-     * in the data stream while using this sensor). Assumptions that the
-     * magnetic field is due to the Earth's poles is avoided. Factory calibration
-     * and temperature compensation is still performed.
-     * </p>
-     */
-    public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14;
-
-    /**
-     * Identical to {@link #TYPE_ROTATION_VECTOR} except that it doesn't
-     * use the geomagnetic field. Therefore the Y axis doesn't
-     * point north, but instead to some other reference, that reference is
-     * allowed to drift by the same order of magnitude as the gyroscope
-     * drift around the Z axis.
-     * <p>
-     * In the ideal case, a phone rotated and returning to the same real-world
-     * orientation should report the same game rotation vector
-     * (without using the earth's geomagnetic field). However, the orientation
-     * may drift somewhat over time.
-     * </p>
-     */
-
-    public static final int TYPE_GAME_ROTATION_VECTOR = 15;
-
-    /**
-     * A constant describing a gyroscope uncalibrated sensor type. See
-     * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
-     * details.
-     * <p>
-     * No gyro-drift compensation is performed.
-     * Factory calibration and temperature compensation is still applied
-     * to the rate of rotation (angular speeds).
-     * </p>
-     */
-    public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16;
-
-    /**
-     * A constant describing the significant motion trigger sensor.
-     * See {@link android.hardware.SensorEvent#values} for more details.
-     * <p>
-     * It triggers when an event occurs and then automatically disables
-     * itself. The sensor continues to operate while the device is asleep
-     * and will automatically wake the device to notify when significant
-     * motion is detected. The application does not need to hold any wake
-     * locks for this sensor to trigger.
-     * </p>
-     */
-    public static final int TYPE_SIGNIFICANT_MOTION = 17;
-
-    /**
+    /** 
      * A constant describing all sensor types.
      */
     public static final int TYPE_ALL = -1;
 
-    /* Reporting mode constants for sensors. Each sensor will have exactly one
-       reporting mode associated with it. */
-    // Events are reported at a constant rate.
-    static int REPORTING_MODE_CONTINUOUS = 1;
-
-    // Events are reported only when the value changes.
-    static int REPORTING_MODE_ON_CHANGE = 2;
-
-    // Upon detection of an event, the sensor deactivates itself and then sends a single event.
-    static int REPORTING_MODE_ONE_SHOT = 3;
-
-    // Note: This needs to be updated, whenever a new sensor is added.
-    private static int[] sSensorReportingModes = {
-            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
-            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
-            REPORTING_MODE_ON_CHANGE, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
-            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE,
-            REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
-            REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ONE_SHOT };
-
-    static int getReportingMode(Sensor sensor) {
-        // mType starts from offset 1.
-        return sSensorReportingModes[sensor.mType - 1];
-    }
-
     /* Some of these fields are set only by the native bindings in
      * SensorManager.
      */
@@ -211,6 +132,7 @@ public final class Sensor {
     private float   mPower;
     private int     mMinDelay;
 
+
     Sensor() {
     }
 
index 84c9131..51a17c1 100644 (file)
 package android.hardware;
 
 /**
+ * <p>
  * This class represents a {@link android.hardware.Sensor Sensor} event and
  * holds informations such as the sensor's type, the time-stamp, accuracy and of
  * course the sensor's {@link SensorEvent#values data}.
+ * </p>
  *
  * <p>
  * <u>Definition of the coordinate system used by the SensorEvent API.</u>
@@ -65,9 +67,15 @@ public class SensorEvent {
      * Sensor.TYPE_ACCELEROMETER}:</h4> All values are in SI units (m/s^2)
      * 
      * <ul>
-     * <li> values[0]: Acceleration minus Gx on the x-axis </li>
-     * <li> values[1]: Acceleration minus Gy on the y-axis </li>
-     * <li> values[2]: Acceleration minus Gz on the z-axis </li>
+     * <p>
+     * values[0]: Acceleration minus Gx on the x-axis
+     * </p>
+     * <p>
+     * values[1]: Acceleration minus Gy on the y-axis
+     * </p>
+     * <p>
+     * values[2]: Acceleration minus Gz on the z-axis
+     * </p>
      * </ul>
      * 
      * <p>
@@ -157,9 +165,15 @@ public class SensorEvent {
      * definition of positive rotation and does not agree with the definition of
      * roll given earlier.
      * <ul>
-     * <li> values[0]: Angular speed around the x-axis </li>
-     * <li> values[1]: Angular speed around the y-axis </li>
-     * <li> values[2]: Angular speed around the z-axis </li>
+     * <p>
+     * values[0]: Angular speed around the x-axis
+     * </p>
+     * <p>
+     * values[1]: Angular speed around the y-axis
+     * </p>
+     * <p>
+     * values[2]: Angular speed around the z-axis
+     * </p>
      * </ul>
      * <p>
      * Typically the output of the gyroscope is integrated over time to
@@ -219,19 +233,22 @@ public class SensorEvent {
      * </p>
      * <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4>
      * <ul>
-     * <li>values[0]: Ambient light level in SI lux units </li>
+     * <p>
+     * values[0]: Ambient light level in SI lux units
      * </ul>
      * 
      * <h4>{@link android.hardware.Sensor#TYPE_PRESSURE Sensor.TYPE_PRESSURE}:</h4>
      * <ul>
-     * <li>values[0]: Atmospheric pressure in hPa (millibar) </li>
+     * <p>
+     * values[0]: Atmospheric pressure in hPa (millibar)
      * </ul>
      *
      * <h4>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:
      * </h4>
      * 
      * <ul>
-     * <li>values[0]: Proximity sensor distance measured in centimeters </li>
+     * <p>
+     * values[0]: Proximity sensor distance measured in centimeters
      * </ul>
      * 
      * <p>
@@ -287,23 +304,39 @@ public class SensorEvent {
      * </p>
      *
      * <ul>
-     * <li> values[0]: x*sin(&#952/2) </li>
-     * <li> values[1]: y*sin(&#952/2) </li>
-     * <li> values[2]: z*sin(&#952/2) </li>
-     * <li> values[3]: cos(&#952/2) <i>(optional: only if value.length = 4)</i> </li>
+     * <p>
+     * values[0]: x*sin(&#952/2)
+     * </p>
+     * <p>
+     * values[1]: y*sin(&#952/2)
+     * </p>
+     * <p>
+     * values[2]: z*sin(&#952/2)
+     * </p>
+     * <p>
+     * values[3]: cos(&#952/2) <i>(optional: only if value.length = 4)</i>
+     * </p>
      * </ul>
      *
      * <h4>{@link android.hardware.Sensor#TYPE_ORIENTATION
      * Sensor.TYPE_ORIENTATION}:</h4> All values are angles in degrees.
      * 
      * <ul>
-     * <li> values[0]: Azimuth, angle between the magnetic north direction and the
+     * <p>
+     * values[0]: Azimuth, angle between the magnetic north direction and the
      * y-axis, around the z-axis (0 to 359). 0=North, 90=East, 180=South,
-     * 270=West </li>
-     * <li> values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
-     * values when the z-axis moves <b>toward</b> the y-axis. </li>
-     * <li> values[2]: Roll, rotation around y-axis (-90 to 90), with positive values
-     * when the x-axis moves <b>toward</b> the z-axis. </li>
+     * 270=West
+     * </p>
+     * 
+     * <p>
+     * values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
+     * values when the z-axis moves <b>toward</b> the y-axis.
+     * </p>
+     * 
+     * <p>
+     * values[2]: Roll, rotation around y-axis (-90 to 90), with positive values
+     * when the x-axis moves <b>toward</b> the z-axis.
+     * </p>
      * </ul>
      * 
      * <p>
@@ -331,7 +364,9 @@ public class SensorEvent {
      * <h4>{@link android.hardware.Sensor#TYPE_RELATIVE_HUMIDITY
      * Sensor.TYPE_RELATIVE_HUMIDITY}:</h4>
      * <ul>
-     * <li> values[0]: Relative ambient air humidity in percent </li>
+     * <p>
+     * values[0]: Relative ambient air humidity in percent
+     * </p>
      * </ul>
      * <p>
      * When relative ambient air humidity and ambient temperature are
@@ -388,58 +423,21 @@ public class SensorEvent {
      * </h4>
      *
      * <ul>
-     * <li> values[0]: ambient (room) temperature in degree Celsius.</li>
+     * <p>
+     * values[0]: ambient (room) temperature in degree Celsius.
      * </ul>
      *
      * @see SensorEvent
      * @see GeomagneticField
-     *
-     * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD_UNCALIBRATED} </h4>
-     * All values are in micro-Tesla (uT) and measure the ambient magnetic field
-     * in the X, Y and Z axis.
-     * <p>
-     * No periodic calibration is performed (ie: there are no discontinuities
-     * in the data stream while using this sensor). Assumptions that the the
-     * magnetic field is due to the Earth's poles is avoided. Factory calibration
-     * and temperature compensation is still performed.
-     * </p>
-     *
-     * <h4> {@link android.hardware.Sensor#TYPE_GYROSCOPE_UNCALIBRATED} </h4>
-     * All values are in radians/second and measure the rate of rotation
-     * around the X, Y and Z axis. An estimation of the drift on each axis is
-     * reported as well.
-     * <p>
-     * No gyro-drift compensation is performed. Factory calibration and temperature
-     * compensation is still applied to the rate of rotation (angular speeds).
-     * </p>
-     * <p>
-     * The coordinate system is the same as is used for the
-     * {@link android.hardware.Sensor#TYPE_ACCELEROMETER}
-     * Rotation is positive in the counter-clockwise direction (right-hand rule).
-     * That is, an observer looking from some positive location on the x, y or z axis
-     * at a device positioned on the origin would report positive rotation if the device
-     * appeared to be rotating counter clockwise.
-     * The range would at least be 17.45 rad/s (ie: ~1000 deg/s).
-     * <ul>
-     * <li> values[0] : angular speed (w/o drift compensation) around the X axis in rad/s </li>
-     * <li> values[1] : angular speed (w/o drift compensation) around the Y axis in rad/s </li>
-     * <li> values[2] : angular speed (w/o drift compensation) around the Z axis in rad/s </li>
-     * <li> values[3] : estimated drift around X axis in rad/s </li>
-     * <li> values[4] : estimated drift around Y axis in rad/s </li>
-     * <li> values[5] : estimated drift around Z axis in rad/s </li>
-     * </ul>
-     * </p>
-     * <h4></h4>
-     * <h4> Pro Tip: Always use the length of the values array while performing operations
-     * on it. In earlier versions, this used to be always 3 which has changed now. </h4>
      */
+
     public final float[] values;
 
     /**
      * The sensor that generated this event. See
      * {@link android.hardware.SensorManager SensorManager} for details.
      */
-    public Sensor sensor;
+   public Sensor sensor;
 
     /**
      * The accuracy of this event. See {@link android.hardware.SensorManager
@@ -447,11 +445,13 @@ public class SensorEvent {
      */
     public int accuracy;
 
+
     /**
      * The time in nanosecond at which the event happened
      */
     public long timestamp;
 
+
     SensorEvent(int size) {
         values = new float[size];
     }
index ce7bc7e..c0d2fae 100644 (file)
@@ -38,11 +38,7 @@ import java.util.List;
  * hours. Note that the system will <i>not</i> disable sensors automatically when
  * the screen turns off.
  * </p>
- * <p class="note">
- * Note: Don't use this mechanism with a Trigger Sensor, have a look
- * at {@link TriggerEventListener}. {@link Sensor#TYPE_SIGNIFICANT_MOTION}
- * is an example of a trigger sensor.
- * </p>
+ *
  * <pre class="prettyprint">
  * public class SensorActivity extends Activity, implements SensorEventListener {
  *     private final SensorManager mSensorManager;
@@ -519,12 +515,6 @@ public abstract class SensorManager {
     /**
      * Unregisters a listener for the sensors with which it is registered.
      *
-     * <p class="note"></p>
-     * Note: Don't use this method with a one shot trigger sensor such as
-     * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
-     * Use {@link #cancelTriggerSensor(TriggerEventListener, Sensor)} instead.
-     * </p>
-     *
      * @param listener
      *        a SensorEventListener object
      *
@@ -534,7 +524,6 @@ public abstract class SensorManager {
      * @see #unregisterListener(SensorEventListener)
      * @see #registerListener(SensorEventListener, Sensor, int)
      *
-     * @throws IllegalArgumentException when sensor is a trigger sensor.
      */
     public void unregisterListener(SensorEventListener listener, Sensor sensor) {
         if (listener == null || sensor == null) {
@@ -569,12 +558,6 @@ public abstract class SensorManager {
      * Registers a {@link android.hardware.SensorEventListener
      * SensorEventListener} for the given sensor.
      *
-     * <p class="note"></p>
-     * Note: Don't use this method with a one shot trigger sensor such as
-     * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
-     * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
-     * </p>
-     *
      * @param listener
      *        A {@link android.hardware.SensorEventListener SensorEventListener}
      *        object.
@@ -598,7 +581,6 @@ public abstract class SensorManager {
      * @see #unregisterListener(SensorEventListener)
      * @see #unregisterListener(SensorEventListener, Sensor)
      *
-     * @throws IllegalArgumentException when sensor is null or a trigger sensor
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {
         return registerListener(listener, sensor, rate, null);
@@ -608,12 +590,6 @@ public abstract class SensorManager {
      * Registers a {@link android.hardware.SensorEventListener
      * SensorEventListener} for the given sensor.
      *
-     * <p class="note"></p>
-     * Note: Don't use this method with a one shot trigger sensor such as
-     * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
-     * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
-     * </p>
-     *
      * @param listener
      *        A {@link android.hardware.SensorEventListener SensorEventListener}
      *        object.
@@ -641,7 +617,6 @@ public abstract class SensorManager {
      * @see #unregisterListener(SensorEventListener)
      * @see #unregisterListener(SensorEventListener, Sensor)
      *
-     * @throws IllegalArgumentException when sensor is null or a trigger sensor
      */
     public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
             Handler handler) {
@@ -1329,68 +1304,6 @@ public abstract class SensorManager {
         Q[3] = rv[2];
     }
 
-    /**
-     * Requests receiving trigger events for a trigger sensor.
-     *
-     * <p>
-     * When the sensor detects a trigger event condition, such as significant motion in
-     * the case of the {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the provided trigger listener
-     * will be invoked once and then its request to receive trigger events will be canceled.
-     * To continue receiving trigger events, the application must request to receive trigger
-     * events again.
-     * </p>
-     *
-     * @param listener The listener on which the
-     *        {@link TriggerEventListener#onTrigger(TriggerEvent)} will be delivered.
-     * @param sensor The sensor to be enabled.
-     *
-     * @return true if the sensor was successfully enabled.
-     *
-     * @throws IllegalArgumentException when sensor is null or not a trigger sensor.
-     */
-    public boolean requestTriggerSensor(TriggerEventListener listener, Sensor sensor) {
-        return requestTriggerSensorImpl(listener, sensor);
-    }
-
-    /**
-     * @hide
-     */
-    protected abstract boolean requestTriggerSensorImpl(TriggerEventListener listener,
-            Sensor sensor);
-
-    /**
-     * Cancels receiving trigger events for a trigger sensor.
-     *
-     * <p>
-     * Note that a Trigger sensor will be auto disabled if
-     * {@link TriggerEventListener#onTrigger(TriggerEvent)} has triggered.
-     * This method is provided in case the user wants to explicitly cancel the request
-     * to receive trigger events.
-     * </p>
-     *
-     * @param listener The listener on which the
-     *        {@link TriggerEventListener#onTrigger(TriggerEvent)}
-     *        is delivered.It should be the same as the one used
-     *        in {@link #requestTriggerSensor(TriggerEventListener, Sensor)}
-     * @param sensor The sensor for which the trigger request should be canceled.
-     *        If null, it cancels receiving trigger for all sensors associated
-     *        with the listener.
-     *
-     * @return true if successfully canceled.
-     *
-     * @throws IllegalArgumentException when sensor is a trigger sensor.
-     */
-    public boolean cancelTriggerSensor(TriggerEventListener listener, Sensor sensor) {
-        return cancelTriggerSensorImpl(listener, sensor);
-    }
-
-    /**
-     * @hide
-     */
-    protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
-            Sensor sensor);
-
-
     private LegacySensorManager getLegacySensorManager() {
         synchronized (mSensorListByType) {
             if (mLegacySensorManager == null) {
index 893d701..9591631 100644 (file)
 
 package android.hardware;
 
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import dalvik.system.CloseGuard;
+
 import android.os.Handler;
 import android.os.Looper;
 import android.os.MessageQueue;
-import android.util.Log;
-import android.util.Pools;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
-import dalvik.system.CloseGuard;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
 
 /**
  * Sensor manager implementation that communicates with the built-in
@@ -46,21 +45,22 @@ public class SystemSensorManager extends SensorManager {
     private static final SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
 
     // Listener list
-    private final HashMap<SensorEventListener, SensorEventQueue> mSensorListeners =
-            new HashMap<SensorEventListener, SensorEventQueue>();
-    private final HashMap<TriggerEventListener, TriggerEventQueue> mTriggerListeners =
-            new HashMap<TriggerEventListener, TriggerEventQueue>();
+    private final ArrayList<SensorEventListenerSensorPair> mListenerDelegates = new ArrayList<SensorEventListenerSensorPair>();
 
-    private static final int MAX_EVENTS = 16;
-    private static Pools.SynchronizedPool<SensorEvent> sSensorEventPool;
-    private static Pools.SynchronizedPool<TriggerEvent> sTriggerEventPool;
+    // Common pool of sensor events.
+    private static SensorEventPool sPool;
 
     // Looper associated with the context in which this instance was created.
     private final Looper mMainLooper;
 
+    // maps a SensorEventListener to a SensorEventQueue
+    private final Hashtable<SensorEventListener, SensorEventQueue> mSensorEventQueueMap;
+
     /** {@hide} */
     public SystemSensorManager(Looper mainLooper) {
         mMainLooper = mainLooper;
+        mSensorEventQueueMap = new Hashtable<SensorEventListener, SensorEventQueue>();
+
         synchronized(sSensorModuleLock) {
             if (!sSensorModuleInitialized) {
                 sSensorModuleInitialized = true;
@@ -81,10 +81,7 @@ public class SystemSensorManager extends SensorManager {
                     }
                 } while (i>0);
 
-                sSensorEventPool = new Pools.SynchronizedPool<SensorEvent>(
-                        Math.max(sFullSensorsList.size()*2, 1));
-                sTriggerEventPool = new Pools.SynchronizedPool<TriggerEvent>(
-                        Math.max(sFullSensorsList.size()*2, 1));
+                sPool = new SensorEventPool( sFullSensorsList.size()*2 );
             }
         }
     }
@@ -105,133 +102,128 @@ public class SystemSensorManager extends SensorManager {
         // Invariants to preserve:
         // - one Looper per SensorEventListener
         // - one Looper per SensorEventQueue
-        // We map SensorEventListener to a SensorEventQueue, which holds the looper
-        if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
-
-        // Trigger Sensors should use the requestTriggerSensor call.
-        if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) return false;
+        // We map SensorEventListeners to a SensorEventQueue, which holds the looper
+
+        if (sensor == null) throw new NullPointerException("sensor cannot be null");
+
+        boolean result;
+        synchronized (mSensorEventQueueMap) {
+            // check if we already have this SensorEventListener, Sensor pair
+            // registered -- if so, we ignore the register. This is not ideal
+            // but this is what the implementation has always been doing.
+            for (SensorEventListenerSensorPair l : mListenerDelegates) {
+                if (l.isSameListenerSensorPair(listener, sensor)) {
+                    // already added, just return silently.
+                    return true;
+                }
+            }
 
-        synchronized (mSensorListeners) {
-            SensorEventQueue queue = mSensorListeners.get(listener);
-            if (queue == null) {
+            // now find the SensorEventQueue associated to this listener
+            SensorEventQueue queue = mSensorEventQueueMap.get(listener);
+            if (queue != null) {
+                result = queue.addSensor(sensor, delay);
+                if (result) {
+                    // create a new ListenerDelegate for this pair
+                    mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
+                }
+            } else {
                 Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
-                queue = new SensorEventQueue(listener, looper);
-                if (!queue.addSensor(sensor, delay)) {
+                queue = new SensorEventQueue(listener, looper.getQueue());
+                result = queue.addSensor(sensor, delay);
+                if (result) {
+                    // create a new ListenerDelegate for this pair
+                    mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
+                    mSensorEventQueueMap.put(listener, queue);
+                } else {
                     queue.dispose();
-                    return false;
                 }
-                mSensorListeners.put(listener, queue);
-                return true;
-            } else {
-                return queue.addSensor(sensor, delay);
             }
         }
+        return result;
     }
 
     /** @hide */
     @Override
     protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
-        // Trigger Sensors should use the cancelTriggerSensor call.
-        if (sensor != null && Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) {
-            return;
-        }
+        synchronized (mSensorEventQueueMap) {
+
+            // remove this listener/sensor from our list
+            final ArrayList<SensorEventListenerSensorPair> copy =
+                    new ArrayList<SensorEventListenerSensorPair>(mListenerDelegates);
+            int lastIndex = copy.size()-1;
+            for (int i=lastIndex ; i>= 0 ; i--) {
+                if (copy.get(i).isSameListenerSensorPair(listener, sensor)) {
+                    mListenerDelegates.remove(i);
+                }
+            }
 
-        synchronized (mSensorListeners) {
-            SensorEventQueue queue = mSensorListeners.get(listener);
+            // find the SensorEventQueue associated to this SensorEventListener
+            SensorEventQueue queue = mSensorEventQueueMap.get(listener);
             if (queue != null) {
-                boolean result;
-                if (sensor == null) {
-                    result = queue.removeAllSensors();
+                if (sensor != null) {
+                    queue.removeSensor(sensor);
                 } else {
-                    result = queue.removeSensor(sensor);
+                    queue.removeAllSensors();
                 }
-                if (result && !queue.hasSensors()) {
-                    mSensorListeners.remove(listener);
+                if (!queue.hasSensors()) {
+                    mSensorEventQueueMap.remove(listener);
                     queue.dispose();
                 }
             }
         }
     }
 
-    /** @hide */
-    @Override
-    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
-        if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
-
-        if (Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) return false;
 
-        synchronized (mTriggerListeners) {
-            TriggerEventQueue queue = mTriggerListeners.get(listener);
-            if (queue == null) {
-                queue = new TriggerEventQueue(listener, mMainLooper, this);
-                if (!queue.addSensor(sensor, 0)) {
-                    queue.dispose();
-                    return false;
-                }
-                mTriggerListeners.put(listener, queue);
-                return true;
-            } else {
-                return queue.addSensor(sensor, 0);
-            }
-        }
-    }
-
-    /** @hide */
-    @Override
-    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
-        if (sensor != null && Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) {
-            return false;
+    /*
+     * ListenerDelegate is essentially a SensorEventListener, Sensor pair
+     * and is associated with a single SensorEventQueue.
+     */
+    private static final class SensorEventListenerSensorPair {
+        private final SensorEventListener mSensorEventListener;
+        private final Sensor mSensor;
+        public SensorEventListenerSensorPair(SensorEventListener listener, Sensor sensor) {
+            mSensorEventListener = listener;
+            mSensor = sensor;
         }
-        synchronized (mTriggerListeners) {
-            TriggerEventQueue queue = mTriggerListeners.get(listener);
-            if (queue != null) {
-                boolean result;
-                if (sensor == null) {
-                    result = queue.removeAllSensors();
-                } else {
-                    result = queue.removeSensor(sensor);
-                }
-                if (result && !queue.hasSensors()) {
-                    mTriggerListeners.remove(listener);
-                    queue.dispose();
-                }
-                return result;
+        public boolean isSameListenerSensorPair(SensorEventListener listener, Sensor sensor) {
+            // if sensor is null, we match only on the listener
+            if (sensor != null) {
+                return (listener == mSensorEventListener) &&
+                        (sensor.getHandle() == mSensor.getHandle());
+            } else {
+                return (listener == mSensorEventListener);
             }
-            return false;
         }
     }
 
     /*
-     * BaseEventQueue is the communication channel with the sensor service,
-     * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
-     * the queues and the listeners.
+     * SensorEventQueue is the communication channel with the sensor service,
+     * there is a one-to-one mapping between SensorEventQueue and
+     * SensorEventListener.
      */
-    private static abstract class BaseEventQueue {
-        private native int nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ,
-                float[] scratch);
+    private static final class SensorEventQueue {
+        private static native int nativeInitSensorEventQueue(SensorEventQueue eventQ, MessageQueue msgQ, float[] scratch);
         private static native int nativeEnableSensor(int eventQ, int handle, int us);
         private static native int nativeDisableSensor(int eventQ, int handle);
         private static native void nativeDestroySensorEventQueue(int eventQ);
         private int nSensorEventQueue;
+        private final SensorEventListener mListener;
         private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
-        protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
-        protected final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
+        private final SparseIntArray mSensorAccuracies = new SparseIntArray();
+        private final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
         private final CloseGuard mCloseGuard = CloseGuard.get();
         private final float[] mScratch = new float[16];
 
-        BaseEventQueue(Looper looper) {
-            nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch);
+        public SensorEventQueue(SensorEventListener listener, MessageQueue msgQ) {
+            nSensorEventQueue = nativeInitSensorEventQueue(this, msgQ, mScratch);
+            mListener = listener;
             mCloseGuard.open("dispose");
         }
-
         public void dispose() {
             dispose(false);
         }
 
         public boolean addSensor(Sensor sensor, int delay) {
-            // Check if already present.
-            if (mActiveSensors.get(sensor.getHandle())) return false;
-
             if (enableSensor(sensor, delay) == 0) {
                 mActiveSensors.put(sensor.getHandle(), true);
                 return true;
@@ -239,7 +231,7 @@ public class SystemSensorManager extends SensorManager {
             return false;
         }
 
-        public boolean removeAllSensors() {
+        public void removeAllSensors() {
             for (int i=0 ; i<mActiveSensors.size(); i++) {
                 if (mActiveSensors.valueAt(i) == true) {
                     int handle = mActiveSensors.keyAt(i);
@@ -252,24 +244,21 @@ public class SystemSensorManager extends SensorManager {
                     }
                 }
             }
-            return true;
         }
 
-        public boolean removeSensor(Sensor sensor) {
+        public void removeSensor(Sensor sensor) {
             final int handle = sensor.getHandle();
             if (mActiveSensors.get(handle)) {
                 disableSensor(sensor);
                 mActiveSensors.put(sensor.getHandle(), false);
-                return true;
             }
-            return false;
         }
 
         public boolean hasSensors() {
             // no more sensors are set
             return mActiveSensors.indexOfValue(true) >= 0;
         }
-
+        
         @Override
         protected void finalize() throws Throwable {
             try {
@@ -302,30 +291,17 @@ public class SystemSensorManager extends SensorManager {
             if (sensor == null) throw new NullPointerException();
             return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
         }
-        protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
-                long timestamp);
-    }
-
-    static final class SensorEventQueue extends BaseEventQueue {
-        private final SensorEventListener mListener;
-
-        public SensorEventQueue(SensorEventListener listener, Looper looper) {
-            super(looper);
-            mListener = listener;
-        }
 
         // Called from native code.
         @SuppressWarnings("unused")
-        @Override
-        protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,
-                long timestamp) {
-            final Sensor sensor = sHandleToSensor.get(handle);
-            SensorEvent t = sSensorEventPool.acquire();
-            if (t == null) t = new SensorEvent(MAX_EVENTS);
+        private void dispatchSensorEvent(int handle, float[] values, int inAccuracy, long timestamp) {
+            // this is always called on the same thread.
+            final SensorEvent t = sPool.getFromPool();
             try {
-                // Copy the entire values array.
-                // Any changes in length will be handled at the native layer.
-                System.arraycopy(values, 0, t.values, 0, t.values.length);
+                final Sensor sensor = sHandleToSensor.get(handle);
+                final SensorEventListener listener = mListener;
+                // FIXME: handle more than 3 values
+                System.arraycopy(values, 0, t.values, 0, 3);
                 t.timestamp = timestamp;
                 t.accuracy = inAccuracy;
                 t.sensor = sensor;
@@ -337,57 +313,72 @@ public class SystemSensorManager extends SensorManager {
                         final int accuracy = mSensorAccuracies.get(handle);
                         if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
                             mSensorAccuracies.put(handle, t.accuracy);
-                            mListener.onAccuracyChanged(t.sensor, t.accuracy);
+                            listener.onAccuracyChanged(t.sensor, t.accuracy);
                         }
                         break;
                     default:
                         // For other sensors, just report the accuracy once
                         if (mFirstEvent.get(handle) == false) {
                             mFirstEvent.put(handle, true);
-                            mListener.onAccuracyChanged(
+                            listener.onAccuracyChanged(
                                     t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
                         }
                         break;
                 }
-                mListener.onSensorChanged(t);
+                listener.onSensorChanged(t);
             } finally {
-                sSensorEventPool.release(t);
+                sPool.returnToPool(t);
             }
         }
     }
 
-    static final class TriggerEventQueue extends BaseEventQueue {
-        private final TriggerEventListener mListener;
-        private SensorManager mManager;
-
-        public TriggerEventQueue(TriggerEventListener listener, Looper looper,
-                SensorManager manager) {
-            super(looper);
-            mListener = listener;
-            mManager = manager;
+    /*
+     * A dumb pool of SensorEvent
+     */
+    private static final class SensorEventPool {
+        private final int mPoolSize;
+        private final SensorEvent mPool[];
+        private int mNumItemsInPool;
+
+        private SensorEvent createSensorEvent() {
+            // maximal size for all legacy events is 3
+            return new SensorEvent(3);
         }
 
-        // Called from native code.
-        @SuppressWarnings("unused")
-        @Override
-        protected void dispatchSensorEvent(int handle, float[] values, int accuracy, long timestamp) {
-            final Sensor sensor = sHandleToSensor.get(handle);
-            TriggerEvent t = sTriggerEventPool.acquire();
-            if (t == null) t = new TriggerEvent(MAX_EVENTS);
-
-            try {
-                // Copy the entire values array.
-                // Any changes in length will be handled at the native layer.
-                System.arraycopy(values, 0, t.values, 0, t.values.length);
-                t.timestamp = timestamp;
-                t.sensor = sensor;
+        SensorEventPool(int poolSize) {
+            mPoolSize = poolSize;
+            mNumItemsInPool = poolSize;
+            mPool = new SensorEvent[poolSize];
+        }
 
-                // A trigger sensor should be auto disabled.
-                mManager.cancelTriggerSensorImpl(mListener, sensor);
+        SensorEvent getFromPool() {
+            SensorEvent t = null;
+            synchronized (this) {
+                if (mNumItemsInPool > 0) {
+                    // remove the "top" item from the pool
+                    final int index = mPoolSize - mNumItemsInPool;
+                    t = mPool[index];
+                    mPool[index] = null;
+                    mNumItemsInPool--;
+                }
+            }
+            if (t == null) {
+                // the pool was empty or this item was removed from the pool for
+                // the first time. In any case, we need to create a new item.
+                t = createSensorEvent();
+            }
+            return t;
+        }
 
-                mListener.onTrigger(t);
-            } finally {
-                sTriggerEventPool.release(t);
+        void returnToPool(SensorEvent t) {
+            synchronized (this) {
+                // is there space left in the pool?
+                if (mNumItemsInPool < mPoolSize) {
+                    // if so, return the item to the pool
+                    mNumItemsInPool++;
+                    final int index = mPoolSize - mNumItemsInPool;
+                    mPool[index] = t;
+                }
             }
         }
     }
diff --git a/core/java/android/hardware/TriggerEvent.java b/core/java/android/hardware/TriggerEvent.java
deleted file mode 100644 (file)
index bdd39f3..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2013 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.hardware;
-
-/**
- * This class represents a Trigger Event - the event
- * associated with a Trigger Sensor. When the sensor detects a trigger
- * event condition, such as significant motion in the case of the
- * {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the {@link TriggerEventListener}
- * is called with the TriggerEvent. The sensor is automatically canceled
- * after the trigger.
- * <p>
- * This class holds information such as the value of the sensor
- * when the trigger happened, the timestamp along with detailed
- * information regarding the Sensor itself.
- * </p>
- * @see android.hardware.SensorManager
- * @see android.hardware.TriggerEvent
- * @see android.hardware.Sensor
- */
-public final class TriggerEvent {
-    /**
-     * <p>
-     * The length and contents of the {@link #values values} array depends on
-     * which {@link android.hardware.Sensor sensor} type is being monitored (see
-     * also {@link SensorEvent} for a definition of the coordinate system used).
-     * </p>
-     * <h4> {@link Sensor#TYPE_SIGNIFICANT_MOTION} </h4>
-     * The value field is of length 1. value[0] = 1.0 when the sensor triggers.
-     * 1.0 is the only allowed value.
-     */
-    public final float[] values;
-
-    /**
-     * The sensor that generated this event. See
-     * {@link android.hardware.SensorManager SensorManager} for details.
-     */
-    public Sensor sensor;
-
-    /**
-     * The time in nanosecond at which the event happened
-     */
-    public long timestamp;
-
-    TriggerEvent(int size) {
-        values = new float[size];
-    }
-}
diff --git a/core/java/android/hardware/TriggerEventListener.java b/core/java/android/hardware/TriggerEventListener.java
deleted file mode 100644 (file)
index 76b2796..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2013 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.hardware;
-
-/**
- * This class is the listener used to handle Trigger Sensors.
- * Trigger Sensors are sensors that trigger an event and are automatically
- * disabled. {@link Sensor#TYPE_SIGNIFICANT_MOTION} is one such example.
- * <p>
- * SensorManager lets you access the device's {@link android.hardware.Sensor
- * sensors}. Get an instance of this class by calling
- * {@link android.content.Context#getSystemService(java.lang.String)
- * Context.getSystemService()} with the argument
- * {@link android.content.Context#SENSOR_SERVICE}.
- * Usage details are explained in the example below.
- * </p>
- *
- * <pre class="prettyprint">
- * class TriggerListener extends TriggerEventListener {
- *     public void onTrigger(TriggerEvent event) {
- *          // Do Work.
- *
- *     // As it is a one shot sensor, it will be canceled automatically.
- *     // SensorManager.requestTriggerSensor(this, mSigMotion); needs to
- *     // be called again, if needed.
- *     }
- * }
- * public class SensorActivity extends Activity {
- *     private final SensorManager mSensorManager;
- *     private final Sensor mSigMotion;
- *     private final TriggerEventListener mListener = new TriggerEventListener();
- *
- *     public SensorActivity() {
- *         mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
- *         mSigMotion = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
- *     }
- *
- *     protected void onResume() {
- *         super.onResume();
- *         mSensorManager.requestTriggerSensor(mListener, mSigMotion);
- *     }
- *
- *     protected void onPause() {
- *         super.onPause();
- *         // Call disable to ensure that the trigger request has been canceled.
- *         mSensorManager.cancelTriggerSensor(mListener, mSigMotion);
- *     }
- *
- * }
- * </pre>
- *
- * @see TriggerEvent
- * @see Sensor
- */
-public abstract class TriggerEventListener {
-    /**
-     * The method that will be called when the sensor
-     * is triggered. Override this method in your implementation
-     * of this class.
-     *
-     * @param event The details of the event.
-     */
-    public abstract void onTrigger(TriggerEvent event);
-}
index 2d67875..1128230 100644 (file)
@@ -54,7 +54,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
         implements HandlerCaller.Callback {
     private static final String TAG = "InputMethodWrapper";
     private static final boolean DEBUG = false;
-    
+
     private static final int DO_DUMP = 1;
     private static final int DO_ATTACH_TOKEN = 10;
     private static final int DO_SET_INPUT_CONTEXT = 20;
index 288ceff..5a9cde1 100644 (file)
@@ -39,7 +39,6 @@ import android.text.method.MovementMethod;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
-import android.util.Slog;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -49,6 +48,7 @@ import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.WindowManager.BadTokenException;
 import android.view.animation.AnimationUtils;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
@@ -352,7 +352,6 @@ public class InputMethodService extends AbstractInputMethodService {
          * Take care of attaching the given window token provided by the system.
          */
         public void attachToken(IBinder token) {
-            Slog.i(TAG, "attachToken: Existing token=" + mToken + " new token=" + token);
             if (mToken == null) {
                 mToken = token;
                 mWindow.setToken(token);
@@ -419,11 +418,16 @@ public class InputMethodService extends AbstractInputMethodService {
          * Handle a request by the system to show the soft input area.
          */
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
-            if (true || DEBUG) Slog.v(TAG, "showSoftInput()");
+            if (DEBUG) Log.v(TAG, "showSoftInput()");
             boolean wasVis = isInputViewShown();
             mShowInputFlags = 0;
             if (onShowInputRequested(flags, false)) {
-                showWindow(true);
+                try {
+                    showWindow(true);
+                } catch (BadTokenException e) {
+                    if (DEBUG) Log.v(TAG, "BadTokenException: IME is done.");
+                    mWindowVisible = false;
+                }
             }
             // If user uses hard keyboard, IME button should always be shown.
             boolean showing = onEvaluateInputViewShown();
@@ -1390,7 +1394,7 @@ public class InputMethodService extends AbstractInputMethodService {
     }
     
     public void showWindow(boolean showInput) {
-        if (true || DEBUG) Slog.v(TAG, "Showing window: showInput=" + showInput
+        if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
                 + " mShowInputRequested=" + mShowInputRequested
                 + " mWindowAdded=" + mWindowAdded
                 + " mWindowCreated=" + mWindowCreated
index 4b60f07..a554611 100644 (file)
@@ -155,4 +155,14 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
     public void setDependencyMet(boolean met) {
         // Base tracker doesn't handle dependencies
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
index fd22b10..1ebf393 100644 (file)
@@ -77,7 +77,7 @@ public class DhcpStateMachine extends StateMachine {
         RENEW
     };
 
-    private String mInterfaceName;
+    private final String mInterfaceName;
     private boolean mRegisteredForPreDhcpNotification = false;
 
     private static final int BASE = Protocol.BASE_DHCP;
@@ -358,7 +358,7 @@ public class DhcpStateMachine extends StateMachine {
         } else if (dhcpAction == DhcpAction.RENEW) {
             if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
             success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpResults);
-            dhcpResults.updateFromDhcpRequest(mDhcpResults);
+            if (success) dhcpResults.updateFromDhcpRequest(mDhcpResults);
         }
         if (success) {
             if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
index 39440c2..db8f0bc 100644 (file)
@@ -203,6 +203,16 @@ public class DummyDataStateTracker implements NetworkStateTracker {
         // not supported on this network
     }
 
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
+
     static private void log(String s) {
         Slog.d(TAG, s);
     }
index 8947162..b744a47 100644 (file)
@@ -407,4 +407,14 @@ public class EthernetDataTracker implements NetworkStateTracker {
     public void setDependencyMet(boolean met) {
         // not supported on this network
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
index 5d13a18..4457a22 100644 (file)
@@ -22,10 +22,13 @@ import android.os.Parcel;
 import android.text.TextUtils;
 
 import java.net.InetAddress;
+import java.net.Inet4Address;
+
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Hashtable;
 
 /**
  * Describes the properties of a network link.
@@ -47,10 +50,15 @@ import java.util.Collections;
  * don't care which is used.  The gateways will be
  * selected based on the destination address and the
  * source address has no relavence.
+ *
+ * Links can also be stacked on top of each other.
+ * This can be used, for example, to represent a tunnel
+ * interface that runs on top of a physical interface.
+ *
  * @hide
  */
 public class LinkProperties implements Parcelable {
-
+    // The interface described by the network link.
     private String mIfaceName;
     private Collection<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
     private Collection<InetAddress> mDnses = new ArrayList<InetAddress>();
@@ -58,6 +66,11 @@ public class LinkProperties implements Parcelable {
     private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
     private ProxyProperties mHttpProxy;
 
+    // Stores the properties of links that are "stacked" above this link.
+    // Indexed by interface name to allow modification and to prevent duplicates being added.
+    private Hashtable<String, LinkProperties> mStackedLinks =
+        new Hashtable<String, LinkProperties>();
+
     public static class CompareResult<T> {
         public Collection<T> removed = new ArrayList<T>();
         public Collection<T> added = new ArrayList<T>();
@@ -87,6 +100,9 @@ public class LinkProperties implements Parcelable {
             for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
             mHttpProxy = (source.getHttpProxy() == null)  ?
                     null : new ProxyProperties(source.getHttpProxy());
+            for (LinkProperties l: source.mStackedLinks.values()) {
+                addStackedLink(l);
+            }
         }
     }
 
@@ -154,10 +170,25 @@ public class LinkProperties implements Parcelable {
         }
     }
 
+    /**
+     * Returns all the routes on this link.
+     */
     public Collection<RouteInfo> getRoutes() {
         return Collections.unmodifiableCollection(mRoutes);
     }
 
+    /**
+     * Returns all the routes on this link and all the links stacked above it.
+     */
+    public Collection<RouteInfo> getAllRoutes() {
+        Collection<RouteInfo> routes = new ArrayList();
+        routes.addAll(mRoutes);
+        for (LinkProperties stacked: mStackedLinks.values()) {
+            routes.addAll(stacked.getAllRoutes());
+        }
+        return routes;
+    }
+
     public void setHttpProxy(ProxyProperties proxy) {
         mHttpProxy = proxy;
     }
@@ -165,6 +196,46 @@ public class LinkProperties implements Parcelable {
         return mHttpProxy;
     }
 
+    /**
+     * Adds a stacked link.
+     *
+     * If there is already a stacked link with the same interfacename as link,
+     * that link is replaced with link. Otherwise, link is added to the list
+     * of stacked links. If link is null, nothing changes.
+     *
+     * @param link The link to add.
+     */
+    public void addStackedLink(LinkProperties link) {
+        if (link != null && link.getInterfaceName() != null) {
+            mStackedLinks.put(link.getInterfaceName(), link);
+        }
+    }
+
+    /**
+     * Removes a stacked link.
+     *
+     * If there a stacked link with the same interfacename as link, it is
+     * removed. Otherwise, nothing changes.
+     *
+     * @param link The link to add.
+     */
+    public void removeStackedLink(LinkProperties link) {
+        if (link != null && link.getInterfaceName() != null) {
+            mStackedLinks.remove(link.getInterfaceName());
+        }
+    }
+
+    /**
+     * Returns all the links stacked on top of this link.
+     */
+    public Collection<LinkProperties> getStackedLinks() {
+        Collection<LinkProperties> stacked = new ArrayList<LinkProperties>();
+        for (LinkProperties link : mStackedLinks.values()) {
+          stacked.add(new LinkProperties(link));
+        }
+        return Collections.unmodifiableCollection(stacked);
+    }
+
     public void clear() {
         mIfaceName = null;
         mLinkAddresses.clear();
@@ -172,6 +243,7 @@ public class LinkProperties implements Parcelable {
         mDomains = null;
         mRoutes.clear();
         mHttpProxy = null;
+        mStackedLinks.clear();
     }
 
     /**
@@ -201,7 +273,29 @@ public class LinkProperties implements Parcelable {
         routes += "] ";
         String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
 
-        return ifaceName + linkAddresses + routes + dns + domainName + proxy;
+        String stacked = "";
+        if (mStackedLinks.values().size() > 0) {
+            stacked += " Stacked: [";
+            for (LinkProperties link: mStackedLinks.values()) {
+                stacked += " [" + link.toString() + " ],";
+            }
+            stacked += "] ";
+        }
+        return ifaceName + linkAddresses + routes + dns + domainName + proxy + stacked;
+    }
+
+    /**
+     * Returns true if this link has an IPv4 address.
+     *
+     * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+     */
+    public boolean hasIPv4Address() {
+        for (LinkAddress address : mLinkAddresses) {
+          if (address.getAddress() instanceof Inet4Address) {
+            return true;
+          }
+        }
+        return false;
     }
 
     /**
@@ -268,6 +362,26 @@ public class LinkProperties implements Parcelable {
                     getHttpProxy().equals(target.getHttpProxy());
     }
 
+    /**
+     * Compares this {@code LinkProperties} stacked links against the target
+     *
+     * @param target LinkProperties to compare.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public boolean isIdenticalStackedLinks(LinkProperties target) {
+        if (!mStackedLinks.keys().equals(target.mStackedLinks.keys())) {
+            return false;
+        }
+        for (LinkProperties stacked : mStackedLinks.values()) {
+            // Hashtable values can never be null.
+            String iface = stacked.getInterfaceName();
+            if (!stacked.equals(target.mStackedLinks.get(iface))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     /**
      * Compares this {@code LinkProperties} instance against the target
@@ -280,6 +394,10 @@ public class LinkProperties implements Parcelable {
      * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
      * 2. Worst case performance is O(n^2).
      *
+     * This method does not check that stacked interfaces are equal, because
+     * stacked interfaces are not so much a property of the link as a
+     * description of connections between links.
+     *
      * @param obj the object to be tested for equality.
      * @return {@code true} if both objects are equal, {@code false} otherwise.
      */
@@ -294,7 +412,8 @@ public class LinkProperties implements Parcelable {
                 isIdenticalAddresses(target) &&
                 isIdenticalDnses(target) &&
                 isIdenticalRoutes(target) &&
-                isIdenticalHttpProxy(target);
+                isIdenticalHttpProxy(target) &&
+                isIdenticalStackedLinks(target);
     }
 
     /**
@@ -376,10 +495,10 @@ public class LinkProperties implements Parcelable {
          */
         CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
 
-        result.removed = new ArrayList<RouteInfo>(mRoutes);
+        result.removed = getAllRoutes();
         result.added.clear();
         if (target != null) {
-            for (RouteInfo r : target.getRoutes()) {
+            for (RouteInfo r : target.getAllRoutes()) {
                 if (! result.removed.remove(r)) {
                     result.added.add(r);
                 }
@@ -401,7 +520,8 @@ public class LinkProperties implements Parcelable {
                 + mDnses.size() * 37
                 + ((null == mDomains) ? 0 : mDomains.hashCode())
                 + mRoutes.size() * 41
-                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()));
+                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
+                + mStackedLinks.hashCode() * 47);
     }
 
     /**
@@ -431,6 +551,8 @@ public class LinkProperties implements Parcelable {
         } else {
             dest.writeByte((byte)0);
         }
+        ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
+        dest.writeList(stackedLinks);
     }
 
     /**
@@ -463,6 +585,11 @@ public class LinkProperties implements Parcelable {
                 if (in.readByte() == 1) {
                     netProp.setHttpProxy((ProxyProperties)in.readParcelable(null));
                 }
+                ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
+                in.readList(stackedLinks, LinkProperties.class.getClassLoader());
+                for (LinkProperties stackedLink: stackedLinks) {
+                    netProp.addStackedLink(stackedLink);
+                }
                 return netProp;
             }
 
index b35d61c..faf739b 100644 (file)
@@ -522,6 +522,16 @@ public class MobileDataStateTracker implements NetworkStateTracker {
     }
 
     @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
+
+    @Override
     public String toString() {
         final CharArrayWriter writer = new CharArrayWriter();
         final PrintWriter pw = new PrintWriter(writer);
index 0a0c1e0..b22159c 100644 (file)
@@ -197,4 +197,14 @@ public interface NetworkStateTracker {
      * An external dependency has been met/unmet
      */
     public void setDependencyMet(boolean met);
+
+    /**
+     * Informs the state tracker that another interface is stacked on top of it.
+     **/
+    public void addStackedLink(LinkProperties link);
+
+    /**
+     * Informs the state tracker that a stacked interface has been removed.
+     **/
+    public void removeStackedLink(LinkProperties link);
 }
index 2bec1c1..9666d9a 100644 (file)
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.util.Log;
+
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -25,6 +27,8 @@ import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.regex.Pattern;
 import java.util.zip.CRC32;
 import java.util.zip.CheckedInputStream;
@@ -34,6 +38,8 @@ import java.util.zip.CheckedInputStream;
  * @hide
  */
 public class FileUtils {
+    private static final String TAG = "FileUtils";
+
     public static final int S_IRWXU = 00700;
     public static final int S_IRUSR = 00400;
     public static final int S_IWUSR = 00200;
@@ -161,7 +167,8 @@ public class FileUtils {
             } else if (max < 0) {  // "tail" mode: keep the last N
                 int len;
                 boolean rolled = false;
-                byte[] last = null, data = null;
+                byte[] last = null;
+                byte[] data = null;
                 do {
                     if (last != null) rolled = true;
                     byte[] tmp = last; last = data; data = tmp;
@@ -237,4 +244,40 @@ public class FileUtils {
             }
         }
     }
+
+    /**
+     * Delete older files in a directory until only those matching the given
+     * constraints remain.
+     *
+     * @param minCount Always keep at least this many files.
+     * @param minAge Always keep files younger than this age.
+     */
+    public static void deleteOlderFiles(File dir, int minCount, long minAge) {
+        if (minCount < 0 || minAge < 0) {
+            throw new IllegalArgumentException("Constraints must be positive or 0");
+        }
+
+        final File[] files = dir.listFiles();
+        if (files == null) return;
+
+        // Sort with newest files first
+        Arrays.sort(files, new Comparator<File>() {
+            @Override
+            public int compare(File lhs, File rhs) {
+                return (int) (rhs.lastModified() - lhs.lastModified());
+            }
+        });
+
+        // Keep at least minCount files
+        for (int i = minCount; i < files.length; i++) {
+            final File file = files[i];
+
+            // Keep files newer than minAge
+            final long age = System.currentTimeMillis() - file.lastModified();
+            if (age > minAge) {
+                Log.d(TAG, "Deleting old file " + file);
+                file.delete();
+            }
+        }
+    }
 }
index 266d0d3..d251ca2 100644 (file)
@@ -4784,6 +4784,13 @@ public final class Settings {
        public static final String WIFI_ON = "wifi_on";
 
        /**
+        * Setting to allow scans to be enabled even wifi is turned off for connectivity.
+        * @hide
+        */
+       public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
+                "wifi_scan_always_enabled";
+
+       /**
         * Used to save the Wifi_ON state prior to tethering.
         * This state will be checked to restore Wifi after
         * the user turns off tethering.
@@ -5345,6 +5352,7 @@ public final class Settings {
             WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
             WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
             WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+            WIFI_SCAN_ALWAYS_AVAILABLE,
             WIFI_NUM_OPEN_NETWORKS_KEPT,
             EMERGENCY_TONE,
             CALL_AUTO_RETRY,
index 60545e5..831ccc5 100644 (file)
@@ -38,7 +38,7 @@ extends CharSequence
      * {@hide}
      */
     void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint p);
+            float x, float y, int flags, Paint p);
 
    /**
      * Just like {@link Paint#measureText}.
@@ -55,12 +55,19 @@ extends CharSequence
      * @hide
      */
     float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
-            float[] advances, int advancesIndex, Paint paint);
+            int flags, float[] advances, int advancesIndex, Paint paint);
+
+    /**
+     * Just like {@link Paint#getTextRunAdvances}.
+     * @hide
+     */
+    float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+            int flags, float[] advances, int advancesIndex, Paint paint, int reserved);
 
     /**
      * Just like {@link Paint#getTextRunCursor}.
      * @hide
      */
-    int getTextRunCursor(int contextStart, int contextEnd, int offset,
+    int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
             int cursorOpt, Paint p);
 }
index 0c881a4..bd9310c 100644 (file)
@@ -159,15 +159,18 @@ class MeasuredText {
         mPos = p + len;
 
         if (mEasy) {
-            return paint.getTextRunAdvances(mChars, p, len, p, len, mWidths, p);
+            int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
+                ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
+            return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
         }
 
         float totalAdvance = 0;
         int level = mLevels[p];
         for (int q = p, i = p + 1, e = p + len;; ++i) {
             if (i == e || mLevels[i] != level) {
+                int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
                 totalAdvance +=
-                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, mWidths, q);
+                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
                 if (i == e) {
                     break;
                 }
index 9e43671..0f30d25 100644 (file)
@@ -1130,20 +1130,20 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
      * {@hide}
      */
     public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint p) {
+            float x, float y, int flags, Paint p) {
         checkRange("drawTextRun", start, end);
 
         int contextLen = contextEnd - contextStart;
         int len = end - start;
         if (contextEnd <= mGapStart) {
-            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, p);
+            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
         } else if (contextStart >= mGapStart) {
             c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
-                    contextLen, x, y, p);
+                    contextLen, x, y, flags, p);
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
-            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, p);
+            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
             TextUtils.recycle(buf);
         }
     }
@@ -1200,7 +1200,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
      * Don't call this yourself -- exists for Paint to use internally.
      * {@hide}
      */
-    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
             float[] advances, int advancesPos, Paint p) {
 
         float ret;
@@ -1210,15 +1210,44 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
 
         if (end <= mGapStart) {
             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
-                    advances, advancesPos);
+                    flags, advances, advancesPos);
         } else if (start >= mGapStart) {
             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
-                    contextStart + mGapLength, contextLen, advances, advancesPos);
+                    contextStart + mGapLength, contextLen, flags, advances, advancesPos);
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
             ret = p.getTextRunAdvances(buf, start - contextStart, len,
-                    0, contextLen, advances, advancesPos);
+                    0, contextLen, flags, advances, advancesPos);
+            TextUtils.recycle(buf);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Don't call this yourself -- exists for Paint to use internally.
+     * {@hide}
+     */
+    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+            float[] advances, int advancesPos, Paint p, int reserved) {
+
+        float ret;
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+
+        if (end <= mGapStart) {
+            ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
+                    flags, advances, advancesPos, reserved);
+        } else if (start >= mGapStart) {
+            ret = p.getTextRunAdvances(mText, start + mGapLength, len,
+                    contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
+        } else {
+            char[] buf = TextUtils.obtain(contextLen);
+            getChars(contextStart, contextEnd, buf, 0);
+            ret = p.getTextRunAdvances(buf, start - contextStart, len,
+                    0, contextLen, flags, advances, advancesPos, reserved);
             TextUtils.recycle(buf);
         }
 
@@ -1241,7 +1270,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
      *
      * @param contextStart the start index of the context
      * @param contextEnd the (non-inclusive) end index of the context
-     * @param flags reserved
+     * @param flags either DIRECTION_RTL or DIRECTION_LTR
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
      * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
@@ -1252,30 +1281,22 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
      */
     @Deprecated
     public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
-                                int cursorOpt, Paint p) {
-        return getTextRunCursor(contextStart, contextEnd, offset, cursorOpt, p);
-    }
-
-    /**
-     * @hide
-     */
-    public int getTextRunCursor(int contextStart, int contextEnd, int offset,
-                                int cursorOpt, Paint p) {
+            int cursorOpt, Paint p) {
 
         int ret;
 
         int contextLen = contextEnd - contextStart;
         if (contextEnd <= mGapStart) {
             ret = p.getTextRunCursor(mText, contextStart, contextLen,
-                    offset, cursorOpt);
+                    flags, offset, cursorOpt);
         } else if (contextStart >= mGapStart) {
             ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
-                    offset + mGapLength, cursorOpt) - mGapLength;
+                    flags, offset + mGapLength, cursorOpt) - mGapLength;
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
             ret = p.getTextRunCursor(buf, 0, contextLen,
-                    offset - contextStart, cursorOpt) + contextStart;
+                    flags, offset - contextStart, cursorOpt) + contextStart;
             TextUtils.recycle(buf);
         }
 
index e34a0ef..1fecf81 100644 (file)
@@ -664,13 +664,14 @@ class TextLine {
             }
         }
 
+        int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
         int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
         if (mCharsValid) {
             return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
-                    offset, cursorOpt);
+                    flags, offset, cursorOpt);
         } else {
             return wp.getTextRunCursor(mText, mStart + spanStart,
-                    mStart + spanLimit, mStart + offset, cursorOpt) - mStart;
+                    mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
         }
     }
 
@@ -737,13 +738,15 @@ class TextLine {
 
         int contextLen = contextEnd - contextStart;
         if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
+            int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
             if (mCharsValid) {
                 ret = wp.getTextRunAdvances(mChars, start, runLen,
-                        contextStart, contextLen, null, 0);
+                        contextStart, contextLen, flags, null, 0);
             } else {
                 int delta = mStart;
-                ret = wp.getTextRunAdvances(mText, delta + start, delta + end,
-                        delta + contextStart, delta + contextEnd, null, 0);
+                ret = wp.getTextRunAdvances(mText, delta + start,
+                        delta + end, delta + contextStart, delta + contextEnd,
+                        flags, null, 0);
             }
         }
 
@@ -783,7 +786,8 @@ class TextLine {
                 wp.setAntiAlias(previousAntiAlias);
             }
 
-            drawTextRun(c, wp, start, end, contextStart, contextEnd, x, y + wp.baselineShift);
+            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
+                    x, y + wp.baselineShift);
         }
 
         return runIsRtl ? -ret : ret;
@@ -966,21 +970,23 @@ class TextLine {
      * @param end the end of the run
      * @param contextStart the start of context for the run
      * @param contextEnd the end of the context for the run
+     * @param runIsRtl true if the run is right-to-left
      * @param x the x position of the left edge of the run
      * @param y the baseline of the run
      */
     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
-            int contextStart, int contextEnd, float x, int y) {
+            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
 
+        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
         if (mCharsValid) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             c.drawTextRun(mChars, start, count, contextStart, contextCount,
-                    x, y, wp);
+                    x, y, flags, wp);
         } else {
             int delta = mStart;
             c.drawTextRun(mText, delta + start, delta + end,
-                    delta + contextStart, delta + contextEnd, x, y, wp);
+                    delta + contextStart, delta + contextEnd, x, y, flags, wp);
         }
     }
 
index 370cbf7..c5a77a6 100644 (file)
@@ -172,9 +172,9 @@ public final class BidiFormatter {
      * A class for building a BidiFormatter with non-default options.
      */
     public static final class Builder {
-        private boolean isRtlContext;
-        private int flags;
-        private TextDirectionHeuristic textDirectionHeuristic;
+        private boolean mIsRtlContext;
+        private int mFlags;
+        private TextDirectionHeuristic mTextDirectionHeuristic;
 
         /**
          * Constructor.
@@ -208,9 +208,9 @@ public final class BidiFormatter {
          * @param isRtlContext Whether the context is RTL or not.
          */
         private void initialize(boolean isRtlContext) {
-            this.isRtlContext = isRtlContext;
-            textDirectionHeuristic = DEFAULT_TEXT_DIRECTION_HEURISTIC;
-            this.flags = DEFAULT_FLAGS;
+            mIsRtlContext = isRtlContext;
+            mTextDirectionHeuristic = DEFAULT_TEXT_DIRECTION_HEURISTIC;
+            mFlags = DEFAULT_FLAGS;
         }
 
         /**
@@ -219,9 +219,9 @@ public final class BidiFormatter {
          */
         public Builder stereoReset(boolean stereoReset) {
             if (stereoReset) {
-                flags |= FLAG_STEREO_RESET;
+                mFlags |= FLAG_STEREO_RESET;
             } else {
-                flags &= ~FLAG_STEREO_RESET;
+                mFlags &= ~FLAG_STEREO_RESET;
             }
             return this;
         }
@@ -234,7 +234,7 @@ public final class BidiFormatter {
          * @return the builder itself.
          */
         public Builder setTextDirectionHeuristic(TextDirectionHeuristic heuristic) {
-            this.textDirectionHeuristic = heuristic;
+            mTextDirectionHeuristic = heuristic;
             return this;
         }
 
@@ -246,11 +246,11 @@ public final class BidiFormatter {
          * @return A BidiFormatter with the specified options.
          */
         public BidiFormatter build() {
-            if (flags == DEFAULT_FLAGS &&
-                    textDirectionHeuristic == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
-                return getDefaultInstanceFromContext(isRtlContext);
+            if (mFlags == DEFAULT_FLAGS &&
+                    mTextDirectionHeuristic == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
+                return getDefaultInstanceFromContext(mIsRtlContext);
             }
-            return new BidiFormatter(isRtlContext, flags, textDirectionHeuristic);
+            return new BidiFormatter(mIsRtlContext, mFlags, mTextDirectionHeuristic);
         }
     }
 
@@ -268,9 +268,9 @@ public final class BidiFormatter {
             DEFAULT_FLAGS,
             DEFAULT_TEXT_DIRECTION_HEURISTIC);
 
-    private final boolean isRtlContext;
-    private final int flags;
-    private final TextDirectionHeuristic defaultTextDirectionHeuristic;
+    private final boolean mIsRtlContext;
+    private final int mFlags;
+    private final TextDirectionHeuristic mDefaultTextDirectionHeuristic;
 
     /**
      * Factory for creating an instance of BidiFormatter given the context directionality.
@@ -296,16 +296,16 @@ public final class BidiFormatter {
      * @param heuristic The default text direction heuristic.
      */
     private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristic heuristic) {
-        this.isRtlContext = isRtlContext;
-        this.flags = flags;
-        this.defaultTextDirectionHeuristic = heuristic;
+        mIsRtlContext = isRtlContext;
+        mFlags = flags;
+        mDefaultTextDirectionHeuristic = heuristic;
     }
 
     /**
      * @return Whether the context directionality is RTL
      */
     public boolean isRtlContext() {
-        return isRtlContext;
+        return mIsRtlContext;
     }
 
     /**
@@ -313,7 +313,7 @@ public final class BidiFormatter {
      * bidi-wrapped, not just after it.
      */
     public boolean getStereoReset() {
-        return (flags & FLAG_STEREO_RESET) != 0;
+        return (mFlags & FLAG_STEREO_RESET) != 0;
     }
 
     /**
@@ -384,7 +384,7 @@ public final class BidiFormatter {
      *     context; else, the empty string.
      */
     public String dirAttr(boolean isRtl) {
-        return (isRtl != isRtlContext) ? (isRtl ? DIR_RTL_STRING :  DIR_LTR_STRING) : EMPTY_STRING;
+        return (isRtl != mIsRtlContext) ? (isRtl ? DIR_RTL_STRING :  DIR_LTR_STRING) : EMPTY_STRING;
     }
 
     /**
@@ -401,7 +401,7 @@ public final class BidiFormatter {
      *     else, the empty string.
      */
     public String markAfter(String str) {
-        return markAfter(str, defaultTextDirectionHeuristic);
+        return markAfter(str, mDefaultTextDirectionHeuristic);
     }
 
     /**
@@ -417,10 +417,10 @@ public final class BidiFormatter {
     public String markAfter(String str, TextDirectionHeuristic heuristic) {
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
         // getExitDir() is called only if needed (short-circuit).
-        if (!isRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
+        if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
             return LRM_STRING;
         }
-        if (isRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
+        if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
             return RLM_STRING;
         }
         return EMPTY_STRING;
@@ -440,7 +440,7 @@ public final class BidiFormatter {
      *     else, the empty string.
      */
     public String markBefore(String str) {
-        return markBefore(str, defaultTextDirectionHeuristic);
+        return markBefore(str, mDefaultTextDirectionHeuristic);
     }
 
     /**
@@ -456,10 +456,10 @@ public final class BidiFormatter {
     public String markBefore(String str, TextDirectionHeuristic heuristic) {
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
         // getEntryDir() is called only if needed (short-circuit).
-        if (!isRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
+        if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
             return LRM_STRING;
         }
-        if (isRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
+        if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
             return RLM_STRING;
         }
         return EMPTY_STRING;
@@ -470,7 +470,7 @@ public final class BidiFormatter {
      * directionality, RLM for RTL context directionality).
      */
     public String mark() {
-        return isRtlContext ? RLM_STRING : LRM_STRING;
+        return mIsRtlContext ? RLM_STRING : LRM_STRING;
     }
 
     /**
@@ -478,7 +478,7 @@ public final class BidiFormatter {
      * returns "left".
      */
     public String startEdge() {
-        return isRtlContext  ? RIGHT : LEFT;
+        return mIsRtlContext ? RIGHT : LEFT;
     }
 
     /**
@@ -486,7 +486,7 @@ public final class BidiFormatter {
      * returns "right".
      */
     public String endEdge() {
-        return isRtlContext ? LEFT : RIGHT;
+        return mIsRtlContext ? LEFT : RIGHT;
     }
 
     /**
@@ -497,7 +497,7 @@ public final class BidiFormatter {
      *          false.
      */
     public boolean isRtl(String str) {
-        return defaultTextDirectionHeuristic.isRtl(str, 0, str.length());
+        return mDefaultTextDirectionHeuristic.isRtl(str, 0, str.length());
     }
 
     /**
@@ -536,7 +536,7 @@ public final class BidiFormatter {
             result.append(markBefore(origStr,
                     isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
         }
-        if (isRtl != isRtlContext) {
+        if (isRtl != mIsRtlContext) {
             result.append("<span ").append(dirAttr(isRtl)).append('>').append(str).append("</span>");
         } else {
             result.append(str);
@@ -570,7 +570,7 @@ public final class BidiFormatter {
      * @return Input string after applying the above processing.
      */
     public String spanWrap(String str, boolean isolate) {
-        return spanWrap(str, defaultTextDirectionHeuristic, isolate);
+        return spanWrap(str, mDefaultTextDirectionHeuristic, isolate);
     }
 
     /**
@@ -581,7 +581,7 @@ public final class BidiFormatter {
      * @return Input string after applying the above processing.
      */
     public String spanWrap(String str) {
-        return spanWrap(str, defaultTextDirectionHeuristic, true /* isolate */);
+        return spanWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
     }
 
     /**
@@ -620,7 +620,7 @@ public final class BidiFormatter {
             result.append(markBefore(str,
                     isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
         }
-        if (isRtl != isRtlContext) {
+        if (isRtl != mIsRtlContext) {
             result.append(isRtl ? RLE : LRE);
             result.append(str);
             result.append(PDF);
@@ -656,7 +656,7 @@ public final class BidiFormatter {
      * @return Input string after applying the above processing.
      */
     public String unicodeWrap(String str, boolean isolate) {
-        return unicodeWrap(str, defaultTextDirectionHeuristic, isolate);
+        return unicodeWrap(str, mDefaultTextDirectionHeuristic, isolate);
     }
 
     /**
@@ -667,7 +667,7 @@ public final class BidiFormatter {
      * @return Input string after applying the above processing.
      */
     public String unicodeWrap(String str) {
-        return unicodeWrap(str, defaultTextDirectionHeuristic, true /* isolate */);
+        return unicodeWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
     }
 
     /**
index 6c48e43..c369ebe 100644 (file)
@@ -1162,14 +1162,14 @@ class GLES20Canvas extends HardwareCanvas {
 
         int modifiers = setupModifiers(paint);
         try {
-            nDrawText(mRenderer, text, index, count, x, y, paint.mNativePaint);
+            nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
     
     private static native void nDrawText(int renderer, char[] text, int index, int count,
-            float x, float y, int paint);
+            float x, float y, int bidiFlags, int paint);
 
     @Override
     public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
@@ -1177,14 +1177,16 @@ class GLES20Canvas extends HardwareCanvas {
         try {
             if (text instanceof String || text instanceof SpannedString ||
                     text instanceof SpannableString) {
-                nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mNativePaint);
+                nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
+                        paint.mNativePaint);
             } else if (text instanceof GraphicsOperations) {
                 ((GraphicsOperations) text).drawText(this, start, end, x, y,
                                                          paint);
             } else {
                 char[] buf = TemporaryBuffer.obtain(end - start);
                 TextUtils.getChars(text, start, end, buf, 0);
-                nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mNativePaint);
+                nDrawText(mRenderer, buf, 0, end - start, x, y,
+                        paint.mBidiFlags, paint.mNativePaint);
                 TemporaryBuffer.recycle(buf);
             }
         } finally {
@@ -1200,20 +1202,21 @@ class GLES20Canvas extends HardwareCanvas {
 
         int modifiers = setupModifiers(paint);
         try {
-            nDrawText(mRenderer, text, start, end, x, y, paint.mNativePaint);
+            nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
     private static native void nDrawText(int renderer, String text, int start, int end,
-            float x, float y, int paint);
+            float x, float y, int bidiFlags, int paint);
 
     @Override
     public void drawText(String text, float x, float y, Paint paint) {
         int modifiers = setupModifiers(paint);
         try {
-            nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mNativePaint);
+            nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
+                    paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
@@ -1229,14 +1232,14 @@ class GLES20Canvas extends HardwareCanvas {
         int modifiers = setupModifiers(paint);
         try {
             nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
-                    paint.mNativePaint);
+                    paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
     private static native void nDrawTextOnPath(int renderer, char[] text, int index, int count,
-            int path, float hOffset, float vOffset, int nativePaint);
+            int path, float hOffset, float vOffset, int bidiFlags, int nativePaint);
 
     @Override
     public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
@@ -1245,25 +1248,28 @@ class GLES20Canvas extends HardwareCanvas {
         int modifiers = setupModifiers(paint);
         try {
             nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
-                    paint.mNativePaint);
+                    paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
     private static native void nDrawTextOnPath(int renderer, String text, int start, int end,
-            int path, float hOffset, float vOffset, int nativePaint);
+            int path, float hOffset, float vOffset, int bidiFlags, int nativePaint);
 
     @Override
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
         if ((index | count | text.length - index - count) < 0) {
             throw new IndexOutOfBoundsException();
         }
+        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
+            throw new IllegalArgumentException("Unknown direction: " + dir);
+        }
 
         int modifiers = setupModifiers(paint);
         try {
-            nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y,
+            nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
                     paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
@@ -1271,31 +1277,32 @@ class GLES20Canvas extends HardwareCanvas {
     }
 
     private static native void nDrawTextRun(int renderer, char[] text, int index, int count,
-            int contextIndex, int contextCount, float x, float y, int nativePaint);
+            int contextIndex, int contextCount, float x, float y, int dir, int nativePaint);
 
     @Override
     public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
         if ((start | end | end - start | text.length() - end) < 0) {
             throw new IndexOutOfBoundsException();
         }
 
         int modifiers = setupModifiers(paint);
         try {
+            int flags = dir == 0 ? 0 : 1;
             if (text instanceof String || text instanceof SpannedString ||
                     text instanceof SpannableString) {
                 nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
-                        contextEnd, x, y, paint.mNativePaint);
+                        contextEnd, x, y, flags, paint.mNativePaint);
             } else if (text instanceof GraphicsOperations) {
                 ((GraphicsOperations) text).drawTextRun(this, start, end,
-                        contextStart, contextEnd, x, y, paint);
+                        contextStart, contextEnd, x, y, flags, paint);
             } else {
                 int contextLen = contextEnd - contextStart;
                 int len = end - start;
                 char[] buf = TemporaryBuffer.obtain(contextLen);
                 TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
                 nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
-                        x, y, paint.mNativePaint);
+                        x, y, flags, paint.mNativePaint);
                 TemporaryBuffer.recycle(buf);
             }
         } finally {
@@ -1304,7 +1311,7 @@ class GLES20Canvas extends HardwareCanvas {
     }
 
     private static native void nDrawTextRun(int renderer, String text, int start, int end,
-            int contextStart, int contextEnd, float x, float y, int nativePaint);
+            int contextStart, int contextEnd, float x, float y, int flags, int nativePaint);
 
     @Override
     public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
index 947cf44..7da2451 100644 (file)
@@ -267,15 +267,15 @@ class GLES20RecordingCanvas extends GLES20Canvas {
 
     @Override
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, Paint paint) {
-        super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, paint);
+            float x, float y, int dir, Paint paint) {
+        super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, dir, paint);
         recordShaderBitmap(paint);
     }
 
     @Override
     public void drawTextRun(CharSequence text, int start, int end, int contextStart,
-            int contextEnd, float x, float y, Paint paint) {
-        super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, paint);
+            int contextEnd, float x, float y, int dir, Paint paint) {
+        super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, dir, paint);
         recordShaderBitmap(paint);
     }
 
diff --git a/core/java/android/view/Overlay.java b/core/java/android/view/Overlay.java
new file mode 100644 (file)
index 0000000..f15d4d2
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2013 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.view;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * An overlay is an extra layer that sits on top of a View (the "host view") which is drawn after
+ * all other content in that view (including children, if the view is a ViewGroup). Interaction
+ * with the overlay layer is done in terms of adding/removing views and drawables. Invalidation and
+ * redrawing of the overlay layer (and its host view) is handled differently for views versus
+ * drawables in the overlay. Views invalidate themselves as usual, causing appropriate redrawing
+ * to occur automatically. Drawables, on the other hand, do not manage invalidation, so changes to
+ * drawable objects should be accompanied by appropriate calls to invalidate() on the host view.
+ *
+ * @see android.view.View#getOverlay()
+ */
+public interface Overlay {
+
+    /**
+     * Adds a Drawable to the overlay. The bounds of the drawable should be relative to
+     * the host view. Any drawable added to the overlay should be removed when it is no longer
+     * needed or no longer visible. There is no automatic invalidation of the host view; changes to
+     * the drawable should be accompanied by appropriate invalidation calls to the host view
+     * to cause the proper area of the view, and the overlay, to be redrawn.
+     *
+     * @param drawable The Drawable to be added to the overlay. This drawable will be
+     * drawn when the view redraws its overlay.
+     * @see #remove(android.graphics.drawable.Drawable)
+     * @see #add(View)
+     */
+    void add(Drawable drawable);
+
+    /**
+     * Removes the specified Drawable from the overlay.
+     *
+     * @param drawable The Drawable to be removed from the overlay.
+     * @see #add(android.graphics.drawable.Drawable)
+     */
+    void remove(Drawable drawable);
+
+    /**
+     * Adds a View to the overlay. The bounds of the added view should be relative to
+     * the host view. Any view added to the overlay should be removed when it is no longer
+     * needed or no longer visible. The view must not be parented elsewhere when it is added
+     * to the overlay.
+     *
+     * @param view The View to be added to the overlay. The added view will be
+     * drawn when the overlay is drawn.
+     * @see #remove(View)
+     * @see #add(android.graphics.drawable.Drawable)
+     */
+    void add(View view);
+
+    /**
+     * Removes the specified View from the overlay.
+     *
+     * @param view The View to be removed from the overlay.
+     * @see #add(View)
+     */
+    void remove(View view);
+
+    /**
+     * Removes all views and drawables from the overlay.
+     */
+    void clear();
+}
index 9955bc1..0492d29 100644 (file)
@@ -39,7 +39,6 @@ public class Surface implements Parcelable {
     private native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas);
 
     private static native void nativeRelease(int nativeObject);
-    private static native void nativeDestroy(int nativeObject);
     private static native boolean nativeIsValid(int nativeObject);
     private static native boolean nativeIsConsumerRunningBehind(int nativeObject);
     private static native int nativeCopyFrom(int nativeObject, int surfaceControlNativeObject);
@@ -106,7 +105,6 @@ public class Surface implements Parcelable {
      * @hide
      */
     public Surface() {
-        mCloseGuard.open("release");
     }
 
     /**
@@ -135,6 +133,7 @@ public class Surface implements Parcelable {
         mCloseGuard.open("release");
     }
 
+    /* called from android_view_Surface_createFromIGraphicBufferProducer() */
     private Surface(int nativeObject) {
         mNativeObject = nativeObject;
         mCloseGuard.open("release");
@@ -146,9 +145,7 @@ public class Surface implements Parcelable {
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-            if (mNativeObject != 0) {
-                nativeRelease(mNativeObject);
-            }
+            release();
         } finally {
             super.finalize();
         }
@@ -175,12 +172,7 @@ public class Surface implements Parcelable {
      * @hide
      */
     public void destroy() {
-        if (mNativeObject != 0) {
-            nativeDestroy(mNativeObject);
-            mNativeObject = 0;
-            mGenerationId++;
-        }
-        mCloseGuard.close();
+        release();
     }
 
     /**
@@ -287,6 +279,10 @@ public class Surface implements Parcelable {
                     "SurfaceControl native object is null. Are you using a released SurfaceControl?");
         }
         mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject);
+        if (mNativeObject == 0) {
+            // nativeCopyFrom released our reference
+            mCloseGuard.close();
+        }
         mGenerationId++;
     }
 
@@ -308,11 +304,15 @@ public class Surface implements Parcelable {
                 nativeRelease(mNativeObject);
             }
             // transfer the reference from other to us
+            if (other.mNativeObject != 0 && mNativeObject == 0) {
+                mCloseGuard.open("release");
+            }
             mNativeObject = other.mNativeObject;
             mGenerationId++;
 
             other.mNativeObject = 0;
             other.mGenerationId++;
+            other.mCloseGuard.close();
         }
     }
 
@@ -326,7 +326,11 @@ public class Surface implements Parcelable {
             throw new IllegalArgumentException("source must not be null");
         }
         mName = source.readString();
-        mNativeObject = nativeReadFromParcel(mNativeObject, source);
+        int nativeObject = nativeReadFromParcel(mNativeObject, source);
+        if (nativeObject !=0 && mNativeObject == 0) {
+            mCloseGuard.open("release");
+        }
+        mNativeObject = nativeObject;
         mGenerationId++;
     }
 
index 2e60f51..2fa9484 100644 (file)
@@ -3223,6 +3223,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     AccessibilityDelegate mAccessibilityDelegate;
 
     /**
+     * The view's overlay layer. Developers get a reference to the overlay via getOverlay()
+     * and add/remove objects to/from the overlay directly through the Overlay methods.
+     */
+    ViewOverlay mOverlay;
+
+    /**
      * Consistency verifier for debugging purposes.
      * @hide
      */
@@ -5038,7 +5044,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             }
 
             if ((mAttachInfo.mAccessibilityFetchFlags
-                    & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+                    & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
+                    && Resources.resourceHasPackage(mID)) {
                 try {
                     String viewId = getResources().getResourceName(mID);
                     info.setViewIdResourceName(viewId);
@@ -6068,8 +6075,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             mTransientStateCount = 0;
             Log.e(VIEW_LOG_TAG, "hasTransientState decremented below 0: " +
                     "unmatched pair of setHasTransientState calls");
-        }
-        if ((hasTransientState && mTransientStateCount == 1) ||
+        } else if ((hasTransientState && mTransientStateCount == 1) ||
                 (!hasTransientState && mTransientStateCount == 0)) {
             // update flag if we've just incremented up from 0 or decremented down to 0
             mPrivateFlags2 = (mPrivateFlags2 & ~PFLAG2_HAS_TRANSIENT_STATE) |
@@ -9589,7 +9595,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 mDisplayList.setTop(mTop);
             }
 
-            onSizeChanged(width, mBottom - mTop, width, oldHeight);
+            sizeChange(width, mBottom - mTop, width, oldHeight);
 
             if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -9662,7 +9668,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 mDisplayList.setBottom(mBottom);
             }
 
-            onSizeChanged(width, mBottom - mTop, width, oldHeight);
+            sizeChange(width, mBottom - mTop, width, oldHeight);
 
             if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -9729,7 +9735,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 mDisplayList.setLeft(left);
             }
 
-            onSizeChanged(mRight - mLeft, height, oldWidth, height);
+            sizeChange(mRight - mLeft, height, oldWidth, height);
 
             if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -9793,7 +9799,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 mDisplayList.setRight(mRight);
             }
 
-            onSizeChanged(mRight - mLeft, height, oldWidth, height);
+            sizeChange(mRight - mLeft, height, oldWidth, height);
 
             if (!matrixIsIdentity) {
                 if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -11553,7 +11559,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                 final int scrollY = mScrollY;
                 final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
 
-                int left, top, right, bottom;
+                int left;
+                int top;
+                int right;
+                int bottom;
 
                 if (drawHorizontalScrollBar) {
                     int size = scrollBar.getSize(false);
@@ -12088,6 +12097,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     void dispatchAttachedToWindow(AttachInfo info, int visibility) {
         //System.out.println("Attached! " + this);
         mAttachInfo = info;
+        if (mOverlay != null) {
+            mOverlay.mAttachInfo = info;
+        }
         mWindowAttachCount++;
         // We will need to evaluate the drawable state at least once.
         mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
@@ -12156,6 +12168,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         }
 
         mAttachInfo = null;
+        if (mOverlay != null) {
+            mOverlay.mAttachInfo = null;
+        }
     }
 
     /**
@@ -12811,6 +12826,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                     // Fast path for layouts with no backgrounds
                     if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                         dispatchDraw(canvas);
+                        if (mOverlay != null && !mOverlay.isEmpty()) {
+                            mOverlay.draw(canvas);
+                        }
                     } else {
                         draw(canvas);
                     }
@@ -13124,6 +13142,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                 mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                 dispatchDraw(canvas);
+                if (mOverlay != null && !mOverlay.isEmpty()) {
+                    mOverlay.draw(canvas);
+                }
             } else {
                 draw(canvas);
             }
@@ -13879,6 +13900,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
             // Step 6, draw decorations (scrollbars)
             onDrawScrollBars(canvas);
 
+            if (mOverlay != null && !mOverlay.isEmpty()) {
+                mOverlay.dispatchDraw(canvas);
+            }
+
             // we're done...
             return;
         }
@@ -14018,6 +14043,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
 
         // Step 6, draw decorations (scrollbars)
         onDrawScrollBars(canvas);
+
+        if (mOverlay != null && !mOverlay.isEmpty()) {
+            mOverlay.dispatchDraw(canvas);
+        }
+    }
+
+    /**
+     * Called by the addToOverlay() methods to create, attach, and size the overlay as necessary
+     */
+    private void setupOverlay() {
+        if (mOverlay == null) {
+            mOverlay = new ViewOverlay(mContext, this);
+            mOverlay.mAttachInfo = mAttachInfo;
+            mOverlay.setRight(mRight);
+            mOverlay.setBottom(mBottom);
+        }
+    }
+
+    /**
+     * Returns the overlay for this view, creating it if it does not yet exist. Adding drawables
+     * and/or views to the overlay will cause them to be displayed whenever the view itself is
+     * redrawn. Objects in the overlay should be actively managed: remove them when they should
+     * not be displayed anymore and invalidate this view appropriately when overlay drawables
+     * change. The overlay will always have the same size as its host view.
+     *
+     * @return The Overlay object for this view.
+     * @see Overlay
+     */
+    public Overlay getOverlay() {
+        setupOverlay();
+        return mOverlay;
     }
 
     /**
@@ -14273,7 +14329,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                         mTransformationInfo.mMatrixDirty = true;
                     }
                 }
-                onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
             }
 
             if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
@@ -14297,6 +14353,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         return changed;
     }
 
+    private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+        onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+        if (mOverlay != null) {
+            mOverlay.setRight(mRight);
+            mOverlay.setBottom(mBottom);
+        }
+    }
+
     /**
      * Finalize inflating a view from XML.  This is called as the last phase
      * of inflation, after all child views have been added.
index 442dfdb..d63f7bc 100644 (file)
@@ -1869,7 +1869,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                     removePointersFromTouchTargets(idBitsToAssign);
 
                     final int childrenCount = mChildrenCount;
-                    if (childrenCount != 0) {
+                    if (childrenCount != 0 || mOverlay != null) {
                         // Find a child that can receive the event.
                         // Scan children from front to back.
                         final View[] children = mChildren;
@@ -1906,6 +1906,27 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                                 break;
                             }
                         }
+                        if (mOverlay != null && newTouchTarget == null) {
+                            // Check to see whether the overlay can handle the event
+                            final View child = mOverlay;
+                            if (canViewReceivePointerEvents(child) &&
+                                    isTransformedTouchPointInView(x, y, child, null)) {
+                                newTouchTarget = getTouchTarget(child);
+                                if (newTouchTarget != null) {
+                                    newTouchTarget.pointerIdBits |= idBitsToAssign;
+                                } else {
+                                    resetCancelNextUpFlag(child);
+                                    if (dispatchTransformedTouchEvent(ev, false, child,
+                                            idBitsToAssign)) {
+                                        mLastTouchDownTime = ev.getDownTime();
+                                        mLastTouchDownX = ev.getX();
+                                        mLastTouchDownY = ev.getY();
+                                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
+                                        alreadyDispatchedToNewTouchTarget = true;
+                                    }
+                                }
+                            }
+                        }
                     }
 
                     if (newTouchTarget == null && mFirstTouchTarget != null) {
@@ -3022,6 +3043,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                 child.mRecreateDisplayList = false;
             }
         }
+        if (mOverlay != null) {
+            mOverlay.mRecreateDisplayList = (mOverlay.mPrivateFlags & PFLAG_INVALIDATED)
+                    == PFLAG_INVALIDATED;
+            mOverlay.mPrivateFlags &= ~PFLAG_INVALIDATED;
+            mOverlay.getDisplayList();
+            mOverlay.mRecreateDisplayList = false;
+        }
     }
 
     /**
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
new file mode 100644 (file)
index 0000000..8c2ab9d
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2013 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.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+
+/**
+ * ViewOverlay is a container that View uses to host all objects (views and drawables) that
+ * are added to its "overlay", gotten through {@link View#getOverlay()}. Views and drawables are
+ * added to the overlay via the add/remove methods in this class. These views and drawables are
+ * then drawn whenever the view itself is drawn, after which it will draw its overlay (if it
+ * exists).
+ *
+ * Besides managing and drawing the list of drawables, this class serves two purposes:
+ * (1) it noops layout calls because children are absolutely positioned and
+ * (2) it forwards all invalidation calls to its host view. The invalidation redirect is
+ * necessary because the overlay is not a child of the host view and invalidation cannot
+ * therefore follow the normal path up through the parent hierarchy.
+ *
+ * @hide
+ */
+class ViewOverlay extends ViewGroup implements Overlay {
+
+    /**
+     * The View for which this is an overlay. Invalidations of the overlay are redirected to
+     * this host view.
+     */
+    View mHostView;
+
+    /**
+     * The set of drawables to draw when the overlay is rendered.
+     */
+    ArrayList<Drawable> mDrawables = null;
+
+    ViewOverlay(Context context, View host) {
+        super(context);
+        mHostView = host;
+        mParent = mHostView.getParent();
+    }
+
+    @Override
+    public void add(Drawable drawable) {
+        if (mDrawables == null) {
+            mDrawables = new ArrayList<Drawable>();
+        }
+        if (!mDrawables.contains(drawable)) {
+            // Make each drawable unique in the overlay; can't add it more than once
+            mDrawables.add(drawable);
+            invalidate(drawable.getBounds());
+        }
+    }
+
+    @Override
+    public void remove(Drawable drawable) {
+        if (mDrawables != null) {
+            mDrawables.remove(drawable);
+            invalidate(drawable.getBounds());
+        }
+    }
+
+    @Override
+    public void add(View child) {
+        super.addView(child);
+    }
+
+    @Override
+    public void remove(View view) {
+        super.removeView(view);
+    }
+
+    @Override
+    public void clear() {
+        removeAllViews();
+        mDrawables.clear();
+    }
+
+    boolean isEmpty() {
+        if (getChildCount() == 0 && (mDrawables == null || mDrawables.size() == 0)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size();
+        for (int i = 0; i < numDrawables; ++i) {
+            mDrawables.get(i).draw(canvas);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        // Noop: children are positioned absolutely
+    }
+
+    /*
+     The following invalidation overrides exist for the purpose of redirecting invalidation to
+     the host view. The overlay is not parented to the host view (since a View cannot be a parent),
+     so the invalidation cannot proceed through the normal parent hierarchy.
+     There is a built-in assumption that the overlay exactly covers the host view, therefore
+     the invalidation rectangles received do not need to be adjusted when forwarded to
+     the host view.
+     */
+
+    @Override
+    public void invalidate(Rect dirty) {
+        super.invalidate(dirty);
+        if (mHostView != null) {
+            dirty.offset(getLeft(), getTop());
+            mHostView.invalidate(dirty);
+        }
+    }
+
+    @Override
+    public void invalidate(int l, int t, int r, int b) {
+        super.invalidate(l, t, r, b);
+        if (mHostView != null) {
+            mHostView.invalidate(l, t, r, b);
+        }
+    }
+
+    @Override
+    public void invalidate() {
+        super.invalidate();
+        if (mHostView != null) {
+            mHostView.invalidate();
+        }
+    }
+
+    @Override
+    void invalidate(boolean invalidateCache) {
+        super.invalidate(invalidateCache);
+        if (mHostView != null) {
+            mHostView.invalidate(invalidateCache);
+        }
+    }
+
+    @Override
+    void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
+        super.invalidateViewProperty(invalidateParent, forceRedraw);
+        if (mHostView != null) {
+            mHostView.invalidateViewProperty(invalidateParent, forceRedraw);
+        }
+    }
+
+    @Override
+    protected void invalidateParentCaches() {
+        super.invalidateParentCaches();
+        if (mHostView != null) {
+            mHostView.invalidateParentCaches();
+        }
+    }
+
+    @Override
+    protected void invalidateParentIfNeeded() {
+        super.invalidateParentIfNeeded();
+        if (mHostView != null) {
+            mHostView.invalidateParentIfNeeded();
+        }
+    }
+
+    public void invalidateChildFast(View child, final Rect dirty) {
+        if (mHostView != null) {
+            // Note: This is not a "fast" invalidation. Would be nice to instead invalidate using DL
+            // properties and a dirty rect instead of causing a real invalidation of the host view
+            int left = child.mLeft;
+            int top = child.mTop;
+            if (!child.getMatrix().isIdentity()) {
+                child.transformRect(dirty);
+            }
+            dirty.offset(left, top);
+            mHostView.invalidate(dirty);
+        }
+    }
+
+    @Override
+    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
+        if (mHostView != null) {
+            mHostView.invalidate(dirty);
+        }
+        return null;
+    }
+}
index a937882..8808af0 100644 (file)
@@ -1947,20 +1947,16 @@ public final class ViewRootImpl implements ViewParent,
             // Would not normally trigger another layout, so just let it pass through as usual
             return true;
         }
+        if (!mLayoutRequesters.contains(view)) {
+            mLayoutRequesters.add(view);
+        }
         if (!mHandlingLayoutInLayoutRequest) {
-            if (!mLayoutRequesters.contains(view)) {
-                mLayoutRequesters.add(view);
-            }
+            // Let the request proceed normally; it will be processed in a second layout pass
+            // if necessary
             return true;
         } else {
-            Log.w("View", "requestLayout() called by " + view + " during second layout pass: " +
-                    "posting to next frame");
-            view.post(new Runnable() {
-                @Override
-                public void run() {
-                    view.requestLayout();
-                }
-            });
+            // Don't let the request proceed during the second layout pass.
+            // It will post to the next frame instead.
             return false;
         }
     }
@@ -1988,59 +1984,50 @@ public final class ViewRootImpl implements ViewParent,
                 // If no layout-request flags are set on the requesting views, there is no problem.
                 // If some requests are still pending, then we need to clear those flags and do
                 // a full request/measure/layout pass to handle this situation.
+                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
+                        false);
+                if (validLayoutRequesters != null) {
+                    // Set this flag to indicate that any further requests are happening during
+                    // the second pass, which may result in posting those requests to the next
+                    // frame instead
+                    mHandlingLayoutInLayoutRequest = true;
 
-                // Check state of layout flags for all requesters
-                ArrayList<View> mValidLayoutRequesters = null;
-                for (int i = 0; i < numViewsRequestingLayout; ++i) {
-                    View view = mLayoutRequesters.get(i);
-                    if ((view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) == View.PFLAG_FORCE_LAYOUT) {
-                        while (view != null && view.mAttachInfo != null && view.mParent != null &&
-                                (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
-                            if ((view.mViewFlags & View.VISIBILITY_MASK) != View.GONE) {
-                                // Only trigger new requests for non-GONE views
-                                Log.w(TAG, "requestLayout() improperly called during " +
-                                        "layout: running second layout pass for " + view);
-                                if (mValidLayoutRequesters == null) {
-                                    mValidLayoutRequesters = new ArrayList<View>();
-                                }
-                                mValidLayoutRequesters.add(view);
-                                break;
-                            }
-                            if (view.mParent instanceof View) {
-                                view = (View) view.mParent;
-                            } else {
-                                view = null;
-                            }
-                        }
-                    }
-                }
-                if (mValidLayoutRequesters != null) {
-                    // Clear flags throughout hierarchy, walking up from each flagged requester
-                    for (int i = 0; i < numViewsRequestingLayout; ++i) {
-                        View view = mLayoutRequesters.get(i);
-                        while (view != null &&
-                                (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
-                            view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
-                            if (view.mParent instanceof View) {
-                                view = (View) view.mParent;
-                            } else {
-                                view = null;
-                            }
-                        }
-                    }
                     // Process fresh layout requests, then measure and layout
-                    mHandlingLayoutInLayoutRequest = true;
-                    int numValidRequests = mValidLayoutRequesters.size();
+                    int numValidRequests = validLayoutRequesters.size();
                     for (int i = 0; i < numValidRequests; ++i) {
-                        mValidLayoutRequesters.get(i).requestLayout();
+                        final View view = validLayoutRequesters.get(i);
+                        Log.w("View", "requestLayout() improperly called by " + view +
+                                " during layout: running second layout pass");
+                        view.requestLayout();
                     }
                     measureHierarchy(host, lp, mView.getContext().getResources(),
                             desiredWindowWidth, desiredWindowHeight);
                     mInLayout = true;
                     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
                     mHandlingLayoutInLayoutRequest = false;
+
+                    // Check the valid requests again, this time without checking/clearing the
+                    // layout flags, since requests happening during the second pass get noop'd
+                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
+                    if (validLayoutRequesters != null) {
+                        final ArrayList<View> finalRequesters = validLayoutRequesters;
+                        // Post second-pass requests to the next frame
+                        getRunQueue().post(new Runnable() {
+                            @Override
+                            public void run() {
+                                int numValidRequests = finalRequesters.size();
+                                for (int i = 0; i < numValidRequests; ++i) {
+                                    final View view = finalRequesters.get(i);
+                                    Log.w("View", "requestLayout() improperly called by " + view +
+                                            " during second layout pass: posting in next frame");
+                                    view.requestLayout();
+                                }
+                            }
+                        });
+                    }
                 }
-                mLayoutRequesters.clear();
+
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
@@ -2048,6 +2035,68 @@ public final class ViewRootImpl implements ViewParent,
         mInLayout = false;
     }
 
+    /**
+     * This method is called during layout when there have been calls to requestLayout() during
+     * layout. It walks through the list of views that requested layout to determine which ones
+     * still need it, based on visibility in the hierarchy and whether they have already been
+     * handled (as is usually the case with ListView children).
+     *
+     * @param layoutRequesters The list of views that requested layout during layout
+     * @param secondLayoutRequests Whether the requests were issued during the second layout pass.
+     * If so, the FORCE_LAYOUT flag was not set on requesters.
+     * @return A list of the actual views that still need to be laid out.
+     */
+    private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters,
+            boolean secondLayoutRequests) {
+
+        int numViewsRequestingLayout = layoutRequesters.size();
+        ArrayList<View> validLayoutRequesters = null;
+        for (int i = 0; i < numViewsRequestingLayout; ++i) {
+            View view = layoutRequesters.get(i);
+            if (view != null && view.mAttachInfo != null && view.mParent != null &&
+                    (secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==
+                            View.PFLAG_FORCE_LAYOUT)) {
+                boolean gone = false;
+                View parent = view;
+                // Only trigger new requests for views in a non-GONE hierarchy
+                while (parent != null) {
+                    if ((parent.mViewFlags & View.VISIBILITY_MASK) == View.GONE) {
+                        gone = true;
+                        break;
+                    }
+                    if (parent.mParent instanceof View) {
+                        parent = (View) parent.mParent;
+                    } else {
+                        parent = null;
+                    }
+                }
+                if (!gone) {
+                    if (validLayoutRequesters == null) {
+                        validLayoutRequesters = new ArrayList<View>();
+                    }
+                    validLayoutRequesters.add(view);
+                }
+            }
+        }
+        if (!secondLayoutRequests) {
+            // If we're checking the layout flags, then we need to clean them up also
+            for (int i = 0; i < numViewsRequestingLayout; ++i) {
+                View view = layoutRequesters.get(i);
+                while (view != null &&
+                        (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
+                    view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+                    if (view.mParent instanceof View) {
+                        view = (View) view.mParent;
+                    } else {
+                        view = null;
+                    }
+                }
+            }
+        }
+        layoutRequesters.clear();
+        return validLayoutRequesters;
+    }
+
     public void requestTransparentRegion(View child) {
         // the test below should not fail unless someone is messing with us
         checkThread();
index 792188b..96ef0b4 100644 (file)
@@ -272,7 +272,7 @@ public interface WindowManager extends ViewManager {
         public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
     
         /**
-         * Window type: window for showing media (e.g. video).  These windows
+         * Window type: window for showing media (such as video).  These windows
          * are displayed behind their attached window.
          */
         public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
@@ -584,14 +584,14 @@ public interface WindowManager extends ViewManager {
         /** Window flag: this window can never receive touch events. */
         public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
         
-        /** Window flag: Even when this window is focusable (its
-         * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events
+        /** Window flag: even when this window is focusable (its
+         * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
          * outside of the window to be sent to the windows behind it.  Otherwise
          * it will consume all pointer events itself, regardless of whether they
          * are inside of the window. */
         public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
         
-        /** Window flag: When set, if the device is asleep when the touch
+        /** Window flag: when set, if the device is asleep when the touch
          * screen is pressed, you will receive this first touch event.  Usually
          * the first touch event is consumed by the system since the user can
          * not see what they are pressing on.
@@ -603,7 +603,7 @@ public interface WindowManager extends ViewManager {
         public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
         
         /** Window flag: place the window within the entire screen, ignoring
-         *  decorations around the border (a.k.a. the status bar).  The
+         *  decorations around the border (such as the status bar).  The
          *  window must correctly position its contents to take the screen
          *  decoration into account.  This flag is normally set for you
          *  by Window as described in {@link Window#setFlags}. */
@@ -613,7 +613,7 @@ public interface WindowManager extends ViewManager {
         public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
         
         /**
-         * Window flag: Hide all screen decorations (e.g. status bar) while
+         * Window flag: hide all screen decorations (such as the status bar) while
          * this window is displayed.  This allows the window to use the entire
          * display space for itself -- the status bar will be hidden when
          * an app window with this flag set is on the top layer.
@@ -631,8 +631,8 @@ public interface WindowManager extends ViewManager {
          */
         public static final int FLAG_FULLSCREEN      = 0x00000400;
         
-        /** Window flag: Override {@link #FLAG_FULLSCREEN and force the
-         *  screen decorations (such as status bar) to be shown. */
+        /** Window flag: override {@link #FLAG_FULLSCREEN} and force the
+         *  screen decorations (such as the status bar) to be shown. */
         public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
         
         /** Window flag: turn on dithering when compositing this window to
@@ -641,7 +641,7 @@ public interface WindowManager extends ViewManager {
         @Deprecated
         public static final int FLAG_DITHER             = 0x00001000;
         
-        /** Window flag: Treat the content of the window as secure, preventing
+        /** Window flag: treat the content of the window as secure, preventing
          * it from appearing in screenshots or from being viewed on non-secure
          * displays.
          *
index 396fd68..d659110 100644 (file)
@@ -6167,6 +6167,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
         private ArrayList<View> mSkippedScrap;
 
         private SparseArray<View> mTransientStateViews;
+        private LongSparseArray<View> mTransientStateViewsById;
 
         public void setViewTypeCount(int viewTypeCount) {
             if (viewTypeCount < 1) {
@@ -6205,6 +6206,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                     mTransientStateViews.valueAt(i).forceLayout();
                 }
             }
+            if (mTransientStateViewsById != null) {
+                final int count = mTransientStateViewsById.size();
+                for (int i = 0; i < count; i++) {
+                    mTransientStateViewsById.valueAt(i).forceLayout();
+                }
+            }
         }
 
         public boolean shouldRecycleViewType(int viewType) {
@@ -6234,6 +6241,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
             if (mTransientStateViews != null) {
                 mTransientStateViews.clear();
             }
+            if (mTransientStateViewsById != null) {
+                mTransientStateViewsById.clear();
+            }
         }
 
         /**
@@ -6281,16 +6291,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
         }
 
         View getTransientStateView(int position) {
-            if (mTransientStateViews == null) {
-                return null;
+            if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
+                long id = mAdapter.getItemId(position);
+                View result = mTransientStateViewsById.get(id);
+                mTransientStateViewsById.remove(id);
+                return result;
             }
-            final int index = mTransientStateViews.indexOfKey(position);
-            if (index < 0) {
-                return null;
+            if (mTransientStateViews != null) {
+                final int index = mTransientStateViews.indexOfKey(position);
+                if (index >= 0) {
+                    View result = mTransientStateViews.valueAt(index);
+                    mTransientStateViews.removeAt(index);
+                    return result;
+                }
             }
-            final View result = mTransientStateViews.valueAt(index);
-            mTransientStateViews.removeAt(index);
-            return result;
+            return null;
         }
 
         /**
@@ -6300,6 +6315,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
             if (mTransientStateViews != null) {
                 mTransientStateViews.clear();
             }
+            if (mTransientStateViewsById != null) {
+                mTransientStateViewsById.clear();
+            }
         }
 
         /**
@@ -6342,11 +6360,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                     mSkippedScrap.add(scrap);
                 }
                 if (scrapHasTransientState) {
-                    if (mTransientStateViews == null) {
-                        mTransientStateViews = new SparseArray<View>();
-                    }
                     scrap.dispatchStartTemporaryDetach();
-                    mTransientStateViews.put(position, scrap);
+                    if (mAdapter != null && mAdapterHasStableIds) {
+                        if (mTransientStateViewsById == null) {
+                            mTransientStateViewsById = new LongSparseArray<View>();
+                        }
+                        mTransientStateViewsById.put(lp.itemId, scrap);
+                    } else {
+                        if (mTransientStateViews == null) {
+                            mTransientStateViews = new SparseArray<View>();
+                        }
+                        mTransientStateViews.put(position, scrap);
+                    }
                 }
                 return;
             }
@@ -6405,10 +6430,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                             removeDetachedView(victim, false);
                         }
                         if (scrapHasTransientState) {
-                            if (mTransientStateViews == null) {
-                                mTransientStateViews = new SparseArray<View>();
+                            if (mAdapter != null && mAdapterHasStableIds) {
+                                if (mTransientStateViewsById == null) {
+                                    mTransientStateViewsById = new LongSparseArray<View>();
+                                }
+                                long id = mAdapter.getItemId(mFirstActivePosition + i);
+                                mTransientStateViewsById.put(id, victim);
+                            } else {
+                                if (mTransientStateViews == null) {
+                                    mTransientStateViews = new SparseArray<View>();
+                                }
+                                mTransientStateViews.put(mFirstActivePosition + i, victim);
                             }
-                            mTransientStateViews.put(mFirstActivePosition + i, victim);
                         }
                         continue;
                     }
@@ -6457,6 +6490,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                     }
                 }
             }
+            if (mTransientStateViewsById != null) {
+                for (int i = 0; i < mTransientStateViewsById.size(); i++) {
+                    final View v = mTransientStateViewsById.valueAt(i);
+                    if (!v.hasTransientState()) {
+                        mTransientStateViewsById.removeAt(i);
+                        i--;
+                    }
+                }
+            }
         }
 
         /**
index adacef2..b084ac4 100644 (file)
@@ -8813,11 +8813,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         }
 
         public void drawTextRun(Canvas c, int start, int end,
-                int contextStart, int contextEnd, float x, float y, Paint p) {
+                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
-                    contextCount, x, y, p);
+                    contextCount, x, y, flags, p);
         }
 
         public float measureText(int start, int end, Paint p) {
@@ -8829,20 +8829,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         }
 
         public float getTextRunAdvances(int start, int end, int contextStart,
-                int contextEnd, float[] advances, int advancesIndex,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
                 Paint p) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             return p.getTextRunAdvances(mChars, start + mStart, count,
-                    contextStart + mStart, contextCount, advances,
+                    contextStart + mStart, contextCount, flags, advances,
                     advancesIndex);
         }
 
-        public int getTextRunCursor(int contextStart, int contextEnd,
+        public float getTextRunAdvances(int start, int end, int contextStart,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
+                Paint p, int reserved) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunAdvances(mChars, start + mStart, count,
+                    contextStart + mStart, contextCount, flags, advances,
+                    advancesIndex, reserved);
+        }
+
+        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
                 int offset, int cursorOpt, Paint p) {
             int contextCount = contextEnd - contextStart;
             return p.getTextRunCursor(mChars, contextStart + mStart,
-                    contextCount, offset + mStart, cursorOpt);
+                    contextCount, flags, offset + mStart, cursorOpt);
         }
     }
 
index 7c2b1b5..91b109e 100644 (file)
@@ -45,6 +45,7 @@ public class Protocol {
     public static final int BASE_WIFI_P2P_SERVICE                                   = 0x00023000;
     public static final int BASE_WIFI_MONITOR                                       = 0x00024000;
     public static final int BASE_WIFI_MANAGER                                       = 0x00025000;
+    public static final int BASE_WIFI_CONTROLLER                                    = 0x00026000;
     public static final int BASE_DHCP                                               = 0x00030000;
     public static final int BASE_DATA_CONNECTION                                    = 0x00040000;
     public static final int BASE_DATA_CONNECTION_AC                                 = 0x00041000;
index e547f23..dd57ef4 100644 (file)
@@ -1257,6 +1257,15 @@ public class StateMachine {
     }
 
     /**
+     * Constructor creates a StateMachine using the handler.
+     *
+     * @param name of the state machine
+     */
+    protected StateMachine(String name, Handler handler) {
+        initStateMachine(name, handler.getLooper());
+    }
+
+    /**
      * Add a new state to the state machine
      * @param state the state to add
      * @param parent the parent of state
@@ -1549,6 +1558,24 @@ public class StateMachine {
      *
      * @param what  is assigned to Message.what
      * @param arg1  is assigned to Message.arg1
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage(int what, int arg1) {
+        // use this obtain so we don't match the obtain(h, what, Object) method
+        return Message.obtain(mSmHandler, what, arg1, 0);
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler,
+     * what, arg1 and arg2
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @param what  is assigned to Message.what
+     * @param arg1  is assigned to Message.arg1
      * @param arg2  is assigned to Message.arg2
      * @return  A Message object from the global pool
      */
@@ -1606,6 +1633,32 @@ public class StateMachine {
      *
      * Message is ignored if state machine has quit.
      */
+    public final void sendMessage(int what, int arg1) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, arg1));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public final void sendMessage(int what, int arg1, int arg2) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, arg1, arg2));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
     public final void sendMessage(int what, int arg1, int arg2, Object obj) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
@@ -1658,6 +1711,32 @@ public class StateMachine {
      *
      * Message is ignored if state machine has quit.
      */
+    public final void sendMessageDelayed(int what, int arg1, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public final void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
     public final void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
             long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
@@ -1686,6 +1765,20 @@ public class StateMachine {
      *
      * Message is ignored if state machine has quit.
      */
+    protected final void sendMessageAtFrontOfQueue(int what) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what));
+    }
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
     protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
@@ -1700,12 +1793,27 @@ public class StateMachine {
      *
      * Message is ignored if state machine has quit.
      */
-    protected final void sendMessageAtFrontOfQueue(int what) {
+    protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
 
-        smh.sendMessageAtFrontOfQueue(obtainMessage(what));
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1));
+    }
+
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2));
     }
 
     /**
index c7fcab8..5db860b 100644 (file)
@@ -35,9 +35,9 @@ import com.android.internal.view.IInputMethodSession;
  */
 oneway interface IInputMethod {
     void attachToken(IBinder token);
-    
+
     void bindInput(in InputBinding binding);
-    
+
     void unbindInput();
 
     void startInput(in IInputContext inputContext, in EditorInfo attribute);
@@ -45,13 +45,13 @@ oneway interface IInputMethod {
     void restartInput(in IInputContext inputContext, in EditorInfo attribute);
 
     void createSession(IInputMethodCallback callback);
-    
+
     void setSessionEnabled(IInputMethodSession session, boolean enabled);
-    
+
     void revokeSession(IInputMethodSession session);
-    
+
     void showSoftInput(int flags, in ResultReceiver resultReceiver);
-    
+
     void hideSoftInput(int flags, in ResultReceiver resultReceiver);
 
     void changeInputMethodSubtype(in InputMethodSubtype subtype);
index 6640555..9a9f6c8 100644 (file)
@@ -752,37 +752,37 @@ public:
     }
 
 
-    static void drawText___CIIFFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+    static void drawText___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
                                       jcharArray text, int index, int count,
-                                      jfloat x, jfloat y, SkPaint* paint) {
+                                      jfloat x, jfloat y, int flags, SkPaint* paint) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, paint);
+        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, flags, paint);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
     }
 
-    static void drawText__StringIIFFPaint(JNIEnv* env, jobject,
+    static void drawText__StringIIFFIPaint(JNIEnv* env, jobject,
                                           SkCanvas* canvas, jstring text,
                                           int start, int end,
-                                          jfloat x, jfloat y, SkPaint* paint) {
+                                          jfloat x, jfloat y, int flags, SkPaint* paint) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        drawTextWithGlyphs(canvas, textArray, start, end, x, y, paint);
+        drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint);
         env->ReleaseStringChars(text, textArray);
     }
 
     static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
             int start, int end,
-            jfloat x, jfloat y, SkPaint* paint) {
+            jfloat x, jfloat y, int flags, SkPaint* paint) {
 
         jint count = end - start;
-        drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, paint);
+        drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, flags, paint);
     }
 
     static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
             int start, int count, int contextCount,
-            jfloat x, jfloat y, SkPaint* paint) {
+            jfloat x, jfloat y, int flags, SkPaint* paint) {
 
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-                textArray, start, count, contextCount);
+                textArray, start, count, contextCount, flags);
         if (value == NULL) {
             return;
         }
@@ -793,7 +793,7 @@ public:
             x -= value->getTotalAdvance();
         }
         paint->setTextAlign(SkPaint::kLeft_Align);
-        doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, paint);
+        doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, flags, paint);
         doDrawTextDecorations(canvas, x, y, value->getTotalAdvance(), paint);
         paint->setTextAlign(align);
     }
@@ -835,8 +835,14 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l
     }
 }
 
+    static void doDrawGlyphs(SkCanvas* canvas, const jchar* glyphArray, int index, int count,
+            jfloat x, jfloat y, int flags, SkPaint* paint) {
+        // Beware: this needs Glyph encoding (already done on the Paint constructor)
+        canvas->drawText(glyphArray + index * 2, count * 2, x, y, *paint);
+    }
+
     static void doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray,
-            int index, int count, jfloat x, jfloat y, SkPaint* paint) {
+            int index, int count, jfloat x, jfloat y, int flags, SkPaint* paint) {
         SkPoint* posPtr = new SkPoint[count];
         for (int indx = 0; indx < count; indx++) {
             posPtr[indx].fX = SkFloatToScalar(x + posArray[indx * 2]);
@@ -846,27 +852,27 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l
         delete[] posPtr;
     }
 
-    static void drawTextRun___CIIIIFFPaint(
+    static void drawTextRun___CIIIIFFIPaint(
         JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
         int count, int contextIndex, int contextCount,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
 
         jchar* chars = env->GetCharArrayElements(text, NULL);
         drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex,
-                count, contextCount, x, y, paint);
+                count, contextCount, x, y, dirFlags, paint);
         env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
     }
 
-    static void drawTextRun__StringIIIIFFPaint(
+    static void drawTextRun__StringIIIIFFIPaint(
         JNIEnv* env, jobject obj, SkCanvas* canvas, jstring text, jint start,
         jint end, jint contextStart, jint contextEnd,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, jint dirFlags, SkPaint* paint) {
 
         jint count = end - start;
         jint contextCount = contextEnd - contextStart;
         const jchar* chars = env->GetStringChars(text, NULL);
         drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart,
-                count, contextCount, x, y, paint);
+                count, contextCount, x, y, dirFlags, paint);
         env->ReleaseStringChars(text, chars);
     }
 
@@ -929,19 +935,21 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l
 
     static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
             SkCanvas* canvas, jcharArray text, int index, int count,
-            SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+            SkPath* path, jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
 
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        TextLayout::drawTextOnPath(paint, textArray + index, count, hOffset, vOffset, path, canvas);
+        TextLayout::drawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseCharArrayElements(text, textArray, 0);
     }
 
     static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
             SkCanvas* canvas, jstring text, SkPath* path,
-            jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+            jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
         const jchar* text_ = env->GetStringChars(text, NULL);
         int count = env->GetStringLength(text);
-        TextLayout::drawTextOnPath(paint, text_, count, hOffset, vOffset, path, canvas);
+        TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseStringChars(text, text_);
     }
 
@@ -1051,21 +1059,21 @@ static JNINativeMethod gCanvasMethods[] = {
         (void*)SkCanvasGlue::drawBitmapMesh},
     {"nativeDrawVertices", "(III[FI[FI[II[SIII)V",
         (void*)SkCanvasGlue::drawVertices},
-    {"native_drawText","(I[CIIFFI)V",
-        (void*) SkCanvasGlue::drawText___CIIFFPaint},
-    {"native_drawText","(ILjava/lang/String;IIFFI)V",
-        (void*) SkCanvasGlue::drawText__StringIIFFPaint},
-    {"native_drawTextRun","(I[CIIIIFFI)V",
-        (void*) SkCanvasGlue::drawTextRun___CIIIIFFPaint},
-    {"native_drawTextRun","(ILjava/lang/String;IIIIFFI)V",
-        (void*) SkCanvasGlue::drawTextRun__StringIIIIFFPaint},
+    {"native_drawText","(I[CIIFFII)V",
+        (void*) SkCanvasGlue::drawText___CIIFFIPaint},
+    {"native_drawText","(ILjava/lang/String;IIFFII)V",
+        (void*) SkCanvasGlue::drawText__StringIIFFIPaint},
+    {"native_drawTextRun","(I[CIIIIFFII)V",
+        (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaint},
+    {"native_drawTextRun","(ILjava/lang/String;IIIIFFII)V",
+        (void*) SkCanvasGlue::drawTextRun__StringIIIIFFIPaint},
     {"native_drawPosText","(I[CII[FI)V",
         (void*) SkCanvasGlue::drawPosText___CII_FPaint},
     {"native_drawPosText","(ILjava/lang/String;[FI)V",
         (void*) SkCanvasGlue::drawPosText__String_FPaint},
-    {"native_drawTextOnPath","(I[CIIIFFI)V",
+    {"native_drawTextOnPath","(I[CIIIFFII)V",
         (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
-    {"native_drawTextOnPath","(ILjava/lang/String;IFFI)V",
+    {"native_drawTextOnPath","(ILjava/lang/String;IFFII)V",
         (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
     {"native_drawPicture", "(II)V", (void*) SkCanvasGlue::drawPicture},
 
index 07f55e0..29a36de 100644 (file)
@@ -401,7 +401,7 @@ public:
         jfloat result = 0;
 
         TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength,
-                NULL /* dont need all advances */, &result);
+                paint->getFlags(), NULL /* dont need all advances */, &result);
 
         env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
         return result;
@@ -426,7 +426,7 @@ public:
         jfloat width = 0;
 
         TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength,
-                NULL /* dont need all advances */, &width);
+                paint->getFlags(), NULL /* dont need all advances */, &width);
 
         env->ReleaseStringChars(text, textArray);
         return width;
@@ -446,7 +446,7 @@ public:
         jfloat width = 0;
 
         TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength,
-                NULL /* dont need all advances */, &width);
+                paint->getFlags(), NULL /* dont need all advances */, &width);
 
         env->ReleaseStringChars(text, textArray);
         return width;
@@ -473,7 +473,7 @@ public:
         jfloat* widthsArray = autoWidths.ptr();
 
         TextLayout::getTextRunAdvances(paint, text, 0, count, count,
-                widthsArray, NULL /* dont need totalAdvance */);
+                paint->getFlags(), widthsArray, NULL /* dont need totalAdvance */);
 
         return count;
     }
@@ -494,8 +494,48 @@ public:
         return count;
     }
 
+    static int doTextGlyphs(JNIEnv* env, SkPaint* paint, const jchar* text, jint start, jint count,
+            jint contextCount, jint flags, jcharArray glyphs) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        NPE_CHECK_RETURN_ZERO(env, text);
+
+        if ((start | count | contextCount) < 0 || contextCount < count || !glyphs) {
+            doThrowAIOOBE(env);
+            return 0;
+        }
+        if (count == 0) {
+            return 0;
+        }
+        size_t glypthsLength = env->GetArrayLength(glyphs);
+        if ((size_t)count > glypthsLength) {
+            doThrowAIOOBE(env);
+            return 0;
+        }
+
+        jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL);
+
+        sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
+                text, start, count, contextCount, flags);
+        const jchar* shapedGlyphs = value->getGlyphs();
+        size_t glyphsCount = value->getGlyphsCount();
+        memcpy(glyphsArray, shapedGlyphs, sizeof(jchar) * glyphsCount);
+
+        env->ReleaseCharArrayElements(glyphs, glyphsArray, JNI_ABORT);
+        return glyphsCount;
+    }
+
+    static int getTextGlyphs__StringIIIII_C(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+            jcharArray glyphs) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        int count = doTextGlyphs(env, paint, textArray + contextStart, start - contextStart,
+                end - start, contextEnd - contextStart, flags, glyphs);
+        env->ReleaseStringChars(text, textArray);
+        return count;
+    }
+
     static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text,
-                                    jint start, jint count, jint contextCount,
+                                    jint start, jint count, jint contextCount, jint flags,
                                     jfloatArray advances, jint advancesIndex) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         NPE_CHECK_RETURN_ZERO(env, text);
@@ -517,7 +557,7 @@ public:
         jfloat advancesArray[count];
         jfloat totalAdvance = 0;
 
-        TextLayout::getTextRunAdvances(paint, text, start, count, contextCount,
+        TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags,
                                        advancesArray, &totalAdvance);
 
         if (advances != NULL) {
@@ -526,32 +566,70 @@ public:
         return totalAdvance;
     }
 
-    static float getTextRunAdvances___CIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+    static jfloat doTextRunAdvancesICU(JNIEnv *env, SkPaint *paint, const jchar *text,
+                                    jint start, jint count, jint contextCount, jint flags,
+                                    jfloatArray advances, jint advancesIndex) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        NPE_CHECK_RETURN_ZERO(env, text);
+
+        if ((start | count | contextCount | advancesIndex) < 0 || contextCount < count) {
+            doThrowAIOOBE(env);
+            return 0;
+        }
+        if (count == 0) {
+            return 0;
+        }
+        if (advances) {
+            size_t advancesLength = env->GetArrayLength(advances);
+            if ((size_t)count > advancesLength) {
+                doThrowAIOOBE(env);
+                return 0;
+            }
+        }
+
+        jfloat advancesArray[count];
+        jfloat totalAdvance = 0;
+
+        TextLayout::getTextRunAdvancesICU(paint, text, start, count, contextCount, flags,
+                                       advancesArray, totalAdvance);
+
+        if (advances != NULL) {
+            env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
+        }
+        return totalAdvance;
+    }
+
+    static float getTextRunAdvances___CIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint,
             jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
-            jfloatArray advances, jint advancesIndex) {
+            jint flags, jfloatArray advances, jint advancesIndex, jint reserved) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        jfloat result = doTextRunAdvances(env, paint, textArray + contextIndex,
-                index - contextIndex, count, contextCount, advances, advancesIndex);
+        jfloat result = (reserved == 0) ?
+                doTextRunAdvances(env, paint, textArray + contextIndex, index - contextIndex,
+                        count, contextCount, flags, advances, advancesIndex) :
+                doTextRunAdvancesICU(env, paint, textArray + contextIndex, index - contextIndex,
+                        count, contextCount, flags, advances, advancesIndex);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
         return result;
     }
 
-    static float getTextRunAdvances__StringIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
-            jstring text, jint start, jint end, jint contextStart, jint contextEnd,
-            jfloatArray advances, jint advancesIndex) {
+    static float getTextRunAdvances__StringIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+            jfloatArray advances, jint advancesIndex, jint reserved) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        jfloat result = doTextRunAdvances(env, paint, textArray + contextStart,
-                start - contextStart, end - start, contextEnd - contextStart,
-                advances, advancesIndex);
+        jfloat result = (reserved == 0) ?
+                doTextRunAdvances(env, paint, textArray + contextStart, start - contextStart,
+                        end - start, contextEnd - contextStart, flags, advances, advancesIndex) :
+                doTextRunAdvancesICU(env, paint, textArray + contextStart, start - contextStart,
+                        end - start, contextEnd - contextStart, flags, advances, advancesIndex);
         env->ReleaseStringChars(text, textArray);
         return result;
     }
 
     static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
-            jint count, jint offset, jint opt) {
+            jint count, jint flags, jint offset, jint opt) {
         jfloat scalarArray[count];
 
-        TextLayout::getTextRunAdvances(paint, text, start, count, start + count,
+        TextLayout::getTextRunAdvances(paint, text, start, count, start + count, flags,
                 scalarArray, NULL /* dont need totalAdvance */);
 
         jint pos = offset - start;
@@ -592,39 +670,39 @@ public:
     }
 
     static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text,
-            jint contextStart, jint contextCount, jint offset, jint cursorOpt) {
+            jint contextStart, jint contextCount, jint flags, jint offset, jint cursorOpt) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount,
+        jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, flags,
                 offset, cursorOpt);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
         return result;
     }
 
     static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text,
-            jint contextStart, jint contextEnd, jint offset, jint cursorOpt) {
+            jint contextStart, jint contextEnd, jint flags, jint offset, jint cursorOpt) {
         const jchar* textArray = env->GetStringChars(text, NULL);
         jint result = doTextRunCursor(env, paint, textArray, contextStart,
-                contextEnd - contextStart, offset, cursorOpt);
+                contextEnd - contextStart, flags, offset, cursorOpt);
         env->ReleaseStringChars(text, textArray);
         return result;
     }
 
     static void getTextPath(JNIEnv* env, SkPaint* paint, const jchar* text, jint count,
-                            jfloat x, jfloat y, SkPath *path) {
-        TextLayout::getTextPath(paint, text, count, x, y, path);
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+        TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
     }
 
-    static void getTextPath___C(JNIEnv* env, jobject clazz, SkPaint* paint,
+    static void getTextPath___C(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
             jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
         const jchar* textArray = env->GetCharArrayElements(text, NULL);
-        getTextPath(env, paint, textArray + index, count, x, y, path);
+        getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path);
         env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
     }
 
-    static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint,
+    static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
             jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        getTextPath(env, paint, textArray + start, end - start, x, y, path);
+        getTextPath(env, paint, textArray + start, end - start, bidiFlags, x, y, path);
         env->ReleaseStringChars(text, textArray);
     }
 
@@ -648,7 +726,7 @@ public:
                          int count, float maxWidth, jfloatArray jmeasured,
                          SkPaint::TextBufferDirection tbd) {
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
-                text, 0, count, count);
+                text, 0, count, count, paint.getFlags());
         if (value == NULL) {
             return 0;
         }
@@ -720,7 +798,7 @@ public:
         SkIRect ir;
 
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
-                text, 0, count, count);
+                text, 0, count, count, paint.getFlags());
         if (value == NULL) {
             return;
         }
@@ -808,15 +886,19 @@ static JNINativeMethod methods[] = {
     {"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS},
     {"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F},
     {"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F},
-    {"native_getTextRunAdvances","(I[CIIII[FI)F",
-        (void*) SkPaintGlue::getTextRunAdvances___CIIII_FI},
-    {"native_getTextRunAdvances","(ILjava/lang/String;IIII[FI)F",
-        (void*) SkPaintGlue::getTextRunAdvances__StringIIII_FI},
-    {"native_getTextRunCursor", "(I[CIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
-    {"native_getTextRunCursor", "(ILjava/lang/String;IIII)I",
+    {"native_getTextRunAdvances","(I[CIIIII[FII)F",
+        (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FII},
+    {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FII)F",
+        (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FII},
+
+
+    {"native_getTextGlyphs","(ILjava/lang/String;IIIII[C)I",
+        (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C},
+    {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
+    {"native_getTextRunCursor", "(ILjava/lang/String;IIIII)I",
         (void*) SkPaintGlue::getTextRunCursor__String},
-    {"native_getTextPath","(I[CIIFFI)V", (void*) SkPaintGlue::getTextPath___C},
-    {"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String},
+    {"native_getTextPath","(II[CIIFFI)V", (void*) SkPaintGlue::getTextPath___C},
+    {"native_getTextPath","(IILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String},
     {"nativeGetStringBounds", "(ILjava/lang/String;IILandroid/graphics/Rect;)V",
                                         (void*) SkPaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(I[CIILandroid/graphics/Rect;)V",
index b77236c..2beedad 100644 (file)
 
 namespace android {
 
+// Returns true if we might need layout.  If bidiFlags force LTR, assume no layout, if
+// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
+// looking for a character >= the first RTL character in unicode and assume we do if
+// we find one.
+bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
+    if (bidiFlags == kBidi_Force_LTR) {
+        return false;
+    }
+    if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
+            bidiFlags == kBidi_Force_RTL) {
+        return true;
+    }
+    for (int i = 0; i < len; ++i) {
+        if (text[i] >= UNICODE_FIRST_RTL_CHAR) {
+            return true;
+        }
+    }
+    return false;
+}
+
 // Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
 // This will draw if canvas is not null, otherwise path must be non-null and it will create
 // a path representing the text that would have been drawn.
 void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
-                            jfloat x, jfloat y, SkPath *path) {
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, len, len);
+            text, 0, len, len, bidiFlags);
     if (value == NULL) {
         return ;
     }
@@ -45,10 +65,10 @@ void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
 }
 
 void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
-                                    jint count, jint contextCount,
+                                    jint count, jint contextCount, jint dirFlags,
                                     jfloat* resultAdvances, jfloat* resultTotalAdvance) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            chars, start, count, contextCount);
+            chars, start, count, contextCount, dirFlags);
     if (value == NULL) {
         return ;
     }
@@ -60,20 +80,29 @@ void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint sta
     }
 }
 
+void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
+                                    jint count, jint contextCount, jint dirFlags,
+                                    jfloat* resultAdvances, jfloat& resultTotalAdvance) {
+    // Compute advances and return them
+    computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
+            resultAdvances, &resultTotalAdvance);
+}
+
 void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
-                             jfloat x, jfloat y, SkPath *path) {
-    handleText(paint, text, len, x, y, path);
+                             jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+    handleText(paint, text, len, bidiFlags, x, y, path);
 }
 
+
 void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
-                                jfloat hOffset, jfloat vOffset,
+                                int bidiFlags, jfloat hOffset, jfloat vOffset,
                                 SkPath* path, SkCanvas* canvas) {
 
     SkScalar h_ = SkFloatToScalar(hOffset);
     SkScalar v_ = SkFloatToScalar(vOffset);
 
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, bidiFlags);
     if (value == NULL) {
         return;
     }
@@ -82,4 +111,73 @@ void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
     canvas->drawTextOnPathHV(value->getGlyphs(), value->getGlyphsCount() * 2, *path, h_, v_, *paint);
 }
 
+void TextLayout::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
+        size_t start, size_t count, size_t contextCount, int dirFlags,
+        jfloat* outAdvances, jfloat* outTotalAdvance) {
+    SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
+    jchar* buffer = tempBuffer.get();
+    SkScalar* scalarArray = (SkScalar*)outAdvances;
+
+    // this is where we'd call harfbuzz
+    // for now we just use ushape.c
+    size_t widths;
+    const jchar* text;
+    if (dirFlags & 0x1) { // rtl, call arabic shaping in case
+        UErrorCode status = U_ZERO_ERROR;
+        // Use fixed length since we need to keep start and count valid
+        u_shapeArabic(chars, contextCount, buffer, contextCount,
+                U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+                U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+                U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+        // we shouldn't fail unless there's an out of memory condition,
+        // in which case we're hosed anyway
+        for (int i = start, e = i + count; i < e; ++i) {
+            if (buffer[i] == UNICODE_NOT_A_CHAR) {
+                buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
+            }
+        }
+        text = buffer + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    } else {
+        text = chars + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    }
+
+    jfloat totalAdvance = 0;
+    if (widths < count) {
+#if DEBUG_ADVANCES
+    ALOGD("ICU -- count=%d", widths);
+#endif
+        // Skia operates on code points, not code units, so surrogate pairs return only
+        // one value. Expand the result so we have one value per UTF-16 code unit.
+
+        // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+        // leaving the remaining widths zero.  Not nice.
+        for (size_t i = 0, p = 0; i < widths; ++i) {
+            totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
+            if (p < count &&
+                    text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
+                    text[p] < UNICODE_FIRST_PRIVATE_USE &&
+                    text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
+                    text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
+                outAdvances[p++] = 0;
+            }
+#if DEBUG_ADVANCES
+            ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
+#endif
+        }
+    } else {
+#if DEBUG_ADVANCES
+    ALOGD("ICU -- count=%d", count);
+#endif
+        for (size_t i = 0; i < count; i++) {
+            totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
+#if DEBUG_ADVANCES
+            ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
+#endif
+        }
+    }
+    *outTotalAdvance = totalAdvance;
+}
+
 }
index fa388c8..a0f9402 100644 (file)
@@ -42,6 +42,17 @@ namespace android {
 #define USE_TEXT_LAYOUT_CACHE 1
 
 enum {
+    kBidi_LTR = 0,
+    kBidi_RTL = 1,
+    kBidi_Default_LTR = 2,
+    kBidi_Default_RTL = 3,
+    kBidi_Force_LTR = 4,
+    kBidi_Force_RTL = 5,
+
+    kBidi_Mask = 0x7
+};
+
+enum {
     kDirection_LTR = 0,
     kDirection_RTL = 1,
 
@@ -52,18 +63,28 @@ class TextLayout {
 public:
 
     static void getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
-                                   jint count, jint contextCount,
+                                   jint count, jint contextCount, jint dirFlags,
                                    jfloat* resultAdvances, jfloat* resultTotalAdvance);
 
+    static void getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
+                                   jint count, jint contextCount, jint dirFlags,
+                                   jfloat* resultAdvances, jfloat& resultTotalAdvance);
+
     static void getTextPath(SkPaint* paint, const jchar* text, jsize len,
-                            jfloat x, jfloat y, SkPath* path);
+                            jint bidiFlags, jfloat x, jfloat y, SkPath* path);
 
     static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len,
-                               jfloat hOffset, jfloat vOffset,
+                               int bidiFlags, jfloat hOffset, jfloat vOffset,
                                SkPath* path, SkCanvas* canvas);
 
 private:
+    static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
+
     static void handleText(SkPaint* paint, const jchar* text, jsize len,
-                           jfloat x, jfloat y, SkPath* path);
+                           int bidiFlags, jfloat x, jfloat y, SkPath* path);
+
+    static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
+            size_t start, size_t count, size_t contextCount, int dirFlags,
+            jfloat* outAdvances, jfloat* outTotalAdvance);
 };
 } // namespace android
index 1a8612e..1ace23e 100644 (file)
@@ -87,7 +87,7 @@ void TextLayoutCache::purgeCaches() {
  * Caching
  */
 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
-            const jchar* text, jint start, jint count, jint contextCount) {
+            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
     AutoMutex _l(mLock);
     nsecs_t startTime = 0;
     if (mDebugEnabled) {
@@ -95,7 +95,7 @@ sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
     }
 
     // Create the key
-    TextLayoutCacheKey key(paint, text, start, count, contextCount);
+    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
 
     // Get value from cache if possible
     sp<TextLayoutValue> value = mCache.get(key);
@@ -111,7 +111,7 @@ sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
         // Compute advances and store them
         mShaper->computeValues(value.get(), paint,
                 reinterpret_cast<const UChar*>(key.getText()), start, count,
-                size_t(contextCount));
+                size_t(contextCount), int(dirFlags));
 
         if (mDebugEnabled) {
             value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
@@ -218,13 +218,14 @@ void TextLayoutCache::dumpCacheStats() {
  * TextLayoutCacheKey
  */
 TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
-        typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
+        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
         hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language()  {
 }
 
 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
-        size_t start, size_t count, size_t contextCount) :
-            start(start), count(count), contextCount(contextCount) {
+        size_t start, size_t count, size_t contextCount, int dirFlags) :
+            start(start), count(count), contextCount(contextCount),
+            dirFlags(dirFlags) {
     textCopy.setTo(text, contextCount);
     typeface = paint->getTypeface();
     textSize = paint->getTextSize();
@@ -241,6 +242,7 @@ TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
         start(other.start),
         count(other.count),
         contextCount(other.contextCount),
+        dirFlags(other.dirFlags),
         typeface(other.typeface),
         textSize(other.textSize),
         textSkewX(other.textSkewX),
@@ -279,6 +281,9 @@ int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutC
     deltaInt = lhs.hinting - rhs.hinting;
     if (deltaInt != 0) return (deltaInt);
 
+    deltaInt = lhs.dirFlags - rhs.dirFlags;
+    if (deltaInt) return (deltaInt);
+
     deltaInt = lhs.variant - rhs.variant;
     if (deltaInt) return (deltaInt);
 
@@ -354,9 +359,9 @@ TextLayoutShaper::~TextLayoutShaper() {
 }
 
 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
-        size_t start, size_t count, size_t contextCount) {
+        size_t start, size_t count, size_t contextCount, int dirFlags) {
 
-    computeValues(paint, chars, start, count, contextCount,
+    computeValues(paint, chars, start, count, contextCount, dirFlags,
             &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
 #if DEBUG_ADVANCES
     ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
@@ -365,7 +370,7 @@ void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* pain
 }
 
 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
-        size_t start, size_t count, size_t contextCount,
+        size_t start, size_t count, size_t contextCount, int dirFlags,
         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
         *outTotalAdvance = 0;
@@ -373,94 +378,110 @@ void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
             return;
         }
 
-        UBiDiLevel bidiReq = UBIDI_DEFAULT_LTR;
-        bool useSingleRun = false;
-        bool isRTL = false;
+        UBiDiLevel bidiReq = 0;
+        bool forceLTR = false;
+        bool forceRTL = false;
+
+        switch (dirFlags & kBidi_Mask) {
+            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
+            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
+            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
+            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
+            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
+            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
+        }
 
-        UBiDi* bidi = ubidi_open();
-        if (bidi) {
-            UErrorCode status = U_ZERO_ERROR;
+        bool useSingleRun = false;
+        bool isRTL = forceRTL;
+        if (forceLTR || forceRTL) {
+            useSingleRun = true;
+        } else {
+            UBiDi* bidi = ubidi_open();
+            if (bidi) {
+                UErrorCode status = U_ZERO_ERROR;
 #if DEBUG_GLYPHS
-            ALOGD("******** ComputeValues -- start");
-            ALOGD("      -- string = '%s'", String8(chars + start, count).string());
-            ALOGD("      -- start = %d", start);
-            ALOGD("      -- count = %d", count);
-            ALOGD("      -- contextCount = %d", contextCount);
-            ALOGD("      -- bidiReq = %d", bidiReq);
+                ALOGD("******** ComputeValues -- start");
+                ALOGD("      -- string = '%s'", String8(chars + start, count).string());
+                ALOGD("      -- start = %d", start);
+                ALOGD("      -- count = %d", count);
+                ALOGD("      -- contextCount = %d", contextCount);
+                ALOGD("      -- bidiReq = %d", bidiReq);
 #endif
-            ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
-            if (U_SUCCESS(status)) {
-                int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
-                ssize_t rc = ubidi_countRuns(bidi, &status);
+                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
+                if (U_SUCCESS(status)) {
+                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
+                    ssize_t rc = ubidi_countRuns(bidi, &status);
 #if DEBUG_GLYPHS
-                ALOGD("      -- paraDir = %d", paraDir);
-                ALOGD("      -- run-count = %d", int(rc));
+                    ALOGD("      -- dirFlags = %d", dirFlags);
+                    ALOGD("      -- paraDir = %d", paraDir);
+                    ALOGD("      -- run-count = %d", int(rc));
 #endif
-                if (U_SUCCESS(status) && rc == 1) {
-                    // Normal case: one run, status is ok
-                    isRTL = (paraDir == 1);
-                    useSingleRun = true;
-                } else if (!U_SUCCESS(status) || rc < 1) {
-                    ALOGW("Need to force to single run -- string = '%s',"
-                            " status = %d, rc = %d",
-                            String8(chars + start, count).string(), status, int(rc));
-                    isRTL = (paraDir == 1);
-                    useSingleRun = true;
-                } else {
-                    int32_t end = start + count;
-                    for (size_t i = 0; i < size_t(rc); ++i) {
-                        int32_t startRun = -1;
-                        int32_t lengthRun = -1;
-                        UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
-
-                        if (startRun == -1 || lengthRun == -1) {
-                            // Something went wrong when getting the visual run, need to clear
-                            // already computed data before doing a single run pass
-                            ALOGW("Visual run is not valid");
-                            outGlyphs->clear();
-                            outAdvances->clear();
-                            outPos->clear();
-                            *outTotalAdvance = 0;
-                            isRTL = (paraDir == 1);
-                            useSingleRun = true;
-                            break;
-                        }
-
-                        if (startRun >= end) {
-                            continue;
-                        }
-                        int32_t endRun = startRun + lengthRun;
-                        if (endRun <= int32_t(start)) {
-                            continue;
-                        }
-                        if (startRun < int32_t(start)) {
-                            startRun = int32_t(start);
-                        }
-                        if (endRun > end) {
-                            endRun = end;
-                        }
-
-                        lengthRun = endRun - startRun;
-                        isRTL = (runDir == UBIDI_RTL);
+                    if (U_SUCCESS(status) && rc == 1) {
+                        // Normal case: one run, status is ok
+                        isRTL = (paraDir == 1);
+                        useSingleRun = true;
+                    } else if (!U_SUCCESS(status) || rc < 1) {
+                        ALOGW("Need to force to single run -- string = '%s',"
+                                " status = %d, rc = %d",
+                                String8(chars + start, count).string(), status, int(rc));
+                        isRTL = (paraDir == 1);
+                        useSingleRun = true;
+                    } else {
+                        int32_t end = start + count;
+                        for (size_t i = 0; i < size_t(rc); ++i) {
+                            int32_t startRun = -1;
+                            int32_t lengthRun = -1;
+                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
+
+                            if (startRun == -1 || lengthRun == -1) {
+                                // Something went wrong when getting the visual run, need to clear
+                                // already computed data before doing a single run pass
+                                ALOGW("Visual run is not valid");
+                                outGlyphs->clear();
+                                outAdvances->clear();
+                                outPos->clear();
+                                *outTotalAdvance = 0;
+                                isRTL = (paraDir == 1);
+                                useSingleRun = true;
+                                break;
+                            }
+
+                            if (startRun >= end) {
+                                continue;
+                            }
+                            int32_t endRun = startRun + lengthRun;
+                            if (endRun <= int32_t(start)) {
+                                continue;
+                            }
+                            if (startRun < int32_t(start)) {
+                                startRun = int32_t(start);
+                            }
+                            if (endRun > end) {
+                                endRun = end;
+                            }
+
+                            lengthRun = endRun - startRun;
+                            isRTL = (runDir == UBIDI_RTL);
 #if DEBUG_GLYPHS
-                        ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
-                                i, startRun, lengthRun, isRTL);
+                            ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
+                                    i, startRun, lengthRun, isRTL);
 #endif
-                        computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
-                                outAdvances, outTotalAdvance, outGlyphs, outPos);
+                            computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
+                                    outAdvances, outTotalAdvance, outGlyphs, outPos);
 
+                        }
                     }
+                } else {
+                    ALOGW("Cannot set Para");
+                    useSingleRun = true;
+                    isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
                 }
+                ubidi_close(bidi);
             } else {
-                ALOGW("Cannot set Para");
+                ALOGW("Cannot ubidi_open()");
                 useSingleRun = true;
                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
             }
-            ubidi_close(bidi);
-        } else {
-            ALOGW("Cannot ubidi_open()");
-            useSingleRun = true;
-            isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
         }
 
         // Default single run case
@@ -897,11 +918,11 @@ TextLayoutEngine::~TextLayoutEngine() {
 }
 
 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
-        jint start, jint count, jint contextCount) {
+        jint start, jint count, jint contextCount, jint dirFlags) {
     sp<TextLayoutValue> value;
 #if USE_TEXT_LAYOUT_CACHE
     value = mTextLayoutCache->getValue(paint, text, start, count,
-            contextCount);
+            contextCount, dirFlags);
     if (value == NULL) {
         ALOGE("Cannot get TextLayoutCache value for text = '%s'",
                 String8(text + start, count).string());
@@ -909,7 +930,7 @@ sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar
 #else
     value = new TextLayoutValue(count);
     mShaper->computeValues(value.get(), paint,
-            reinterpret_cast<const UChar*>(text), start, count, contextCount);
+            reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
 #endif
     return value;
 }
index 6858c0e..f9b9900 100644 (file)
@@ -70,7 +70,7 @@ public:
     TextLayoutCacheKey();
 
     TextLayoutCacheKey(const SkPaint* paint, const UChar* text, size_t start, size_t count,
-            size_t contextCount);
+            size_t contextCount, int dirFlags);
 
     TextLayoutCacheKey(const TextLayoutCacheKey& other);
 
@@ -97,6 +97,7 @@ private:
     size_t start;
     size_t count;
     size_t contextCount;
+    int dirFlags;
     SkTypeface* typeface;
     SkScalar textSize;
     SkScalar textSkewX;
@@ -180,7 +181,7 @@ public:
     virtual ~TextLayoutShaper();
 
     void computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
-            size_t start, size_t count, size_t contextCount);
+            size_t start, size_t count, size_t contextCount, int dirFlags);
 
     void purgeCaches();
 
@@ -214,7 +215,7 @@ private:
     size_t shapeFontRun(const SkPaint* paint);
 
     void computeValues(const SkPaint* paint, const UChar* chars,
-            size_t start, size_t count, size_t contextCount,
+            size_t start, size_t count, size_t contextCount, int dirFlags,
             Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
             Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
 
@@ -251,7 +252,7 @@ public:
     void operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc);
 
     sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
-            jint count, jint contextCount);
+            jint count, jint contextCount, jint dirFlags);
 
     /**
      * Clear the cache
@@ -303,7 +304,7 @@ public:
      * the call. Be careful of this when doing optimization.
      **/
     sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
-            jint count, jint contextCount);
+            jint count, jint contextCount, jint dirFlags);
 
     void purgeCaches();
 
index 6374494..3083cb1 100644 (file)
@@ -31,7 +31,7 @@
 static struct {
     jclass clazz;
     jmethodID dispatchSensorEvent;
-} gBaseEventQueueClassInfo;
+} gSensorEventQueueClassInfo;
 
 namespace android {
 
@@ -145,7 +145,7 @@ private:
                 env->SetFloatArrayRegion(mScratch, 0, 16, buffer[i].data);
 
                 env->CallVoidMethod(mReceiverObject,
-                        gBaseEventQueueClassInfo.dispatchSensorEvent,
+                        gSensorEventQueueClassInfo.dispatchSensorEvent,
                         buffer[i].sensor,
                         mScratch,
                         buffer[i].vector.status,
@@ -209,9 +209,9 @@ static JNINativeMethod gSystemSensorManagerMethods[] = {
             (void*)nativeGetNextSensor },
 };
 
-static JNINativeMethod gBaseEventQueueMethods[] = {
-    {"nativeInitBaseEventQueue",
-            "(Landroid/hardware/SystemSensorManager$BaseEventQueue;Landroid/os/MessageQueue;[F)I",
+static JNINativeMethod gSensorEventQueueMethods[] = {
+    {"nativeInitSensorEventQueue",
+            "(Landroid/hardware/SystemSensorManager$SensorEventQueue;Landroid/os/MessageQueue;[F)I",
             (void*)nativeInitSensorEventQueue },
 
     {"nativeEnableSensor",
@@ -245,13 +245,13 @@ int register_android_hardware_SensorManager(JNIEnv *env)
     jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager",
             gSystemSensorManagerMethods, NELEM(gSystemSensorManagerMethods));
 
-    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$BaseEventQueue",
-            gBaseEventQueueMethods, NELEM(gBaseEventQueueMethods));
+    jniRegisterNativeMethods(env, "android/hardware/SystemSensorManager$SensorEventQueue",
+            gSensorEventQueueMethods, NELEM(gSensorEventQueueMethods));
 
-    FIND_CLASS(gBaseEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$BaseEventQueue");
+    FIND_CLASS(gSensorEventQueueClassInfo.clazz, "android/hardware/SystemSensorManager$SensorEventQueue");
 
-    GET_METHOD_ID(gBaseEventQueueClassInfo.dispatchSensorEvent,
-            gBaseEventQueueClassInfo.clazz,
+    GET_METHOD_ID(gSensorEventQueueClassInfo.dispatchSensorEvent,
+            gSensorEventQueueClassInfo.clazz,
             "dispatchSensorEvent", "(I[FIJ)V");
 
     return 0;
index f5f22b2..faae11e 100644 (file)
@@ -136,6 +136,10 @@ static jboolean android_net_utils_runDhcpCommon(JNIEnv* env, jobject clazz, jstr
         result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength,
                 dns, server, &lease, vendorInfo, domains);
     }
+    if (result != 0) {
+        ALOGD("dhcp_do_request failed");
+    }
+
     env->ReleaseStringUTFChars(ifname, nameStr);
     if (result == 0) {
         env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.clear);
index ba62f6d..10c92ba 100644 (file)
@@ -568,9 +568,9 @@ static void android_view_GLES20Canvas_resetPaintFilter(JNIEnv* env, jobject claz
 // ----------------------------------------------------------------------------
 
 static void renderText(OpenGLRenderer* renderer, const jchar* text, int count,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, int flags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, flags);
     if (value == NULL) {
         return;
     }
@@ -584,9 +584,9 @@ static void renderText(OpenGLRenderer* renderer, const jchar* text, int count,
 }
 
 static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int count,
-        SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+        SkPath* path, jfloat hOffset, jfloat vOffset, int flags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, flags);
     if (value == NULL) {
         return;
     }
@@ -599,9 +599,9 @@ static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int co
 
 static void renderTextRun(OpenGLRenderer* renderer, const jchar* text,
         jint start, jint count, jint contextCount, jfloat x, jfloat y,
-        SkPaint* paint) {
+        int flags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, start, count, contextCount);
+            text, start, count, contextCount, flags);
     if (value == NULL) {
         return;
     }
@@ -616,62 +616,64 @@ static void renderTextRun(OpenGLRenderer* renderer, const jchar* text,
 
 static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jcharArray text, jint index, jint count,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, jint flags, SkPaint* paint) {
     jchar* textArray = env->GetCharArrayElements(text, NULL);
-    renderText(renderer, textArray + index, count, x, y, paint);
+    renderText(renderer, textArray + index, count, x, y, flags, paint);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
 }
 
 static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jstring text, jint start, jint end,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, jint flags, SkPaint* paint) {
     const jchar* textArray = env->GetStringChars(text, NULL);
-    renderText(renderer, textArray + start, end - start, x, y, paint);
+    renderText(renderer, textArray + start, end - start, x, y, flags, paint);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void android_view_GLES20Canvas_drawTextArrayOnPath(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jcharArray text, jint index, jint count,
-        SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+        SkPath* path, jfloat hOffset, jfloat vOffset, jint flags, SkPaint* paint) {
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     renderTextOnPath(renderer, textArray + index, count, path,
-            hOffset, vOffset, paint);
+            hOffset, vOffset, flags, paint);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
 }
 
 static void android_view_GLES20Canvas_drawTextOnPath(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jstring text, jint start, jint end,
-        SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+        SkPath* path, jfloat hOffset, jfloat vOffset, jint flags, SkPaint* paint) {
     const jchar* textArray = env->GetStringChars(text, NULL);
     renderTextOnPath(renderer, textArray + start, end - start, path,
-            hOffset, vOffset, paint);
+            hOffset, vOffset, flags, paint);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jcharArray text, jint index, jint count,
-        jint contextIndex, jint contextCount, jfloat x, jfloat y, SkPaint* paint) {
+        jint contextIndex, jint contextCount, jfloat x, jfloat y, jint dirFlags,
+        SkPaint* paint) {
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     renderTextRun(renderer, textArray + contextIndex, index - contextIndex,
-            count, contextCount, x, y, paint);
+            count, contextCount, x, y, dirFlags, paint);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
  }
 
 static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jstring text, jint start, jint end,
-        jint contextStart, int contextEnd, jfloat x, jfloat y, SkPaint* paint) {
+        jint contextStart, int contextEnd, jfloat x, jfloat y, jint dirFlags,
+        SkPaint* paint) {
     const jchar* textArray = env->GetStringChars(text, NULL);
     jint count = end - start;
     jint contextCount = contextEnd - contextStart;
     renderTextRun(renderer, textArray + contextStart, start - contextStart,
-            count, contextCount, x, y, paint);
+            count, contextCount, x, y, dirFlags, paint);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void renderPosText(OpenGLRenderer* renderer, const jchar* text, int count,
-        const jfloat* positions, SkPaint* paint) {
+        const jfloat* positions, jint dirFlags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, dirFlags);
     if (value == NULL) {
         return;
     }
@@ -689,7 +691,7 @@ static void android_view_GLES20Canvas_drawPosTextArray(JNIEnv* env, jobject claz
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     jfloat* positions = env->GetFloatArrayElements(pos, NULL);
 
-    renderPosText(renderer, textArray + index, count, positions, paint);
+    renderPosText(renderer, textArray + index, count, positions, kBidi_LTR, paint);
 
     env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
@@ -701,7 +703,7 @@ static void android_view_GLES20Canvas_drawPosText(JNIEnv* env, jobject clazz,
     const jchar* textArray = env->GetStringChars(text, NULL);
     jfloat* positions = env->GetFloatArrayElements(pos, NULL);
 
-    renderPosText(renderer, textArray + start, end - start, positions, paint);
+    renderPosText(renderer, textArray + start, end - start, positions, kBidi_LTR, paint);
 
     env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT);
     env->ReleaseStringChars(text, textArray);
@@ -1005,16 +1007,16 @@ static JNINativeMethod gMethods[] = {
     { "nSetupPaintFilter",  "(III)V",          (void*) android_view_GLES20Canvas_setupPaintFilter },
     { "nResetPaintFilter",  "(I)V",            (void*) android_view_GLES20Canvas_resetPaintFilter },
 
-    { "nDrawText",          "(I[CIIFFI)V",    (void*) android_view_GLES20Canvas_drawTextArray },
-    { "nDrawText",          "(ILjava/lang/String;IIFFI)V",
+    { "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
+    { "nDrawText",          "(ILjava/lang/String;IIFFII)V",
             (void*) android_view_GLES20Canvas_drawText },
 
-    { "nDrawTextOnPath",    "(I[CIIIFFI)V",   (void*) android_view_GLES20Canvas_drawTextArrayOnPath },
-    { "nDrawTextOnPath",    "(ILjava/lang/String;IIIFFI)V",
+    { "nDrawTextOnPath",    "(I[CIIIFFII)V",   (void*) android_view_GLES20Canvas_drawTextArrayOnPath },
+    { "nDrawTextOnPath",    "(ILjava/lang/String;IIIFFII)V",
             (void*) android_view_GLES20Canvas_drawTextOnPath },
 
-    { "nDrawTextRun",       "(I[CIIIIFFI)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
-    { "nDrawTextRun",       "(ILjava/lang/String;IIIIFFI)V",
+    { "nDrawTextRun",       "(I[CIIIIFFII)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
+    { "nDrawTextRun",       "(ILjava/lang/String;IIIIFFII)V",
             (void*) android_view_GLES20Canvas_drawTextRun },
 
     { "nDrawPosText",       "(I[CII[FI)V",     (void*) android_view_GLES20Canvas_drawPosTextArray },
index 9c78abe..4671282 100644 (file)
@@ -151,11 +151,6 @@ static void nativeRelease(JNIEnv* env, jclass clazz, jint nativeObject) {
     sur->decStrong(&sRefBaseOwner);
 }
 
-static void nativeDestroy(JNIEnv* env, jclass clazz, jint nativeObject) {
-    sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
-    sur->decStrong(&sRefBaseOwner);
-}
-
 static jboolean nativeIsValid(JNIEnv* env, jclass clazz, jint nativeObject) {
     sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
     return isSurfaceValid(sur) ? JNI_TRUE : JNI_FALSE;
@@ -332,19 +327,32 @@ static jint nativeReadFromParcel(JNIEnv* env, jclass clazz,
         doThrowNPE(env);
         return 0;
     }
+
     sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
-    if (self != NULL) {
-        self->decStrong(&sRefBaseOwner);
+    sp<IBinder> binder(parcel->readStrongBinder());
+
+    // update the Surface only if the underlying IGraphicBufferProducer
+    // has changed.
+    if (self != NULL
+            && (self->getIGraphicBufferProducer()->asBinder() == binder)) {
+        // same IGraphicBufferProducer, return ourselves
+        return int(self.get());
     }
 
     sp<Surface> sur;
-    sp<IGraphicBufferProducer> gbp(
-            interface_cast<IGraphicBufferProducer>(parcel->readStrongBinder()));
+    sp<IGraphicBufferProducer> gbp(interface_cast<IGraphicBufferProducer>(binder));
     if (gbp != NULL) {
+        // we have a new IGraphicBufferProducer, create a new Surface for it
         sur = new Surface(gbp);
+        // and keep a reference before passing to java
         sur->incStrong(&sRefBaseOwner);
     }
 
+    if (self != NULL) {
+        // and loose the java reference to ourselves
+        self->decStrong(&sRefBaseOwner);
+    }
+
     return int(sur.get());
 }
 
@@ -366,8 +374,6 @@ static JNINativeMethod gSurfaceMethods[] = {
             (void*)nativeCreateFromSurfaceTexture },
     {"nativeRelease", "(I)V",
             (void*)nativeRelease },
-    {"nativeDestroy", "(I)V",
-            (void*)nativeDestroy },
     {"nativeIsValid", "(I)Z",
             (void*)nativeIsValid },
     {"nativeIsConsumerRunningBehind", "(I)Z",
index 8a53cc3..5a1c0f8 100644 (file)
     <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" />
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
     <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
+    <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" />
 
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
index aef7bdc..102815f 100644 (file)
     <string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"Umožňuje aplikaci číst historii všech adres URL navštívených v Prohlížeči a všechny záložky v Prohlížeči. Poznámka: Pro prohlížeče třetí strany a jiné aplikace umožňující procházení webu toto oprávnění platit nemusí."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="3714785165273314490">"psaní webových záložek a historie"</string>
     <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"Umožňuje aplikaci upravit historii prohlížeče nebo záložky uložené v tabletu. Aplikace s tímto oprávněním může vymazat či pozměnit data prohlížeče. Poznámka: Pro prohlížeče třetí strany a jiné aplikace umožňující procházení webu toto oprávnění platit nemusí."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Umožňuje aplikaci upravit historii prohlížeče nebo záložky uložené v telefonu. Aplikace s tímto oprávněním může vymazat či pozměnit data prohlížeče .Poznámka: Pro prohlížeče třetí strany a jiné aplikace umožňující procházení webu toto oprávnění platit nemusí."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Umožňuje aplikaci upravit historii prohlížeče nebo záložky uložené v telefonu. Aplikace s tímto oprávněním může vymazat či pozměnit data prohlížečePoznámka: Pro prohlížeče třetí strany a jiné aplikace umožňující procházení webu toto oprávnění platit nemusí."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"nastavení budíku"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Umožňuje aplikaci nastavit budík v nainstalované aplikaci budík. Některé aplikace budík tuto funkci nemusí obsahovat."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"přidat hlasovou zprávu"</string>
index 1fc59b6..eeb6878 100644 (file)
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que la aplicación inicie la IU de confirmación de copia de seguridad completa. No todas las aplicaciones pueden utilizar este permiso."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"mostrar ventanas no autorizadas"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que la aplicación cree ventanas para la interfaz de usuario interna del sistema. Las aplicaciones normales no deben usar este permiso."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"destacar sobre otras aplicaciones"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"mostrar sobre otras aplicaciones"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite a la aplicación escribir por encima de otras aplicaciones o partes de la interfaz de usuario que pueden interferir en tu uso de la interfaz en cualquier aplicación o cambiar lo que crees que ves en otras aplicaciones."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar la velocidad de la animación global"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que la aplicación cambie la velocidad de animación global (animaciones más rápidas o más lentas) en cualquier momento."</string>
index 98d7477..8aedc15 100644 (file)
     <string name="dlg_confirm_kill_storage_users_text" msgid="5100428757107469454">"Po włączeniu nośnika USB niektóre używane aplikacje zostaną zatrzymane i mogą być niedostępne do chwili jego wyłączenia."</string>
     <string name="dlg_error_title" msgid="7323658469626514207">"Operacja USB zakończona niepowodzeniem"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
-    <string name="usb_mtp_notification_title" msgid="3699913097391550394">"Podłączono jako urządzenie multimedialne."</string>
+    <string name="usb_mtp_notification_title" msgid="3699913097391550394">"Podłączono urządzenie multimedialne"</string>
     <string name="usb_ptp_notification_title" msgid="1960817192216064833">"Podłączono jako aparat."</string>
     <string name="usb_cd_installer_notification_title" msgid="6774712827892090754">"Podłączono jako nośnik instalacyjny."</string>
     <string name="usb_accessory_notification_title" msgid="7848236974087653666">"Podłączono akcesorium USB"</string>
     <string name="sync_really_delete" msgid="2572600103122596243">"Usuń elementy."</string>
     <string name="sync_undo_deletes" msgid="2941317360600338602">"Cofnij usunięcia"</string>
     <string name="sync_do_nothing" msgid="3743764740430821845">"Nie wykonuj teraz żadnych czynności."</string>
-    <string name="choose_account_label" msgid="5655203089746423927">"Wybierz konto."</string>
+    <string name="choose_account_label" msgid="5655203089746423927">"Wybierz konto"</string>
     <string name="add_account_label" msgid="2935267344849993553">"Dodaj konto"</string>
     <string name="add_account_button_label" msgid="3611982894853435874">"Dodaj konto"</string>
     <string name="number_picker_increment_button" msgid="2412072272832284313">"Zwiększ"</string>
index 88c56bc..4fb58b8 100644 (file)
@@ -40,7 +40,7 @@
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Serviciul a fost activat pentru:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Serviciul a fost dezactivat."</string>
     <string name="serviceRegistered" msgid="6275019082598102493">"Înregistrarea a reuşit."</string>
-    <string name="serviceErased" msgid="1288584695297200972">"Ştergerea a reuşit."</string>
+    <string name="serviceErased" msgid="1288584695297200972">"Ștergerea a reuşit."</string>
     <string name="passwordIncorrect" msgid="7612208839450128715">"Parolă incorectă."</string>
     <string name="mmiComplete" msgid="8232527495411698359">"MMI finalizat."</string>
     <string name="badPin" msgid="9015277645546710014">"Codul PIN vechi introdus nu este corect."</string>
     <string name="contentServiceSync" msgid="8353523060269335667">"Sincronizare"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sincronizare"</string>
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Prea multe ştergeri <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
-    <string name="low_memory" product="tablet" msgid="6494019234102154896">"Stocarea pe tabletă este plină. Ştergeţi câteva fişiere pentru a elibera spaţiu."</string>
-    <string name="low_memory" product="default" msgid="3475999286680000541">"Stocarea pe telefon este plină. Ştergeţi câteva fişiere pentru a elibera spaţiu."</string>
+    <string name="low_memory" product="tablet" msgid="6494019234102154896">"Stocarea pe tabletă este plină. Ștergeţi câteva fişiere pentru a elibera spaţiu."</string>
+    <string name="low_memory" product="default" msgid="3475999286680000541">"Stocarea pe telefon este plină. Ștergeţi câteva fişiere pentru a elibera spaţiu."</string>
     <string name="me" msgid="6545696007631404292">"Eu"</string>
     <string name="power_dialog" product="tablet" msgid="8545351420865202853">"Opţiuni tablet PC"</string>
     <string name="power_dialog" product="default" msgid="1319919075463988638">"Opţiuni telefon"</string>
     <string name="policydesc_resetPassword" msgid="605963962301904458">"Editaţi parola de deblocare a ecranului."</string>
     <string name="policylab_forceLock" msgid="2274085384704248431">"Blocaţi ecranul"</string>
     <string name="policydesc_forceLock" msgid="1141797588403827138">"Stabiliţi modul şi timpul în care se blochează ecranul."</string>
-    <string name="policylab_wipeData" msgid="3910545446758639713">"Ştergere integrală date"</string>
-    <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"Ştergeţi datele de pe tabletă fără avertisment, efectuând resetarea configurării din fabrică."</string>
-    <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"Ştergeţi datele din telefon fără avertisment, efectuând resetarea configurării din fabrică."</string>
+    <string name="policylab_wipeData" msgid="3910545446758639713">"Ștergere integrală date"</string>
+    <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"Ștergeţi datele de pe tabletă fără avertisment, efectuând resetarea configurării din fabrică."</string>
+    <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"Ștergeţi datele din telefon fără avertisment, efectuând resetarea configurării din fabrică."</string>
     <string name="policylab_setGlobalProxy" msgid="2784828293747791446">"Setaţi serverul proxy global pentru dispozitiv"</string>
     <string name="policydesc_setGlobalProxy" msgid="6387497466660154931">"Setaţi serverul proxy global pentru dispozitiv care să fie utilizat cât timp politica este activă. Numai primul administrator al dispozitivului poate seta serverul proxy global activ."</string>
     <string name="policylab_expirePassword" msgid="885279151847254056">"Expirare parolă blocare ecran"</string>
     <string name="search_go" msgid="8298016669822141719">"Căutaţi"</string>
     <string name="searchview_description_search" msgid="6749826639098512120">"Căutaţi"</string>
     <string name="searchview_description_query" msgid="5911778593125355124">"Interogare de căutare"</string>
-    <string name="searchview_description_clear" msgid="1330281990951833033">"Ştergeţi interogarea"</string>
+    <string name="searchview_description_clear" msgid="1330281990951833033">"Ștergeţi interogarea"</string>
     <string name="searchview_description_submit" msgid="2688450133297983542">"Trimiteţi interogarea"</string>
     <string name="searchview_description_voice" msgid="2453203695674994440">"Căutare vocală"</string>
     <string name="enable_explore_by_touch_warning_title" msgid="7460694070309730149">"Activaţi Exploraţi prin atingere?"</string>
     <string name="copy" msgid="2681946229533511987">"Copiaţi"</string>
     <string name="paste" msgid="5629880836805036433">"Inseraţi"</string>
     <string name="replace" msgid="5781686059063148930">"Înlocuiţi..."</string>
-    <string name="delete" msgid="6098684844021697789">"Ştergeţi"</string>
+    <string name="delete" msgid="6098684844021697789">"Ștergeţi"</string>
     <string name="copyUrl" msgid="2538211579596067402">"Copiaţi adresa URL"</string>
     <string name="selectTextMode" msgid="1018691815143165326">"Selectaţi text"</string>
     <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selectare text"</string>
     <string name="addToDictionary" msgid="4352161534510057874">"Adăugaţi în dicţionar"</string>
-    <string name="deleteText" msgid="6979668428458199034">"Ştergeţi"</string>
+    <string name="deleteText" msgid="6979668428458199034">"Ștergeţi"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metodă de intrare"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Acţiuni pentru text"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Spaţiul de stocare aproape ocupat"</string>
     <string name="capital_off" msgid="6815870386972805832">"DEZACTIVAT"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Finalizare acţiune utilizând"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"Se utilizează în mod prestabilit pentru această acţiune."</string>
-    <string name="clearDefaultHintMsg" msgid="3252584689512077257">"Ştergeţi setările prestabilite din Setări de sistem &gt; Aplicaţii &gt; Descărcate."</string>
+    <string name="clearDefaultHintMsg" msgid="3252584689512077257">"Ștergeţi setările prestabilite din Setări de sistem &gt; Aplicaţii &gt; Descărcate."</string>
     <string name="chooseActivity" msgid="7486876147751803333">"Alegeţi o acţiune"</string>
     <string name="chooseUsbActivity" msgid="6894748416073583509">"Alegeţi o aplicaţie pentru dispozitivul USB"</string>
     <string name="noApplications" msgid="2991814273936504689">"Această acţiune nu poate fi efectuată de nicio aplicaţie."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depanarea USB este conectată"</string>
     <string name="adb_active_notification_message" msgid="1016654627626476142">"Atingeţi pentru a dezactiva depanarea USB."</string>
     <string name="select_input_method" msgid="4653387336791222978">"Alegeți metoda de introducere de text"</string>
-    <string name="configure_input_methods" msgid="9091652157722495116">"Configurați metode introducere"</string>
+    <string name="configure_input_methods" msgid="9091652157722495116">"Setați metode introducere text"</string>
     <string name="use_physical_keyboard" msgid="6203112478095117625">"Tastatură fizică"</string>
     <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
     <string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"Selectaţi aspectul tastaturii"</string>
     <string name="gpsVerifNo" msgid="1146564937346454865">"Nu"</string>
     <string name="sync_too_many_deletes" msgid="5296321850662746890">"Limita pentru ştergere a fost depăşită"</string>
     <string name="sync_too_many_deletes_desc" msgid="496551671008694245">"Există <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> (de) elemente şterse pentru <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, contul <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. Ce doriţi să faceţi?"</string>
-    <string name="sync_really_delete" msgid="2572600103122596243">"Ştergeţi elementele"</string>
+    <string name="sync_really_delete" msgid="2572600103122596243">"Ștergeţi elementele"</string>
     <string name="sync_undo_deletes" msgid="2941317360600338602">"Anulaţi aceste ştergeri"</string>
     <string name="sync_do_nothing" msgid="3743764740430821845">"Nu trebuie să luaţi nicio măsură deocamdată"</string>
     <string name="choose_account_label" msgid="5655203089746423927">"Alegeţi un cont"</string>
     <string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Reduceţi valoarea pentru an"</string>
     <string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
     <string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Anulaţi"</string>
-    <string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Ştergeţi"</string>
+    <string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Ștergeţi"</string>
     <string name="keyboardview_keycode_done" msgid="1992571118466679775">"Terminat"</string>
     <string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"Schimbarea modului"</string>
     <string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
index ccdddd8..6a8407f 100644 (file)
 
     <!-- Flag indicating if the speed up audio on mt call code should be executed -->
     <bool name="config_speed_up_audio_on_mt_calls">false</bool>
+
+    <!-- Class name of the framework account picker activity.
+         Can be customized for other product types -->
+    <string name="config_chooseAccountActivity"
+            >android/android.accounts.ChooseAccountActivity</string>
+    <!-- Class name of the account type and account picker activity.
+         Can be customized for other product types -->
+    <string name="config_chooseTypeAndAccountActivity"
+            >android/android.accounts.ChooseTypeAndAccountActivity</string>
+
 </resources>
index 140ff70..d57d56a 100644 (file)
   <java-symbol type="string" name="media_route_status_available" />
   <java-symbol type="string" name="media_route_status_not_available" />
   <java-symbol type="string" name="owner_name" />
+  <java-symbol type="string" name="config_chooseAccountActivity" />
+  <java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+
 
   <java-symbol type="plurals" name="abbrev_in_num_days" />
   <java-symbol type="plurals" name="abbrev_in_num_hours" />
index 8c7245c..249b598 100644 (file)
@@ -20,6 +20,6 @@
 
 <!-- If you edit this version, also edit the version in the partner-supplied
     apns-conf.xml configuration file -->
-<apns version="7">
+<apns version="8">
 
 </apns>
index fffaa00..274ac6b 100644 (file)
@@ -22,6 +22,7 @@ import android.test.suitebuilder.annotation.SmallTest;
 import junit.framework.TestCase;
 
 import java.net.InetAddress;
+import java.util.ArrayList;
 
 public class LinkPropertiesTest extends TestCase {
     private static String ADDRV4 = "75.208.6.1";
@@ -256,4 +257,30 @@ public class LinkPropertiesTest extends TestCase {
         assertEquals(3, lp.compareRoutes(lp2).added.size());
         assertEquals(3, lp.compareRoutes(lp2).removed.size());
     }
+
+    @SmallTest
+    public void testStackedInterfaces() {
+        LinkProperties rmnet0 = new LinkProperties();
+        rmnet0.setInterfaceName("rmnet0");
+
+        LinkProperties clat4 = new LinkProperties();
+        clat4.setInterfaceName("clat4");
+
+        assertEquals(0, rmnet0.getStackedLinks().size());
+        rmnet0.addStackedLink(clat4);
+        assertEquals(1, rmnet0.getStackedLinks().size());
+        rmnet0.addStackedLink(clat4);
+        assertEquals(1, rmnet0.getStackedLinks().size());
+        assertEquals(0, clat4.getStackedLinks().size());
+
+        // Modify an item in the returned collection to see what happens.
+        for (LinkProperties link : rmnet0.getStackedLinks()) {
+            if (link.getInterfaceName().equals("clat4")) {
+               link.setInterfaceName("newname");
+            }
+        }
+        for (LinkProperties link : rmnet0.getStackedLinks()) {
+            assertFalse("newname".equals(link.getInterfaceName()));
+        }
+    }
 }
index 4d0b892..0f2b803 100644 (file)
 
 package android.os;
 
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+
 import android.content.Context;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.google.android.collect.Sets;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
+import java.util.Arrays;
+import java.util.HashSet;
 
+import libcore.io.IoUtils;
+
+@MediumTest
 public class FileUtilsTest extends AndroidTestCase {
     private static final String TEST_DATA =
             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
+    private File mDir;
     private File mTestFile;
     private File mCopyFile;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        File testDir = getContext().getDir("testing", Context.MODE_PRIVATE);
-        mTestFile = new File(testDir, "test.file");
-        mCopyFile = new File(testDir, "copy.file");
-        FileWriter writer = new FileWriter(mTestFile);
-        try {
-            writer.write(TEST_DATA, 0, TEST_DATA.length());
-        } finally {
-            writer.close();
-        }
+        mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
+        mTestFile = new File(mDir, "test.file");
+        mCopyFile = new File(mDir, "copy.file");
     }
 
     @Override
     protected void tearDown() throws Exception {
-        if (mTestFile.exists()) mTestFile.delete();
-        if (mCopyFile.exists()) mCopyFile.delete();
+        IoUtils.deleteContents(mDir);
     }
 
     // TODO: test setPermissions(), getPermissions()
 
-    @MediumTest
     public void testCopyFile() throws Exception {
+        stageFile(mTestFile, TEST_DATA);
         assertFalse(mCopyFile.exists());
         FileUtils.copyFile(mTestFile, mCopyFile);
         assertTrue(mCopyFile.exists());
         assertEquals(TEST_DATA, FileUtils.readTextFile(mCopyFile, 0, null));
     }
 
-    @MediumTest
     public void testCopyToFile() throws Exception {
         final String s = "Foo Bar";
         assertFalse(mCopyFile.exists());
-        FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile);        assertTrue(mCopyFile.exists());
+        FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile);
+        assertTrue(mCopyFile.exists());
         assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null));
     }
 
-    @MediumTest
     public void testIsFilenameSafe() throws Exception {
         assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
         assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23")));
@@ -78,8 +82,9 @@ public class FileUtilsTest extends AndroidTestCase {
         assertFalse(FileUtils.isFilenameSafe(new File("foo\nbar")));
     }
 
-    @MediumTest
     public void testReadTextFile() throws Exception {
+        stageFile(mTestFile, TEST_DATA);
+
         assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null));
 
         assertEquals("ABCDE", FileUtils.readTextFile(mTestFile, 5, null));
@@ -97,8 +102,8 @@ public class FileUtilsTest extends AndroidTestCase {
         assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -100, "<>"));
     }
 
-    @MediumTest
     public void testReadTextFileWithZeroLengthFile() throws Exception {
+        stageFile(mTestFile, TEST_DATA);
         new FileOutputStream(mTestFile).close();  // Zero out the file
         assertEquals("", FileUtils.readTextFile(mTestFile, 0, null));
         assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>"));
@@ -106,4 +111,81 @@ public class FileUtilsTest extends AndroidTestCase {
         assertEquals("", FileUtils.readTextFile(mTestFile, -1, "<>"));
         assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
     }
+
+    public void testDeleteOlderEmptyDir() throws Exception {
+        FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
+        assertDirContents();
+    }
+
+    public void testDeleteOlderTypical() throws Exception {
+        touch("file1", HOUR_IN_MILLIS);
+        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 3, DAY_IN_MILLIS);
+        assertDirContents("file1", "file2", "file3");
+    }
+
+    public void testDeleteOlderInFuture() throws Exception {
+        touch("file1", -HOUR_IN_MILLIS);
+        touch("file2", HOUR_IN_MILLIS);
+        touch("file3", WEEK_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
+        assertDirContents("file1", "file2");
+
+        touch("file1", -HOUR_IN_MILLIS);
+        touch("file2", HOUR_IN_MILLIS);
+        touch("file3", WEEK_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
+        assertDirContents("file1", "file2");
+    }
+
+    public void testDeleteOlderOnlyAge() throws Exception {
+        touch("file1", HOUR_IN_MILLIS);
+        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
+        assertDirContents("file1");
+    }
+
+    public void testDeleteOlderOnlyCount() throws Exception {
+        touch("file1", HOUR_IN_MILLIS);
+        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 2, 0);
+        assertDirContents("file1", "file2");
+    }
+
+    private void touch(String name, long age) throws Exception {
+        final File file = new File(mDir, name);
+        file.createNewFile();
+        file.setLastModified(System.currentTimeMillis() - age);
+    }
+
+    private void stageFile(File file, String data) throws Exception {
+        FileWriter writer = new FileWriter(file);
+        try {
+            writer.write(data, 0, data.length());
+        } finally {
+            writer.close();
+        }
+    }
+
+    private void assertDirContents(String... expected) {
+        final HashSet<String> expectedSet = Sets.newHashSet(expected);
+        String[] actual = mDir.list();
+        if (actual == null) actual = new String[0];
+
+        assertEquals(
+                "Expected " + Arrays.toString(expected) + " but actual " + Arrays.toString(actual),
+                expected.length, actual.length);
+        for (String actualFile : actual) {
+            assertTrue("Unexpected actual file " + actualFile, expectedSet.contains(actualFile));
+        }
+    }
 }
index 61b537d..48bea78 100644 (file)
Binary files a/docs/downloads/training/BitmapFun.zip and b/docs/downloads/training/BitmapFun.zip differ
index f2df7be..ec0469c 100644 (file)
@@ -19,12 +19,11 @@ page.metaDescription=The official site for Android developers. Provides the Andr
                     <div class="content-right col-5">
                     <h1>Google I/O 2013</h1>
                     <p>Android will be at Google I/O on May 15-17, 2013, with sessions covering a variety of topics
-                    such as design, performance, and how to extend your app with the latest Android features.
-                    Registration opens on March 13, 2013 at 7:00 AM PDT (GMT-7).</p>
+                    such as design, performance, and how to extend your app with the latest Android features.</p>
                     <p>For more information about event details and planned sessions,
                     stay tuned to <a
                     href="http://google.com/+GoogleDevelopers">+Google Developers</a>.</p>
-                    <p><a href="https://developers.google.com/events/io/register" class="button">Register here</a></p>
+                    <p><a href="https://developers.google.com/events/io/" class="button">Learn more</a></p>
                     </div>
                 </li>
                 <li class="item carousel-home">
index 417ec5b..b1608c3 100644 (file)
@@ -3,10 +3,6 @@ parent.title=Displaying Bitmaps Efficiently
 parent.link=index.html
 
 trainingnavtop=true
-next.title=Displaying Bitmaps in Your UI
-next.link=display-bitmap.html
-previous.title=Processing Bitmaps Off the UI Thread
-previous.link=process-bitmap.html
 
 @jd:body
 
index 4572c42..ed1836c 100644 (file)
@@ -3,8 +3,6 @@ parent.title=Displaying Bitmaps Efficiently
 parent.link=index.html
 
 trainingnavtop=true
-previous.title=Caching Bitmaps
-previous.link=cache-bitmap.html
 
 @jd:body
 
index b91172b..a828291 100644 (file)
@@ -26,7 +26,7 @@ next.link=load-bitmap.html
 </div>
 </div>
 
-<p>This class covers some common techniques for processing and loading {@link
+<p>Learn how to use common techniques to process and load {@link
 android.graphics.Bitmap} objects in a way that keeps your user interface (UI) components responsive
 and avoids exceeding your application memory limit. If you're not careful, bitmaps can quickly
 consume your available memory budget leading to an application crash due to the dreaded
@@ -70,6 +70,9 @@ exception:<br />{@code java.lang.OutofMemoryError: bitmap size exceeds VM budget
     <dd>This lesson walks you through using a memory and disk bitmap cache to improve the
     responsiveness and fluidity of your UI when loading multiple bitmaps.</dd>
 
+  <dt><b><a href="manage-memory.html">Managing Bitmap Memory</a></b></dt>
+    <dd>This lesson explains how to manage bitmap memory to maximize your app's performance.</dd>
+
   <dt><b><a href="display-bitmap.html">Displaying Bitmaps in Your UI</a></b></dt>
     <dd>This lesson brings everything together, showing you how to load multiple bitmaps into
     components like {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView}
index 283f272..633ffd2 100644 (file)
@@ -3,8 +3,6 @@ parent.title=Displaying Bitmaps Efficiently
 parent.link=index.html
 
 trainingnavtop=true
-next.title=Processing Bitmaps Off the UI Thread
-next.link=process-bitmap.html
 
 @jd:body
 
@@ -167,4 +165,4 @@ mImageView.setImageBitmap(
 <p>You can follow a similar process to decode bitmaps from other sources, by substituting the
 appropriate {@link
 android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options)
-BitmapFactory.decode*} method as needed.</p>
\ No newline at end of file
+BitmapFactory.decode*} method as needed.</p>
diff --git a/docs/html/training/displaying-bitmaps/manage-memory.jd b/docs/html/training/displaying-bitmaps/manage-memory.jd
new file mode 100644 (file)
index 0000000..60ac2e6
--- /dev/null
@@ -0,0 +1,297 @@
+page.title=Managing Bitmap Memory
+parent.title=Displaying Bitmaps Efficiently
+parent.link=index.html
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#recycle">Manage Memory on Android 2.3.3 and Lower</a></li>
+  <li><a href="#inBitmap">Manage Memory on Android 3.0 and Higher</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html">Memory Analysis for Android Applications</a> blog post</li>
+  <li><a href="http://www.google.com/events/io/2011/sessions/memory-management-for-android-apps.html">Memory management for Android Apps</a> Google I/O presentation</li>
+  <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li>
+  <li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a>
+  <p class="filename">BitmapFun.zip</p>
+</div>
+
+</div>
+</div>
+
+<p>In addition to the steps described in <a href="cache-bitmap.html">Caching Bitmaps</a>,
+there are  specific things you can do to facilitate garbage collection
+and bitmap reuse. The recommended strategy depends on which version(s)
+of Android you are targeting. The {@code BitmapFun} sample app included with
+this class shows you how to design your app to work efficiently across
+different versions of Android.</p>
+
+<p>To set the stage for this lesson, here is how Android's management of
+bitmap memory has evolved:</p>
+<ul> 
+  <li>
+On Android Android 2.2 (API level 8) and lower, when garbage 
+collection occurs, your app's threads get stopped. This causes a lag that
+can degrade performance. 
+<strong>Android 2.3 adds concurrent garbage collection, which means that
+the memory is reclaimed soon after a bitmap is no longer referenced.</strong>
+</li>
+
+  <li>On Android 2.3.3 (API level 10) and lower, the backing pixel data for a
+bitmap is stored in native memory. It is separate from the bitmap itself,
+which is stored in the Dalvik heap. The pixel data in native memory is
+not released in a predictable manner, potentially causing an application
+to briefly exceed its memory limits and crash.
+<strong>As of Android 3.0 (API Level 11), the pixel data is stored on the
+Dalvik heap along with the associated bitmap.</strong></li>
+
+</ul>
+
+<p>The following sections describe how to optimize bitmap memory
+management for different Android versions.</p>
+
+<h2 id="recycle">Manage Memory on Android 2.3.3 and Lower</h2>
+
+<p>On Android 2.3.3 (API level 10) and lower, using 
+{@link android.graphics.Bitmap#recycle recycle()}
+is recommended. If you're displaying large amounts of bitmap data in your app,
+you're likely to run into
+{@link java.lang.OutOfMemoryError} errors. The
+{@link android.graphics.Bitmap#recycle recycle()} method allows an app
+to reclaim memory as soon as possible.</p>
+
+<p class="note"><strong>Caution:</strong> You should use
+{@link android.graphics.Bitmap#recycle recycle()} only when you are sure that the
+bitmap is no longer being used. If you call {@link android.graphics.Bitmap#recycle recycle()}
+and later attempt to draw the bitmap, you will get the error:
+{@code &quot;Canvas: trying to use a recycled bitmap&quot;}.</p>
+
+<p>The following code snippet gives an example of calling
+{@link android.graphics.Bitmap#recycle recycle()}. It uses reference counting
+(in the variables {@code mDisplayRefCount} and {@code mCacheRefCount}) to track 
+whether a bitmap is currently being displayed or in the cache. The
+code recycles the bitmap when these conditions are met:</p>
+
+<ul>
+<li>The reference count for both {@code mDisplayRefCount} and 
+{@code mCacheRefCount} is 0.</li>
+<li>The bitmap is not {@code null}, and it hasn't been recycled yet.</li>
+</ul>
+
+<pre>private int mCacheRefCount = 0;
+private int mDisplayRefCount = 0;
+...
+// Notify the drawable that the displayed state has changed.
+// Keep a count to determine when the drawable is no longer displayed.
+public void setIsDisplayed(boolean isDisplayed) {
+    synchronized (this) {
+        if (isDisplayed) {
+            mDisplayRefCount++;
+            mHasBeenDisplayed = true;
+        } else {
+            mDisplayRefCount--;
+        }
+    }
+    // Check to see if recycle() can be called.
+    checkState();
+}
+
+// Notify the drawable that the cache state has changed.
+// Keep a count to determine when the drawable is no longer being cached.
+public void setIsCached(boolean isCached) {
+    synchronized (this) {
+        if (isCached) {
+            mCacheRefCount++;
+        } else {
+            mCacheRefCount--;
+        }
+    }
+    // Check to see if recycle() can be called.
+    checkState();
+}
+
+private synchronized void checkState() {
+    // If the drawable cache and display ref counts = 0, and this drawable
+    // has been displayed, then recycle.
+    if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
+            && hasValidBitmap()) {
+        getBitmap().recycle();
+    }
+}
+
+private synchronized boolean hasValidBitmap() {
+    Bitmap bitmap = getBitmap();
+    return bitmap != null && !bitmap.isRecycled();
+}</pre>
+
+<h2 id="inBitmap">Manage Memory on Android 3.0 and Higher</h2>
+
+<p>Android 3.0 (API Level 11) introduces the
+{@link android.graphics.BitmapFactory.Options#inBitmap BitmapFactory.Options.inBitmap}
+field. If this option is set, decode methods that take the 
+{@link android.graphics.BitmapFactory.Options Options} object
+will attempt to reuse an existing bitmap when loading content. This means
+that the bitmap's memory is reused, resulting in improved performance, and
+removing both memory allocation and de-allocation. There are some caveats in using
+{@link android.graphics.BitmapFactory.Options#inBitmap}:</p>
+<ul>
+  <li>The reused bitmap must be of the same size as the source content (to make
+sure that the same amount of memory is used), and in JPEG or PNG format
+(whether as a resource or as a stream).</li>
+
+
+<li>The {@link android.graphics.Bitmap.Config configuration} of the reused bitmap
+overrides the setting of
+{@link android.graphics.BitmapFactory.Options#inPreferredConfig}, if set. </li>
+
+  <li>You should always use the returned bitmap of the decode method,
+because you can't assume that reusing the bitmap worked (for example, if there is
+a size mismatch).</li>
+
+<h3>Save a bitmap for later use</h3>
+
+<p>The following snippet demonstrates how an existing bitmap is stored for possible
+later use in the sample app. When an app is running on Android 3.0 or higher and 
+a bitmap is evicted from the {@link android.util.LruCache},
+a soft reference to the bitmap is placed
+in a {@link java.util.HashSet}, for possible reuse later with
+{@link android.graphics.BitmapFactory.Options#inBitmap}:
+
+<pre>HashSet&lt;SoftReference&lt;Bitmap&gt;&gt; mReusableBitmaps;
+private LruCache&lt;String, BitmapDrawable&gt; mMemoryCache;
+
+// If you're running on Honeycomb or newer, create
+// a HashSet of references to reusable bitmaps.
+if (Utils.hasHoneycomb()) {
+    mReusableBitmaps = new HashSet&lt;SoftReference&lt;Bitmap&gt;&gt;();
+}
+
+mMemoryCache = new LruCache&lt;String, BitmapDrawable&gt;(mCacheParams.memCacheSize) {
+
+    // Notify the removed entry that is no longer being cached.
+    &#64;Override
+    protected void entryRemoved(boolean evicted, String key,
+            BitmapDrawable oldValue, BitmapDrawable newValue) {
+        if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
+            // The removed entry is a recycling drawable, so notify it
+            // that it has been removed from the memory cache.
+            ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
+        } else {
+            // The removed entry is a standard BitmapDrawable.
+            if (Utils.hasHoneycomb()) {
+                // We're running on Honeycomb or later, so add the bitmap
+                // to a SoftReference set for possible use with inBitmap later.
+                mReusableBitmaps.add
+                        (new SoftReference&lt;Bitmap&gt;(oldValue.getBitmap()));
+            }
+        }
+    }
+....
+}</pre>
+
+
+<h3>Use an existing bitmap</h3>
+<p>In the running app, decoder methods check to see if there is an existing
+bitmap they can use. For example:</p>
+
+<pre>public static Bitmap decodeSampledBitmapFromFile(String filename,
+        int reqWidth, int reqHeight, ImageCache cache) {
+
+    final BitmapFactory.Options options = new BitmapFactory.Options();
+    ...
+    BitmapFactory.decodeFile(filename, options);
+    ...
+
+    // If we're running on Honeycomb or newer, try to use inBitmap.
+    if (Utils.hasHoneycomb()) {
+        addInBitmapOptions(options, cache);
+    }
+    ...
+    return BitmapFactory.decodeFile(filename, options);
+}</pre
+
+<p>The next snippet shows the {@code addInBitmapOptions()} method that is called in the
+above snippet. It looks for an existing bitmap to set as the value for
+{@link android.graphics.BitmapFactory.Options#inBitmap}. Note that this
+method only sets a value for {@link android.graphics.BitmapFactory.Options#inBitmap}
+if it finds a suitable match (your code should never assume that a match will be found):</p>
+
+<pre>private static void addInBitmapOptions(BitmapFactory.Options options,
+        ImageCache cache) {
+    // inBitmap only works with mutable bitmaps, so force the decoder to
+    // return mutable bitmaps.
+    options.inMutable = true;
+
+    if (cache != null) {
+        // Try to find a bitmap to use for inBitmap.
+        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
+
+        if (inBitmap != null) {
+            // If a suitable bitmap has been found, set it as the value of
+            // inBitmap.
+            options.inBitmap = inBitmap;
+        }
+    }
+}
+
+// This method iterates through the reusable bitmaps, looking for one 
+// to use for inBitmap:
+protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
+        Bitmap bitmap = null;
+
+    if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
+        final Iterator&lt;SoftReference&lt;Bitmap&gt;&gt; iterator
+                = mReusableBitmaps.iterator();
+        Bitmap item;
+
+        while (iterator.hasNext()) {
+            item = iterator.next().get();
+
+            if (null != item && item.isMutable()) {
+                // Check to see it the item can be used for inBitmap.
+                if (canUseForInBitmap(item, options)) {
+                    bitmap = item;
+
+                    // Remove from reusable set so it can't be used again.
+                    iterator.remove();
+                    break;
+                }
+            } else {
+                // Remove from the set if the reference has been cleared.
+                iterator.remove();
+            }
+        }
+    }
+    return bitmap;
+}</pre>
+
+<p>Finally, this method determines whether a candidate bitmap
+satisfies the size criteria to be used for
+{@link android.graphics.BitmapFactory.Options#inBitmap}:</p>
+
+<pre>private static boolean canUseForInBitmap(
+        Bitmap candidate, BitmapFactory.Options targetOptions) {
+    int width = targetOptions.outWidth / targetOptions.inSampleSize;
+    int height = targetOptions.outHeight / targetOptions.inSampleSize;
+
+    // Returns true if "candidate" can be used for inBitmap re-use with
+    // "targetOptions".
+    return candidate.getWidth() == width && candidate.getHeight() == height;
+}</pre>
+
+</body>
+</html>
index d4fcff3..272b8bc 100644 (file)
@@ -3,10 +3,6 @@ parent.title=Displaying Bitmaps Efficiently
 parent.link=index.html
 
 trainingnavtop=true
-next.title=Caching Bitmaps
-next.link=cache-bitmap.html
-previous.title=Loading Large Bitmaps Efficiently
-previous.link=load-bitmap.html
 
 @jd:body
 
index 79980be..985fc44 100644 (file)
             Caching Bitmaps
           </a>
           </li>
+          <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/manage-memory.html">
+            Managing Bitmap Memory
+          </a>
+          </li>
           <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/display-bitmap.html">
             Displaying Bitmaps in Your UI
           </a></li>
index 9bb90f1..cc7f23f 100644 (file)
@@ -62,6 +62,18 @@ public class Canvas {
     @SuppressWarnings({"UnusedDeclaration"})
     private int mSurfaceFormat;
 
+    /**
+     * Flag for drawTextRun indicating left-to-right run direction.
+     * @hide
+     */
+    public static final int DIRECTION_LTR = 0;
+    
+    /**
+     * Flag for drawTextRun indicating right-to-left run direction.
+     * @hide
+     */
+    public static final int DIRECTION_RTL = 1;
+
     // Maximum bitmap size as defined in Skia's native code
     // (see SkCanvas.cpp, SkDraw.cpp)
     private static final int MAXMIMUM_BITMAP_SIZE = 32766;
@@ -683,8 +695,16 @@ public class Canvas {
     }
 
     public enum EdgeType {
+
+        /**
+         * Black-and-White: Treat edges by just rounding to nearest pixel boundary
+         */
         BW(0),  //!< treat edges by just rounding to nearest pixel boundary
-        AA(1);  //!< treat edges by rounding-out, since they may be antialiased
+
+        /**
+         * Antialiased: Treat edges by rounding-out, since they may be antialiased
+         */
+        AA(1);
         
         EdgeType(int nativeInt) {
             this.nativeInt = nativeInt;
@@ -703,7 +723,9 @@ public class Canvas {
      * therefore you can skip making the draw calls).
      *
      * @param rect  the rect to compare with the current clip
-     * @param type  specifies how to treat the edges (BW or antialiased)
+     * @param type  {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *              since that means it may affect a larger area (more pixels) than
+     *              non-antialiased ({@link Canvas.EdgeType#BW}).
      * @return      true if the rect (transformed by the canvas' matrix)
      *              does not intersect with the canvas' clip
      */
@@ -720,10 +742,9 @@ public class Canvas {
      * (i.e. the bounds of the path intersects, but the path does not).
      *
      * @param path        The path to compare with the current clip
-     * @param type        true if the path should be considered antialiased,
-     *                    since that means it may
-     *                    affect a larger area (more pixels) than
-     *                    non-antialiased.
+     * @param type        {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *                    since that means it may affect a larger area (more pixels) than
+     *                    non-antialiased ({@link Canvas.EdgeType#BW}).
      * @return            true if the path (transformed by the canvas' matrix)
      *                    does not intersect with the canvas' clip
      */
@@ -745,9 +766,9 @@ public class Canvas {
      *                    current clip
      * @param bottom      The bottom of the rectangle to compare with the
      *                    current clip
-     * @param type        true if the rect should be considered antialiased,
-     *                    since that means it may affect a larger area (more
-     *                    pixels) than non-antialiased.
+     * @param type        {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *                    since that means it may affect a larger area (more pixels) than
+     *                    non-antialiased ({@link Canvas.EdgeType#BW}).
      * @return            true if the rect (transformed by the canvas' matrix)
      *                    does not intersect with the canvas' clip
      */
@@ -1333,7 +1354,8 @@ public class Canvas {
             (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, index, count, x, y, paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, index, count, x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1346,7 +1368,8 @@ public class Canvas {
      * @param paint The paint used for the text (e.g. color, size, style)
      */
     public void drawText(String text, float x, float y, Paint paint) {
-        native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1364,7 +1387,8 @@ public class Canvas {
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, start, end, x, y, paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, start, end, x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1383,14 +1407,16 @@ public class Canvas {
     public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
         if (text instanceof String || text instanceof SpannedString ||
             text instanceof SpannableString) {
-            native_drawText(mNativeCanvas, text.toString(), start, end, x, y, paint.mNativePaint);
+            native_drawText(mNativeCanvas, text.toString(), start, end, x, y,
+                            paint.mBidiFlags, paint.mNativePaint);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawText(this, start, end, x, y,
                                                      paint);
         } else {
             char[] buf = TemporaryBuffer.obtain(end - start);
             TextUtils.getChars(text, start, end, buf, 0);
-            native_drawText(mNativeCanvas, buf, 0, end - start, x, y, paint.mNativePaint);
+            native_drawText(mNativeCanvas, buf, 0, end - start, x, y,
+                    paint.mBidiFlags, paint.mNativePaint);
             TemporaryBuffer.recycle(buf);
         }
     }
@@ -1411,11 +1437,13 @@ public class Canvas {
      *         + count.
      * @param x the x position at which to draw the text
      * @param y the y position at which to draw the text
+     * @param dir the run direction, either {@link #DIRECTION_LTR} or
+     *         {@link #DIRECTION_RTL}.
      * @param paint the paint
      * @hide
      */
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1426,9 +1454,12 @@ public class Canvas {
         if ((index | count | text.length - index - count) < 0) {
             throw new IndexOutOfBoundsException();
         }
+        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown dir: " + dir);
+        }
 
         native_drawTextRun(mNativeCanvas, text, index, count,
-                contextIndex, contextCount, x, y, paint.mNativePaint);
+                contextIndex, contextCount, x, y, dir, paint.mNativePaint);
     }
 
     /**
@@ -1444,11 +1475,12 @@ public class Canvas {
      *            position can be used for shaping context.
      * @param x the x position at which to draw the text
      * @param y the y position at which to draw the text
+     * @param dir the run direction, either 0 for LTR or 1 for RTL.
      * @param paint the paint
      * @hide
      */
     public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1460,20 +1492,22 @@ public class Canvas {
             throw new IndexOutOfBoundsException();
         }
 
+        int flags = dir == 0 ? 0 : 1;
+
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             native_drawTextRun(mNativeCanvas, text.toString(), start, end,
-                    contextStart, contextEnd, x, y, paint.mNativePaint);
+                    contextStart, contextEnd, x, y, flags, paint.mNativePaint);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawTextRun(this, start, end,
-                    contextStart, contextEnd, x, y, paint);
+                    contextStart, contextEnd, x, y, flags, paint);
         } else {
             int contextLen = contextEnd - contextStart;
             int len = end - start;
             char[] buf = TemporaryBuffer.obtain(contextLen);
             TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
             native_drawTextRun(mNativeCanvas, buf, start - contextStart, len,
-                    0, contextLen, x, y, paint.mNativePaint);
+                    0, contextLen, x, y, flags, paint.mNativePaint);
             TemporaryBuffer.recycle(buf);
         }
     }
@@ -1539,7 +1573,8 @@ public class Canvas {
             throw new ArrayIndexOutOfBoundsException();
         }
         native_drawTextOnPath(mNativeCanvas, text, index, count,
-                              path.ni(), hOffset, vOffset, paint.mNativePaint);
+                              path.ni(), hOffset, vOffset,
+                              paint.mBidiFlags, paint.mNativePaint);
     }
 
     /**
@@ -1558,7 +1593,7 @@ public class Canvas {
     public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
         if (text.length() > 0) {
             native_drawTextOnPath(mNativeCanvas, text, path.ni(), hOffset, vOffset,
-                    paint.mNativePaint);
+                    paint.mBidiFlags, paint.mNativePaint);
         }
     }
 
@@ -1720,18 +1755,18 @@ public class Canvas {
     
     private static native void native_drawText(int nativeCanvas, char[] text,
                                                int index, int count, float x,
-                                               float y, int paint);
+                                               float y, int flags, int paint);
     private static native void native_drawText(int nativeCanvas, String text,
                                                int start, int end, float x,
-                                               float y, int paint);
+                                               float y, int flags, int paint);
 
     private static native void native_drawTextRun(int nativeCanvas, String text,
             int start, int end, int contextStart, int contextEnd,
-            float x, float y, int paint);
+            float x, float y, int flags, int paint);
 
     private static native void native_drawTextRun(int nativeCanvas, char[] text,
             int start, int count, int contextStart, int contextCount,
-            float x, float y, int paint);
+            float x, float y, int flags, int paint);
 
     private static native void native_drawPosText(int nativeCanvas,
                                                   char[] text, int index,
@@ -1744,13 +1779,13 @@ public class Canvas {
                                                      char[] text, int index,
                                                      int count, int path,
                                                      float hOffset,
-                                                     float vOffset,
+                                                     float vOffset, int bidiFlags,
                                                      int paint);
     private static native void native_drawTextOnPath(int nativeCanvas,
                                                      String text, int path,
                                                      float hOffset, 
                                                      float vOffset, 
-                                                     int paint);
+                                                     int flags, int paint);
     private static native void native_drawPicture(int nativeCanvas,
                                                   int nativePicture);
     private static native void finalizer(int nativeCanvas);
index 8da20f2..216dc6c 100644 (file)
@@ -69,6 +69,11 @@ public class Paint {
      */
     public int shadowColor;
 
+    /**
+     * @hide
+     */
+    public  int         mBidiFlags = BIDI_DEFAULT_LTR;
+    
     static final Style[] sStyleArray = {
         Style.FILL, Style.STROKE, Style.FILL_AND_STROKE
     };
@@ -115,6 +120,74 @@ public class Paint {
     public static final int HINTING_ON = 0x1;
 
     /**
+     * Bidi flag to set LTR paragraph direction.
+     * 
+     * @hide
+     */
+    public static final int BIDI_LTR = 0x0;
+
+    /**
+     * Bidi flag to set RTL paragraph direction.
+     * 
+     * @hide
+     */
+    public static final int BIDI_RTL = 0x1;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * LTR.
+     * 
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_LTR = 0x2;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * RTL.
+     * 
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_RTL = 0x3;
+
+    /**
+     * Bidi flag to override direction to all LTR (ignore bidi).
+     * 
+     * @hide
+     */
+    public static final int BIDI_FORCE_LTR = 0x4;
+
+    /**
+     * Bidi flag to override direction to all RTL (ignore bidi).
+     * 
+     * @hide
+     */
+    public static final int BIDI_FORCE_RTL = 0x5;
+
+    /**
+     * Maximum Bidi flag value.
+     * @hide
+     */
+    private static final int BIDI_MAX_FLAG_VALUE = BIDI_FORCE_RTL;
+
+    /**
+     * Mask for bidi flags.
+     * @hide
+     */
+    private static final int BIDI_FLAG_MASK = 0x7;
+
+    /**
+     * Flag for getTextRunAdvances indicating left-to-right run direction.
+     * @hide
+     */
+    public static final int DIRECTION_LTR = 0;
+
+    /**
+     * Flag for getTextRunAdvances indicating right-to-left run direction.
+     * @hide
+     */
+    public static final int DIRECTION_RTL = 1;
+
+    /**
      * Option for getTextRunCursor to compute the valid cursor after
      * offset or the limit of the context, whichever is less.
      * @hide
@@ -322,6 +395,7 @@ public class Paint {
         shadowRadius = 0;
         shadowColor = 0;
 
+        mBidiFlags = BIDI_DEFAULT_LTR;
         setTextLocale(Locale.getDefault());
     }
     
@@ -361,6 +435,7 @@ public class Paint {
         shadowRadius = paint.shadowRadius;
         shadowColor = paint.shadowColor;
 
+        mBidiFlags = paint.mBidiFlags;
         mLocale = paint.mLocale;
     }
 
@@ -377,6 +452,29 @@ public class Paint {
     }
 
     /**
+     * Return the bidi flags on the paint.
+     * 
+     * @return the bidi flags on the paint
+     * @hide
+     */
+    public int getBidiFlags() {
+        return mBidiFlags;
+    }
+
+    /**
+     * Set the bidi flags on the paint.
+     * @hide
+     */
+    public void setBidiFlags(int flags) {
+        // only flag value is the 3-bit BIDI control setting
+        flags &= BIDI_FLAG_MASK;
+        if (flags > BIDI_MAX_FLAG_VALUE) {
+            throw new IllegalArgumentException("unknown bidi flag: " + flags);
+        }
+        mBidiFlags = flags;
+    }
+
+    /**
      * Return the paint's flags. Use the Flag enum to test flag values.
      * 
      * @return the paint's flags (see enums ending in _Flag for bit masks)
@@ -1570,19 +1668,76 @@ public class Paint {
     }
 
     /**
+     * Return the glyph Ids for the characters in the string.
+     *
+     * @param text   The text to measure
+     * @param start  The index of the first char to to measure
+     * @param end    The end of the text slice to measure
+     * @param contextStart the index of the first character to use for shaping context,
+     * must be <= start
+     * @param contextEnd the index past the last character to use for shaping context,
+     * must be >= end
+     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+     * or {@link #DIRECTION_RTL}
+     * @param glyphs array to receive the glyph Ids of the characters.
+     *               Must be at least a large as the text.
+     * @return       the number of glyphs in the returned array
+     *
+     * @hide
+     *
+     * Used only for BiDi / RTL Tests
+     */
+    public int getTextGlyphs(String text, int start, int end, int contextStart, int contextEnd,
+            int flags, char[] glyphs) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
+        if ((start | end | contextStart | contextEnd | (end - start)
+                | (start - contextStart) | (contextEnd - end) | (text.length() - end)
+                | (text.length() - contextEnd)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end - start > glyphs.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        return native_getTextGlyphs(mNativePaint, text, start, end, contextStart, contextEnd,
+                flags, glyphs);
+    }
+
+    /**
      * Convenience overload that takes a char array instead of a
      * String.
      *
-     * @see #getTextRunAdvances(String, int, int, int, int, float[], int)
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
      * @hide
      */
     public float getTextRunAdvances(char[] chars, int index, int count,
-            int contextIndex, int contextCount, float[] advances,
+            int contextIndex, int contextCount, int flags, float[] advances,
             int advancesIndex) {
+        return getTextRunAdvances(chars, index, count, contextIndex, contextCount, flags,
+                advances, advancesIndex, 0 /* use Harfbuzz*/);
+    }
+
+    /**
+     * Convenience overload that takes a char array instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int, int)
+     * @hide
+     */
+    public float getTextRunAdvances(char[] chars, int index, int count,
+            int contextIndex, int contextCount, int flags, float[] advances,
+            int advancesIndex, int reserved) {
 
         if (chars == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
         if ((index | count | contextIndex | contextCount | advancesIndex
                 | (index - contextIndex) | (contextCount - count)
                 | ((contextIndex + contextCount) - (index + count))
@@ -1597,13 +1752,13 @@ public class Paint {
         }
         if (!mHasCompatScaling) {
             return native_getTextRunAdvances(mNativePaint, chars, index, count,
-                    contextIndex, contextCount, advances, advancesIndex);
+                    contextIndex, contextCount, flags, advances, advancesIndex, reserved);
         }
 
         final float oldSize = getTextSize();
         setTextSize(oldSize * mCompatScaling);
         float res = native_getTextRunAdvances(mNativePaint, chars, index, count,
-                contextIndex, contextCount, advances, advancesIndex);
+                contextIndex, contextCount, flags, advances, advancesIndex, reserved);
         setTextSize(oldSize);
 
         if (advances != null) {
@@ -1618,12 +1773,26 @@ public class Paint {
      * Convenience overload that takes a CharSequence instead of a
      * String.
      *
-     * @see #getTextRunAdvances(String, int, int, int, int, float[], int)
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
      * @hide
      */
     public float getTextRunAdvances(CharSequence text, int start, int end,
-            int contextStart, int contextEnd, float[] advances,
+            int contextStart, int contextEnd, int flags, float[] advances,
             int advancesIndex) {
+        return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags,
+                advances, advancesIndex, 0 /* use Harfbuzz */);
+    }
+
+    /**
+     * Convenience overload that takes a CharSequence instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvances(CharSequence text, int start, int end,
+            int contextStart, int contextEnd, int flags, float[] advances,
+            int advancesIndex, int reserved) {
 
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
@@ -1638,16 +1807,16 @@ public class Paint {
 
         if (text instanceof String) {
             return getTextRunAdvances((String) text, start, end,
-                    contextStart, contextEnd, advances, advancesIndex);
+                    contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         }
         if (text instanceof SpannedString ||
             text instanceof SpannableString) {
             return getTextRunAdvances(text.toString(), start, end,
-                    contextStart, contextEnd, advances, advancesIndex);
+                    contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         }
         if (text instanceof GraphicsOperations) {
             return ((GraphicsOperations) text).getTextRunAdvances(start, end,
-                    contextStart, contextEnd, advances, advancesIndex, this);
+                    contextStart, contextEnd, flags, advances, advancesIndex, this);
         }
         if (text.length() == 0 || end == start) {
             return 0f;
@@ -1658,7 +1827,7 @@ public class Paint {
         char[] buf = TemporaryBuffer.obtain(contextLen);
         TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
         float result = getTextRunAdvances(buf, start - contextStart, len,
-                0, contextLen, advances, advancesIndex);
+                0, contextLen, flags, advances, advancesIndex, reserved);
         TemporaryBuffer.recycle(buf);
         return result;
     }
@@ -1695,6 +1864,8 @@ public class Paint {
      * must be <= start
      * @param contextEnd the index past the last character to use for shaping context,
      * must be >= end
+     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+     * or {@link #DIRECTION_RTL}
      * @param advances array to receive the advances, must have room for all advances,
      * can be null if only total advance is needed
      * @param advancesIndex the position in advances at which to put the
@@ -1704,11 +1875,63 @@ public class Paint {
      * @hide
      */
     public float getTextRunAdvances(String text, int start, int end, int contextStart,
-            int contextEnd, float[] advances, int advancesIndex) {
+            int contextEnd, int flags, float[] advances, int advancesIndex) {
+        return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags,
+                advances, advancesIndex, 0 /* use Harfbuzz*/);
+    }
+
+    /**
+     * Returns the total advance width for the characters in the run
+     * between start and end, and if advances is not null, the advance
+     * assigned to each of these characters (java chars).
+     *
+     * <p>The trailing surrogate in a valid surrogate pair is assigned
+     * an advance of 0.  Thus the number of returned advances is
+     * always equal to count, not to the number of unicode codepoints
+     * represented by the run.
+     *
+     * <p>In the case of conjuncts or combining marks, the total
+     * advance is assigned to the first logical character, and the
+     * following characters are assigned an advance of 0.
+     *
+     * <p>This generates the sum of the advances of glyphs for
+     * characters in a reordered cluster as the width of the first
+     * logical character in the cluster, and 0 for the widths of all
+     * other characters in the cluster.  In effect, such clusters are
+     * treated like conjuncts.
+     *
+     * <p>The shaping bounds limit the amount of context available
+     * outside start and end that can be used for shaping analysis.
+     * These bounds typically reflect changes in bidi level or font
+     * metrics across which shaping does not occur.
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the first character to measure
+     * @param end the index past the last character to measure
+     * @param contextStart the index of the first character to use for shaping context,
+     * must be <= start
+     * @param contextEnd the index past the last character to use for shaping context,
+     * must be >= end
+     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+     * or {@link #DIRECTION_RTL}
+     * @param advances array to receive the advances, must have room for all advances,
+     * can be null if only total advance is needed
+     * @param advancesIndex the position in advances at which to put the
+     * advance corresponding to the character at start
+     * @param reserved int reserved value
+     * @return the total advance
+     *
+     * @hide
+     */
+    public float getTextRunAdvances(String text, int start, int end, int contextStart,
+            int contextEnd, int flags, float[] advances, int advancesIndex, int reserved) {
 
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
         if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
                 | (start - contextStart) | (contextEnd - end)
                 | (text.length() - contextEnd)
@@ -1723,13 +1946,13 @@ public class Paint {
 
         if (!mHasCompatScaling) {
             return native_getTextRunAdvances(mNativePaint, text, start, end,
-                    contextStart, contextEnd, advances, advancesIndex);
+                    contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         }
 
         final float oldSize = getTextSize();
         setTextSize(oldSize * mCompatScaling);
         float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
-                contextStart, contextEnd, advances, advancesIndex);
+                contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         setTextSize(oldSize);
 
         if (advances != null) {
@@ -1758,6 +1981,7 @@ public class Paint {
      * @param text the text
      * @param contextStart the start of the context
      * @param contextLength the length of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -1766,7 +1990,7 @@ public class Paint {
      * @hide
      */
     public int getTextRunCursor(char[] text, int contextStart, int contextLength,
-            int offset, int cursorOpt) {
+            int flags, int offset, int cursorOpt) {
         int contextEnd = contextStart + contextLength;
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
@@ -1776,7 +2000,7 @@ public class Paint {
         }
 
         return native_getTextRunCursor(mNativePaint, text,
-                contextStart, contextLength, offset, cursorOpt);
+                contextStart, contextLength, flags, offset, cursorOpt);
     }
 
     /**
@@ -1797,6 +2021,7 @@ public class Paint {
      * @param text the text
      * @param contextStart the start of the context
      * @param contextEnd the end of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -1805,22 +2030,22 @@ public class Paint {
      * @hide
      */
     public int getTextRunCursor(CharSequence text, int contextStart,
-           int contextEnd, int offset, int cursorOpt) {
+           int contextEnd, int flags, int offset, int cursorOpt) {
 
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             return getTextRunCursor(text.toString(), contextStart, contextEnd,
-                    offset, cursorOpt);
+                    flags, offset, cursorOpt);
         }
         if (text instanceof GraphicsOperations) {
             return ((GraphicsOperations) text).getTextRunCursor(
-                    contextStart, contextEnd, offset, cursorOpt, this);
+                    contextStart, contextEnd, flags, offset, cursorOpt, this);
         }
 
         int contextLen = contextEnd - contextStart;
         char[] buf = TemporaryBuffer.obtain(contextLen);
         TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
-        int result = getTextRunCursor(buf, 0, contextLen, offset - contextStart, cursorOpt);
+        int result = getTextRunCursor(buf, 0, contextLen, flags, offset - contextStart, cursorOpt);
         TemporaryBuffer.recycle(buf);
         return result;
     }
@@ -1843,6 +2068,7 @@ public class Paint {
      * @param text the text
      * @param contextStart the start of the context
      * @param contextEnd the end of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -1851,7 +2077,7 @@ public class Paint {
      * @hide
      */
     public int getTextRunCursor(String text, int contextStart, int contextEnd,
-            int offset, int cursorOpt) {
+            int flags, int offset, int cursorOpt) {
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
                 | (text.length() - contextEnd) | cursorOpt) < 0)
@@ -1860,7 +2086,7 @@ public class Paint {
         }
 
         return native_getTextRunCursor(mNativePaint, text,
-                contextStart, contextEnd, offset, cursorOpt);
+                contextStart, contextEnd, flags, offset, cursorOpt);
     }
 
     /**
@@ -1881,7 +2107,7 @@ public class Paint {
         if ((index | count) < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, index, count, x, y,
+        native_getTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, 
                 path.ni());
     }
 
@@ -1903,7 +2129,7 @@ public class Paint {
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, start, end, x, y,
+        native_getTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, 
                 path.ni());
     }
     
@@ -1995,22 +2221,26 @@ public class Paint {
     private static native int native_getTextWidths(int native_object,
                             String text, int start, int end, float[] widths);
 
+    private static native int native_getTextGlyphs(int native_object,
+            String text, int start, int end, int contextStart, int contextEnd,
+            int flags, char[] glyphs);
+
     private static native float native_getTextRunAdvances(int native_object,
             char[] text, int index, int count, int contextIndex, int contextCount,
-            float[] advances, int advancesIndex);
+            int flags, float[] advances, int advancesIndex, int reserved);
     private static native float native_getTextRunAdvances(int native_object,
             String text, int start, int end, int contextStart, int contextEnd,
-            float[] advances, int advancesIndex);
+            int flags, float[] advances, int advancesIndex, int reserved);
 
     private native int native_getTextRunCursor(int native_object, char[] text,
-            int contextStart, int contextLength, int offset, int cursorOpt);
+            int contextStart, int contextLength, int flags, int offset, int cursorOpt);
     private native int native_getTextRunCursor(int native_object, String text,
-            int contextStart, int contextEnd, int offset, int cursorOpt);
+            int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
 
-    private static native void native_getTextPath(int native_object, char[] text,
-            int index, int count, float x, float y, int path);
-    private static native void native_getTextPath(int native_object, String text,
-            int start, int end, float x, float y, int path);
+    private static native void native_getTextPath(int native_object, int bidiFlags,
+                char[] text, int index, int count, float x, float y, int path);
+    private static native void native_getTextPath(int native_object, int bidiFlags,
+                String text, int start, int end, float x, float y, int path);
     private static native void nativeGetStringBounds(int nativePaint,
                                 String text, int start, int end, Rect bounds);
     private static native void nativeGetCharArrayBounds(int nativePaint,
index a1cc2e8..4642a4f 100644 (file)
@@ -103,14 +103,9 @@ void Caches::initFont() {
 void Caches::initExtensions() {
     if (mExtensions.hasDebugMarker()) {
         eventMark = glInsertEventMarkerEXT;
-        if ((drawDeferDisabled || drawReorderDisabled)) {
-            startMark = glPushGroupMarkerEXT;
-            endMark = glPopGroupMarkerEXT;
-        } else {
-            startMark = startMarkNull;
-            endMark = endMarkNull;
-        }
 
+        startMark = glPushGroupMarkerEXT;
+        endMark = glPopGroupMarkerEXT;
     } else {
         eventMark = eventMarkNull;
         startMark = startMarkNull;
index a4e9950..8455545 100644 (file)
 namespace android {
 namespace uirenderer {
 
+/////////////////////////////////////////////////////////////////////////////////
+// Operation Batches
+/////////////////////////////////////////////////////////////////////////////////
+
 class DrawOpBatch {
 public:
-    DrawOpBatch() {
-        mOps.clear();
-    }
+    DrawOpBatch() { mOps.clear(); }
 
-    ~DrawOpBatch() {
-        mOps.clear();
-    }
+    virtual ~DrawOpBatch() { mOps.clear(); }
 
     void add(DrawOp* op) {
         // NOTE: ignore empty bounds special case, since we don't merge across those ops
@@ -48,8 +48,9 @@ public:
         mOps.add(op);
     }
 
-    bool intersects(Rect& rect) {
+    virtual bool intersects(Rect& rect) {
         if (!rect.intersects(mBounds)) return false;
+
         for (unsigned int i = 0; i < mOps.size(); i++) {
             if (rect.intersects(mOps[i]->state.mBounds)) {
 #if DEBUG_DEFER
@@ -64,27 +65,217 @@ public:
         return false;
     }
 
-    Vector<DrawOp*> mOps;
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+        DEFER_LOGD("replaying draw batch %p", this);
+
+        status_t status = DrawGlInfo::kStatusDone;
+        DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+        for (unsigned int i = 0; i < mOps.size(); i++) {
+            DrawOp* op = mOps[i];
+
+            renderer.restoreDisplayState(op->state, kStateDeferFlag_Draw);
+
+#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
+            renderer.eventMark(strlen(op->name()), op->name());
+#endif
+            status |= op->applyDraw(renderer, dirty, 0, op->state.mMultipliedAlpha);
+            logBuffer.writeCommand(0, op->name());
+        }
+        return status;
+    }
+
+    inline int count() const { return mOps.size(); }
 private:
+    Vector<DrawOp*> mOps;
     Rect mBounds;
 };
 
-void DeferredDisplayList::clear() {
+class StateOpBatch : public DrawOpBatch {
+public:
+    // creates a single operation batch
+    StateOpBatch(StateOp* op) : mOp(op) {}
+
+    bool intersects(Rect& rect) {
+        // if something checks for intersection, it's trying to go backwards across a state op,
+        // something not currently supported - state ops are always barriers
+        CRASH();
+        return false;
+    }
+
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+        DEFER_LOGD("replaying state op batch %p", this);
+        renderer.restoreDisplayState(mOp->state, 0);
+
+        // use invalid save count because it won't be used at flush time - RestoreToCountOp is the
+        // only one to use it, and we don't use that class at flush time, instead calling
+        // renderer.restoreToCount directly
+        int saveCount = -1;
+        mOp->applyState(renderer, saveCount);
+        return DrawGlInfo::kStatusDone;
+    }
+
+private:
+    StateOp* mOp;
+};
+
+class RestoreToCountBatch : public DrawOpBatch {
+public:
+    RestoreToCountBatch(int restoreCount) : mRestoreCount(restoreCount) {}
+
+    bool intersects(Rect& rect) {
+        // if something checks for intersection, it's trying to go backwards across a state op,
+        // something not currently supported - state ops are always barriers
+        CRASH();
+        return false;
+    }
+
+    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty) {
+        DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount);
+        renderer.restoreToCount(mRestoreCount);
+
+        return DrawGlInfo::kStatusDone;
+    }
+
+private:
+    /*
+     * The count used here represents the flush() time saveCount. This is as opposed to the
+     * DisplayList record time, or defer() time values (which are RestoreToCountOp's mCount, and
+     * (saveCount + mCount) respectively). Since the count is different from the original
+     * RestoreToCountOp, we don't store a pointer to the op, as elsewhere.
+     */
+    const int mRestoreCount;
+};
+
+/////////////////////////////////////////////////////////////////////////////////
+// DeferredDisplayList
+/////////////////////////////////////////////////////////////////////////////////
+
+void DeferredDisplayList::resetBatchingState() {
     for (int i = 0; i < kOpBatch_Count; i++) {
         mBatchIndices[i] = -1;
     }
+}
+
+void DeferredDisplayList::clear() {
+    resetBatchingState();
+    mComplexClipStackStart = -1;
+
     for (unsigned int i = 0; i < mBatches.size(); i++) {
         delete mBatches[i];
     }
     mBatches.clear();
+    mSaveStack.clear();
 }
 
-void DeferredDisplayList::add(DrawOp* op, bool disallowReorder) {
-    if (CC_UNLIKELY(disallowReorder)) {
-        if (!mBatches.isEmpty()) {
-            mBatches[0]->add(op);
-            return;
+/////////////////////////////////////////////////////////////////////////////////
+// Operation adding
+/////////////////////////////////////////////////////////////////////////////////
+
+int DeferredDisplayList::getStateOpDeferFlags() const {
+    // For both clipOp and save(Layer)Op, we don't want to save drawing info, and only want to save
+    // the clip if we aren't recording a complex clip (and can thus trust it to be a rect)
+    return recordingComplexClip() ? 0 : kStateDeferFlag_Clip;
+}
+
+int DeferredDisplayList::getDrawOpDeferFlags() const {
+    return kStateDeferFlag_Draw | getStateOpDeferFlags();
+}
+
+/**
+ * When an clipping operation occurs that could cause a complex clip, record the operation and all
+ * subsequent clipOps, save/restores (if the clip flag is set). During a flush, instead of loading
+ * the clip from deferred state, we play back all of the relevant state operations that generated
+ * the complex clip.
+ *
+ * Note that we don't need to record the associated restore operation, since operations at defer
+ * time record whether they should store the renderer's current clip
+ */
+void DeferredDisplayList::addClip(OpenGLRenderer& renderer, ClipOp* op) {
+    if (recordingComplexClip() || op->canCauseComplexClip() || !renderer.hasRectToRectTransform()) {
+        DEFER_LOGD("%p Received complex clip operation %p", this, op);
+
+        // NOTE: defer clip op before setting mComplexClipStackStart so previous clip is recorded
+        storeStateOpBarrier(renderer, op);
+
+        if (!recordingComplexClip()) {
+            mComplexClipStackStart = renderer.getSaveCount() - 1;
+            DEFER_LOGD("    Starting complex clip region, start is %d", mComplexClipStackStart);
         }
+    }
+}
+
+/**
+ * For now, we record save layer operations as barriers in the batch list, preventing drawing
+ * operations from reordering around the saveLayer and it's associated restore()
+ *
+ * In the future, we should send saveLayer commands (if they can be played out of order) and their
+ * contained drawing operations to a seperate list of batches, so that they may draw at the
+ * beginning of the frame. This would avoid targetting and removing an FBO in the middle of a frame.
+ *
+ * saveLayer operations should be pulled to the beginning of the frame if the canvas doesn't have a
+ * complex clip, and if the flags (kClip_SaveFlag & kClipToLayer_SaveFlag) are set.
+ */
+void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer,
+        SaveLayerOp* op, int newSaveCount) {
+    DEFER_LOGD("%p adding saveLayerOp %p, flags %x, new count %d",
+            this, op, op->getFlags(), newSaveCount);
+
+    storeStateOpBarrier(renderer, op);
+    mSaveStack.push(newSaveCount);
+}
+
+/**
+ * Takes save op and it's return value - the new save count - and stores it into the stream as a
+ * barrier if it's needed to properly modify a complex clip
+ */
+void DeferredDisplayList::addSave(OpenGLRenderer& renderer, SaveOp* op, int newSaveCount) {
+    int saveFlags = op->getFlags();
+    DEFER_LOGD("%p adding saveOp %p, flags %x, new count %d", this, op, saveFlags, newSaveCount);
+
+    if (recordingComplexClip() && (saveFlags & SkCanvas::kClip_SaveFlag)) {
+        // store and replay the save operation, as it may be needed to correctly playback the clip
+        DEFER_LOGD("    adding save barrier with new save count %d", newSaveCount);
+        storeStateOpBarrier(renderer, op);
+        mSaveStack.push(newSaveCount);
+    }
+}
+
+/**
+ * saveLayer() commands must be associated with a restoreToCount batch that will clean up and draw
+ * the layer in the deferred list
+ *
+ * other save() commands which occur as children of a snapshot with complex clip will be deferred,
+ * and must be restored
+ *
+ * Either will act as a barrier to draw operation reordering, as we want to play back layer
+ * save/restore and complex canvas modifications (including save/restore) in order.
+ */
+void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, int newSaveCount) {
+    DEFER_LOGD("%p addRestoreToCount %d", this, newSaveCount);
+
+    if (recordingComplexClip() && newSaveCount <= mComplexClipStackStart) {
+        mComplexClipStackStart = -1;
+        resetBatchingState();
+    }
+
+    if (mSaveStack.isEmpty() || newSaveCount > mSaveStack.top()) {
+        return;
+    }
+
+    while (!mSaveStack.isEmpty() && mSaveStack.top() >= newSaveCount) mSaveStack.pop();
+
+    storeRestoreToCountBarrier(mSaveStack.size() + 1);
+}
+
+void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
+    if (renderer.storeDisplayState(op->state, getDrawOpDeferFlags())) {
+        return; // quick rejected
+    }
+
+    op->onDrawOpDeferred(renderer);
+
+    if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) {
+        // TODO: elegant way to reuse batches?
         DrawOpBatch* b = new DrawOpBatch();
         b->add(op);
         mBatches.add(b);
@@ -138,9 +329,41 @@ void DeferredDisplayList::add(DrawOp* op, bool disallowReorder) {
     targetBatch->add(op);
 }
 
-status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty, int32_t flags,
-        uint32_t level) {
-    ATRACE_CALL();
+void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op) {
+    DEFER_LOGD("%p adding state op barrier at pos %d", this, mBatches.size());
+
+    renderer.storeDisplayState(op->state, getStateOpDeferFlags());
+    mBatches.add(new StateOpBatch(op));
+    resetBatchingState();
+}
+
+void DeferredDisplayList::storeRestoreToCountBarrier(int newSaveCount) {
+    DEFER_LOGD("%p adding restore to count %d barrier, pos %d",
+            this, newSaveCount, mBatches.size());
+
+    mBatches.add(new RestoreToCountBatch(newSaveCount));
+    resetBatchingState();
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+// Replay / flush
+/////////////////////////////////////////////////////////////////////////////////
+
+static status_t replayBatchList(Vector<DrawOpBatch*>& batchList,
+        OpenGLRenderer& renderer, Rect& dirty) {
+    status_t status = DrawGlInfo::kStatusDone;
+
+    int opCount = 0;
+    for (unsigned int i = 0; i < batchList.size(); i++) {
+        status |= batchList[i]->replay(renderer, dirty);
+        opCount += batchList[i]->count();
+    }
+    DEFER_LOGD("--flushed, drew %d batches (total %d ops)", batchList.size(), opCount);
+    return status;
+}
+
+status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) {
+    ATRACE_NAME("flush drawing commands");
     status_t status = DrawGlInfo::kStatusDone;
 
     if (isEmpty()) return status; // nothing to flush
@@ -148,29 +371,12 @@ status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty, int32
     DEFER_LOGD("--flushing");
     renderer.eventMark("Flush");
 
-    DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers();
-    int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
-    int opCount = 0;
-    for (unsigned int i = 0; i < mBatches.size(); i++) {
-        DrawOpBatch* batch = mBatches[i];
-        for (unsigned int j = 0; j < batch->mOps.size(); j++) {
-            DrawOp* op = batch->mOps[j];
-
-            renderer.restoreDisplayState(op->state);
-
-#if DEBUG_DEFER
-            op->output(2);
-#endif
-            status |= op->applyDraw(renderer, dirty, level,
-                    op->state.mMultipliedAlpha >= 0, op->state.mMultipliedAlpha);
-            opCount++;
-        }
-    }
+    renderer.restoreToCount(1);
+    status |= replayBatchList(mBatches, renderer, dirty);
+    renderer.resetDrawModifiers();
 
-    DEFER_LOGD("--flushed, drew %d batches (total %d ops)", mBatches.size(), opCount);
+    DEFER_LOGD("--flush complete, returning %x", status);
 
-    renderer.restoreToCount(restoreTo);
-    renderer.setDrawModifiers(restoreDrawModifiers);
     clear();
     return status;
 }
index 4fcb297..8e908fa 100644 (file)
 namespace android {
 namespace uirenderer {
 
+class ClipOp;
 class DrawOp;
+class SaveOp;
+class SaveLayerOp;
+class StateOp;
 class DrawOpBatch;
 class OpenGLRenderer;
-class SkiaShader;
 
 class DeferredDisplayList {
 public:
@@ -55,18 +58,42 @@ public:
      * Plays back all of the draw ops recorded into batches to the renderer.
      * Adjusts the state of the renderer as necessary, and restores it when complete
      */
-    status_t flush(OpenGLRenderer& renderer, Rect& dirty, int32_t flags,
-            uint32_t level);
+    status_t flush(OpenGLRenderer& renderer, Rect& dirty);
+
+    void addClip(OpenGLRenderer& renderer, ClipOp* op);
+    void addSaveLayer(OpenGLRenderer& renderer, SaveLayerOp* op, int newSaveCount);
+    void addSave(OpenGLRenderer& renderer, SaveOp* op, int newSaveCount);
+    void addRestoreToCount(OpenGLRenderer& renderer, int newSaveCount);
 
     /**
      * Add a draw op into the DeferredDisplayList, reordering as needed (for performance) if
      * disallowReorder is false, respecting draw order when overlaps occur
      */
-    void add(DrawOp* op, bool disallowReorder);
+    void addDrawOp(OpenGLRenderer& renderer, DrawOp* op);
 
 private:
+    /*
+     * Resets the batching back-pointers, creating a barrier in the operation stream so that no ops
+     * added in the future will be inserted into a batch that already exist.
+     */
+    void resetBatchingState();
+
     void clear();
 
+    void storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op);
+    void storeRestoreToCountBarrier(int newSaveCount);
+
+    bool recordingComplexClip() const { return mComplexClipStackStart >= 0; }
+
+    int getStateOpDeferFlags() const;
+    int getDrawOpDeferFlags() const;
+
+    /*
+     *
+     * at defer time, stores the savecount of save/saveLayer ops that were 
+     */
+    Vector<int> mSaveStack;
+    int mComplexClipStackStart;
 
     Vector<DrawOpBatch*> mBatches;
     int mBatchIndices[kOpBatch_Count];
index 5781f4d..4743f58 100644 (file)
@@ -61,6 +61,12 @@ void DisplayList::destroyDisplayListDeferred(DisplayList* displayList) {
 
 void DisplayList::clearResources() {
     mDisplayListData = NULL;
+
+    mClipRectOp = NULL;
+    mSaveLayerOp = NULL;
+    mSaveOp = NULL;
+    mRestoreToCountOp = NULL;
+
     delete mTransformMatrix;
     delete mTransformCamera;
     delete mTransformMatrix3D;
@@ -156,6 +162,13 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde
         return;
     }
 
+    // allocate reusable ops for state-deferral
+    LinearAllocator& alloc = mDisplayListData->allocator;
+    mClipRectOp = new (alloc) ClipRectOp();
+    mSaveLayerOp = new (alloc) SaveLayerOp();
+    mSaveOp = new (alloc) SaveOp();
+    mRestoreToCountOp = new (alloc) RestoreToCountOp();
+
     mFunctorCount = recorder.getFunctorCount();
 
     Caches& caches = Caches::getInstance();
@@ -318,7 +331,7 @@ void DisplayList::updateMatrix() {
     }
 }
 
-void DisplayList::outputViewProperties(uint32_t level) {
+void DisplayList::outputViewProperties(const int level) {
     updateMatrix();
     if (mLeft != 0 || mTop != 0) {
         ALOGD("%*sTranslate (left, top) %d, %d", level * 2, "", mLeft, mTop);
@@ -358,10 +371,17 @@ void DisplayList::outputViewProperties(uint32_t level) {
     }
 }
 
-status_t DisplayList::setViewProperties(OpenGLRenderer& renderer, Rect& dirty,
-        int32_t flags, uint32_t level, DeferredDisplayList* deferredList) {
-    status_t status = DrawGlInfo::kStatusDone;
-#if DEBUG_DISPLAYLIST
+/*
+ * For property operations, we pass a savecount of 0, since the operations aren't part of the
+ * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
+ * base saveCount (i.e., how RestoreToCount uses saveCount + mCount)
+ */
+#define PROPERTY_SAVECOUNT 0
+
+template <class T>
+void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler,
+        const int level) {
+#if DEBUG_DISPLAY_LIST
     outputViewProperties(level);
 #endif
     updateMatrix();
@@ -381,86 +401,121 @@ status_t DisplayList::setViewProperties(OpenGLRenderer& renderer, Rect& dirty,
         }
     }
     if (mAlpha < 1 && !mCaching) {
-        if (deferredList) {
-            // flush since we'll either enter a Layer, or set alpha, both not supported in deferral
-            status |= deferredList->flush(renderer, dirty, flags, level);
-        }
-
         if (!mHasOverlappingRendering) {
             renderer.setAlpha(mAlpha);
         } else {
             // TODO: should be able to store the size of a DL at record time and not
             // have to pass it into this call. In fact, this information might be in the
             // location/size info that we store with the new native transform data.
-            int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
+            int saveFlags = SkCanvas::kHasAlphaLayer_SaveFlag;
             if (mClipChildren) {
-                flags |= SkCanvas::kClipToLayer_SaveFlag;
+                saveFlags |= SkCanvas::kClipToLayer_SaveFlag;
             }
-            renderer.saveLayerAlpha(0, 0, mRight - mLeft, mBottom - mTop,
-                    mMultipliedAlpha, flags);
+            handler(mSaveLayerOp->reinit(0, 0, mRight - mLeft, mBottom - mTop,
+                    mMultipliedAlpha, SkXfermode::kSrcOver_Mode, saveFlags), PROPERTY_SAVECOUNT);
         }
     }
     if (mClipChildren && !mCaching) {
-        if (deferredList && CC_UNLIKELY(!renderer.hasRectToRectTransform())) {
-            // flush, since clip will likely be a region
-            status |= deferredList->flush(renderer, dirty, flags, level);
-        }
-        renderer.clipRect(0, 0, mRight - mLeft, mBottom - mTop,
-                SkRegion::kIntersect_Op);
+        handler(mClipRectOp->reinit(0, 0, mRight - mLeft, mBottom - mTop, SkRegion::kIntersect_Op),
+                PROPERTY_SAVECOUNT);
+    }
+}
+
+class DeferOperationHandler {
+public:
+    DeferOperationHandler(DeferStateStruct& deferStruct, int multipliedAlpha, int level)
+        : mDeferStruct(deferStruct), mMultipliedAlpha(multipliedAlpha), mLevel(level) {}
+    inline void operator()(DisplayListOp* operation, int saveCount) {
+        operation->defer(mDeferStruct, saveCount, mLevel, mMultipliedAlpha);
+    }
+private:
+    DeferStateStruct& mDeferStruct;
+    const int mMultipliedAlpha;
+    const int mLevel;
+};
+
+void DisplayList::defer(DeferStateStruct& deferStruct, const int level) {
+    DeferOperationHandler handler(deferStruct, mCaching ? mMultipliedAlpha : -1, level);
+    iterate<DeferOperationHandler>(deferStruct.mRenderer, handler, level);
+}
+
+class ReplayOperationHandler {
+public:
+    ReplayOperationHandler(ReplayStateStruct& replayStruct, int multipliedAlpha, int level)
+        : mReplayStruct(replayStruct), mMultipliedAlpha(multipliedAlpha), mLevel(level) {}
+    inline void operator()(DisplayListOp* operation, int saveCount) {
+#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
+        replayStruct.mRenderer.eventMark(operation->name());
+#endif
+        operation->replay(mReplayStruct, saveCount, mLevel, mMultipliedAlpha);
     }
-    return status;
+private:
+    ReplayStateStruct& mReplayStruct;
+    const int mMultipliedAlpha;
+    const int mLevel;
+};
+
+void DisplayList::replay(ReplayStateStruct& replayStruct, const int level) {
+    ReplayOperationHandler handler(replayStruct, mCaching ? mMultipliedAlpha : -1, level);
+
+    replayStruct.mRenderer.startMark(mName.string());
+    iterate<ReplayOperationHandler>(replayStruct.mRenderer, handler, level);
+    replayStruct.mRenderer.endMark();
+
+    DISPLAY_LIST_LOGD("%*sDone (%p, %s), returning %d", level * 2, "", this, mName.string(),
+            replayStruct.mDrawGlStatus);
 }
 
-status_t DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, uint32_t level,
-        DeferredDisplayList* deferredList) {
-    status_t drawGlStatus = DrawGlInfo::kStatusDone;
+/**
+ * This function serves both defer and replay modes, and will organize the displayList's component
+ * operations for a single frame:
+ *
+ * Every 'simple' operation that affects just the matrix and alpha (or other factors of
+ * DeferredDisplayState) may be issued directly to the renderer, but complex operations (with custom
+ * defer logic) and operations in displayListOps are issued through the 'handler' which handles the
+ * defer vs replay logic, per operation
+ */
+template <class T>
+void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) {
+    if (mSize == 0 || mAlpha <= 0) {
+        DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, mName.string());
+        return;
+    }
 
 #if DEBUG_DISPLAY_LIST
     Rect* clipRect = renderer.getClipRect();
     DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.f, %.0f, %.0f",
-            (level+1)*2, "", this, mName.string(), clipRect->left, clipRect->top,
+            level * 2, "", this, mName.string(), clipRect->left, clipRect->top,
             clipRect->right, clipRect->bottom);
 #endif
 
-    renderer.startMark(mName.string());
+    int restoreTo = renderer.getSaveCount();
+    handler(mSaveOp->reinit(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+            PROPERTY_SAVECOUNT);
 
-    int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
-    DISPLAY_LIST_LOGD("%*sSave %d %d", level * 2, "",
+    DISPLAY_LIST_LOGD("%*sSave %d %d", (level + 1) * 2, "",
             SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
 
-    drawGlStatus |= setViewProperties(renderer, dirty, flags, level, deferredList);
+    setViewProperties<T>(renderer, handler, level + 1);
 
     if (renderer.quickRejectNoScissor(0, 0, mWidth, mHeight)) {
         DISPLAY_LIST_LOGD("%*sRestoreToCount %d", level * 2, "", restoreTo);
-        renderer.restoreToCount(restoreTo);
-        renderer.endMark();
-        return drawGlStatus;
+        handler(mRestoreToCountOp->reinit(restoreTo), PROPERTY_SAVECOUNT);
+        return;
     }
 
     DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
     int saveCount = renderer.getSaveCount() - 1;
     for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
         DisplayListOp *op = mDisplayListData->displayListOps[i];
-#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
-        renderer.eventMark(strlen(op->name()), op->name());
-#endif
-        drawGlStatus |= op->replay(renderer, dirty, flags,
-                saveCount, level, mCaching, mMultipliedAlpha, deferredList);
+
+        handler(op, saveCount);
         logBuffer.writeCommand(level, op->name());
     }
 
-    DISPLAY_LIST_LOGD("%*sRestoreToCount %d", level * 2, "", restoreTo);
+    DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo);
+    handler(mRestoreToCountOp->reinit(restoreTo), PROPERTY_SAVECOUNT);
     renderer.restoreToCount(restoreTo);
-    renderer.endMark();
-
-    DISPLAY_LIST_LOGD("%*sDone (%p, %s), returning %d", (level + 1) * 2, "", this, mName.string(),
-            drawGlStatus);
-
-    if (!level && CC_LIKELY(deferredList)) {
-        drawGlStatus |= deferredList->flush(renderer, dirty, flags, level);
-    }
-
-    return drawGlStatus;
 }
 
 }; // namespace uirenderer
index feee69c..5392587 100644 (file)
@@ -24,6 +24,8 @@
 #include <SkCamera.h>
 #include <SkMatrix.h>
 
+#include <private/hwui/DrawGlInfo.h>
+
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
 #include <utils/String8.h>
@@ -57,10 +59,33 @@ class Layer;
 class SkiaColorFilter;
 class SkiaShader;
 
+class ClipRectOp;
+class SaveLayerOp;
+class SaveOp;
+class RestoreToCountOp;
+
+struct DeferStateStruct {
+    DeferStateStruct(DeferredDisplayList& deferredList, OpenGLRenderer& renderer, int replayFlags)
+            : mDeferredList(deferredList), mRenderer(renderer), mReplayFlags(replayFlags) {}
+    DeferredDisplayList& mDeferredList;
+    OpenGLRenderer& mRenderer;
+    const int mReplayFlags;
+};
+
+struct ReplayStateStruct {
+    ReplayStateStruct(OpenGLRenderer& renderer, Rect& dirty, int replayFlags)
+            : mRenderer(renderer), mDirty(dirty), mReplayFlags(replayFlags),
+            mDrawGlStatus(DrawGlInfo::kStatusDone) {}
+    OpenGLRenderer& mRenderer;
+    Rect& mDirty;
+    const int mReplayFlags;
+    status_t mDrawGlStatus;
+};
+
 /**
  * Refcounted structure that holds data used in display list stream
  */
-class DisplayListData: public LightRefBase<DisplayListData> {
+class DisplayListData : public LightRefBase<DisplayListData> {
 public:
     LinearAllocator allocator;
     Vector<DisplayListOp*> displayListOps;
@@ -79,9 +104,6 @@ public:
         kReplayFlag_ClipChildren = 0x1
     };
 
-    status_t setViewProperties(OpenGLRenderer& renderer, Rect& dirty,
-            int32_t flags, uint32_t level, DeferredDisplayList* deferredList);
-    void outputViewProperties(uint32_t level);
 
     ANDROID_API size_t getSize();
     ANDROID_API static void destroyDisplayListDeferred(DisplayList* displayList);
@@ -89,8 +111,9 @@ public:
 
     void initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing = false);
 
-    status_t replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, uint32_t level = 0,
-            DeferredDisplayList* deferredList = NULL);
+
+    void defer(DeferStateStruct& deferStruct, const int level);
+    void replay(ReplayStateStruct& replayStruct, const int level);
 
     void output(uint32_t level = 0);
 
@@ -426,6 +449,14 @@ public:
     }
 
 private:
+    void outputViewProperties(const int level);
+
+    template <class T>
+    inline void setViewProperties(OpenGLRenderer& renderer, T& handler, const int level);
+
+    template <class T>
+    inline void iterate(OpenGLRenderer& renderer, T& handler, const int level);
+
     void init();
 
     void clearResources();
@@ -490,6 +521,22 @@ private:
     SkMatrix* mStaticMatrix;
     SkMatrix* mAnimationMatrix;
     bool mCaching;
+
+    /**
+     * State operations - needed to defer displayList property operations (for example, when setting
+     * an alpha causes a SaveLayerAlpha to occur). These operations point into mDisplayListData's
+     * allocation, or null if uninitialized.
+     *
+     * These are initialized (via friend constructors) when a displayList is issued in either replay
+     * or deferred mode. If replaying, the ops are not used until the next frame. If deferring, the
+     * ops may be stored in the DeferredDisplayList to be played back a second time.
+     *
+     * They should be used at most once per frame (one call to iterate)
+     */
+    ClipRectOp* mClipRectOp;
+    SaveLayerOp* mSaveLayerOp;
+    SaveOp* mSaveOp;
+    RestoreToCountOp* mRestoreToCountOp;
 }; // class DisplayList
 
 }; // namespace uirenderer
index 105f45f..9988bb8 100644 (file)
@@ -78,17 +78,26 @@ public:
         kOpLogFlag_JSON = 0x2 // TODO: add?
     };
 
-    // If a DeferredDisplayList is supplied, DrawOps will be stored until the list is flushed
-    // NOTE: complex clips and layers prevent deferral
-    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, int saveCount,
-            uint32_t level, bool caching, int multipliedAlpha,
-            DeferredDisplayList* deferredList) = 0;
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) = 0;
 
-    virtual void output(int level, uint32_t flags = 0) = 0;
+    virtual void replay(ReplayStateStruct& replayStruct, int saveCount,
+            int level, int multipliedAlpha) = 0;
+
+    virtual void output(int level, uint32_t logFlags = 0) = 0;
 
     // NOTE: it would be nice to declare constants and overriding the implementation in each op to
     // point at the constants, but that seems to require a .cpp file
     virtual const char* name() = 0;
+
+    /**
+     * Stores the relevant canvas state of the object between deferral and replay (if the canvas
+     * state supports being stored) See OpenGLRenderer::simpleClipAndState()
+     *
+     * TODO: don't reserve space for StateOps that won't be deferred
+     */
+    DeferredDisplayState state;
+
 };
 
 class StateOp : public DisplayListOp {
@@ -97,28 +106,22 @@ public:
 
     virtual ~StateOp() {}
 
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        // default behavior only affects immediate, deferrable state, issue directly to renderer
+        applyState(deferStruct.mRenderer, saveCount);
+    }
+
     /**
      * State operations are applied directly to the renderer, but can cause the deferred drawing op
      * list to flush
      */
-    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, int saveCount,
-            uint32_t level, bool caching, int multipliedAlpha, DeferredDisplayList* deferredList) {
-        status_t status = DrawGlInfo::kStatusDone;
-        if (deferredList && requiresDrawOpFlush(renderer)) {
-            // will be setting renderer state that affects ops in deferredList, so flush list first
-            status |= deferredList->flush(renderer, dirty, flags, level);
-        }
-        applyState(renderer, saveCount);
-        return status;
+    virtual void replay(ReplayStateStruct& replayStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        applyState(replayStruct.mRenderer, saveCount);
     }
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) = 0;
-
-    /**
-     * Returns true if it affects renderer drawing state in such a way to break deferral
-     * see OpenGLRenderer::disallowDeferral()
-     */
-    virtual bool requiresDrawOpFlush(OpenGLRenderer& renderer) { return false; }
 };
 
 class DrawOp : public DisplayListOp {
@@ -126,36 +129,35 @@ public:
     DrawOp(SkPaint* paint)
             : mPaint(paint), mQuickRejected(false) {}
 
-    /** Draw operations are stored in the deferredList with information necessary for playback */
-    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, int saveCount,
-            uint32_t level, bool caching, int multipliedAlpha, DeferredDisplayList* deferredList) {
-        if (mQuickRejected && CC_LIKELY(flags & DisplayList::kReplayFlag_ClipChildren)) {
-            return DrawGlInfo::kStatusDone;
-        }
-
-        if (!deferredList || renderer.disallowDeferral()) {
-            // dispatch draw immediately, since the renderer's state is too complex for deferral
-            return applyDraw(renderer, dirty, level, caching, multipliedAlpha);
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        if (mQuickRejected &&
+                CC_LIKELY(deferStruct.mReplayFlags & DisplayList::kReplayFlag_ClipChildren)) {
+            return;
         }
 
-        if (!caching) multipliedAlpha = -1;
         state.mMultipliedAlpha = multipliedAlpha;
         if (!getLocalBounds(state.mBounds)) {
             // empty bounds signify bounds can't be calculated
             state.mBounds.setEmpty();
         }
 
-        if (!renderer.storeDisplayState(state)) {
-            // op wasn't quick-rejected, so defer
-            deferredList->add(this, renderer.getCaches().drawReorderDisabled);
-            onDrawOpDeferred(renderer);
+        deferStruct.mDeferredList.addDrawOp(deferStruct.mRenderer, this);
+    }
+
+    virtual void replay(ReplayStateStruct& replayStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        if (mQuickRejected &&
+                CC_LIKELY(replayStruct.mReplayFlags & DisplayList::kReplayFlag_ClipChildren)) {
+            return;
         }
 
-        return DrawGlInfo::kStatusDone;
+        replayStruct.mDrawGlStatus |= applyDraw(replayStruct.mRenderer, replayStruct.mDirty,
+                level, multipliedAlpha);
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) = 0;
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) = 0;
 
     virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
     }
@@ -174,11 +176,6 @@ public:
 
     float strokeWidthOutset() { return mPaint->getStrokeWidth() * 0.5f; }
 
-    /**
-     * Stores the relevant canvas state of the object between deferral and replay (if the canvas
-     * state supports being stored) See OpenGLRenderer::simpleClipAndState()
-     */
-    DeferredDisplayState state;
 protected:
     SkPaint* getPaint(OpenGLRenderer& renderer, bool alwaysCopy = false) {
         return renderer.filterPaint(mPaint, alwaysCopy);
@@ -225,88 +222,113 @@ protected:
 ///////////////////////////////////////////////////////////////////////////////
 
 class SaveOp : public StateOp {
+    friend class DisplayList; // give DisplayList private constructor/reinit access
 public:
     SaveOp(int flags)
             : mFlags(flags) {}
 
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        int newSaveCount = deferStruct.mRenderer.save(mFlags);
+        deferStruct.mDeferredList.addSave(deferStruct.mRenderer, this, newSaveCount);
+    }
+
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
         renderer.save(mFlags);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Save flags %x", mFlags);
     }
 
     virtual const char* name() { return "Save"; }
 
+    int getFlags() const { return mFlags; }
 private:
+    SaveOp() {}
+    DisplayListOp* reinit(int flags) {
+        mFlags = flags;
+        return this;
+    }
+
     int mFlags;
 };
 
 class RestoreToCountOp : public StateOp {
+    friend class DisplayList; // give DisplayList private constructor/reinit access
 public:
     RestoreToCountOp(int count)
             : mCount(count) {}
 
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        deferStruct.mDeferredList.addRestoreToCount(deferStruct.mRenderer, saveCount + mCount);
+        deferStruct.mRenderer.restoreToCount(saveCount + mCount);
+    }
+
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
         renderer.restoreToCount(saveCount + mCount);
-
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Restore to count %d", mCount);
     }
 
     virtual const char* name() { return "RestoreToCount"; }
-    // Note: don't have to return true for requiresDrawOpFlush - even though restore can create a
-    // complex clip, the clip and matrix are overridden by DeferredDisplayList::flush()
 
 private:
+    RestoreToCountOp() {}
+    DisplayListOp* reinit(int count) {
+        mCount = count;
+        return this;
+    }
+
     int mCount;
 };
 
 class SaveLayerOp : public StateOp {
+    friend class DisplayList; // give DisplayList private constructor/reinit access
 public:
-    SaveLayerOp(float left, float top, float right, float bottom, SkPaint* paint, int flags)
-            : mArea(left, top, right, bottom), mPaint(paint), mFlags(flags) {}
+    SaveLayerOp(float left, float top, float right, float bottom,
+            int alpha, SkXfermode::Mode mode, int flags)
+            : mArea(left, top, right, bottom), mAlpha(alpha), mMode(mode), mFlags(flags) {}
 
-    virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
-        SkPaint* paint = renderer.filterPaint(mPaint);
-        renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, paint, mFlags);
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        // NOTE: don't bother with actual saveLayer, instead issuing it at flush time
+        int newSaveCount = deferStruct.mRenderer.save(mFlags);
+        deferStruct.mDeferredList.addSaveLayer(deferStruct.mRenderer, this, newSaveCount);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
-        OP_LOG("SaveLayer of area " RECT_STRING, RECT_ARGS(mArea));
-    }
-
-    virtual const char* name() { return "SaveLayer"; }
-    virtual bool requiresDrawOpFlush(OpenGLRenderer& renderer) { return true; }
-
-private:
-    Rect mArea;
-    SkPaint* mPaint;
-    int mFlags;
-};
-
-class SaveLayerAlphaOp : public StateOp {
-public:
-    SaveLayerAlphaOp(float left, float top, float right, float bottom, int alpha, int flags)
-            : mArea(left, top, right, bottom), mAlpha(alpha), mFlags(flags) {}
-
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
-        renderer.saveLayerAlpha(mArea.left, mArea.top, mArea.right, mArea.bottom, mAlpha, mFlags);
+        renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, mAlpha, mMode, mFlags);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
-        OP_LOG("SaveLayerAlpha of area " RECT_STRING, RECT_ARGS(mArea));
+    virtual void output(int level, uint32_t logFlags) {
+        OP_LOG("SaveLayer%s of area " RECT_STRING,
+                (isSaveLayerAlpha() ? "Alpha" : ""),RECT_ARGS(mArea));
     }
 
-    virtual const char* name() { return "SaveLayerAlpha"; }
-    virtual bool requiresDrawOpFlush(OpenGLRenderer& renderer) { return true; }
+    virtual const char* name() { return isSaveLayerAlpha() ? "SaveLayerAlpha" : "SaveLayer"; }
+
+    int getFlags() { return mFlags; }
 
 private:
+    // Special case, reserved for direct DisplayList usage
+    SaveLayerOp() {}
+    DisplayListOp* reinit(float left, float top, float right, float bottom,
+            int alpha, SkXfermode::Mode mode, int flags) {
+        mArea.set(left, top, right, bottom);
+        mAlpha = alpha;
+        mMode = mode;
+        mFlags = flags;
+        return this;
+    }
+
+    bool isSaveLayerAlpha() { return mAlpha < 255 && mMode == SkXfermode::kSrcOver_Mode; }
     Rect mArea;
     int mAlpha;
+    SkXfermode::Mode mMode;
     int mFlags;
 };
 
@@ -319,7 +341,7 @@ public:
         renderer.translate(mDx, mDy);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Translate by %f %f", mDx, mDy);
     }
 
@@ -339,7 +361,7 @@ public:
         renderer.rotate(mDegrees);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Rotate by %f degrees", mDegrees);
     }
 
@@ -358,7 +380,7 @@ public:
         renderer.scale(mSx, mSy);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Scale by %f %f", mSx, mSy);
     }
 
@@ -378,7 +400,7 @@ public:
         renderer.skew(mSx, mSy);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Skew by %f %f", mSx, mSy);
     }
 
@@ -398,7 +420,7 @@ public:
         renderer.setMatrix(mMatrix);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("SetMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix));
     }
 
@@ -417,7 +439,7 @@ public:
         renderer.concatMatrix(mMatrix);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("ConcatMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix));
     }
 
@@ -427,75 +449,97 @@ private:
     SkMatrix* mMatrix;
 };
 
-class ClipRectOp : public StateOp {
+class ClipOp : public StateOp {
+public:
+    ClipOp(SkRegion::Op op) : mOp(op) {}
+
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        // NOTE: must defer op BEFORE applying state, since it may read clip
+        deferStruct.mDeferredList.addClip(deferStruct.mRenderer, this);
+
+        // TODO: Can we avoid applying complex clips at defer time?
+        applyState(deferStruct.mRenderer, saveCount);
+    }
+
+    bool canCauseComplexClip() {
+        return ((mOp != SkRegion::kIntersect_Op) && (mOp != SkRegion::kReplace_Op)) || !isRect();
+    }
+
+protected:
+    ClipOp() {}
+    virtual bool isRect() { return false; }
+
+    SkRegion::Op mOp;
+};
+
+class ClipRectOp : public ClipOp {
+    friend class DisplayList; // give DisplayList private constructor/reinit access
 public:
     ClipRectOp(float left, float top, float right, float bottom, SkRegion::Op op)
-            : mArea(left, top, right, bottom), mOp(op) {}
+            : ClipOp(op), mArea(left, top, right, bottom) {}
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
         renderer.clipRect(mArea.left, mArea.top, mArea.right, mArea.bottom, mOp);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("ClipRect " RECT_STRING, RECT_ARGS(mArea));
     }
 
     virtual const char* name() { return "ClipRect"; }
 
-    virtual bool requiresDrawOpFlush(OpenGLRenderer& renderer) {
-        // TODO: currently, we flush when we *might* cause a clip region to exist. Ideally, we
-        // should only flush when a non-rectangular clip would result
-        return !renderer.hasRectToRectTransform() || !hasRectToRectOp();
-    }
+protected:
+    virtual bool isRect() { return true; }
 
 private:
-    inline bool hasRectToRectOp() {
-        return mOp == SkRegion::kIntersect_Op || mOp == SkRegion::kReplace_Op;
+    ClipRectOp() {}
+    DisplayListOp* reinit(float left, float top, float right, float bottom, SkRegion::Op op) {
+        mOp = op;
+        mArea.set(left, top, right, bottom);
+        return this;
     }
+
     Rect mArea;
-    SkRegion::Op mOp;
 };
 
-class ClipPathOp : public StateOp {
+class ClipPathOp : public ClipOp {
 public:
     ClipPathOp(SkPath* path, SkRegion::Op op)
-            : mPath(path), mOp(op) {}
+            : ClipOp(op), mPath(path) {}
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
         renderer.clipPath(mPath, mOp);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         SkRect bounds = mPath->getBounds();
         OP_LOG("ClipPath bounds " RECT_STRING,
                 bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
     }
 
     virtual const char* name() { return "ClipPath"; }
-    virtual bool requiresDrawOpFlush(OpenGLRenderer& renderer) { return true; }
 
 private:
     SkPath* mPath;
-    SkRegion::Op mOp;
 };
 
-class ClipRegionOp : public StateOp {
+class ClipRegionOp : public ClipOp {
 public:
     ClipRegionOp(SkRegion* region, SkRegion::Op op)
-            : mRegion(region), mOp(op) {}
+            : ClipOp(op), mRegion(region) {}
 
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) {
         renderer.clipRegion(mRegion, mOp);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         SkIRect bounds = mRegion->getBounds();
         OP_LOG("ClipRegion bounds %d %d %d %d",
                 bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
     }
 
     virtual const char* name() { return "ClipRegion"; }
-    virtual bool requiresDrawOpFlush(OpenGLRenderer& renderer) { return true; }
 
 private:
     SkRegion* mRegion;
@@ -508,7 +552,7 @@ public:
         renderer.resetShader();
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOGS("ResetShader");
     }
 
@@ -523,7 +567,7 @@ public:
         renderer.setupShader(mShader);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("SetupShader, shader %p", mShader);
     }
 
@@ -539,7 +583,7 @@ public:
         renderer.resetColorFilter();
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOGS("ResetColorFilter");
     }
 
@@ -555,7 +599,7 @@ public:
         renderer.setupColorFilter(mColorFilter);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("SetupColorFilter, filter %p", mColorFilter);
     }
 
@@ -571,7 +615,7 @@ public:
         renderer.resetShadow();
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOGS("ResetShadow");
     }
 
@@ -587,7 +631,7 @@ public:
         renderer.setupShadow(mRadius, mDx, mDy, mColor);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("SetupShadow, radius %f, %f, %f, color %#x", mRadius, mDx, mDy, mColor);
     }
 
@@ -606,7 +650,7 @@ public:
         renderer.resetPaintFilter();
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOGS("ResetPaintFilter");
     }
 
@@ -622,7 +666,7 @@ public:
         renderer.setupPaintFilter(mClearBits, mSetBits);
     }
 
-    virtual void output(int level, uint32_t flags = 0) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("SetupPaintFilter, clear %#x, set %#x", mClearBits, mSetBits);
     }
 
@@ -645,9 +689,9 @@ public:
                     paint),
             mBitmap(bitmap) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
-        bool makeCopy = caching && multipliedAlpha < 255;
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
+        bool makeCopy = multipliedAlpha >= 0 && multipliedAlpha < 255;
         SkPaint* paint = getPaint(renderer, makeCopy);
         if (makeCopy) {
             // The paint is safe to modify since we're working on a copy
@@ -657,7 +701,7 @@ public:
         return ret;
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw bitmap %p at %f %f", mBitmap, mLocalBounds.left, mLocalBounds.top);
     }
 
@@ -679,12 +723,12 @@ public:
         transform.mapRect(mLocalBounds);
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawBitmap(mBitmap, mMatrix, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw bitmap %p matrix " MATRIX_STRING, mBitmap, MATRIX_ARGS(mMatrix));
     }
 
@@ -705,14 +749,14 @@ public:
             : DrawBoundedOp(dstLeft, dstTop, dstRight, dstBottom, paint),
             mBitmap(bitmap), mSrc(srcLeft, srcTop, srcRight, srcBottom) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawBitmap(mBitmap, mSrc.left, mSrc.top, mSrc.right, mSrc.bottom,
                 mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom,
                 getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw bitmap %p src="RECT_STRING", dst="RECT_STRING,
                 mBitmap, RECT_ARGS(mSrc), RECT_ARGS(mLocalBounds));
     }
@@ -732,13 +776,13 @@ public:
     DrawBitmapDataOp(SkBitmap* bitmap, float left, float top, SkPaint* paint)
             : DrawBitmapOp(bitmap, left, top, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawBitmapData(mBitmap, mLocalBounds.left,
                 mLocalBounds.top, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw bitmap %p", mBitmap);
     }
 
@@ -756,13 +800,13 @@ public:
             mBitmap(bitmap), mMeshWidth(meshWidth), mMeshHeight(meshHeight),
             mVertices(vertices), mColors(colors) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawBitmapMesh(mBitmap, mMeshWidth, mMeshHeight,
                 mVertices, mColors, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw bitmap %p mesh %d x %d", mBitmap, mMeshWidth, mMeshHeight);
     }
 
@@ -790,8 +834,8 @@ public:
             mColors(colors), mxDivsCount(width), myDivsCount(height),
             mNumColors(numColors), mAlpha(alpha), mMode(mode) {};
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         // NOTE: not calling the virtual method, which takes a paint
         return renderer.drawPatch(mBitmap, mxDivs, myDivs, mColors,
                 mxDivsCount, myDivsCount, mNumColors,
@@ -799,7 +843,7 @@ public:
                 mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode);
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw patch "RECT_STRING, RECT_ARGS(mLocalBounds));
     }
 
@@ -825,12 +869,12 @@ public:
     DrawColorOp(int color, SkXfermode::Mode mode)
             : DrawOp(0), mColor(color), mMode(mode) {};
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawColor(mColor, mMode);
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw color %#x, mode %d", mColor, mMode);
     }
 
@@ -869,13 +913,13 @@ public:
     DrawRectOp(float left, float top, float right, float bottom, SkPaint* paint)
             : DrawStrokableOp(left, top, right, bottom, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawRect(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Rect "RECT_STRING, RECT_ARGS(mLocalBounds));
     }
 
@@ -888,12 +932,12 @@ public:
             : DrawBoundedOp(rects, count, paint),
             mRects(rects), mCount(count) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawRects(mRects, mCount, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Rects count %d", mCount);
     }
 
@@ -914,13 +958,13 @@ public:
             float rx, float ry, SkPaint* paint)
             : DrawStrokableOp(left, top, right, bottom, paint), mRx(rx), mRy(ry) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawRoundRect(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom, mRx, mRy, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy);
     }
 
@@ -937,12 +981,12 @@ public:
             : DrawStrokableOp(x - radius, y - radius, x + radius, y + radius, paint),
             mX(x), mY(y), mRadius(radius) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawCircle(mX, mY, mRadius, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Circle x %f, y %f, r %f", mX, mY, mRadius);
     }
 
@@ -959,13 +1003,13 @@ public:
     DrawOvalOp(float left, float top, float right, float bottom, SkPaint* paint)
             : DrawStrokableOp(left, top, right, bottom, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawOval(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Oval "RECT_STRING, RECT_ARGS(mLocalBounds));
     }
 
@@ -979,14 +1023,14 @@ public:
             : DrawStrokableOp(left, top, right, bottom, paint),
             mStartAngle(startAngle), mSweepAngle(sweepAngle), mUseCenter(useCenter) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawArc(mLocalBounds.left, mLocalBounds.top,
                 mLocalBounds.right, mLocalBounds.bottom,
                 mStartAngle, mSweepAngle, mUseCenter, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Arc "RECT_STRING", start %f, sweep %f, useCenter %d",
                 RECT_ARGS(mLocalBounds), mStartAngle, mSweepAngle, mUseCenter);
     }
@@ -1011,8 +1055,8 @@ public:
         mLocalBounds.set(left, top, left + width, top + height);
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawPath(mPath, getPaint(renderer));
     }
 
@@ -1021,7 +1065,7 @@ public:
         renderer.getCaches().pathCache.precache(mPath, paint);
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds));
     }
 
@@ -1042,12 +1086,12 @@ public:
         mLocalBounds.outset(strokeWidthOutset());
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawLines(mPoints, mCount, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Lines count %d", mCount);
     }
 
@@ -1069,12 +1113,12 @@ public:
     DrawPointsOp(float* points, int count, SkPaint* paint)
             : DrawLinesOp(points, count, paint) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawPoints(mPoints, mCount, getPaint(renderer));
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Points count %d", mCount);
     }
 
@@ -1086,7 +1130,7 @@ public:
     DrawSomeTextOp(const char* text, int bytesCount, int count, SkPaint* paint)
             : DrawOp(paint), mText(text), mBytesCount(bytesCount), mCount(count) {};
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw some text, %d bytes", mBytesCount);
     }
 
@@ -1116,8 +1160,8 @@ public:
         /* TODO: inherit from DrawBounded and init mLocalBounds */
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawTextOnPath(mText, mBytesCount, mCount, mPath,
                 mHOffset, mVOffset, getPaint(renderer));
     }
@@ -1138,8 +1182,8 @@ public:
         /* TODO: inherit from DrawBounded and init mLocalBounds */
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawPosText(mText, mBytesCount, mCount, mPositions, getPaint(renderer));
     }
 
@@ -1169,6 +1213,7 @@ public:
             break;
         }
         mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom);
+        memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float));
     }
 
     /*
@@ -1179,18 +1224,20 @@ public:
     virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
         SkPaint* paint = getPaint(renderer);
         FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
-        const bool pureTranslate = state.mMatrix.isPureTranslate();
-        const mat4 transform = renderer.findBestFontTransform(state.mMatrix);
-        fontRenderer.precache(paint, mText, mCount, transform);
+        const mat4& transform = renderer.findBestFontTransform(state.mMatrix);
+        if (mPrecacheTransform != transform) {
+            fontRenderer.precache(paint, mText, mCount, transform);
+            mPrecacheTransform = transform;
+        }
     }
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         return renderer.drawText(mText, mBytesCount, mCount, mX, mY,
                 mPositions, getPaint(renderer), mLength);
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Text of count %d, bytes %d", mCount, mBytesCount);
     }
 
@@ -1210,6 +1257,7 @@ private:
     float mY;
     const float* mPositions;
     float mLength;
+    mat4 mPrecacheTransform;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1221,15 +1269,15 @@ public:
     DrawFunctorOp(Functor* functor)
             : DrawOp(0), mFunctor(functor) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         renderer.startMark("GL functor");
         status_t ret = renderer.callDrawGLFunction(mFunctor, dirty);
         renderer.endMark();
         return ret;
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Functor %p", mFunctor);
     }
 
@@ -1245,21 +1293,26 @@ public:
             : DrawBoundedOp(0, 0, displayList->getWidth(), displayList->getHeight(), 0),
             mDisplayList(displayList), mFlags(flags) {}
 
-    virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, int saveCount,
-            uint32_t level, bool caching, int multipliedAlpha, DeferredDisplayList* deferredList) {
+    virtual void defer(DeferStateStruct& deferStruct, int saveCount,
+            int level, int multipliedAlpha) {
+        if (mDisplayList && mDisplayList->isRenderable()) {
+            mDisplayList->defer(deferStruct, level + 1);
+        }
+    }
+
+    virtual void replay(ReplayStateStruct& replayStruct, int saveCount,
+            int level, int multipliedAlpha) {
         if (mDisplayList && mDisplayList->isRenderable()) {
-            return mDisplayList->replay(renderer, dirty, mFlags, level + 1, deferredList);
+            mDisplayList->replay(replayStruct, level + 1);
         }
-        return DrawGlInfo::kStatusDone;
     }
 
-    // NOT USED, since replay is overridden
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) { return DrawGlInfo::kStatusDone; }
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) { return DrawGlInfo::kStatusDone; }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Display List %p, flags %#x", mDisplayList, mFlags);
-        if (mDisplayList && (flags & kOpLogFlag_Recurse)) {
+        if (mDisplayList && (logFlags & kOpLogFlag_Recurse)) {
             mDisplayList->output(level + 1);
         }
     }
@@ -1276,11 +1329,11 @@ public:
     DrawLayerOp(Layer* layer, float x, float y, SkPaint* paint)
             : DrawOp(paint), mLayer(layer), mX(x), mY(y) {}
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
-            bool caching, int multipliedAlpha) {
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
+            int multipliedAlpha) {
         int oldAlpha = -1;
 
-        if (caching && multipliedAlpha < 255) {
+        if (multipliedAlpha >= 0 && multipliedAlpha < 255) {
             oldAlpha = mLayer->getAlpha();
             mLayer->setAlpha(multipliedAlpha);
         }
@@ -1291,7 +1344,7 @@ public:
         return ret;
     }
 
-    virtual void output(int level, uint32_t flags) {
+    virtual void output(int level, uint32_t logFlags) {
         OP_LOG("Draw Layer %p at %f %f", mLayer, mX, mY);
     }
 
index b011443..11a655e 100644 (file)
@@ -175,14 +175,8 @@ void DisplayListRenderer::restoreToCount(int saveCount) {
 }
 
 int DisplayListRenderer::saveLayer(float left, float top, float right, float bottom,
-        SkPaint* p, int flags) {
-    addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, p, flags));
-    return OpenGLRenderer::save(flags);
-}
-
-int DisplayListRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
-        int alpha, int flags) {
-    addStateOp(new (alloc()) SaveLayerAlphaOp(left, top, right, bottom, alpha, flags));
+        int alpha, SkXfermode::Mode mode, int flags) {
+    addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, alpha, mode, flags));
     return OpenGLRenderer::save(flags);
 }
 
index 38619bf..73b9b66 100644 (file)
@@ -79,9 +79,7 @@ public:
     virtual void restoreToCount(int saveCount);
 
     virtual int saveLayer(float left, float top, float right, float bottom,
-            SkPaint* p, int flags);
-    virtual int saveLayerAlpha(float left, float top, float right, float bottom,
-                int alpha, int flags);
+            int alpha, SkXfermode::Mode mode, int flags);
 
     virtual void translate(float dx, float dy);
     virtual void rotate(float degrees);
index 7b7357e..75e280c 100644 (file)
@@ -93,6 +93,14 @@ public:
         return *this;
     }
 
+    friend bool operator==(const Matrix4& a, const Matrix4& b) {
+        return !memcmp(&a.data[0], &b.data[0], 16 * sizeof(float));
+    }
+
+    friend bool operator!=(const Matrix4& a, const Matrix4& b) {
+        return !(a == b);
+    }
+
     void loadIdentity();
 
     void load(const float* v);
index 7fe0a69..e576f76 100644 (file)
@@ -112,10 +112,7 @@ static const Blender gBlendsSwap[] = {
 
 OpenGLRenderer::OpenGLRenderer():
         mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) {
-    mDrawModifiers.mShader = NULL;
-    mDrawModifiers.mColorFilter = NULL;
-    mDrawModifiers.mHasShadow = false;
-    mDrawModifiers.mHasDrawFilter = false;
+    resetDrawModifiers();
 
     memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
 
@@ -635,38 +632,17 @@ bool OpenGLRenderer::restoreSnapshot() {
 ///////////////////////////////////////////////////////////////////////////////
 
 int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
-        SkPaint* p, int flags) {
+        int alpha, SkXfermode::Mode mode, int flags) {
     const GLuint previousFbo = mSnapshot->fbo;
     const int count = saveSnapshot(flags);
 
     if (!mSnapshot->isIgnored()) {
-        int alpha = 255;
-        SkXfermode::Mode mode;
-
-        if (p) {
-            alpha = p->getAlpha();
-            mode = getXfermode(p->getXfermode());
-        } else {
-            mode = SkXfermode::kSrcOver_Mode;
-        }
-
         createLayer(left, top, right, bottom, alpha, mode, flags, previousFbo);
     }
 
     return count;
 }
 
-int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bottom,
-        int alpha, int flags) {
-    if (alpha >= 255) {
-        return saveLayer(left, top, right, bottom, NULL, flags);
-    } else {
-        SkPaint paint;
-        paint.setAlpha(alpha);
-        return saveLayer(left, top, right, bottom, &paint, flags);
-    }
-}
-
 /**
  * Layers are viewed by Skia are slightly different than layers in image editing
  * programs (for instance.) When a layer is created, previously created layers
@@ -1225,36 +1201,55 @@ void OpenGLRenderer::clearLayerRegions() {
 // State Deferral
 ///////////////////////////////////////////////////////////////////////////////
 
-bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state) {
+void OpenGLRenderer::resetDrawModifiers() {
+    mDrawModifiers.mShader = NULL;
+    mDrawModifiers.mColorFilter = NULL;
+    mDrawModifiers.mHasShadow = false;
+    mDrawModifiers.mHasDrawFilter = false;
+}
+
+bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
     const Rect& currentClip = *(mSnapshot->clipRect);
     const mat4& currentMatrix = *(mSnapshot->transform);
 
-    // state only has bounds initialized in local coordinates
-    if (!state.mBounds.isEmpty()) {
-        currentMatrix.mapRect(state.mBounds);
-        if (!state.mBounds.intersect(currentClip)) {
-            // quick rejected
-            return true;
+    if (stateDeferFlags & kStateDeferFlag_Draw) {
+        // state has bounds initialized in local coordinates
+        if (!state.mBounds.isEmpty()) {
+            currentMatrix.mapRect(state.mBounds);
+            if (!state.mBounds.intersect(currentClip)) {
+                // quick rejected
+                return true;
+            }
+        } else {
+            state.mBounds.set(currentClip);
         }
+        state.mDrawModifiers = mDrawModifiers;
+        state.mAlpha = mSnapshot->alpha;
+    }
+
+    if (stateDeferFlags & kStateDeferFlag_Clip) {
+        state.mClip.set(currentClip);
     } else {
-        state.mBounds.set(currentClip);
+        state.mClip.setEmpty();
     }
 
-    state.mClip.set(currentClip);
+    // transform always deferred
     state.mMatrix.load(currentMatrix);
-    state.mDrawModifiers = mDrawModifiers;
     return false;
 }
 
-void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state) {
+void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, int stateDeferFlags) {
     currentTransform().load(state.mMatrix);
 
-    // NOTE: a clip RECT will be saved and restored, but DeferredDisplayState doesn't support
-    // complex clips. In the future, we should add support for deferral of operations clipped by
-    // these. for now, we don't defer with complex clips (see OpenGLRenderer::disallowDeferral())
-    mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom);
-    dirtyClip();
-    mDrawModifiers = state.mDrawModifiers;
+    if (stateDeferFlags & kStateDeferFlag_Draw) {
+        mDrawModifiers = state.mDrawModifiers;
+        mSnapshot->alpha = state.mAlpha;
+    }
+
+    if (!state.mClip.isEmpty()) { //stateDeferFlags & kStateDeferFlag_Clip) {
+        mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom);
+        dirtyClip();
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1805,16 +1800,21 @@ void OpenGLRenderer::finishDrawTexture() {
 // Drawing
 ///////////////////////////////////////////////////////////////////////////////
 
-status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags) {
+status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty,
+        int32_t replayFlags) {
     // All the usual checks and setup operations (quickReject, setupDraw, etc.)
     // will be performed by the display list itself
     if (displayList && displayList->isRenderable()) {
         if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
-            return displayList->replay(*this, dirty, flags, 0);
+            ReplayStateStruct replayStruct(*this, dirty, replayFlags);
+            displayList->replay(replayStruct, 0);
+            return replayStruct.mDrawGlStatus;
         }
 
         DeferredDisplayList deferredList;
-        return displayList->replay(*this, dirty, flags, 0, &deferredList);
+        DeferStateStruct deferStruct(deferredList, *this, replayFlags);
+        displayList->defer(deferStruct, 0);
+        return deferredList.flush(*this, dirty);
     }
 
     return DrawGlInfo::kStatusDone;
index e961af2..3aa9975 100644 (file)
@@ -65,6 +65,11 @@ struct DrawModifiers {
     int mPaintFilterSetBits;
 };
 
+enum StateDeferFlags {
+    kStateDeferFlag_Draw = 0x1,
+    kStateDeferFlag_Clip = 0x2
+};
+
 struct DeferredDisplayState {
     Rect mBounds; // local bounds, mapped with matrix to be in screen space coordinates, clipped.
     int mMultipliedAlpha; // -1 if invalid (because caching not set)
@@ -72,8 +77,8 @@ struct DeferredDisplayState {
     // the below are set and used by the OpenGLRenderer at record and deferred playback
     Rect mClip;
     mat4 mMatrix;
-    SkiaShader* mShader;
     DrawModifiers mDrawModifiers;
+    float mAlpha;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -188,10 +193,18 @@ public:
     virtual void restore();
     virtual void restoreToCount(int saveCount);
 
+    ANDROID_API int saveLayer(float left, float top, float right, float bottom,
+            SkPaint* paint, int flags) {
+        SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
+        if (paint) mode = getXfermode(paint->getXfermode());
+        return saveLayer(left, top, right, bottom, paint ? paint->getAlpha() : 255, mode, flags);
+    }
+    ANDROID_API int saveLayerAlpha(float left, float top, float right, float bottom,
+            int alpha, int flags) {
+        return saveLayer(left, top, right, bottom, alpha, SkXfermode::kSrcOver_Mode, flags);
+    }
     virtual int saveLayer(float left, float top, float right, float bottom,
-            SkPaint* p, int flags);
-    virtual int saveLayerAlpha(float left, float top, float right, float bottom,
-            int alpha, int flags);
+            int alpha, SkXfermode::Mode mode, int flags);
 
     virtual void translate(float dx, float dy);
     virtual void rotate(float degrees);
@@ -211,7 +224,7 @@ public:
     virtual bool clipRegion(SkRegion* region, SkRegion::Op op);
     virtual Rect* getClipRect();
 
-    virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags);
+    virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t replayFlags);
     virtual void outputDisplayList(DisplayList* displayList);
     virtual status_t drawLayer(Layer* layer, float x, float y, SkPaint* paint);
     virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
@@ -261,21 +274,11 @@ public:
 
     SkPaint* filterPaint(SkPaint* paint, bool alwaysCopy = false);
 
-    bool disallowDeferral() {
-        // returns true if the OpenGLRenderer's state can be completely represented by
-        // a DeferredDisplayState object
-        return !mSnapshot->clipRegion->isEmpty() ||
-                mSnapshot->alpha < 1.0 ||
-                (mSnapshot->flags & Snapshot::kFlagIsLayer) ||
-                (mSnapshot->flags & Snapshot::kFlagFboTarget); // ensure we're not in a layer
-    }
-
-    bool storeDisplayState(DeferredDisplayState& state);
-    void restoreDisplayState(const DeferredDisplayState& state);
-
-    const DrawModifiers& getDrawModifiers() { return mDrawModifiers; }
-    void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; }
+    void resetDrawModifiers();
+    bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags);
+    void restoreDisplayState(const DeferredDisplayState& state, int stateDeferFlags);
 
+    // TODO: what does this mean? no perspective? no rotate?
     ANDROID_API bool isCurrentTransformSimple() {
         return mSnapshot->transform->isSimple();
     }
@@ -284,6 +287,11 @@ public:
         return mCaches;
     }
 
+    // simple rect clip
+    bool isCurrentClipSimple() {
+        return mSnapshot->clipRegion->isEmpty();
+    }
+
     /**
      * Sets the alpha on the current snapshot. This alpha value will be modulated
      * with other alpha values when drawing primitives.
index c932087..02c1aa1 100644 (file)
  */
 
 #define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <cutils/compiler.h>
 
 #include <utils/JenkinsHash.h>
+#include <utils/Trace.h>
 
 #include <SkGlyph.h>
 #include <SkUtils.h>
@@ -261,11 +263,8 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float
 }
 
 CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) {
-    CachedGlyphInfo* cachedGlyph = NULL;
-    ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
-    if (index >= 0) {
-        cachedGlyph = mCachedGlyphs.valueAt(index);
-
+    CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit);
+    if (cachedGlyph) {
         // Is the glyph still in texture cache?
         if (!cachedGlyph->mIsValid) {
             const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit,
@@ -346,11 +345,13 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le
 }
 
 void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
+    ATRACE_NAME("precacheText");
+
     if (numGlyphs == 0 || text == NULL) {
         return;
     }
-    int glyphsCount = 0;
 
+    int glyphsCount = 0;
     while (glyphsCount < numGlyphs) {
         glyph_t glyph = GET_GLYPH(text);
 
@@ -360,7 +361,6 @@ void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
         }
 
         CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true);
-
         glyphsCount++;
     }
 }
index 312c252..efa8089 100644 (file)
@@ -120,14 +120,14 @@ interface IAudioService {
     oneway void dispatchMediaKeyEvent(in KeyEvent keyEvent);
     void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent);
 
-    oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
+           void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
     oneway void unregisterMediaButtonIntent(in PendingIntent pi,  in ComponentName c);
 
     oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
     oneway void unregisterMediaButtonEventReceiverForCalls();
 
-    int registerRemoteControlClient(in PendingIntent mediaIntent,
-           in IRemoteControlClient rcClient, in String callingPackageName);
+           int registerRemoteControlClient(in PendingIntent mediaIntent,
+               in IRemoteControlClient rcClient, in String callingPackageName);
     oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
            in IRemoteControlClient rcClient);
 
index dfd0e94..c3cc1e6 100644 (file)
@@ -276,7 +276,7 @@ final public class MediaMuxer {
      */
     public void release() {
         if (mState == MUXER_STATE_STARTED) {
-            throw new IllegalStateException("Can't release when muxer is started");
+            stop();
         }
         if (mNativeObject != 0) {
             nativeRelease(mNativeObject);
index 5c74552..fa60ca6 100644 (file)
@@ -43,7 +43,7 @@ public class MediaRecorderStressTestRunner extends InstrumentationTestRunner {
     public static int mBitRate = profile.videoBitRate;
     public static boolean mRemoveVideo = true;
     public static int mDuration = 60 * 1000; // 60 seconds
-    public static int mTimeLapseDuration = 180 * 1000; // 3 minutes
+    public static int mTimeLapseDuration = 15 * 60 * 1000; // 15 minutes
     public static double mCaptureRate = 0.5; // 2 sec timelapse interval
 
     @Override
index 6eb9891..199f179 100644 (file)
@@ -50,16 +50,10 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
     private Camera mCamera;
 
     private static final int CAMERA_ID = 0;
-    private static final int NUMBER_OF_CAMERA_STRESS_LOOPS = 100;
-    private static final int NUMBER_OF_RECORDER_STRESS_LOOPS = 100;
-    private static final int NUMBER_OF_RECORDERANDPLAY_STRESS_LOOPS = 50;
-    private static final int NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER = 200;
-    private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 25;
-    private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 5* 1000; // 5 seconds
+    private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 1;
+    private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 30 * 1000; // 30 seconds
     private static final int USE_TEST_RUNNER_PROFILE = -1;
     private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds
-    private static final long WAIT_TIME_CAMERA_TEST = 3 * 1000; // 3 seconds
-    private static final long WAIT_TIME_RECORDER_TEST = 6 * 1000; // 6 seconds
     private static final String OUTPUT_FILE_EXT = ".3gp";
     private static final String MEDIA_STRESS_OUTPUT = "mediaStressOutput.txt";
 
@@ -150,158 +144,6 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
         }
     }
 
-    //Test case for stressing the camera preview.
-    @LargeTest
-    public void testStressCamera() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-        Log.v(TAG, "Camera start preview stress test");
-        mOutput.write("Total number of loops:" + NUMBER_OF_CAMERA_STRESS_LOOPS + "\n");
-        try {
-            Log.v(TAG, "Start preview");
-            mOutput.write("No of loop: ");
-
-            for (int i = 0; i< NUMBER_OF_CAMERA_STRESS_LOOPS; i++) {
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mCamera = Camera.open(CAMERA_ID);
-                    }
-                });
-                mCamera.setErrorCallback(mCameraErrorCallback);
-                mCamera.setPreviewDisplay(mSurfaceHolder);
-                mCamera.startPreview();
-                Thread.sleep(WAIT_TIME_CAMERA_TEST);
-                mCamera.stopPreview();
-                mCamera.release();
-                if (i == 0) {
-                    mOutput.write(i + 1);
-                } else {
-                    mOutput.write(String.format(", %d", (i + 1)));
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, e.toString());
-            fail("Camera startup preview stress test");
-        }
-    }
-
-    //Test case for stressing the camera preview.
-    @LargeTest
-    public void testStressRecorder() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-        Log.v(TAG, "H263 video record: reset after prepare Stress test");
-        mOutput.write("Total number of loops:" + NUMBER_OF_RECORDER_STRESS_LOOPS + "\n");
-        try {
-            mOutput.write("No of loop: ");
-            Log.v(TAG, "Start preview");
-            for (int i = 0; i < NUMBER_OF_RECORDER_STRESS_LOOPS; i++) {
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mRecorder = new MediaRecorder();
-                    }
-                });
-                Log.v(TAG, "counter = " + i);
-                String fileName = String.format("%s/temp%d%s",
-                        Environment.getExternalStorageDirectory(),
-                        i, OUTPUT_FILE_EXT);
-
-                Log.v(TAG, fileName);
-                mRecorder.setOnErrorListener(mRecorderErrorCallback);
-                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-                mRecorder.setOutputFile(fileName);
-                mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate);
-                mRecorder.setVideoSize(176,144);
-                Log.v(TAG, "setEncoder");
-                mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
-                mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-                Log.v(TAG, "setPreview");
-                mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
-                Log.v(TAG, "prepare");
-                mRecorder.prepare();
-                Log.v(TAG, "before release");
-                Thread.sleep(WAIT_TIME_RECORDER_TEST);
-                mRecorder.reset();
-                mRecorder.release();
-                if (i == 0) {
-                    mOutput.write(i + 1);
-                } else {
-                    mOutput.write(String.format(", %d", (i + 1)));
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, e.toString());
-            fail("H263 video recording stress test");
-        }
-    }
-
-    //Stress test case for switching camera and video recorder preview.
-    @LargeTest
-    public void testStressCameraSwitchRecorder() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-        Log.v(TAG, "Camera and video recorder preview switching");
-        mOutput.write("Total number of loops: " +
-                NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER + "\n");
-        try {
-            Log.v(TAG, "Start preview");
-            mOutput.write("No of loop: ");
-            for (int i = 0; i < NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER; i++) {
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mCamera = Camera.open(CAMERA_ID);
-                    }
-                });
-                mCamera.setErrorCallback(mCameraErrorCallback);
-                mCamera.setPreviewDisplay(mSurfaceHolder);
-                mCamera.startPreview();
-                Thread.sleep(WAIT_TIME_CAMERA_TEST);
-                mCamera.stopPreview();
-                mCamera.release();
-                mCamera = null;
-                Log.v(TAG, "release camera");
-                String fileName = String.format("%s/temp%d%s",
-                        Environment.getExternalStorageDirectory(),
-                        i, OUTPUT_FILE_EXT);
-                Log.v(TAG, fileName);
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mRecorder = new MediaRecorder();
-                    }
-                });
-                mRecorder.setOnErrorListener(mRecorderErrorCallback);
-                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-                mRecorder.setOutputFile(fileName);
-                mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate);
-                mRecorder.setVideoSize(176,144);
-                Log.v(TAG, "Media recorder setEncoder");
-                mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
-                Log.v(TAG, "mediaRecorder setPreview");
-                mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
-                Log.v(TAG, "prepare");
-                mRecorder.prepare();
-                Log.v(TAG, "before release");
-                Thread.sleep(WAIT_TIME_CAMERA_TEST);
-                mRecorder.release();
-                Log.v(TAG, "release video recorder");
-                if (i == 0) {
-                    mOutput.write(i + 1);
-                } else {
-                    mOutput.write(String.format(", %d", (i + 1)));
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, e.toString());
-            fail("Camera and recorder switch mode");
-        }
-    }
-
     public void validateRecordedVideo(String recordedFile) {
         try {
             MediaPlayer mp = new MediaPlayer();
index f993ab5..fc4c0f5 100644 (file)
@@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 LOCAL_PACKAGE_NAME := Shell
 LOCAL_CERTIFICATE := platform
 
index b42db45..ffb4c20 100644 (file)
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
-    
-    <application android:hasCode="false" android:label="@string/app_label">
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+
+    <application android:label="@string/app_label">
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="com.android.shell"
+            android:grantUriPermissions="true"
+            android:exported="false">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_provider_paths" />
+        </provider>
+
+        <activity
+            android:name=".BugreportWarningActivity"
+            android:theme="@*android:style/Theme.Holo.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true"
+            android:exported="false" />
+
+        <receiver
+            android:name=".BugreportReceiver"
+            android:permission="android.permission.DUMP">
+            <intent-filter>
+                <action android:name="android.intent.action.BUGREPORT_FINISHED" />
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/packages/Shell/res/layout/confirm_repeat.xml b/packages/Shell/res/layout/confirm_repeat.xml
new file mode 100644 (file)
index 0000000..dc250d6
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="16dip"
+    android:paddingEnd="16dip"
+    android:paddingTop="8dip"
+    android:paddingBottom="16dip"
+    android:orientation="vertical">
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/bugreport_confirm"
+        android:paddingBottom="16dip"
+        style="?android:attr/textAppearanceMedium" />
+    <CheckBox
+        android:id="@android:id/checkbox"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/bugreport_confirm_repeat" />
+</LinearLayout>
index 50610d5..e5606c7 100644 (file)
 
 <resources>
     <string name="app_label">Shell</string>
+
+    <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
+    <string name="bugreport_finished_title">Bug report captured</string>
+    <!-- Text of notification indicating that touching will share the captured bugreport. [CHAR LIMIT=100] -->
+    <string name="bugreport_finished_text">Touch to share your bug report</string>
+
+    <!-- Body of dialog informing user about contents of a bugreport. [CHAR LIMIT=NONE] -->
+    <string name="bugreport_confirm">Bug reports contain data from the system\'s various log files, including personal and private information.  Only share bug reports with apps and people you trust.</string>
+    <!-- Checkbox that indicates this dialog should be shown again when the next bugreport is taken. [CHAR LIMIT=50] -->
+    <string name="bugreport_confirm_repeat">Show this message next time</string>
 </resources>
diff --git a/packages/Shell/res/xml/file_provider_paths.xml b/packages/Shell/res/xml/file_provider_paths.xml
new file mode 100644 (file)
index 0000000..225c757
--- /dev/null
@@ -0,0 +1,3 @@
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <files-path name="bugreports" path="bugreports/" />
+</paths>
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
new file mode 100644 (file)
index 0000000..3748e89
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Preferences related to bug reports.
+ */
+public class BugreportPrefs {
+    private static final String PREFS_BUGREPORT = "bugreports";
+
+    private static final String KEY_WARNING_STATE = "warning-state";
+
+    public static final int STATE_UNKNOWN = 0;
+    public static final int STATE_SHOW = 1;
+    public static final int STATE_HIDE = 2;
+
+    public static int getWarningState(Context context, int def) {
+        final SharedPreferences prefs = context.getSharedPreferences(
+                PREFS_BUGREPORT, Context.MODE_PRIVATE);
+        return prefs.getInt(KEY_WARNING_STATE, def);
+    }
+
+    public static void setWarningState(Context context, int value) {
+        final SharedPreferences prefs = context.getSharedPreferences(
+                PREFS_BUGREPORT, Context.MODE_PRIVATE);
+        prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+    }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
new file mode 100644 (file)
index 0000000..de04909
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.getWarningState;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.support.v4.content.FileProvider;
+import android.text.format.DateUtils;
+import android.util.Patterns;
+
+import com.google.android.collect.Lists;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * Receiver that handles finished bugreports, usually by attaching them to an
+ * {@link Intent#ACTION_SEND}.
+ */
+public class BugreportReceiver extends BroadcastReceiver {
+    private static final String TAG = "Shell";
+
+    private static final String AUTHORITY = "com.android.shell";
+
+    private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+    private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+
+    /**
+     * Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are
+     * roughly 17MB of disk space.
+     */
+    private static final int MIN_KEEP_COUNT = 8;
+
+    /**
+     * Always keep bugreports taken in the last week.
+     */
+    private static final long MIN_KEEP_AGE = DateUtils.WEEK_IN_MILLIS;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+        // Files are kept on private storage, so turn into Uris that we can
+        // grant temporary permissions for.
+        final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
+        final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
+
+        Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+        Intent notifIntent;
+
+        // Send through warning dialog by default
+        if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
+            notifIntent = buildWarningIntent(context, sendIntent);
+        } else {
+            notifIntent = sendIntent;
+        }
+        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Notification.Builder builder = new Notification.Builder(context);
+        builder.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb);
+        builder.setContentTitle(context.getString(R.string.bugreport_finished_title));
+        builder.setContentText(context.getString(R.string.bugreport_finished_text));
+        builder.setContentIntent(PendingIntent.getActivity(
+                context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        builder.setAutoCancel(true);
+        NotificationManager.from(context).notify(TAG, 0, builder.build());
+
+        // Clean up older bugreports in background
+        final PendingResult result = goAsync();
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                FileUtils.deleteOlderFiles(
+                        bugreportFile.getParentFile(), MIN_KEEP_COUNT, MIN_KEEP_AGE);
+                result.finish();
+                return null;
+            }
+        }.execute();
+    }
+
+    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
+        final Intent intent = new Intent(context, BugreportWarningActivity.class);
+        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+        return intent;
+    }
+
+    /**
+     * Build {@link Intent} that can be used to share the given bugreport.
+     */
+    private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.setType("application/vnd.android.bugreport");
+
+        intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+        intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
+
+        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
+        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+        final Account sendToAccount = findSendToAccount(context);
+        if (sendToAccount != null) {
+            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
+        }
+
+        return intent;
+    }
+
+    /**
+     * Find the best matching {@link Account} based on build properties.
+     */
+    private static Account findSendToAccount(Context context) {
+        final AccountManager am = (AccountManager) context.getSystemService(
+                Context.ACCOUNT_SERVICE);
+
+        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
+        if (!preferredDomain.startsWith("@")) {
+            preferredDomain = "@" + preferredDomain;
+        }
+
+        final Account[] accounts = am.getAccounts();
+        Account foundAccount = null;
+        for (Account account : accounts) {
+            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+                if (!preferredDomain.isEmpty()) {
+                    // if we have a preferred domain and it matches, return; otherwise keep
+                    // looking
+                    if (account.name.endsWith(preferredDomain)) {
+                        return account;
+                    } else {
+                        foundAccount = account;
+                    }
+                    // if we don't have a preferred domain, just return since it looks like
+                    // an email address
+                } else {
+                    return account;
+                }
+            }
+        }
+        return foundAccount;
+    }
+
+    private static File getFileExtra(Intent intent, String key) {
+        final String path = intent.getStringExtra(key);
+        if (path != null) {
+            return new File(path);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
new file mode 100644 (file)
index 0000000..a1d879a
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_HIDE;
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
+import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.BugreportPrefs.setWarningState;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * Dialog that warns about contents of a bugreport.
+ */
+public class BugreportWarningActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
+
+    private Intent mSendIntent;
+    private CheckBox mConfirmRepeat;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mSendIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+
+        // We need to touch the extras to unpack them so they get migrated to
+        // ClipData correctly.
+        mSendIntent.hasExtra(Intent.EXTRA_STREAM);
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mView = LayoutInflater.from(this).inflate(R.layout.confirm_repeat, null);
+        ap.mPositiveButtonText = getString(android.R.string.ok);
+        ap.mNegativeButtonText = getString(android.R.string.cancel);
+        ap.mPositiveButtonListener = this;
+        ap.mNegativeButtonListener = this;
+
+        mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
+        mConfirmRepeat.setChecked(getWarningState(this, STATE_UNKNOWN) == STATE_SHOW);
+
+        setupAlert();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == AlertDialog.BUTTON_POSITIVE) {
+            // Remember confirm state, and launch target
+            setWarningState(this, mConfirmRepeat.isChecked() ? STATE_SHOW : STATE_HIDE);
+            startActivity(mSendIntent);
+        }
+
+        finish();
+    }
+}
index f3eecf2..627235f 100644 (file)
@@ -627,10 +627,26 @@ public class ImageWallpaper extends WallpaperService {
             }
             
             mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+            if (mEglContext == EGL_NO_CONTEXT) {
+                throw new RuntimeException("createContext failed " +
+                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            }
+
+            int attribs[] = {
+                EGL_WIDTH, 1,
+                EGL_HEIGHT, 1,
+                EGL_NONE
+            };
+            EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
+            mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
 
             int[] maxSize = new int[1];
             Rect frame = surfaceHolder.getSurfaceFrame();
             glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
+
+            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+            mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
+
             if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
                 mEgl.eglDestroyContext(mEglDisplay, mEglContext);
                 mEgl.eglTerminate(mEglDisplay);
@@ -639,9 +655,8 @@ public class ImageWallpaper extends WallpaperService {
                     maxSize[0] + "x" + maxSize[0]);
                 return false;
             }
-    
+
             mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
-    
             if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
                 int error = mEgl.eglGetError();
                 if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
index cd7324c..c68bab5 100644 (file)
@@ -20,6 +20,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.view.View;
@@ -104,7 +105,8 @@ public class EmergencyButton extends Button {
             Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            getContext().startActivity(intent);
+            getContext().startActivityAsUser(intent,
+                    new UserHandle(mLockPatternUtils.getCurrentUser()));
         }
     }
 
index 06f06b5..78d7caa 100644 (file)
@@ -310,9 +310,7 @@ public class KeyguardHostView extends KeyguardViewBase {
         mKeyguardSelectorView = (KeyguardSelectorView) findViewById(R.id.keyguard_selector_view);
         mViewStateManager.setSecurityViewContainer(mSecurityViewContainer);
 
-        if (!(mContext instanceof Activity)) {
-            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
-        }
+        setBackButtonEnabled(false);
 
         addDefaultWidgets();
 
@@ -329,6 +327,13 @@ public class KeyguardHostView extends KeyguardViewBase {
         updateSecurityViews();
     }
 
+    private void setBackButtonEnabled(boolean enabled) {
+        if (mContext instanceof Activity) return;  // always enabled in activity mode
+        setSystemUiVisibility(enabled ?
+                getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_BACK :
+                getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
+    }
+
     private boolean shouldEnableAddWidget() {
         return numWidgets() < MAX_WIDGETS && mUserSetupCompleted;
     }
@@ -907,6 +912,10 @@ public class KeyguardHostView extends KeyguardViewBase {
             // Discard current runnable if we're switching back to the selector view
             setOnDismissAction(null);
         }
+        if (securityMode == SecurityMode.Account && !mLockPatternUtils.isPermanentlyLocked()) {
+            // we're showing account as a backup, provide a way to get back to primary
+            setBackButtonEnabled(true);
+        }
         mCurrentSecuritySelection = securityMode;
     }
 
@@ -1579,6 +1588,12 @@ public class KeyguardHostView extends KeyguardViewBase {
     }
 
     public boolean handleBackKey() {
+        if (mCurrentSecuritySelection == SecurityMode.Account) {
+            // go back to primary screen and re-disable back
+            setBackButtonEnabled(false);
+            showPrimarySecurityScreen(false /*turningOff*/);
+            return true;
+        }
         if (mCurrentSecuritySelection != SecurityMode.None) {
             mCallback.dismiss(false);
             return true;
index c83a919..72d249a 100644 (file)
@@ -96,6 +96,7 @@ import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
@@ -154,6 +155,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     private boolean mLockdownEnabled;
     private LockdownVpnTracker mLockdownTracker;
 
+    private Nat464Xlat mClat;
+
     /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
     private Object mRulesLock = new Object();
     /** Currently active network rules by UID. */
@@ -544,9 +547,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         mVpn = new Vpn(mContext, mVpnCallback, mNetd);
         mVpn.startMonitoring(mContext, mTrackerHandler);
 
+        mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
+
         try {
             mNetd.registerObserver(mTethering);
             mNetd.registerObserver(mDataActivityObserver);
+            mNetd.registerObserver(mClat);
         } catch (RemoteException e) {
             loge("Error registering observer :" + e);
         }
@@ -1430,11 +1436,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
             boolean toDefaultTable) {
-        String iface = lp.getInterfaceName();
-        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr);
+        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
         if (bestRoute == null) {
-            bestRoute = RouteInfo.makeHostRoute(addr, iface);
+            bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
         } else {
+            String iface = bestRoute.getInterface();
             if (bestRoute.getGateway().equals(addr)) {
                 // if there is no better route, add the implied hostroute for our gateway
                 bestRoute = RouteInfo.makeHostRoute(addr, iface);
@@ -1449,9 +1455,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
             boolean toDefaultTable) {
-        String ifaceName = lp.getInterfaceName();
-        if ((ifaceName == null) || (lp == null) || (r == null)) {
-            if (DBG) log("modifyRoute got unexpected null: " + ifaceName + ", " + lp + ", " + r);
+        if ((lp == null) || (r == null)) {
+            if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
             return false;
         }
 
@@ -1460,8 +1465,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             return false;
         }
 
+        String ifaceName = r.getInterface();
+        if(ifaceName == null) {
+            loge("Error modifying route - no interface name");
+            return false;
+        }
+
         if (r.isHostRoute() == false) {
-            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), r.getGateway());
+            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), r.getGateway());
             if (bestRoute != null) {
                 if (bestRoute.getGateway().equals(r.getGateway())) {
                     // if there is no better route, add the implied hostroute for our gateway
@@ -2271,6 +2282,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             }
         }
 
+        // Update 464xlat state.
+        // TODO: Move to handleConnect()
+        NetworkStateTracker tracker = mNetTrackers[netType];
+        if (mClat.requiresClat(netType, tracker)) {
+            if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+                mClat.startClat(tracker);
+            } else {
+                mClat.stopClat();
+            }
+        }
+
         // TODO: Temporary notifying upstread change to Tethering.
         //       @see bug/4455071
         /** Notify TetheringService if interface name has been changed. */
@@ -2300,7 +2322,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             routeDiff = curLp.compareRoutes(newLp);
             dnsDiff = curLp.compareDnses(newLp);
         } else if (newLp != null) {
-            routeDiff.added = newLp.getRoutes();
+            routeDiff.added = newLp.getAllRoutes();
             dnsDiff.added = newLp.getDnses();
         }
 
index d0048bf..f6c9d82 100644 (file)
@@ -1199,7 +1199,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
             mCurId = info.getId();
             mCurToken = new Binder();
             try {
-                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
+                if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
                 mIWindowManager.addWindowToken(mCurToken,
                         WindowManager.LayoutParams.TYPE_INPUT_METHOD);
             } catch (RemoteException e) {
@@ -1243,11 +1243,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                     unbindCurrentMethodLocked(false, false);
                     return;
                 }
-                // Remove commands relating to the previous service. Otherwise WindowManagerService
-                // will reject the command because the token attached to these messages is invalid.
-                mCaller.removeMessages(MSG_SHOW_SOFT_INPUT);
-                mCaller.removeMessages(MSG_HIDE_SOFT_INPUT);
-                if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
+                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
                 if (mCurClient != null) {
@@ -1693,7 +1689,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                     }
                 }
 
-                if (true || DEBUG) Slog.v(TAG, "Client requesting input be shown");
+                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
                 return showCurrentInputLocked(flags, resultReceiver);
             }
         } finally {
@@ -1717,8 +1713,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
 
         boolean res = false;
         if (mCurMethod != null) {
-            if (true ||DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken,
-                    new RuntimeException("here").fillInStackTrace());
+            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
                     resultReceiver));
@@ -1790,13 +1785,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mShowExplicitlyRequested || mShowForced)) {
-            if (true ||DEBUG) Slog.v(TAG,
-                    "Not hiding: explicit show not cancelled by non-explicit hide");
+            if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
             return false;
         }
         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
-            if (true ||DEBUG) Slog.v(TAG,
-                    "Not hiding: forced show not cancelled by not-always hide");
+            if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
             return false;
         }
         boolean res;
@@ -2307,10 +2300,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
             case MSG_SHOW_SOFT_INPUT:
                 args = (SomeArgs)msg.obj;
                 try {
-                    if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
                             + msg.arg1 + ", " + args.arg2 + ")");
-                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
-                            (ResultReceiver)args.arg2);
+                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -2318,10 +2310,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
             case MSG_HIDE_SOFT_INPUT:
                 args = (SomeArgs)msg.obj;
                 try {
-                    if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
                             + args.arg2 + ")");
-                    ((IInputMethod)args.arg1).hideSoftInput(0,
-                            (ResultReceiver)args.arg2);
+                    ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -2329,7 +2320,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
             case MSG_ATTACH_TOKEN:
                 args = (SomeArgs)msg.obj;
                 try {
-                    if (true || DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
+                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
                 } catch (RemoteException e) {
                 }
index 7686705..2210a18 100644 (file)
@@ -233,6 +233,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
             try {
                 mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up);
             } catch (RemoteException e) {
+            } catch (RuntimeException e) {
             }
         }
         mObservers.finishBroadcast();
@@ -248,6 +249,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
             try {
                 mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up);
             } catch (RemoteException e) {
+            } catch (RuntimeException e) {
             }
         }
         mObservers.finishBroadcast();
@@ -262,6 +264,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
             try {
                 mObservers.getBroadcastItem(i).interfaceAdded(iface);
             } catch (RemoteException e) {
+            } catch (RuntimeException e) {
             }
         }
         mObservers.finishBroadcast();
@@ -281,6 +284,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
             try {
                 mObservers.getBroadcastItem(i).interfaceRemoved(iface);
             } catch (RemoteException e) {
+            } catch (RuntimeException e) {
             }
         }
         mObservers.finishBroadcast();
@@ -295,6 +299,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
             try {
                 mObservers.getBroadcastItem(i).limitReached(limitName, iface);
             } catch (RemoteException e) {
+            } catch (RuntimeException e) {
             }
         }
         mObservers.finishBroadcast();
@@ -309,6 +314,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
             try {
                 mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(label, active);
             } catch (RemoteException e) {
+            } catch (RuntimeException e) {
             }
         }
         mObservers.finishBroadcast();
index b06c60a..1ac6bdf 100644 (file)
@@ -368,6 +368,13 @@ class ServiceRecord extends Binder {
                         return;
                     }
                     try {
+                        if (foregroundNoti.icon == 0) {
+                            // Notifications whose icon is 0 are defined to not show
+                            // a notification, silently ignoring it.  We don't want to
+                            // just ignore it, we want to prevent the service from
+                            // being foreground.
+                            throw new RuntimeException("icon must be non-zero");
+                        }
                         int[] outId = new int[1];
                         nm.enqueueNotificationInternal(localPackageName, localPackageName,
                                 appUid, appPid, null, localForegroundId, localForegroundNoti,
diff --git a/services/java/com/android/server/connectivity/Nat464Xlat.java b/services/java/com/android/server/connectivity/Nat464Xlat.java
new file mode 100644 (file)
index 0000000..2884eaf
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+
+import java.net.Inet4Address;
+
+import android.content.Context;
+import android.net.IConnectivityManager;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkStateTracker;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.net.BaseNetworkObserver;
+
+/**
+ * @hide
+ *
+ * Class to manage a 464xlat CLAT daemon.
+ */
+public class Nat464Xlat extends BaseNetworkObserver {
+    private Context mContext;
+    private INetworkManagementService mNMService;
+    private IConnectivityManager mConnService;
+    private NetworkStateTracker mTracker;
+    private Handler mHandler;
+
+    // Whether we started clatd and expect it to be running.
+    private boolean mIsStarted;
+    // Whether the clatd interface exists (i.e., clatd is running).
+    private boolean mIsRunning;
+    // The LinkProperties of the clat interface.
+    private LinkProperties mLP;
+
+    // This must match the interface name in clatd.conf.
+    private static final String CLAT_INTERFACE_NAME = "clat4";
+
+    private static final String TAG = "Nat464Xlat";
+
+    public Nat464Xlat(Context context, INetworkManagementService nmService,
+                      IConnectivityManager connService, Handler handler) {
+        mContext = context;
+        mNMService = nmService;
+        mConnService = connService;
+        mHandler = handler;
+
+        mIsStarted = false;
+        mIsRunning = false;
+        mLP = new LinkProperties();
+    }
+
+    /**
+     * Determines whether an interface requires clat.
+     * @param netType the network type (one of the
+     *   android.net.ConnectivityManager.TYPE_* constants)
+     * @param tracker the NetworkStateTracker corresponding to the network type.
+     * @return true if the interface requires clat, false otherwise.
+     */
+    public boolean requiresClat(int netType, NetworkStateTracker tracker) {
+        LinkProperties lp = tracker.getLinkProperties();
+        // Only support clat on mobile for now.
+        Slog.d(TAG, "requiresClat: netType=" + netType + ", hasIPv4Address=" +
+               lp.hasIPv4Address());
+        return netType == TYPE_MOBILE && !lp.hasIPv4Address();
+    }
+
+    /**
+     * Starts the clat daemon.
+     * @param lp The link properties of the interface to start clatd on.
+     */
+    public void startClat(NetworkStateTracker tracker) {
+        if (mIsStarted) {
+            Slog.e(TAG, "startClat: already started");
+            return;
+        }
+        mTracker = tracker;
+        LinkProperties lp = mTracker.getLinkProperties();
+        String iface = lp.getInterfaceName();
+        Slog.i(TAG, "Starting clatd on " + iface + ", lp=" + lp);
+        try {
+            mNMService.startClatd(iface);
+        } catch(RemoteException e) {
+            Slog.e(TAG, "Error starting clat daemon: " + e);
+        }
+        mIsStarted = true;
+    }
+
+    /**
+     * Stops the clat daemon.
+     */
+    public void stopClat() {
+        if (mIsStarted) {
+            Slog.i(TAG, "Stopping clatd");
+            try {
+                mNMService.stopClatd();
+            } catch(RemoteException e) {
+                Slog.e(TAG, "Error stopping clat daemon: " + e);
+            }
+            mIsStarted = false;
+            mIsRunning = false;
+            mTracker = null;
+            mLP.clear();
+        } else {
+            Slog.e(TAG, "stopClat: already stopped");
+        }
+    }
+
+    public boolean isStarted() {
+        return mIsStarted;
+    }
+
+    public boolean isRunning() {
+        return mIsRunning;
+    }
+
+    @Override
+    public void interfaceAdded(String iface) {
+        if (iface.equals(CLAT_INTERFACE_NAME)) {
+            Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+                   " added, mIsRunning = " + mIsRunning + " -> true");
+            mIsRunning = true;
+
+            // Get the network configuration of the clat interface, store it
+            // in our link properties, and stack it on top of the interface
+            // it's running on.
+            try {
+                InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
+                mLP.clear();
+                mLP.setInterfaceName(iface);
+                RouteInfo ipv4Default = new RouteInfo(new LinkAddress(Inet4Address.ANY, 0), null,
+                                                      iface);
+                mLP.addRoute(ipv4Default);
+                mLP.addLinkAddress(config.getLinkAddress());
+                mTracker.addStackedLink(mLP);
+                Slog.i(TAG, "Adding stacked link. tracker LP: " +
+                       mTracker.getLinkProperties());
+            } catch(RemoteException e) {
+                Slog.e(TAG, "Error getting link properties: " + e);
+            }
+
+            // Inform ConnectivityService that things have changed.
+            Message msg = mHandler.obtainMessage(
+                NetworkStateTracker.EVENT_CONFIGURATION_CHANGED,
+                mTracker.getNetworkInfo());
+            Slog.i(TAG, "sending message to ConnectivityService: " + msg);
+            msg.sendToTarget();
+        }
+    }
+
+    @Override
+    public void interfaceRemoved(String iface) {
+        if (iface == CLAT_INTERFACE_NAME) {
+            if (mIsRunning) {
+                NetworkUtils.resetConnections(
+                    CLAT_INTERFACE_NAME,
+                    NetworkUtils.RESET_IPV4_ADDRESSES);
+            }
+            Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+                   " removed, mIsRunning = " + mIsRunning + " -> false");
+            mIsRunning = false;
+            mTracker.removeStackedLink(mLP);
+            mLP.clear();
+            Slog.i(TAG, "mLP = " + mLP);
+        }
+    }
+};
index e4a7ead..32f39b7 100644 (file)
@@ -35,6 +35,7 @@ import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
 import android.net.NetworkUtils;
+import android.net.RouteInfo;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -1345,7 +1346,21 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
                         linkProperties = mConnService.getLinkProperties(upType);
                     } catch (RemoteException e) { }
                     if (linkProperties != null) {
-                        iface = linkProperties.getInterfaceName();
+                        // Find the interface with the default IPv4 route. It may be the
+                        // interface described by linkProperties, or one of the interfaces
+                        // stacked on top of it.
+                        Log.i(TAG, "Finding IPv4 upstream interface on: " + linkProperties);
+                        RouteInfo ipv4Default = RouteInfo.selectBestRoute(
+                            linkProperties.getAllRoutes(), Inet4Address.ANY);
+                        if (ipv4Default != null) {
+                            iface = ipv4Default.getInterface();
+                            Log.i(TAG, "Found interface " + ipv4Default.getInterface());
+                        } else {
+                            Log.i(TAG, "No IPv4 upstream interface, giving up.");
+                        }
+                    }
+
+                    if (iface != null) {
                         String[] dnsServers = mDefaultDnsServers;
                         Collection<InetAddress> dnses = linkProperties.getDnses();
                         if (dnses != null) {
index 457e92d..4a74149 100644 (file)
@@ -82,7 +82,7 @@ final class ElectronBeam {
     private int mDisplayHeight;     // real height, not rotated
     private SurfaceSession mSurfaceSession;
     private SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private Surface mSurface;
     private NaturalSurfaceLayout mSurfaceLayout;
     private EGLDisplay mEglDisplay;
     private EGLConfig mEglConfig;
@@ -519,6 +519,7 @@ final class ElectronBeam {
 
             mSurfaceControl.setLayerStack(mDisplayLayerStack);
             mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight);
+            mSurface = new Surface();
             mSurface.copyFrom(mSurfaceControl);
             
             mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl);
diff --git a/services/java/com/android/server/wifi/README.txt b/services/java/com/android/server/wifi/README.txt
new file mode 100644 (file)
index 0000000..c03bff5
--- /dev/null
@@ -0,0 +1,12 @@
+WifiService: Implements the IWifiManager 3rd party API. The API and the device state information (screen on/off, battery state, sleep policy) go as input into the WifiController which tracks high level states as to whether STA or AP mode is operational and controls the WifiStateMachine to handle bringup and shut down.
+
+WifiController: Acts as a controller to the WifiStateMachine based on various inputs (API and device state). Runs on the same thread created in WifiService.
+
+WifiSettingsStore: Tracks the various settings (wifi toggle, airplane toggle, tethering toggle, scan mode toggle) and provides API to figure if wifi should be turned on or off.
+
+WifiTrafficPoller: Polls traffic on wifi and notifies apps listening on it.
+
+WifiNotificationController: Controls whether the open network notification is displayed or not based on the scan results.
+
+WifiStateMachine: Tracks the various states on STA and AP connectivity and handles bring up and shut down.
+
diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java
new file mode 100644 (file)
index 0000000..4d7c434
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
+import android.net.wifi.WifiStateMachine;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.wifi.WifiService.LockList;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+class WifiController extends StateMachine {
+    private static final String TAG = "WifiController";
+    private static final boolean DBG = false;
+    private Context mContext;
+    private boolean mScreenOff;
+    private boolean mDeviceIdle;
+    private int mPluggedType;
+    private int mStayAwakeConditions;
+    private long mIdleMillis;
+    private int mSleepPolicy;
+
+    private AlarmManager mAlarmManager;
+    private PendingIntent mIdleIntent;
+    private static final int IDLE_REQUEST = 0;
+
+    /**
+     * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
+     * Settings.Global value is not present. This timeout value is chosen as
+     * the approximate point at which the battery drain caused by Wi-Fi
+     * being enabled but not active exceeds the battery drain caused by
+     * re-establishing a connection to the mobile data network.
+     */
+    private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
+
+    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+    private static final String ACTION_DEVICE_IDLE =
+            "com.android.server.WifiManager.action.DEVICE_IDLE";
+
+    /* References to values tracked in WifiService */
+    final WifiStateMachine mWifiStateMachine;
+    final WifiSettingsStore mSettingsStore;
+    final LockList mLocks;
+
+    /**
+     * Temporary for computing UIDS that are responsible for starting WIFI.
+     * Protected by mWifiStateTracker lock.
+     */
+    private final WorkSource mTmpWorkSource = new WorkSource();
+
+    private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
+
+    static final int CMD_EMERGENCY_MODE_CHANGED     = BASE + 1;
+    static final int CMD_SCREEN_ON                  = BASE + 2;
+    static final int CMD_SCREEN_OFF                 = BASE + 3;
+    static final int CMD_BATTERY_CHANGED            = BASE + 4;
+    static final int CMD_DEVICE_IDLE                = BASE + 5;
+    static final int CMD_LOCKS_CHANGED              = BASE + 6;
+    static final int CMD_SCAN_ALWAYS_MODE_CHANGED   = BASE + 7;
+    static final int CMD_WIFI_TOGGLED               = BASE + 8;
+    static final int CMD_AIRPLANE_TOGGLED           = BASE + 9;
+    static final int CMD_SET_AP                     = BASE + 10;
+
+    private DefaultState mDefaultState = new DefaultState();
+    private StaEnabledState mStaEnabledState = new StaEnabledState();
+    private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
+    private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
+    private ApEnabledState mApEnabledState = new ApEnabledState();
+    private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
+    private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
+    private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
+    private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
+    private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
+    private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
+    private EcmState mEcmState = new EcmState();
+
+    WifiController(Context context, WifiService service, Looper looper) {
+        super(TAG, looper);
+        mContext = context;
+        mWifiStateMachine = service.mWifiStateMachine;
+        mSettingsStore = service.mSettingsStore;
+        mLocks = service.mLocks;
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
+        mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
+
+        addState(mDefaultState);
+            addState(mApStaDisabledState, mDefaultState);
+            addState(mStaEnabledState, mDefaultState);
+                addState(mDeviceActiveState, mStaEnabledState);
+                addState(mDeviceInactiveState, mStaEnabledState);
+                    addState(mScanOnlyLockHeldState, mDeviceInactiveState);
+                    addState(mFullLockHeldState, mDeviceInactiveState);
+                    addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
+                    addState(mNoLockHeldState, mDeviceInactiveState);
+            addState(mStaDisabledWithScanState, mDefaultState);
+            addState(mApEnabledState, mDefaultState);
+            addState(mEcmState, mDefaultState);
+        setInitialState(mApStaDisabledState);
+        setLogRecSize(25);
+        setLogOnlyTransitions(true);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_DEVICE_IDLE);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(ACTION_DEVICE_IDLE)) {
+                            sendMessage(CMD_DEVICE_IDLE);
+                        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_NETWORK_INFO);
+                        }
+                    }
+                },
+                new IntentFilter(filter));
+
+        initializeAndRegisterForSettingsChange(looper);
+    }
+
+    private void initializeAndRegisterForSettingsChange(Looper looper) {
+        Handler handler = new Handler(looper);
+        readStayAwakeConditions();
+        registerForStayAwakeModeChange(handler);
+        readWifiIdleTime();
+        registerForWifiIdleTimeChange(handler);
+        readStayAwakeConditions();
+        registerForWifiSleepPolicyChange(handler);
+    }
+
+    private void readStayAwakeConditions() {
+        mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+    }
+
+    private void readWifiIdleTime() {
+        mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(),
+                Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
+    }
+
+    private void readWifiSleepPolicy() {
+        mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SLEEP_POLICY,
+                Settings.Global.WIFI_SLEEP_POLICY_NEVER);
+    }
+
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForStayAwakeModeChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readStayAwakeConditions();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
+                false, contentObserver);
+    }
+
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForWifiIdleTimeChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readWifiIdleTime();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
+                false, contentObserver);
+    }
+
+    /**
+     * Observes changes to wifi sleep policy
+     */
+    private void registerForWifiSleepPolicyChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readWifiSleepPolicy();
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
+                false, contentObserver);
+    }
+
+    /**
+     * Determines whether the Wi-Fi chipset should stay awake or be put to
+     * sleep. Looks at the setting for the sleep policy and the current
+     * conditions.
+     *
+     * @see #shouldDeviceStayAwake(int)
+     */
+    private boolean shouldWifiStayAwake(int pluggedType) {
+        if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
+            // Never sleep
+            return true;
+        } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
+                (pluggedType != 0)) {
+            // Never sleep while plugged, and we're plugged
+            return true;
+        } else {
+            // Default
+            return shouldDeviceStayAwake(pluggedType);
+        }
+    }
+
+    /**
+     * Determine whether the bit value corresponding to {@code pluggedType} is set in
+     * the bit string mStayAwakeConditions. This determines whether the device should
+     * stay awake based on the current plugged type.
+     *
+     * @param pluggedType the type of plug (USB, AC, or none) for which the check is
+     * being made
+     * @return {@code true} if {@code pluggedType} indicates that the device is
+     * supposed to stay awake, {@code false} otherwise.
+     */
+    private boolean shouldDeviceStayAwake(int pluggedType) {
+        return (mStayAwakeConditions & pluggedType) != 0;
+    }
+
+    private void updateBatteryWorkSource() {
+        mTmpWorkSource.clear();
+        if (mDeviceIdle) {
+            mLocks.updateWorkSource(mTmpWorkSource);
+        }
+        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
+    }
+
+    class DefaultState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_SCREEN_ON:
+                    mAlarmManager.cancel(mIdleIntent);
+                    mScreenOff = false;
+                    mDeviceIdle = false;
+                    updateBatteryWorkSource();
+                    break;
+                case CMD_SCREEN_OFF:
+                    mScreenOff = true;
+                    /*
+                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+                    * AND the "stay on while plugged in" setting doesn't match the
+                    * current power conditions (i.e, not plugged in, plugged in to USB,
+                    * or plugged in to AC).
+                    */
+                    if (!shouldWifiStayAwake(mPluggedType)) {
+                        //Delayed shutdown if wifi is connected
+                        if (mNetworkInfo.getDetailedState() ==
+                                NetworkInfo.DetailedState.CONNECTED) {
+                            if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
+                            mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                                    System.currentTimeMillis() + mIdleMillis, mIdleIntent);
+                        } else {
+                            sendMessage(CMD_DEVICE_IDLE);
+                        }
+                    }
+                    break;
+                case CMD_DEVICE_IDLE:
+                    mDeviceIdle = true;
+                    updateBatteryWorkSource();
+                    break;
+                case CMD_BATTERY_CHANGED:
+                    /*
+                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+                    * AND we are transitioning from a state in which the device was supposed
+                    * to stay awake to a state in which it is not supposed to stay awake.
+                    * If "stay awake" state is not changing, we do nothing, to avoid resetting
+                    * the already-set timer.
+                    */
+                    int pluggedType = msg.arg1;
+                    if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
+                    if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
+                            !shouldWifiStayAwake(pluggedType)) {
+                        long triggerTime = System.currentTimeMillis() + mIdleMillis;
+                        if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
+                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+                    }
+
+                    mPluggedType = pluggedType;
+                    break;
+                case CMD_SET_AP:
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                case CMD_LOCKS_CHANGED:
+                case CMD_WIFI_TOGGLED:
+                case CMD_AIRPLANE_TOGGLED:
+                case CMD_EMERGENCY_MODE_CHANGED:
+                    break;
+                default:
+                    throw new RuntimeException("WifiController.handleMessage " + msg.what);
+            }
+            return HANDLED;
+        }
+
+    }
+
+    class ApStaDisabledState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(false);
+        }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isWifiToggleEnabled()) {
+                        if (mDeviceIdle == false) {
+                            transitionTo(mDeviceActiveState);
+                        } else {
+                            checkLocksAndTransitionWhenDeviceIdle();
+                        }
+                    }
+                    break;
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    if (mSettingsStore.isScanAlwaysAvailable()) {
+                        transitionTo(mStaDisabledWithScanState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    if (msg.arg1 == 1) {
+                        mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
+                                true);
+                        transitionTo(mApEnabledState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+    }
+
+    class StaEnabledState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(true);
+        }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                    if (! mSettingsStore.isWifiToggleEnabled()) {
+                        if (mSettingsStore.isScanAlwaysAvailable()) {
+                            transitionTo(mStaDisabledWithScanState);
+                        } else {
+                            transitionTo(mApStaDisabledState);
+                        }
+                    }
+                    break;
+                case CMD_AIRPLANE_TOGGLED:
+                    /* When wi-fi is turned off due to airplane,
+                    * disable entirely (including scan)
+                    */
+                    if (! mSettingsStore.isWifiToggleEnabled()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_EMERGENCY_MODE_CHANGED:
+                    if (msg.arg1 == 1) {
+                        transitionTo(mEcmState);
+                        break;
+                    }
+                default:
+                    return NOT_HANDLED;
+
+            }
+            return HANDLED;
+        }
+    }
+
+    class StaDisabledWithScanState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(true);
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
+            mWifiStateMachine.setDriverStart(true);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                    if (mSettingsStore.isWifiToggleEnabled()) {
+                        if (mDeviceIdle == false) {
+                            transitionTo(mDeviceActiveState);
+                        } else {
+                            checkLocksAndTransitionWhenDeviceIdle();
+                        }
+                    }
+                    break;
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isAirplaneModeOn() &&
+                            ! mSettingsStore.isWifiToggleEnabled()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    if (! mSettingsStore.isScanAlwaysAvailable()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    // Before starting tethering, turn off supplicant for scan mode
+                    if (msg.arg1 == 1) {
+                        deferMessage(msg);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class ApEnabledState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isAirplaneModeOn()) {
+                        mWifiStateMachine.setHostApRunning(null, false);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    if (msg.arg1 == 0) {
+                        mWifiStateMachine.setHostApRunning(null, false);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class EcmState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(false);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) {
+                if (mSettingsStore.isWifiToggleEnabled()) {
+                    if (mDeviceIdle == false) {
+                        transitionTo(mDeviceActiveState);
+                    } else {
+                        checkLocksAndTransitionWhenDeviceIdle();
+                    }
+                } else if (mSettingsStore.isScanAlwaysAvailable()) {
+                    transitionTo(mStaDisabledWithScanState);
+                } else {
+                    transitionTo(mApStaDisabledState);
+                }
+                return HANDLED;
+            } else {
+                return NOT_HANDLED;
+            }
+        }
+    }
+
+    /* Parent: StaEnabledState */
+    class DeviceActiveState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(false);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (msg.what == CMD_DEVICE_IDLE) {
+                checkLocksAndTransitionWhenDeviceIdle();
+                // We let default state handle the rest of work
+            }
+            return NOT_HANDLED;
+        }
+    }
+
+    /* Parent: StaEnabledState */
+    class DeviceInactiveState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_LOCKS_CHANGED:
+                    checkLocksAndTransitionWhenDeviceIdle();
+                    updateBatteryWorkSource();
+                    return HANDLED;
+                case CMD_SCREEN_ON:
+                    transitionTo(mDeviceActiveState);
+                    // More work in default state
+                    return NOT_HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
+    class ScanOnlyLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+            mWifiStateMachine.setDriverStart(true);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
+    class FullLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(false);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
+    class FullHighPerfLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(true);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
+    class NoLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setDriverStart(false);
+        }
+    }
+
+    private void checkLocksAndTransitionWhenDeviceIdle() {
+        if (mLocks.hasLocks()) {
+            switch (mLocks.getStrongestLockMode()) {
+                case WIFI_MODE_FULL:
+                    transitionTo(mFullLockHeldState);
+                    break;
+                case WIFI_MODE_FULL_HIGH_PERF:
+                    transitionTo(mFullHighPerfLockHeldState);
+                    break;
+                case WIFI_MODE_SCAN_ONLY:
+                    transitionTo(mScanOnlyLockHeldState);
+                    break;
+                default:
+                    loge("Illegal lock " + mLocks.getStrongestLockMode());
+            }
+        } else {
+            if (mSettingsStore.isScanAlwaysAvailable()) {
+                transitionTo(mScanOnlyLockHeldState);
+            } else {
+                transitionTo(mNoLockHeldState);
+            }
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+
+        pw.println("mScreenOff " + mScreenOff);
+        pw.println("mDeviceIdle " + mDeviceIdle);
+        pw.println("mPluggedType " + mPluggedType);
+        pw.println("mIdleMillis " + mIdleMillis);
+        pw.println("mSleepPolicy " + mSleepPolicy);
+    }
+}
index d675822..bc6bdaf 100644 (file)
 package com.android.server.wifi;
 
 import android.app.ActivityManager;
-import android.app.AlarmManager;
 import android.app.AppOpsManager;
-import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiInfo;
@@ -33,12 +32,9 @@ import android.net.wifi.WifiManager;
 import android.net.wifi.WifiStateMachine;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiWatchdogStateMachine;
-import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.os.Binder;
@@ -63,39 +59,35 @@ import java.net.Inet4Address;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.android.internal.R;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.AsyncChannel;
 import com.android.server.am.BatteryStatsService;
-import com.android.internal.R;
-
+import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
+import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
+import static com.android.server.wifi.WifiController.CMD_SET_AP;
+import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 /**
  * WifiService handles remote WiFi operation requests by implementing
  * the IWifiManager interface.
  *
  * @hide
  */
-//TODO: Clean up multiple locks and implement WifiService
-// as a SM to track soft AP/client/adhoc bring up based
-// on device idle state, airplane mode and boot.
-
 public final class WifiService extends IWifiManager.Stub {
     private static final String TAG = "WifiService";
     private static final boolean DBG = false;
 
-    private final WifiStateMachine mWifiStateMachine;
+    final WifiStateMachine mWifiStateMachine;
 
     private final Context mContext;
 
-    private AlarmManager mAlarmManager;
-    private PendingIntent mIdleIntent;
-    private static final int IDLE_REQUEST = 0;
-    private boolean mScreenOff;
-    private boolean mDeviceIdle;
-    private boolean mEmergencyCallbackMode = false;
-    private int mPluggedType;
-
-    private final LockList mLocks = new LockList();
+    final LockList mLocks = new LockList();
     // some wifi lock statistics
     private int mFullHighPerfLocksAcquired;
     private int mFullHighPerfLocksReleased;
@@ -119,19 +111,7 @@ public final class WifiService extends IWifiManager.Stub {
     /* Polls traffic stats and notifies clients */
     private WifiTrafficPoller mTrafficPoller;
     /* Tracks the persisted states for wi-fi & airplane mode */
-    private WifiSettingsStore mSettingsStore;
-
-    /**
-     * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
-     * Settings.Global value is not present. This timeout value is chosen as
-     * the approximate point at which the battery drain caused by Wi-Fi
-     * being enabled but not active exceeds the battery drain caused by
-     * re-establishing a connection to the mobile data network.
-     */
-    private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
-
-    private static final String ACTION_DEVICE_IDLE =
-            "com.android.server.WifiManager.action.DEVICE_IDLE";
+    final WifiSettingsStore mSettingsStore;
 
     /* The work source (UID) that triggered the current WIFI scan, synchronized
      * on this */
@@ -139,8 +119,6 @@ public final class WifiService extends IWifiManager.Stub {
 
     private boolean mIsReceiverRegistered = false;
 
-    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
-
     /**
      * Asynchronous channel to WifiStateMachine
      */
@@ -195,7 +173,7 @@ public final class WifiService extends IWifiManager.Stub {
                     break;
                 }
                 default: {
-                    Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg);
+                    Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
                     break;
                 }
             }
@@ -243,11 +221,6 @@ public final class WifiService extends IWifiManager.Stub {
     }
     WifiStateMachineHandler mWifiStateMachineHandler;
 
-    /**
-     * Temporary for computing UIDS that are responsible for starting WIFI.
-     * Protected by mWifiStateTracker lock.
-     */
-    private final WorkSource mTmpWorkSource = new WorkSource();
     private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
 
     public WifiService(Context context) {
@@ -260,20 +233,24 @@ public final class WifiService extends IWifiManager.Stub {
         mBatteryStats = BatteryStatsService.getService();
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
 
-        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
-        Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
-        mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
-
         mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
         mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
         mSettingsStore = new WifiSettingsStore(mContext);
 
+        HandlerThread wifiThread = new HandlerThread("WifiService");
+        wifiThread.start();
+        mClientHandler = new ClientHandler(wifiThread.getLooper());
+        mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
+        mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
+        mWifiController.start();
+
+        registerForScanModeChange();
         mContext.registerReceiver(
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
                         if (mSettingsStore.handleAirplaneModeToggled()) {
-                            updateWifiState();
+                            mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
                         }
                     }
                 },
@@ -289,13 +266,10 @@ public final class WifiService extends IWifiManager.Stub {
                         }
                     }
                 }, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
-
-        HandlerThread wifiThread = new HandlerThread("WifiService");
-        wifiThread.start();
-        mClientHandler = new ClientHandler(wifiThread.getLooper());
-        mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
     }
 
+    private WifiController mWifiController;
+
     /** Tell battery stats about a new WIFI scan */
     private void noteScanStart() {
         WorkSource scanWorkSource = null;
@@ -342,7 +316,7 @@ public final class WifiService extends IWifiManager.Stub {
      */
     public void checkAndStartWifi() {
         /* Check if wi-fi needs to be enabled */
-        boolean wifiEnabled = mSettingsStore.shouldWifiBeEnabled();
+        boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
         Slog.i(TAG, "WifiService starting up with Wi-Fi " +
                 (wifiEnabled ? "enabled" : "disabled"));
 
@@ -430,11 +404,7 @@ public final class WifiService extends IWifiManager.Stub {
             Binder.restoreCallingIdentity(ident);
         }
 
-        if (enable) {
-            reportStartWorkSource();
-        }
-
-        mWifiStateMachine.setWifiEnabled(enable);
+        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
 
         if (enable) {
             if (!mIsReceiverRegistered) {
@@ -470,7 +440,7 @@ public final class WifiService extends IWifiManager.Stub {
      */
     public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
-        mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled);
+        mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
     }
 
     /**
@@ -507,6 +477,26 @@ public final class WifiService extends IWifiManager.Stub {
     }
 
     /**
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public boolean isScanningAlwaysAvailable() {
+        // TODO: implement
+        return true;
+    }
+
+    /**
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public void setScanningAlwaysAvailable(boolean enable) {
+        // TODO: implement
+    }
+
+
+    /**
      * see {@link android.net.wifi.WifiManager#disconnect()}
      */
     public void disconnect() {
@@ -777,7 +767,7 @@ public final class WifiService extends IWifiManager.Stub {
          * of WifiLock & device idle status unless wifi enabled status is toggled
          */
 
-        mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode);
+        mWifiStateMachine.setDriverStart(true);
         mWifiStateMachine.reconnectCommand();
     }
 
@@ -796,7 +786,7 @@ public final class WifiService extends IWifiManager.Stub {
          * TODO: if a stop is issued, wifi is brought up only by startWifi
          * unless wifi enabled status is toggled
          */
-        mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
+        mWifiStateMachine.setDriverStart(false);
     }
 
     /**
@@ -848,175 +838,39 @@ public final class WifiService extends IWifiManager.Stub {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-
-            long idleMillis =
-                Settings.Global.getLong(mContext.getContentResolver(),
-                                        Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
-            int stayAwakeConditions =
-                Settings.Global.getInt(mContext.getContentResolver(),
-                                       Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
             if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                if (DBG) {
-                    Slog.d(TAG, "ACTION_SCREEN_ON");
-                }
-                mAlarmManager.cancel(mIdleIntent);
-                mScreenOff = false;
-                setDeviceIdleAndUpdateWifi(false);
+                mWifiController.sendMessage(CMD_SCREEN_ON);
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                if (DBG) {
-                    Slog.d(TAG, "ACTION_SCREEN_OFF");
-                }
-                mScreenOff = true;
-                /*
-                 * Set a timer to put Wi-Fi to sleep, but only if the screen is off
-                 * AND the "stay on while plugged in" setting doesn't match the
-                 * current power conditions (i.e, not plugged in, plugged in to USB,
-                 * or plugged in to AC).
-                 */
-                if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
-                    //Delayed shutdown if wifi is connected
-                    if (mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
-                        if (DBG) Slog.d(TAG, "setting ACTION_DEVICE_IDLE: " + idleMillis + " ms");
-                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
-                                + idleMillis, mIdleIntent);
-                    } else {
-                        setDeviceIdleAndUpdateWifi(true);
-                    }
-                }
-            } else if (action.equals(ACTION_DEVICE_IDLE)) {
-                setDeviceIdleAndUpdateWifi(true);
+                mWifiController.sendMessage(CMD_SCREEN_OFF);
             } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-                /*
-                 * Set a timer to put Wi-Fi to sleep, but only if the screen is off
-                 * AND we are transitioning from a state in which the device was supposed
-                 * to stay awake to a state in which it is not supposed to stay awake.
-                 * If "stay awake" state is not changing, we do nothing, to avoid resetting
-                 * the already-set timer.
-                 */
                 int pluggedType = intent.getIntExtra("plugged", 0);
-                if (DBG) {
-                    Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType);
-                }
-                if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
-                        !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
-                    long triggerTime = System.currentTimeMillis() + idleMillis;
-                    if (DBG) {
-                        Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms");
-                    }
-                    mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
-                }
-
-                mPluggedType = pluggedType;
+                mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
             } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
                         BluetoothAdapter.STATE_DISCONNECTED);
                 mWifiStateMachine.sendBluetoothAdapterStateChange(state);
             } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
-                mEmergencyCallbackMode = intent.getBooleanExtra("phoneinECMState", false);
-                updateWifiState();
+                boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
+                mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
             }
         }
-
-        /**
-         * Determines whether the Wi-Fi chipset should stay awake or be put to
-         * sleep. Looks at the setting for the sleep policy and the current
-         * conditions.
-         *
-         * @see #shouldDeviceStayAwake(int, int)
-         */
-        private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) {
-            //Never sleep as long as the user has not changed the settings
-            int wifiSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.WIFI_SLEEP_POLICY,
-                    Settings.Global.WIFI_SLEEP_POLICY_NEVER);
-
-            if (wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
-                // Never sleep
-                return true;
-            } else if ((wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
-                    (pluggedType != 0)) {
-                // Never sleep while plugged, and we're plugged
-                return true;
-            } else {
-                // Default
-                return shouldDeviceStayAwake(stayAwakeConditions, pluggedType);
-            }
-        }
-
-        /**
-         * Determine whether the bit value corresponding to {@code pluggedType} is set in
-         * the bit string {@code stayAwakeConditions}. Because a {@code pluggedType} value
-         * of {@code 0} isn't really a plugged type, but rather an indication that the
-         * device isn't plugged in at all, there is no bit value corresponding to a
-         * {@code pluggedType} value of {@code 0}. That is why we shift by
-         * {@code pluggedType - 1} instead of by {@code pluggedType}.
-         * @param stayAwakeConditions a bit string specifying which "plugged types" should
-         * keep the device (and hence Wi-Fi) awake.
-         * @param pluggedType the type of plug (USB, AC, or none) for which the check is
-         * being made
-         * @return {@code true} if {@code pluggedType} indicates that the device is
-         * supposed to stay awake, {@code false} otherwise.
-         */
-        private boolean shouldDeviceStayAwake(int stayAwakeConditions, int pluggedType) {
-            return (stayAwakeConditions & pluggedType) != 0;
-        }
     };
 
-    private void setDeviceIdleAndUpdateWifi(boolean deviceIdle) {
-        mDeviceIdle = deviceIdle;
-        reportStartWorkSource();
-        updateWifiState();
-    }
-
-    private synchronized void reportStartWorkSource() {
-        mTmpWorkSource.clear();
-        if (mDeviceIdle) {
-            for (int i=0; i<mLocks.mList.size(); i++) {
-                mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource);
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForScanModeChange() {
+        ContentObserver contentObserver = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mSettingsStore.handleWifiScanAlwaysAvailableToggled();
+                mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
             }
-        }
-        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
-    }
-
-    private void updateWifiState() {
-        boolean lockHeld = mLocks.hasLocks();
-        int strongestLockMode = WifiManager.WIFI_MODE_FULL;
-        boolean wifiShouldBeStarted;
-
-        if (mEmergencyCallbackMode) {
-            wifiShouldBeStarted = false;
-        } else {
-            wifiShouldBeStarted = !mDeviceIdle || lockHeld;
-        }
-
-        if (lockHeld) {
-            strongestLockMode = mLocks.getStrongestLockMode();
-        }
-        /* If device is not idle, lockmode cannot be scan only */
-        if (!mDeviceIdle && strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY) {
-            strongestLockMode = WifiManager.WIFI_MODE_FULL;
-        }
-
-        /* Disable tethering when airplane mode is enabled */
-        if (mSettingsStore.isAirplaneModeOn()) {
-            mWifiStateMachine.setWifiApEnabled(null, false);
-        }
+        };
 
-        if (mSettingsStore.shouldWifiBeEnabled()) {
-            if (wifiShouldBeStarted) {
-                reportStartWorkSource();
-                mWifiStateMachine.setWifiEnabled(true);
-                mWifiStateMachine.setScanOnlyMode(
-                        strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
-                mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode);
-                mWifiStateMachine.setHighPerfModeEnabled(strongestLockMode
-                        == WifiManager.WIFI_MODE_FULL_HIGH_PERF);
-            } else {
-                mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
-            }
-        } else {
-            mWifiStateMachine.setWifiEnabled(false);
-        }
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
+                false, contentObserver);
     }
 
     private void registerForBroadcasts() {
@@ -1024,7 +878,7 @@ public final class WifiService extends IWifiManager.Stub {
         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
-        intentFilter.addAction(ACTION_DEVICE_IDLE);
+        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
         intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         mContext.registerReceiver(mReceiver, intentFilter);
@@ -1043,12 +897,9 @@ public final class WifiService extends IWifiManager.Stub {
         pw.println("Stay-awake conditions: " +
                 Settings.Global.getInt(mContext.getContentResolver(),
                                        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
-        pw.println("mScreenOff " + mScreenOff);
-        pw.println("mDeviceIdle " + mDeviceIdle);
-        pw.println("mPluggedType " + mPluggedType);
-        pw.println("mEmergencyCallbackMode " + mEmergencyCallbackMode);
         pw.println("mMulticastEnabled " + mMulticastEnabled);
         pw.println("mMulticastDisabled " + mMulticastDisabled);
+        mWifiController.dump(fd, pw, args);
         mSettingsStore.dump(fd, pw, args);
         mNotificationController.dump(fd, pw, args);
         mTrafficPoller.dump(fd, pw, args);
@@ -1099,18 +950,18 @@ public final class WifiService extends IWifiManager.Stub {
         }
     }
 
-    private class LockList {
+    class LockList {
         private List<WifiLock> mList;
 
         private LockList() {
             mList = new ArrayList<WifiLock>();
         }
 
-        private synchronized boolean hasLocks() {
+        synchronized boolean hasLocks() {
             return !mList.isEmpty();
         }
 
-        private synchronized int getStrongestLockMode() {
+        synchronized int getStrongestLockMode() {
             if (mList.isEmpty()) {
                 return WifiManager.WIFI_MODE_FULL;
             }
@@ -1126,6 +977,12 @@ public final class WifiService extends IWifiManager.Stub {
             return WifiManager.WIFI_MODE_SCAN_ONLY;
         }
 
+        synchronized void updateWorkSource(WorkSource ws) {
+            for (int i = 0; i < mLocks.mList.size(); i++) {
+                ws.add(mLocks.mList.get(i).mWorkSource);
+            }
+        }
+
         private void addLock(WifiLock lock) {
             if (findLockByBinder(lock.mBinder) < 0) {
                 mList.add(lock);
@@ -1145,9 +1002,10 @@ public final class WifiService extends IWifiManager.Stub {
 
         private int findLockByBinder(IBinder binder) {
             int size = mList.size();
-            for (int i = size - 1; i >= 0; i--)
+            for (int i = size - 1; i >= 0; i--) {
                 if (mList.get(i).mBinder == binder)
                     return i;
+            }
             return -1;
         }
 
@@ -1231,12 +1089,7 @@ public final class WifiService extends IWifiManager.Stub {
                 ++mScanLocksAcquired;
                 break;
             }
-
-            // Be aggressive about adding new locks into the accounted state...
-            // we want to over-report rather than under-report.
-            reportStartWorkSource();
-
-            updateWifiState();
+            mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             return true;
         } catch (RemoteException e) {
             return false;
@@ -1303,11 +1156,8 @@ public final class WifiService extends IWifiManager.Stub {
                         ++mScanLocksReleased;
                         break;
                 }
+                mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             }
-
-            // TODO - should this only happen if you hadLock?
-            updateWifiState();
-
         } catch (RemoteException e) {
         } finally {
             Binder.restoreCallingIdentity(ident);
index d7c8752..3ff8061 100644 (file)
@@ -37,7 +37,11 @@ final class WifiSettingsStore {
     private int mPersistWifiState = WIFI_DISABLED;
     /* Tracks current airplane mode state */
     private boolean mAirplaneModeOn = false;
-    /* Tracks whether wifi is enabled from WifiStateMachine's perspective */
+
+    /* Tracks the setting of scan being available even when wi-fi is turned off
+     */
+    private boolean mScanAlwaysAvailable;
+
     private final Context mContext;
 
     /* Tracks if we have checked the saved wi-fi state after boot */
@@ -47,9 +51,10 @@ final class WifiSettingsStore {
         mContext = context;
         mAirplaneModeOn = getPersistedAirplaneModeOn();
         mPersistWifiState = getPersistedWifiState();
+        mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
     }
 
-    synchronized boolean shouldWifiBeEnabled() {
+    synchronized boolean isWifiToggleEnabled() {
         if (!mCheckSavedStateAtBoot) {
             mCheckSavedStateAtBoot = true;
             if (testAndClearWifiSavedState()) return true;
@@ -70,6 +75,10 @@ final class WifiSettingsStore {
        return mAirplaneModeOn;
     }
 
+    synchronized boolean isScanAlwaysAvailable() {
+        return mScanAlwaysAvailable;
+    }
+
     synchronized boolean handleWifiToggled(boolean wifiEnabled) {
         // Can Wi-Fi be toggled in airplane mode ?
         if (mAirplaneModeOn && !isAirplaneToggleable()) {
@@ -114,6 +123,10 @@ final class WifiSettingsStore {
         return true;
     }
 
+    synchronized void handleWifiScanAlwaysAvailableToggled() {
+        mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+    }
+
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("mPersistWifiState " + mPersistWifiState);
         pw.println("mAirplaneModeOn " + mAirplaneModeOn);
@@ -175,4 +188,10 @@ final class WifiSettingsStore {
         return Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
     }
+
+    private boolean getPersistedScanAlwaysAvailable() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+                0) == 1;
+    }
 }
index fbdd333..dffb617 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -32,6 +32,7 @@ import android.test.InstrumentationTestCase;
 import android.test.InstrumentationTestRunner;
 import android.util.Log;
 
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -48,16 +49,22 @@ import java.util.Map;
 public class AppLaunch extends InstrumentationTestCase {
 
     private static final int JOIN_TIMEOUT = 10000;
-    private static final String TAG = "AppLaunch";
+    private static final String TAG = AppLaunch.class.getSimpleName();
     private static final String KEY_APPS = "apps";
+    private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
+    private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle
+    private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches
+    private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps
 
     private Map<String, Intent> mNameToIntent;
     private Map<String, String> mNameToProcess;
     private Map<String, String> mNameToResultKey;
-
+    private Map<String, Long> mNameToLaunchTime;
     private IActivityManager mAm;
+    private int mLaunchIterations = 10;
+    private Bundle mResult = new Bundle();
 
-    public void testMeasureStartUpTime() throws RemoteException {
+    public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException {
         InstrumentationTestRunner instrumentation =
                 (InstrumentationTestRunner)getInstrumentation();
         Bundle args = instrumentation.getArguments();
@@ -66,25 +73,59 @@ public class AppLaunch extends InstrumentationTestCase {
         createMappings();
         parseArgs(args);
 
-        Bundle results = new Bundle();
+        // do initial app launch, without force stopping
         for (String app : mNameToResultKey.keySet()) {
-            try {
-                startApp(app, results);
-                sleep(750);
-                closeApp(app);
-                sleep(2000);
-            } catch (NameNotFoundException e) {
-                Log.i(TAG, "Application " + app + " not found");
+            long launchTime = startApp(app, false);
+            if (launchTime <=0 ) {
+                mNameToLaunchTime.put(app, -1L);
+                // simply pass the app if launch isn't successful
+                // error should have already been logged by startApp
+                continue;
+            }
+            sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
+            closeApp(app, false);
+            sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
+        }
+        // do the real app launch now
+        for (int i = 0; i < mLaunchIterations; i++) {
+            for (String app : mNameToResultKey.keySet()) {
+                long totalLaunchTime = mNameToLaunchTime.get(app);
+                long launchTime = 0;
+                if (totalLaunchTime < 0) {
+                    // skip if the app has previous failures
+                    continue;
+                }
+                launchTime = startApp(app, true);
+                if (launchTime <= 0) {
+                    // if it fails once, skip the rest of the launches
+                    mNameToLaunchTime.put(app, -1L);
+                    continue;
+                }
+                totalLaunchTime += launchTime;
+                mNameToLaunchTime.put(app, totalLaunchTime);
+                sleep(POST_LAUNCH_IDLE_TIMEOUT);
+                closeApp(app, true);
+                sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
+            }
+        }
+        for (String app : mNameToResultKey.keySet()) {
+            long totalLaunchTime = mNameToLaunchTime.get(app);
+            if (totalLaunchTime != -1) {
+                mResult.putDouble(mNameToResultKey.get(app),
+                        ((double) totalLaunchTime) / mLaunchIterations);
             }
-
         }
-        instrumentation.sendStatus(0, results);
+        instrumentation.sendStatus(0, mResult);
     }
 
     private void parseArgs(Bundle args) {
         mNameToResultKey = new LinkedHashMap<String, String>();
+        mNameToLaunchTime = new HashMap<String, Long>();
+        String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
+        if (launchIterations != null) {
+            mLaunchIterations = Integer.parseInt(launchIterations);
+        }
         String appList = args.getString(KEY_APPS);
-
         if (appList == null)
             return;
 
@@ -97,6 +138,7 @@ public class AppLaunch extends InstrumentationTestCase {
             }
 
             mNameToResultKey.put(parts[0], parts[1]);
+            mNameToLaunchTime.put(parts[0], 0L);
         }
     }
 
@@ -118,23 +160,26 @@ public class AppLaunch extends InstrumentationTestCase {
                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                 startIntent.setClassName(ri.activityInfo.packageName,
                         ri.activityInfo.name);
-                mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
-                mNameToProcess.put(ri.loadLabel(pm).toString(),
-                        ri.activityInfo.processName);
+                String appName = ri.loadLabel(pm).toString();
+                if (appName != null) {
+                    mNameToIntent.put(appName, startIntent);
+                    mNameToProcess.put(appName, ri.activityInfo.processName);
+                }
             }
         }
     }
 
-    private void startApp(String appName, Bundle results)
+    private long startApp(String appName, boolean forceStopBeforeLaunch)
             throws NameNotFoundException, RemoteException {
         Log.i(TAG, "Starting " + appName);
 
         Intent startIntent = mNameToIntent.get(appName);
         if (startIntent == null) {
             Log.w(TAG, "App does not exist: " + appName);
-            return;
+            mResult.putString(mNameToResultKey.get(appName), "App does not exist");
+            return -1;
         }
-        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent);
+        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch);
         Thread t = new Thread(runnable);
         t.start();
         try {
@@ -143,27 +188,38 @@ public class AppLaunch extends InstrumentationTestCase {
             // ignore
         }
         WaitResult result = runnable.getResult();
-        if(t.isAlive() || (result != null && result.result != ActivityManager.START_SUCCESS)) {
+        // report error if any of the following is true:
+        // * launch thread is alive
+        // * result is not null, but:
+        //   * result is not START_SUCESS
+        //   * or in case of no force stop, result is not TASK_TO_FRONT either
+        if (t.isAlive() || (result != null
+                && ((result.result != ActivityManager.START_SUCCESS)
+                        && (!forceStopBeforeLaunch
+                                && result.result != ActivityManager.START_TASK_TO_FRONT)))) {
             Log.w(TAG, "Assuming app " + appName + " crashed.");
-            reportError(appName, mNameToProcess.get(appName), results);
-            return;
+            reportError(appName, mNameToProcess.get(appName));
+            return -1;
         }
-        results.putString(mNameToResultKey.get(appName), String.valueOf(result.thisTime));
+        return result.thisTime;
     }
 
-    private void closeApp(String appName) {
+    private void closeApp(String appName, boolean forceStopApp) {
         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
         homeIntent.addCategory(Intent.CATEGORY_HOME);
         homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
         getInstrumentation().getContext().startActivity(homeIntent);
-        Intent startIntent = mNameToIntent.get(appName);
-        if (startIntent != null) {
-            String packageName = startIntent.getComponent().getPackageName();
-            try {
-                mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Error closing app", e);
+        sleep(POST_LAUNCH_IDLE_TIMEOUT);
+        if (forceStopApp) {
+            Intent startIntent = mNameToIntent.get(appName);
+            if (startIntent != null) {
+                String packageName = startIntent.getComponent().getPackageName();
+                try {
+                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error closing app", e);
+                }
             }
         }
     }
@@ -176,7 +232,7 @@ public class AppLaunch extends InstrumentationTestCase {
         }
     }
 
-    private void reportError(String appName, String processName, Bundle results) {
+    private void reportError(String appName, String processName) {
         ActivityManager am = (ActivityManager) getInstrumentation()
                 .getContext().getSystemService(Context.ACTIVITY_SERVICE);
         List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
@@ -186,12 +242,12 @@ public class AppLaunch extends InstrumentationTestCase {
                     continue;
 
                 Log.w(TAG, appName + " crashed: " + crash.shortMsg);
-                results.putString(mNameToResultKey.get(appName), crash.shortMsg);
+                mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
                 return;
             }
         }
 
-        results.putString(mNameToResultKey.get(appName),
+        mResult.putString(mNameToResultKey.get(appName),
                 "Crashed for unknown reason");
         Log.w(TAG, appName
                 + " not found in process list, most likely it is crashed");
@@ -200,8 +256,11 @@ public class AppLaunch extends InstrumentationTestCase {
     private class AppLaunchRunnable implements Runnable {
         private Intent mLaunchIntent;
         private IActivityManager.WaitResult mResult;
-        public AppLaunchRunnable(Intent intent) {
+        private boolean mForceStopBeforeLaunch;
+
+        public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) {
             mLaunchIntent = intent;
+            mForceStopBeforeLaunch = forceStopBeforeLaunch;
         }
 
         public IActivityManager.WaitResult getResult() {
@@ -211,7 +270,9 @@ public class AppLaunch extends InstrumentationTestCase {
         public void run() {
             try {
                 String packageName = mLaunchIntent.getComponent().getPackageName();
-                mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+                if (mForceStopBeforeLaunch) {
+                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+                }
                 String mimeType = mLaunchIntent.getType();
                 if (mimeType == null && mLaunchIntent.getData() != null
                         && "content".equals(mLaunchIntent.getData().getScheme())) {
diff --git a/tests/BiDiTests/res/layout/canvas.xml b/tests/BiDiTests/res/layout/canvas.xml
new file mode 100644 (file)
index 0000000..0319a83
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/canvas"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+    <LinearLayout android:orientation="vertical"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent">
+
+        <SeekBar android:id="@+id/seekbar"
+                 android:layout_height="wrap_content"
+                 android:layout_width="match_parent"
+                />
+
+        <view class="com.android.bidi.BiDiTestView"
+              android:id="@+id/testview"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="#FF0000"
+                />
+
+    </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
index b88a885..209597e 100644 (file)
@@ -101,6 +101,7 @@ public class BiDiTestActivity extends Activity {
 
         addItem(result, "Basic", BiDiTestBasic.class, R.id.basic);
 
+        addItem(result, "Canvas", BiDiTestCanvas.class, R.id.canvas);
         addItem(result, "Canvas2", BiDiTestCanvas2.class, R.id.canvas2);
 
         addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr);
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java
new file mode 100644 (file)
index 0000000..33ed731
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bidi;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+
+import static com.android.bidi.BiDiTestConstants.FONT_MAX_SIZE;
+import static com.android.bidi.BiDiTestConstants.FONT_MIN_SIZE;
+
+public class BiDiTestCanvas extends Fragment {
+
+    static final int INIT_TEXT_SIZE = (FONT_MAX_SIZE - FONT_MIN_SIZE) / 2;
+
+    private BiDiTestView testView;
+    private SeekBar textSizeSeekBar;
+    private View currentView;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        currentView = inflater.inflate(R.layout.canvas, container, false);
+        return currentView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        testView = (BiDiTestView) currentView.findViewById(R.id.testview);
+        testView.setCurrentTextSize(INIT_TEXT_SIZE);
+
+        textSizeSeekBar = (SeekBar) currentView.findViewById(R.id.seekbar);
+        textSizeSeekBar.setProgress(INIT_TEXT_SIZE);
+        textSizeSeekBar.setMax(FONT_MAX_SIZE - FONT_MIN_SIZE);
+
+        textSizeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                testView.setCurrentTextSize(FONT_MIN_SIZE + progress);
+            }
+
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+        });
+    }
+}
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
new file mode 100644 (file)
index 0000000..0b1974a
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bidi;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class BiDiTestView extends View {
+
+    private static final String TAG = "BiDiTestView";
+
+    private static final int BORDER_PADDING = 4;
+    private static final int TEXT_PADDING = 16;
+    private static final int TEXT_SIZE = 16;
+    private static final int ORIGIN = 80;
+
+    private static final float DEFAULT_ITALIC_SKEW_X = -0.25f;
+
+    private Rect rect = new Rect();
+
+    private String NORMAL_TEXT;
+    private String NORMAL_LONG_TEXT;
+    private String NORMAL_LONG_TEXT_2;
+    private String NORMAL_LONG_TEXT_3;
+    private String ITALIC_TEXT;
+    private String BOLD_TEXT;
+    private String BOLD_ITALIC_TEXT;
+    private String ARABIC_TEXT;
+    private String CHINESE_TEXT;
+    private String MIXED_TEXT_1;
+    private String HEBREW_TEXT;
+    private String RTL_TEXT;
+    private String THAI_TEXT;
+
+    private int currentTextSize;
+
+    public BiDiTestView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public BiDiTestView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public BiDiTestView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        NORMAL_TEXT = context.getString(R.string.normal_text);
+        NORMAL_LONG_TEXT = context.getString(R.string.normal_long_text);
+        NORMAL_LONG_TEXT_2 = context.getString(R.string.normal_long_text_2);
+        NORMAL_LONG_TEXT_3 = context.getString(R.string.normal_long_text_3);
+        ITALIC_TEXT = context.getString(R.string.italic_text);
+        BOLD_TEXT = context.getString(R.string.bold_text);
+        BOLD_ITALIC_TEXT = context.getString(R.string.bold_italic_text);
+        ARABIC_TEXT = context.getString(R.string.arabic_text);
+        CHINESE_TEXT = context.getString(R.string.chinese_text);
+        MIXED_TEXT_1 = context.getString(R.string.mixed_text_1);
+        HEBREW_TEXT = context.getString(R.string.hebrew_text);
+        RTL_TEXT = context.getString(R.string.rtl);
+        THAI_TEXT = context.getString(R.string.pointer_location);
+    }
+
+    public void setCurrentTextSize(int size) {
+        currentTextSize = size;
+        invalidate();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        drawInsideRect(canvas, new Paint(), Color.BLACK);
+
+        int deltaX = 0;
+
+        deltaX  = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN,
+                true, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN,
+                false, true,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN,
+                true, true,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT_2, ORIGIN, ORIGIN + 4 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT_3, ORIGIN, ORIGIN + 6 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test Arabic ligature
+        deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 8 * currentTextSize,
+                false, false,  Paint.DIRECTION_RTL, currentTextSize);
+
+        // Test Chinese
+        deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 10 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test Mixed (English and Arabic)
+        deltaX = testString(canvas, MIXED_TEXT_1, ORIGIN, ORIGIN + 12 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test Hebrew
+        deltaX = testString(canvas, RTL_TEXT, ORIGIN, ORIGIN + 14 * currentTextSize,
+                false, false,  Paint.DIRECTION_RTL, currentTextSize);
+
+        // Test Thai
+        deltaX = testString(canvas, THAI_TEXT, ORIGIN, ORIGIN + 16 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+    }
+
+    private int testString(Canvas canvas, String text, int x, int y,
+            boolean isItalic, boolean isBold, int dir, int textSize) {
+
+        TextPaint paint = new TextPaint();
+        paint.setAntiAlias(true);
+
+        // Set paint properties
+        boolean oldFakeBold = paint.isFakeBoldText();
+        paint.setFakeBoldText(isBold);
+
+        float oldTextSkewX = paint.getTextSkewX();
+        if (isItalic) {
+            paint.setTextSkewX(DEFAULT_ITALIC_SKEW_X);
+        }
+
+        paint.setTextSize(textSize);
+        paint.setColor(Color.WHITE);
+        canvas.drawText(text, x, y, paint);
+
+        int length = text.length();
+        float[] advances = new float[length];
+        float textWidthHB = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0);
+        setPaintDir(paint, dir);
+        float textWidthICU = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0,
+                1 /* use ICU */);
+
+        logAdvances(text, textWidthHB, textWidthICU, advances);
+        drawMetricsAroundText(canvas, x, y, textWidthHB, textWidthICU, textSize, Color.RED, Color.GREEN);
+
+        // Restore old paint properties
+        paint.setFakeBoldText(oldFakeBold);
+        paint.setTextSkewX(oldTextSkewX);
+
+        return (int) Math.ceil(textWidthHB) + TEXT_PADDING;
+    }
+
+    private void setPaintDir(Paint paint, int dir) {
+        Log.v(TAG, "Setting Paint dir=" + dir);
+        paint.setBidiFlags(dir);
+    }
+
+    private void drawInsideRect(Canvas canvas, Paint paint, int color) {
+        paint.setColor(color);
+        int width = getWidth();
+        int height = getHeight();
+        rect.set(BORDER_PADDING, BORDER_PADDING, width - BORDER_PADDING, height - BORDER_PADDING);
+        canvas.drawRect(rect, paint);
+    }
+
+    private void drawMetricsAroundText(Canvas canvas, int x, int y, float textWidthHB,
+            float textWidthICU, int textSize, int color, int colorICU) {
+        Paint paint = new Paint();
+        paint.setColor(color);
+        canvas.drawLine(x, y - textSize, x, y + 8, paint);
+        canvas.drawLine(x, y + 8, x + textWidthHB, y + 8, paint);
+        canvas.drawLine(x + textWidthHB, y - textSize, x + textWidthHB, y + 8, paint);
+        paint.setColor(colorICU);
+        canvas.drawLine(x + textWidthICU, y - textSize, x + textWidthICU, y + 8, paint);
+    }
+
+    private void logAdvances(String text, float textWidth, float textWidthICU, float[] advances) {
+        Log.v(TAG, "Advances for text: " + text + " total= " + textWidth + " - totalICU= " + textWidthICU);
+//        int length = advances.length;
+//        for(int n=0; n<length; n++){
+//            Log.v(TAG, "adv[" + n + "]=" + advances[n]);
+//        }
+    }
+}
index bef5824..e0684fb 100644 (file)
@@ -71,6 +71,10 @@ interface IWifiManager
 
     DhcpInfo getDhcpInfo();
 
+    boolean isScanningAlwaysAvailable();
+
+    void setScanningAlwaysAvailable(boolean enable);
+
     boolean acquireWifiLock(IBinder lock, int lockType, String tag, in WorkSource ws);
 
     void updateWifiLockWorkSource(IBinder lock, in WorkSource ws);
index eb2f74c..2385c24 100644 (file)
@@ -395,6 +395,23 @@ class WifiConfigStore {
         return ret;
     }
 
+    void disableAllNetworks() {
+        boolean networkDisabled = false;
+        for(WifiConfiguration config : mConfiguredNetworks.values()) {
+            if(config != null && config.status != Status.DISABLED) {
+                if(mWifiNative.disableNetwork(config.networkId)) {
+                    networkDisabled = true;
+                    config.status = Status.DISABLED;
+                } else {
+                    loge("Disable network failed on " + config.networkId);
+                }
+            }
+        }
+
+        if (networkDisabled) {
+            sendConfiguredNetworksChangedBroadcast();
+        }
+    }
     /**
      * Disable a network. Note that there is no saveConfig operation.
      * @param netId network to be disabled
index ed5d22c..8cdfe03 100644 (file)
@@ -134,7 +134,12 @@ public class WifiStateMachine extends StateMachine {
     private boolean mEnableBackgroundScan = false;
     private int mRssiPollToken = 0;
     private int mReconnectCount = 0;
-    private boolean mIsScanMode = false;
+    /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
+    * In CONNECT_MODE, the STA can scan and connect to an access point
+    * In SCAN_ONLY_MODE, the STA can only scan for access points
+    * In SCAN_ONLY_WIFI_OFF_MODE, the STA can only scan for access points with wifi toggle being off
+    */
+    private int mOperationalMode = CONNECT_MODE;
     private boolean mScanResultIsPending = false;
     /* Tracks if state machine has received any screen state change broadcast yet.
      * We can miss one of these at boot.
@@ -285,8 +290,8 @@ public class WifiStateMachine extends StateMachine {
     /* Supplicant commands after driver start*/
     /* Initiate a scan */
     static final int CMD_START_SCAN                       = BASE + 71;
-    /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */
-    static final int CMD_SET_SCAN_MODE                    = BASE + 72;
+    /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */
+    static final int CMD_SET_OPERATIONAL_MODE             = BASE + 72;
     /* Disconnect from a network */
     static final int CMD_DISCONNECT                       = BASE + 73;
     /* Reconnect to a network */
@@ -342,16 +347,13 @@ public class WifiStateMachine extends StateMachine {
     public static final int CMD_DISABLE_P2P_REQ           = BASE + 132;
     public static final int CMD_DISABLE_P2P_RSP           = BASE + 133;
 
-    private static final int CONNECT_MODE   = 1;
-    private static final int SCAN_ONLY_MODE = 2;
+    public static final int CONNECT_MODE                   = 1;
+    public static final int SCAN_ONLY_MODE                 = 2;
+    public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE   = 3;
 
     private static final int SUCCESS = 1;
     private static final int FAILURE = -1;
 
-    /* Phone in emergency call back mode */
-    private static final int IN_ECM_STATE = 1;
-    private static final int NOT_IN_ECM_STATE = 0;
-
     /**
      * The maximum number of times we will retry a connection to an access point
      * for which we have failed in acquiring an IP address from DHCP. A value of
@@ -496,9 +498,6 @@ public class WifiStateMachine extends StateMachine {
      */
     private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
 
-    private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid());
-    private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid());
-
     private static final int SCAN_REQUEST = 0;
     private static final String ACTION_START_SCAN =
         "com.android.server.WifiManager.action.START_SCAN";
@@ -623,7 +622,7 @@ public class WifiStateMachine extends StateMachine {
                     @Override
                     public void onReceive(Context context, Intent intent) {
                        int counter = intent.getIntExtra(DELAYED_STOP_COUNTER, 0);
-                       sendMessage(obtainMessage(CMD_DELAYED_STOP_DRIVER, counter, 0));
+                       sendMessage(CMD_DELAYED_STOP_DRIVER, counter, 0);
                     }
                 },
                 new IntentFilter(ACTION_DELAYED_DRIVER_STOP));
@@ -714,8 +713,7 @@ public class WifiStateMachine extends StateMachine {
     /**
      * TODO: doc
      */
-    public void setWifiEnabled(boolean enable) {
-        mLastEnableUid.set(Binder.getCallingUid());
+    public void setSupplicantRunning(boolean enable) {
         if (enable) {
             sendMessage(CMD_START_SUPPLICANT);
         } else {
@@ -726,10 +724,9 @@ public class WifiStateMachine extends StateMachine {
     /**
      * TODO: doc
      */
-    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) {
-        mLastApEnableUid.set(Binder.getCallingUid());
+    public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
         if (enable) {
-            sendMessage(obtainMessage(CMD_START_AP, wifiConfig));
+            sendMessage(CMD_START_AP, wifiConfig);
         } else {
             sendMessage(CMD_STOP_AP);
         }
@@ -818,27 +815,23 @@ public class WifiStateMachine extends StateMachine {
     /**
      * TODO: doc
      */
-    public void setDriverStart(boolean enable, boolean ecm) {
+    public void setDriverStart(boolean enable) {
         if (enable) {
             sendMessage(CMD_START_DRIVER);
         } else {
-            sendMessage(obtainMessage(CMD_STOP_DRIVER, ecm ? IN_ECM_STATE : NOT_IN_ECM_STATE, 0));
+            sendMessage(CMD_STOP_DRIVER);
         }
     }
 
     public void captivePortalCheckComplete() {
-        sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE));
+        sendMessage(CMD_CAPTIVE_CHECK_COMPLETE);
     }
 
     /**
      * TODO: doc
      */
-    public void setScanOnlyMode(boolean enable) {
-        if (enable) {
-            sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0));
-        } else {
-            sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0));
-        }
+    public void setOperationalMode(int mode) {
+        sendMessage(CMD_SET_OPERATIONAL_MODE, mode, 0);
     }
 
     /**
@@ -941,7 +934,7 @@ public class WifiStateMachine extends StateMachine {
      * @param bssid BSSID of the network
      */
     public void addToBlacklist(String bssid) {
-        sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid));
+        sendMessage(CMD_BLACKLIST_NETWORK, bssid);
     }
 
     /**
@@ -949,15 +942,15 @@ public class WifiStateMachine extends StateMachine {
      *
      */
     public void clearBlacklist() {
-        sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST));
+        sendMessage(CMD_CLEAR_BLACKLIST);
     }
 
     public void enableRssiPolling(boolean enabled) {
-       sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0));
+       sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
     }
 
     public void enableBackgroundScanCommand(boolean enabled) {
-       sendMessage(obtainMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0));
+       sendMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0);
     }
 
     public void enableAllNetworks() {
@@ -969,7 +962,7 @@ public class WifiStateMachine extends StateMachine {
      */
     public void startFilteringMulticastV4Packets() {
         mFilteringMulticastV4Packets.set(true);
-        sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0));
+        sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0);
     }
 
     /**
@@ -977,21 +970,21 @@ public class WifiStateMachine extends StateMachine {
      */
     public void stopFilteringMulticastV4Packets() {
         mFilteringMulticastV4Packets.set(false);
-        sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0));
+        sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0);
     }
 
     /**
      * Start filtering Multicast v4 packets
      */
     public void startFilteringMulticastV6Packets() {
-        sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0));
+        sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0);
     }
 
     /**
      * Stop filtering Multicast v4 packets
      */
     public void stopFilteringMulticastV6Packets() {
-        sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0));
+        sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0);
     }
 
     /**
@@ -1001,7 +994,7 @@ public class WifiStateMachine extends StateMachine {
      * @param enable true if enable, false otherwise
      */
     public void setHighPerfModeEnabled(boolean enable) {
-        sendMessage(obtainMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0));
+        sendMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0);
     }
 
     /**
@@ -1015,7 +1008,7 @@ public class WifiStateMachine extends StateMachine {
                     Settings.Global.WIFI_COUNTRY_CODE,
                     countryCode);
         }
-        sendMessage(obtainMessage(CMD_SET_COUNTRY_CODE, countryCode));
+        sendMessage(CMD_SET_COUNTRY_CODE, countryCode);
     }
 
     /**
@@ -1029,7 +1022,7 @@ public class WifiStateMachine extends StateMachine {
                     Settings.Global.WIFI_FREQUENCY_BAND,
                     band);
         }
-        sendMessage(obtainMessage(CMD_SET_FREQUENCY_BAND, band, 0));
+        sendMessage(CMD_SET_FREQUENCY_BAND, band, 0);
     }
 
     /**
@@ -1050,7 +1043,7 @@ public class WifiStateMachine extends StateMachine {
      * Send a message indicating bluetooth adapter connection state changed
      */
     public void sendBluetoothAdapterStateChange(int state) {
-        sendMessage(obtainMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0));
+        sendMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0);
     }
 
     /**
@@ -1114,7 +1107,7 @@ public class WifiStateMachine extends StateMachine {
         pw.println("mLastBssid " + mLastBssid);
         pw.println("mLastNetworkId " + mLastNetworkId);
         pw.println("mReconnectCount " + mReconnectCount);
-        pw.println("mIsScanMode " + mIsScanMode);
+        pw.println("mOperationalMode " + mOperationalMode);
         pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
         pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
         pw.println("Supplicant status " + mWifiNative.status());
@@ -1136,11 +1129,11 @@ public class WifiStateMachine extends StateMachine {
         if (screenOn) enableAllNetworks();
         if (mUserWantsSuspendOpt.get()) {
             if (screenOn) {
-                sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0));
+                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0);
             } else {
                 //Allow 2s for suspend optimizations to be set
                 mSuspendWakeLock.acquire(2000);
-                sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0));
+                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0);
             }
         }
         mScreenBroadcastReceived.set(true);
@@ -1914,7 +1907,7 @@ public class WifiStateMachine extends StateMachine {
                 case WifiMonitor.WPS_OVERLAP_EVENT:
                 case CMD_BLACKLIST_NETWORK:
                 case CMD_CLEAR_BLACKLIST:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_RSSI_POLL:
@@ -1929,6 +1922,7 @@ public class WifiStateMachine extends StateMachine {
                 case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
                 case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
                 case CMD_NO_NETWORKS_PERIODIC_SCAN:
+                case CMD_DISABLE_P2P_RSP:
                     break;
                 case DhcpStateMachine.CMD_ON_QUIT:
                     mDhcpStateMachine = null;
@@ -1942,8 +1936,8 @@ public class WifiStateMachine extends StateMachine {
                     }
                     break;
                 case WifiMonitor.DRIVER_HUNG_EVENT:
-                    setWifiEnabled(false);
-                    setWifiEnabled(true);
+                    setSupplicantRunning(false);
+                    setSupplicantRunning(true);
                     break;
                 case WifiManager.CONNECT_NETWORK:
                     replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
@@ -2143,7 +2137,7 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -2160,8 +2154,6 @@ public class WifiStateMachine extends StateMachine {
     class SupplicantStartedState extends State {
         @Override
         public void enter() {
-            /* Initialize for connect mode operation at start */
-            mIsScanMode = false;
             /* Wifi is available as long as we have a connection to supplicant */
             mNetworkInfo.setIsAvailable(true);
 
@@ -2176,7 +2168,6 @@ public class WifiStateMachine extends StateMachine {
         }
         @Override
         public boolean processMessage(Message message) {
-            WifiConfiguration config;
             switch(message.what) {
                 case CMD_STOP_SUPPLICANT:   /* Supplicant stopped by user */
                     if (mP2pSupported) {
@@ -2206,87 +2197,13 @@ public class WifiStateMachine extends StateMachine {
                     boolean ok = mWifiNative.ping();
                     replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                     break;
-                case CMD_ADD_OR_UPDATE_NETWORK:
-                    config = (WifiConfiguration) message.obj;
-                    replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK,
-                            mWifiConfigStore.addOrUpdateNetwork(config));
-                    break;
-                case CMD_REMOVE_NETWORK:
-                    ok = mWifiConfigStore.removeNetwork(message.arg1);
-                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
-                    break;
-                case CMD_ENABLE_NETWORK:
-                    ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1);
-                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
-                    break;
-                case CMD_ENABLE_ALL_NETWORKS:
-                    long time =  android.os.SystemClock.elapsedRealtime();
-                    if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) {
-                        mWifiConfigStore.enableAllNetworks();
-                        mLastEnableAllNetworksTime = time;
-                    }
-                    break;
-                case WifiManager.DISABLE_NETWORK:
-                    if (mWifiConfigStore.disableNetwork(message.arg1,
-                            WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) {
-                        replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                    }
-                    break;
-                case CMD_BLACKLIST_NETWORK:
-                    mWifiNative.addToBlacklist((String)message.obj);
-                    break;
-                case CMD_CLEAR_BLACKLIST:
-                    mWifiNative.clearBlacklist();
-                    break;
-                case CMD_SAVE_CONFIG:
-                    ok = mWifiConfigStore.saveConfig();
-                    replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
-
-                    // Inform the backup manager about a data change
-                    IBackupManager ibm = IBackupManager.Stub.asInterface(
-                            ServiceManager.getService(Context.BACKUP_SERVICE));
-                    if (ibm != null) {
-                        try {
-                            ibm.dataChanged("com.android.providers.settings");
-                        } catch (Exception e) {
-                            // Try again later
-                        }
-                    }
-                    break;
-                case CMD_GET_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what,
-                            mWifiConfigStore.getConfiguredNetworks());
-                    break;
                     /* Cannot start soft AP while in client mode */
                 case CMD_START_AP:
                     loge("Failed to start soft AP with a running supplicant");
                     setWifiApState(WIFI_AP_STATE_FAILED);
                     break;
-                case CMD_SET_SCAN_MODE:
-                    mIsScanMode = (message.arg1 == SCAN_ONLY_MODE);
-                    break;
-                case WifiManager.SAVE_NETWORK:
-                    config = (WifiConfiguration) message.obj;
-                    NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
-                    if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
-                    } else {
-                        loge("Failed to save network");
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                    }
-                    break;
-                case WifiManager.FORGET_NETWORK:
-                    if (mWifiConfigStore.forgetNetwork(message.arg1)) {
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
-                    } else {
-                        loge("Failed to forget network");
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                    }
+                case CMD_SET_OPERATIONAL_MODE:
+                    mOperationalMode = message.arg1;
                     break;
                 default:
                     return NOT_HANDLED;
@@ -2344,7 +2261,7 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -2452,7 +2369,7 @@ public class WifiStateMachine extends StateMachine {
                 mWifiNative.stopFilteringMulticastV4Packets();
             }
 
-            if (mIsScanMode) {
+            if (mOperationalMode != CONNECT_MODE) {
                 mWifiNative.disconnect();
                 transitionTo(mScanModeState);
             } else {
@@ -2510,8 +2427,8 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_STOP_DRIVER:
                     int mode = message.arg1;
 
-                    /* Already doing a delayed stop && not in ecm state */
-                    if (mInDelayedStop && mode != IN_ECM_STATE) {
+                    /* Already doing a delayed stop */
+                    if (mInDelayedStop) {
                         if (DBG) log("Already in delayed stop");
                         break;
                     }
@@ -2519,20 +2436,15 @@ public class WifiStateMachine extends StateMachine {
                     mDelayedStopCounter++;
                     if (DBG) log("Delayed stop message " + mDelayedStopCounter);
 
-                    if (mode == IN_ECM_STATE) {
-                        /* send a shut down immediately */
-                        sendMessage(obtainMessage(CMD_DELAYED_STOP_DRIVER, mDelayedStopCounter, 0));
-                    } else {
-                        /* send regular delayed shut down */
-                        Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null);
-                        driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter);
-                        mDriverStopIntent = PendingIntent.getBroadcast(mContext,
-                                DRIVER_STOP_REQUEST, driverStopIntent,
-                                PendingIntent.FLAG_UPDATE_CURRENT);
+                    /* send regular delayed shut down */
+                    Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null);
+                    driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter);
+                    mDriverStopIntent = PendingIntent.getBroadcast(mContext,
+                            DRIVER_STOP_REQUEST, driverStopIntent,
+                            PendingIntent.FLAG_UPDATE_CURRENT);
 
-                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
-                                + mDriverStopDelayMs, mDriverStopIntent);
-                    }
+                    mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+                            + mDriverStopDelayMs, mDriverStopIntent);
                     break;
                 case CMD_START_DRIVER:
                     if (mInDelayedStop) {
@@ -2638,7 +2550,7 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -2713,30 +2625,42 @@ public class WifiStateMachine extends StateMachine {
     }
 
     class ScanModeState extends State {
+        private int mLastOperationMode;
+        @Override
+        public void enter() {
+            mWifiConfigStore.disableAllNetworks();
+            mLastOperationMode = mOperationalMode;
+            if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+                mWifiP2pChannel.sendMessage(CMD_DISABLE_P2P_REQ);
+                setWifiState(WIFI_STATE_DISABLED);
+            }
+        }
+        @Override
+        public void exit() {
+            if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+                setWifiState(WIFI_STATE_ENABLED);
+                mWifiP2pChannel.sendMessage(CMD_ENABLE_P2P);
+            }
+            mWifiConfigStore.enableAllNetworks();
+            mWifiNative.reconnect();
+        }
         @Override
         public boolean processMessage(Message message) {
             switch(message.what) {
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
-                        /* Ignore */
-                        return HANDLED;
-                    } else {
-                        mWifiNative.reconnect();
-                        mIsScanMode = false;
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 == CONNECT_MODE) {
+                        mOperationalMode = CONNECT_MODE;
                         transitionTo(mDisconnectedState);
+                    } else {
+                        // Nothing to do
+                        return HANDLED;
                     }
                     break;
+                // Handle scan. All the connection related commands are
+                // handled only in ConnectModeState
                 case CMD_START_SCAN:
                     startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP);
                     break;
-                    /* Ignore */
-                case CMD_DISCONNECT:
-                case CMD_RECONNECT:
-                case CMD_REASSOCIATE:
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -2747,7 +2671,8 @@ public class WifiStateMachine extends StateMachine {
     class ConnectModeState extends State {
         @Override
         public boolean processMessage(Message message) {
-            StateChangeResult stateChangeResult;
+            WifiConfiguration config;
+            boolean ok;
             switch(message.what) {
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                     mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
@@ -2787,6 +2712,60 @@ public class WifiStateMachine extends StateMachine {
                         mTemporarilyDisconnectWifi = false;
                     }
                     break;
+                case CMD_ADD_OR_UPDATE_NETWORK:
+                    config = (WifiConfiguration) message.obj;
+                    replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK,
+                            mWifiConfigStore.addOrUpdateNetwork(config));
+                    break;
+                case CMD_REMOVE_NETWORK:
+                    ok = mWifiConfigStore.removeNetwork(message.arg1);
+                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+                    break;
+                case CMD_ENABLE_NETWORK:
+                    ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1);
+                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+                    break;
+                case CMD_ENABLE_ALL_NETWORKS:
+                    long time =  android.os.SystemClock.elapsedRealtime();
+                    if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) {
+                        mWifiConfigStore.enableAllNetworks();
+                        mLastEnableAllNetworksTime = time;
+                    }
+                    break;
+                case WifiManager.DISABLE_NETWORK:
+                    if (mWifiConfigStore.disableNetwork(message.arg1,
+                            WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) {
+                        replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
+                    } else {
+                        replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                    }
+                    break;
+                case CMD_BLACKLIST_NETWORK:
+                    mWifiNative.addToBlacklist((String)message.obj);
+                    break;
+                case CMD_CLEAR_BLACKLIST:
+                    mWifiNative.clearBlacklist();
+                    break;
+                case CMD_SAVE_CONFIG:
+                    ok = mWifiConfigStore.saveConfig();
+                    replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
+
+                    // Inform the backup manager about a data change
+                    IBackupManager ibm = IBackupManager.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKUP_SERVICE));
+                    if (ibm != null) {
+                        try {
+                            ibm.dataChanged("com.android.providers.settings");
+                        } catch (Exception e) {
+                            // Try again later
+                        }
+                    }
+                    break;
+                case CMD_GET_CONFIGURED_NETWORKS:
+                    replyToMessage(message, message.what,
+                            mWifiConfigStore.getConfiguredNetworks());
+                    break;
                     /* Do a redundant disconnect without transition */
                 case CMD_DISCONNECT:
                     mWifiNative.disconnect();
@@ -2804,7 +2783,7 @@ public class WifiStateMachine extends StateMachine {
                      * For an existing network, a network id is passed
                      */
                     int netId = message.arg1;
-                    WifiConfiguration config = (WifiConfiguration) message.obj;
+                    config = (WifiConfiguration) message.obj;
 
                     /* Save the network config */
                     if (config != null) {
@@ -2826,26 +2805,46 @@ public class WifiStateMachine extends StateMachine {
                         break;
                     }
                     break;
+                case WifiManager.SAVE_NETWORK:
+                    config = (WifiConfiguration) message.obj;
+                    NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+                    if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+                        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+                    } else {
+                        loge("Failed to save network");
+                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                    }
+                    break;
+                case WifiManager.FORGET_NETWORK:
+                    if (mWifiConfigStore.forgetNetwork(message.arg1)) {
+                        replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
+                    } else {
+                        loge("Failed to forget network");
+                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                    }
+                    break;
                 case WifiManager.START_WPS:
                     WpsInfo wpsInfo = (WpsInfo) message.obj;
-                    WpsResult result;
+                    WpsResult wpsResult;
                     switch (wpsInfo.setup) {
                         case WpsInfo.PBC:
-                            result = mWifiConfigStore.startWpsPbc(wpsInfo);
+                            wpsResult = mWifiConfigStore.startWpsPbc(wpsInfo);
                             break;
                         case WpsInfo.KEYPAD:
-                            result = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo);
+                            wpsResult = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo);
                             break;
                         case WpsInfo.DISPLAY:
-                            result = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo);
+                            wpsResult = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo);
                             break;
                         default:
-                            result = new WpsResult(Status.FAILURE);
+                            wpsResult = new WpsResult(Status.FAILURE);
                             loge("Invalid setup for WPS");
                             break;
                     }
-                    if (result.status == Status.SUCCESS) {
-                        replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, result);
+                    if (wpsResult.status == Status.SUCCESS) {
+                        replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
                         transitionTo(mWpsRunningState);
                     } else {
                         loge("Failed to start WPS with config " + wpsInfo.toString());
@@ -2881,7 +2880,7 @@ public class WifiStateMachine extends StateMachine {
         public void enter() {
             mRssiPollToken++;
             if (mEnableRssiPolling) {
-                sendMessage(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0));
+                sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
             }
         }
 
@@ -2915,8 +2914,8 @@ public class WifiStateMachine extends StateMachine {
                         transitionTo(mDisconnectingState);
                     }
                     break;
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 != CONNECT_MODE) {
                         sendMessage(CMD_DISCONNECT);
                         deferMessage(message);
                     }
@@ -3152,8 +3151,8 @@ public class WifiStateMachine extends StateMachine {
         @Override
         public boolean processMessage(Message message) {
             switch (message.what) {
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 != CONNECT_MODE) {
                         deferMessage(message);
                     }
                     break;
@@ -3259,11 +3258,9 @@ public class WifiStateMachine extends StateMachine {
                                 ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
                     ret = NOT_HANDLED;
                     break;
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
-                        //Supplicant disconnect to prevent further connects
-                        mWifiNative.disconnect();
-                        mIsScanMode = true;
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 != CONNECT_MODE) {
+                        mOperationalMode = message.arg1;
                         transitionTo(mScanModeState);
                     }
                     break;
@@ -3396,7 +3393,7 @@ public class WifiStateMachine extends StateMachine {
                  * or put the state machine out of connect mode
                  */
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case WifiManager.CONNECT_NETWORK:
                 case CMD_ENABLE_NETWORK:
                 case CMD_RECONNECT:
@@ -3459,7 +3456,7 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -3547,7 +3544,8 @@ public class WifiStateMachine extends StateMachine {
                     if (message.arg1 == mTetherToken) {
                         loge("Failed to get tether update, shutdown soft access point");
                         transitionTo(mSoftApStartedState);
-                        sendMessage(CMD_STOP_AP);
+                        // Needs to be first thing handled
+                        sendMessageAtFrontOfQueue(CMD_STOP_AP);
                     }
                     break;
                 case CMD_START_SUPPLICANT:
@@ -3556,7 +3554,7 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -3578,7 +3576,7 @@ public class WifiStateMachine extends StateMachine {
                     TetherStateChange stateChange = (TetherStateChange) message.obj;
                     if (!isWifiTethered(stateChange.active)) {
                         loge("Tethering reports wifi as untethered!, shut down soft Ap");
-                        setWifiApEnabled(null, false);
+                        setHostApRunning(null, false);
                     }
                     return HANDLED;
                 case CMD_STOP_AP:
@@ -3612,13 +3610,15 @@ public class WifiStateMachine extends StateMachine {
                     if (isWifiTethered(stateChange.active)) break;
 
                     transitionTo(mSoftApStartedState);
-                    sendMessage(CMD_STOP_AP);
+                    // Needs to be first thing handled
+                    sendMessageAtFrontOfQueue(CMD_STOP_AP);
                     break;
                 case CMD_TETHER_NOTIFICATION_TIMED_OUT:
                     if (message.arg1 == mTetherToken) {
                         loge("Failed to get tether update, force stop access point");
                         transitionTo(mSoftApStartedState);
-                        sendMessage(CMD_STOP_AP);
+                        // Needs to be first thing handled
+                        sendMessageAtFrontOfQueue(CMD_STOP_AP);
                     }
                     break;
                 case CMD_START_SUPPLICANT:
@@ -3627,7 +3627,7 @@ public class WifiStateMachine extends StateMachine {
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
index 55ea34f..81d2e11 100644 (file)
@@ -252,4 +252,14 @@ public class WifiStateTracker implements NetworkStateTracker {
     public void setDependencyMet(boolean met) {
         // not supported on this network
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
index 53e6b51..eb47a4a 100644 (file)
@@ -587,8 +587,9 @@ public class WifiWatchdogStateMachine extends StateMachine {
                     break;
 
                 case EVENT_WIFI_RADIO_STATE_CHANGE:
-                    if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING)
+                    if (msg.arg1 == WifiManager.WIFI_STATE_DISABLING) {
                         transitionTo(mNotConnectedState);
+                    }
                     break;
 
                 default: