Improvements to low power mode.

Add new public API for monitoring low power mode.

BatteryService now puts device in to low power mode when
battery level is low.

Window manager now watches low power mode to turn off
animations.

Modifying the animator scale now gets propagated to all
processes.

Change-Id: I8fa566994764ddd4e1977631e28381ab9409f8ee
diff --git a/Android.mk b/Android.mk
index a018716..a1d91c3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -231,6 +231,7 @@
 	core/java/android/view/IWindowId.aidl \
 	core/java/android/view/IWindowManager.aidl \
 	core/java/android/view/IWindowSession.aidl \
+	core/java/android/view/IWindowSessionCallback.aidl \
 	core/java/android/speech/IRecognitionListener.aidl \
 	core/java/android/speech/IRecognitionService.aidl \
 	core/java/android/speech/tts/ITextToSpeechCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index dcab4d5..0d11e44 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21325,12 +21325,14 @@
   public final class PowerManager {
     method public void goToSleep(long);
     method public boolean isInteractive();
+    method public boolean isPowerSaveMode();
     method public deprecated boolean isScreenOn();
     method public android.os.PowerManager.WakeLock newWakeLock(int, java.lang.String);
     method public void reboot(java.lang.String);
     method public void userActivity(long, boolean);
     method public void wakeUp(long);
     field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
+    field public static final java.lang.String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
     field public static final deprecated int FULL_WAKE_LOCK = 26; // 0x1a
     field public static final int ON_AFTER_RELEASE = 536870912; // 0x20000000
     field public static final int PARTIAL_WAKE_LOCK = 1; // 0x1
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 61194e9..658180b 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -41,6 +41,7 @@
     void goToSleep(long time, int reason, int flags);
     void nap(long time);
     boolean isInteractive();
+    boolean isPowerSaveMode();
 
     void reboot(boolean confirm, String reason, boolean wait);
     void shutdown(boolean confirm, boolean wait);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d5177e8..92e80a5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.SdkConstant;
 import android.content.Context;
 import android.util.Log;
 
@@ -685,6 +686,30 @@
     }
 
     /**
+     * Returns true if the device is currently in power save mode.  When in this mode,
+     * applications should reduce their functionality in order to conserve battery as
+     * much as possible.  You can monitor for changes to this state with
+     * {@link #ACTION_POWER_SAVE_MODE_CHANGED}.
+     *
+     * @return Returns true if currently in low power mode, else false.
+     */
+    public boolean isPowerSaveMode() {
+        try {
+            return mService.isPowerSaveMode();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes.
+     * This broadcast is only sent to registered receivers.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_SAVE_MODE_CHANGED
+            = "android.os.action.POWER_SAVE_MODE_CHANGED";
+
+    /**
      * A wake lock is a mechanism to indicate that your application needs
      * to have the device stay on.
      * <p>
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index a52ccdf..ae59bbc 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -30,6 +30,7 @@
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
 import android.view.IWindowSession;
+import android.view.IWindowSessionCallback;
 import android.view.KeyEvent;
 import android.view.InputEvent;
 import android.view.MagnificationSpec;
@@ -56,7 +57,7 @@
     boolean stopViewServer();            // Transaction #2
     boolean isViewServerRunning();       // Transaction #3
 
-    IWindowSession openSession(in IInputMethodClient client,
+    IWindowSession openSession(in IWindowSessionCallback callback, in IInputMethodClient client,
             in IInputContext inputContext);
     boolean inputMethodClientHasFocus(IInputMethodClient client);
 
@@ -130,6 +131,8 @@
     void setAnimationScale(int which, float scale);
     void setAnimationScales(in float[] scales);
 
+    float getCurrentAnimatorScale();
+
     // For testing
     void setInTouchMode(boolean showFocus);
 
diff --git a/core/java/android/view/IWindowSessionCallback.aidl b/core/java/android/view/IWindowSessionCallback.aidl
new file mode 100644
index 0000000..88931ce
--- /dev/null
+++ b/core/java/android/view/IWindowSessionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2014, 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;
+
+/**
+ * Callback to active sessions of the window manager
+ *
+ * {@hide}
+ */
+oneway interface IWindowSessionCallback
+{
+    void onAnimatorScaleChanged(float scale);
+}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index b4779f4..0ebf2e1 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -147,9 +147,14 @@
                     InputMethodManager imm = InputMethodManager.getInstance();
                     IWindowManager windowManager = getWindowManagerService();
                     sWindowSession = windowManager.openSession(
+                            new IWindowSessionCallback.Stub() {
+                                @Override
+                                public void onAnimatorScaleChanged(float scale) {
+                                    ValueAnimator.setDurationScale(scale);
+                                }
+                            },
                             imm.getClient(), imm.getInputContext());
-                    float animatorScale = windowManager.getAnimationScale(2);
-                    ValueAnimator.setDurationScale(animatorScale);
+                    ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to open window session", e);
                 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d844818..f57c3a0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -74,6 +74,8 @@
     <protected-broadcast android:name="android.intent.action.USER_FOREGROUND" />
     <protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
 
+    <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
+
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.ENTER_DESK_MODE" />
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index cb5946a..fe5c2ef 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -128,6 +128,8 @@
     private int mPlugType;
     private int mLastPlugType = -1; // Extra state so we can detect first run
 
+    private boolean mBatteryLevelLow;
+
     private long mDischargeStartTime;
     private int mDischargeStartLevel;
 
@@ -222,14 +224,30 @@
     }
 
     /**
-     * Returns true if battery level is below the first warning threshold.
+     * Returns whether we currently consider the battery level to be low.
      */
-    public boolean isBatteryLow() {
+    public boolean getBatteryLevelLow() {
         synchronized (mLock) {
-            return mBatteryProps.batteryPresent && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel;
+            return mBatteryLevelLow;
         }
     }
 
+    public boolean isBatteryLowLocked() {
+        final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
+        final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
+
+        /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
+         * - is just un-plugged (previously was plugged) and battery level is
+         *   less than or equal to WARNING, or
+         * - is not plugged and battery level falls to WARNING boundary
+         *   (becomes <= mLowBatteryWarningLevel).
+         */
+        return !plugged
+                && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel
+                && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
+    }
+
     /**
      * Returns a non-zero value if an  unsupported charger is attached.
      */
@@ -382,19 +400,7 @@
                 logOutlier = true;
             }
 
-            final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
-            final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
-
-            /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
-             * - is just un-plugged (previously was plugged) and battery level is
-             *   less than or equal to WARNING, or
-             * - is not plugged and battery level falls to WARNING boundary
-             *   (becomes <= mLowBatteryWarningLevel).
-             */
-            final boolean sendBatteryLow = !plugged
-                    && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-                    && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel
-                    && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
+            mBatteryLevelLow = isBatteryLowLocked();
 
             sendIntentLocked();
 
@@ -422,7 +428,7 @@
                 });
             }
 
-            if (sendBatteryLow) {
+            if (mBatteryLevelLow) {
                 mSentLowBatteryBroadcast = true;
                 mHandler.post(new Runnable() {
                     @Override
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 06dd3ed..fdaf55e 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.IVibratorService;
 import android.os.PowerManager;
+import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.IBinder;
@@ -64,6 +65,7 @@
     private final PowerManager.WakeLock mWakeLock;
     private final IAppOpsService mAppOpsService;
     private final IBatteryStats mBatteryStatsService;
+    private PowerManagerInternal mPowerManagerInternal;
     private InputManager mIm;
 
     volatile VibrateThread mThread;
@@ -169,14 +171,19 @@
         mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
         mSettingObserver = new SettingsObserver(mH);
 
+        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+        mPowerManagerInternal.registerLowPowerModeObserver(
+                new PowerManagerInternal.LowPowerModeListener() {
+            @Override
+            public void onLowPowerModeChanged(boolean enabled) {
+                updateInputDeviceVibrators();
+            }
+        });
+
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES),
                 true, mSettingObserver, UserHandle.USER_ALL);
 
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), false,
-                mSettingObserver, UserHandle.USER_ALL);
-
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -448,8 +455,7 @@
                 } catch (SettingNotFoundException snfe) {
                 }
 
-                mLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(),
-                         Settings.Global.LOW_POWER_MODE, 0) != 0;
+                mLowPowerMode = mPowerManagerInternal.getLowPowerModeEnabled();
 
                 if (mVibrateInputDevicesSetting) {
                     if (!mInputDeviceListenerRegistered) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index aca17bf..d8671d9 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -406,6 +406,12 @@
     // If true, the device is in low power mode.
     private boolean mLowPowerModeEnabled;
 
+    // Current state of the low power mode setting.
+    private boolean mLowPowerModeSetting;
+
+    // True if the battery level is currently considered low.
+    private boolean mBatteryLevelLow;
+
     private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners
             = new ArrayList<PowerManagerInternal.LowPowerModeListener>();
 
@@ -502,6 +508,7 @@
             // Register for broadcasts from other components of the system.
             IntentFilter filter = new IntentFilter();
             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+            filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
             mContext.registerReceiver(new BatteryReceiver(), filter, null, mHandler);
 
             filter = new IntentFilter();
@@ -638,7 +645,18 @@
 
         final boolean lowPowerModeEnabled = Settings.Global.getInt(resolver,
                 Settings.Global.LOW_POWER_MODE, 0) != 0;
-        if (lowPowerModeEnabled != mLowPowerModeEnabled) {
+        if (lowPowerModeEnabled != mLowPowerModeSetting) {
+            mLowPowerModeSetting = lowPowerModeEnabled;
+            updateLowPowerModeLocked();
+        }
+
+        mDirty |= DIRTY_SETTINGS;
+    }
+
+    void updateLowPowerModeLocked() {
+        final boolean lowPowerModeEnabled = mLowPowerModeSetting || mBatteryLevelLow;
+        if (mLowPowerModeEnabled != lowPowerModeEnabled) {
+            mLowPowerModeEnabled = lowPowerModeEnabled;
             powerHintInternal(POWER_HINT_LOW_POWER_MODE, lowPowerModeEnabled ? 1 : 0);
             mLowPowerModeEnabled = lowPowerModeEnabled;
             BackgroundThread.getHandler().post(new Runnable() {
@@ -652,11 +670,12 @@
                     for (int i=0; i<listeners.size(); i++) {
                         listeners.get(i).onLowPowerModeChanged(lowPowerModeEnabled);
                     }
+                    Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    mContext.sendBroadcast(intent);
                 }
             });
         }
-
-        mDirty |= DIRTY_SETTINGS;
     }
 
     private void handleSettingsChangedLocked() {
@@ -1137,9 +1156,11 @@
         if ((dirty & DIRTY_BATTERY_STATE) != 0) {
             final boolean wasPowered = mIsPowered;
             final int oldPlugType = mPlugType;
+            final boolean oldLevelLow = mBatteryLevelLow;
             mIsPowered = mBatteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
             mPlugType = mBatteryService.getPlugType();
             mBatteryLevel = mBatteryService.getBatteryLevel();
+            mBatteryLevelLow = mBatteryService.getBatteryLevelLow();
 
             if (DEBUG_SPEW) {
                 Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
@@ -1175,6 +1196,10 @@
                     mNotifier.onWirelessChargingStarted();
                 }
             }
+
+            if (oldLevelLow != mBatteryLevelLow) {
+                updateLowPowerModeLocked();
+            }
         }
     }
 
@@ -1896,6 +1921,12 @@
         }
     }
 
+    private boolean isLowPowerModeInternal() {
+        synchronized (mLock) {
+            return mLowPowerModeEnabled;
+        }
+    }
+
     private void handleBatteryStateChangedLocked() {
         mDirty |= DIRTY_BATTERY_STATE;
         updatePowerStateLocked();
@@ -2764,6 +2795,16 @@
             }
         }
 
+        @Override // Binder call
+        public boolean isPowerSaveMode() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return isLowPowerModeInternal();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         /**
          * Reboots the device.
          *
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 35b7f99..cfd09e5 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -541,7 +541,7 @@
                 if (isMagnifyingLocked()) {
                     setMagnifiedRegionBorderShownLocked(false, false);
                     final long delay = (long) (mLongAnimationDuration
-                            * mWindowManagerService.mWindowAnimationScale);
+                            * mWindowManagerService.getWindowAnimationScaleLocked());
                     Message message = mHandler.obtainMessage(
                             MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
                     mHandler.sendMessageDelayed(message, delay);
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 7fe895b..63ae98e 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -87,7 +87,7 @@
             anim.initialize(width, height, width, height);
         }
         anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
-        anim.scaleCurrentDuration(mService.mTransitionAnimationScale);
+        anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
         int zorder = anim.getZAdjustment();
         int adj = 0;
         if (zorder == Animation.ZORDER_TOP) {
@@ -227,7 +227,8 @@
                 if (!animating) {
                     if (WindowManagerService.DEBUG_ANIM) Slog.v(
                         TAG, "Starting animation in " + mAppToken +
-                        " @ " + currentTime + " scale=" + mService.mTransitionAnimationScale
+                        " @ " + currentTime + " scale="
+                        + mService.getTransitionAnimationScaleLocked()
                         + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
                     animation.setStartTime(currentTime);
                     animating = true;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index ca9076f..3200b54 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import android.view.IWindowId;
+import android.view.IWindowSessionCallback;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
 import com.android.internal.view.IInputMethodManager;
@@ -54,6 +55,7 @@
 final class Session extends IWindowSession.Stub
         implements IBinder.DeathRecipient {
     final WindowManagerService mService;
+    final IWindowSessionCallback mCallback;
     final IInputMethodClient mClient;
     final IInputContext mInputContext;
     final int mUid;
@@ -62,14 +64,17 @@
     SurfaceSession mSurfaceSession;
     int mNumWindow = 0;
     boolean mClientDead = false;
+    float mLastReportedAnimatorScale;
 
-    public Session(WindowManagerService service, IInputMethodClient client,
-            IInputContext inputContext) {
+    public Session(WindowManagerService service, IWindowSessionCallback callback,
+            IInputMethodClient client, IInputContext inputContext) {
         mService = service;
+        mCallback = callback;
         mClient = client;
         mInputContext = inputContext;
         mUid = Binder.getCallingUid();
         mPid = Binder.getCallingPid();
+        mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
         StringBuilder sb = new StringBuilder();
         sb.append("Session{");
         sb.append(Integer.toHexString(System.identityHashCode(this)));
@@ -464,6 +469,9 @@
             if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
                     WindowManagerService.TAG, "  NEW SURFACE SESSION " + mSurfaceSession);
             mService.mSessions.add(this);
+            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
+                mService.dispatchNewAnimatorScaleLocked(this);
+            }
         }
         mNumWindow++;
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index da584d8..1db8b55 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -24,6 +24,7 @@
 import android.util.TimeUtils;
 import android.view.IWindowId;
 
+import android.view.IWindowSessionCallback;
 import android.view.WindowContentFrameStats;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.policy.PolicyManager;
@@ -339,7 +340,7 @@
     /**
      * All currently active sessions with clients.
      */
-    final HashSet<Session> mSessions = new HashSet<Session>();
+    final ArraySet<Session> mSessions = new ArraySet<Session>();
 
     /**
      * Mapping from an IWindow IBinder to the server's Window object.
@@ -562,9 +563,10 @@
     PowerManager mPowerManager;
     PowerManagerInternal mPowerManagerInternal;
 
-    float mWindowAnimationScale = 1.0f;
-    float mTransitionAnimationScale = 1.0f;
-    float mAnimatorDurationScale = 1.0f;
+    float mWindowAnimationScaleSetting = 1.0f;
+    float mTransitionAnimationScaleSetting = 1.0f;
+    float mAnimatorDurationScaleSetting = 1.0f;
+    boolean mAnimationsDisabled = false;
 
     final InputManagerService mInputManager;
     final DisplayManagerInternal mDisplayManagerInternal;
@@ -780,6 +782,19 @@
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
         mPowerManagerInternal.setPolicy(mPolicy); // TODO: register as local service instead
+        mPowerManagerInternal.registerLowPowerModeObserver(
+                new PowerManagerInternal.LowPowerModeListener() {
+            @Override
+            public void onLowPowerModeChanged(boolean enabled) {
+                synchronized (mWindowMap) {
+                    if (mAnimationsDisabled != enabled) {
+                        mAnimationsDisabled = enabled;
+                        dispatchNewAnimatorScaleLocked(null);
+                    }
+                }
+            }
+        });
+        mAnimationsDisabled = mPowerManagerInternal.getLowPowerModeEnabled();
         mScreenFrozenLock = mPowerManager.newWakeLock(
                 PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
         mScreenFrozenLock.setReferenceCounted(false);
@@ -799,12 +814,12 @@
         );
 
         // Get persisted window scale setting
-        mWindowAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
-                Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
-        mTransitionAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
-                Settings.Global.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
+        mWindowAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
+        mTransitionAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScaleSetting);
         setAnimatorDurationScale(Settings.Global.getFloat(context.getContentResolver(),
-                Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale));
+                Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
 
         // Track changes to DevicePolicyManager state so we can enable/disable keyguard.
         IntentFilter filter = new IntentFilter();
@@ -5188,9 +5203,9 @@
 
         scale = fixScale(scale);
         switch (which) {
-            case 0: mWindowAnimationScale = scale; break;
-            case 1: mTransitionAnimationScale = scale; break;
-            case 2: mAnimatorDurationScale = scale; break;
+            case 0: mWindowAnimationScaleSetting = scale; break;
+            case 1: mTransitionAnimationScaleSetting = scale; break;
+            case 2: mAnimatorDurationScaleSetting = scale; break;
         }
 
         // Persist setting
@@ -5206,13 +5221,14 @@
 
         if (scales != null) {
             if (scales.length >= 1) {
-                mWindowAnimationScale = fixScale(scales[0]);
+                mWindowAnimationScaleSetting = fixScale(scales[0]);
             }
             if (scales.length >= 2) {
-                mTransitionAnimationScale = fixScale(scales[1]);
+                mTransitionAnimationScaleSetting = fixScale(scales[1]);
             }
             if (scales.length >= 3) {
-                setAnimatorDurationScale(fixScale(scales[2]));
+                mAnimatorDurationScaleSetting = fixScale(scales[2]);
+                dispatchNewAnimatorScaleLocked(null);
             }
         }
 
@@ -5221,24 +5237,43 @@
     }
 
     private void setAnimatorDurationScale(float scale) {
-        mAnimatorDurationScale = scale;
+        mAnimatorDurationScaleSetting = scale;
         ValueAnimator.setDurationScale(scale);
     }
 
+    public float getWindowAnimationScaleLocked() {
+        return mAnimationsDisabled ? 0 : mWindowAnimationScaleSetting;
+    }
+
+    public float getTransitionAnimationScaleLocked() {
+        return mAnimationsDisabled ? 0 : mTransitionAnimationScaleSetting;
+    }
+
     @Override
     public float getAnimationScale(int which) {
         switch (which) {
-            case 0: return mWindowAnimationScale;
-            case 1: return mTransitionAnimationScale;
-            case 2: return mAnimatorDurationScale;
+            case 0: return mWindowAnimationScaleSetting;
+            case 1: return mTransitionAnimationScaleSetting;
+            case 2: return mAnimatorDurationScaleSetting;
         }
         return 0;
     }
 
     @Override
     public float[] getAnimationScales() {
-        return new float[] { mWindowAnimationScale, mTransitionAnimationScale,
-                mAnimatorDurationScale };
+        return new float[] { mWindowAnimationScaleSetting, mTransitionAnimationScaleSetting,
+                mAnimatorDurationScaleSetting };
+    }
+
+    @Override
+    public float getCurrentAnimatorScale() {
+        synchronized(mWindowMap) {
+            return mAnimationsDisabled ? 0 : mAnimatorDurationScaleSetting;
+        }
+    }
+
+    void dispatchNewAnimatorScaleLocked(Session session) {
+        mH.obtainMessage(H.NEW_ANIMATOR_SCALE, session).sendToTarget();
     }
 
     @Override
@@ -6094,7 +6129,7 @@
                     && screenRotationAnimation.hasScreenshot()) {
                 if (screenRotationAnimation.setRotationInTransaction(
                         rotation, mFxSession,
-                        MAX_ANIMATION_DURATION, mTransitionAnimationScale,
+                        MAX_ANIMATION_DURATION, getTransitionAnimationScaleLocked(),
                         displayInfo.logicalWidth, displayInfo.logicalHeight)) {
                     scheduleAnimationLocked();
                 }
@@ -7179,6 +7214,8 @@
         public static final int SHOW_DISPLAY_MASK = 33;
         public static final int ALL_WINDOWS_DRAWN = 34;
 
+        public static final int NEW_ANIMATOR_SCALE = 35;
+
         @Override
         public void handleMessage(Message msg) {
             if (DEBUG_WINDOW_TRACE) {
@@ -7429,11 +7466,12 @@
 
                 case PERSIST_ANIMATION_SCALE: {
                     Settings.Global.putFloat(mContext.getContentResolver(),
-                            Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
+                            Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
                     Settings.Global.putFloat(mContext.getContentResolver(),
-                            Settings.Global.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
+                            Settings.Global.TRANSITION_ANIMATION_SCALE,
+                            mTransitionAnimationScaleSetting);
                     Settings.Global.putFloat(mContext.getContentResolver(),
-                            Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale);
+                            Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting);
                     break;
                 }
 
@@ -7636,6 +7674,33 @@
                         }
                     }
                 }
+                case NEW_ANIMATOR_SCALE: {
+                    float scale = getCurrentAnimatorScale();
+                    ValueAnimator.setDurationScale(scale);
+                    Session session = (Session)msg.obj;
+                    if (session != null) {
+                        try {
+                            session.mCallback.onAnimatorScaleChanged(scale);
+                        } catch (RemoteException e) {
+                        }
+                    } else {
+                        ArrayList<IWindowSessionCallback> callbacks
+                                = new ArrayList<IWindowSessionCallback>();
+                        synchronized (mWindowMap) {
+                            for (int i=0; i<mSessions.size(); i++) {
+                                callbacks.add(mSessions.valueAt(i).mCallback);
+                            }
+
+                        }
+                        for (int i=0; i<callbacks.size(); i++) {
+                            try {
+                                callbacks.get(i).onAnimatorScaleChanged(scale);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                }
+                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG, "handleMessage: exit");
@@ -7648,11 +7713,11 @@
     // -------------------------------------------------------------
 
     @Override
-    public IWindowSession openSession(IInputMethodClient client,
+    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
             IInputContext inputContext) {
         if (client == null) throw new IllegalArgumentException("null client");
         if (inputContext == null) throw new IllegalArgumentException("null inputContext");
-        Session session = new Session(this, client, inputContext);
+        Session session = new Session(this, callback, client, inputContext);
         return session;
     }
 
@@ -8762,7 +8827,7 @@
                             displayInfo.appWidth, displayInfo.appHeight, transit);
                     appAnimator.thumbnailAnimation = anim;
                     anim.restrictDuration(MAX_ANIMATION_DURATION);
-                    anim.scaleCurrentDuration(mTransitionAnimationScale);
+                    anim.scaleCurrentDuration(getTransitionAnimationScaleLocked());
                     Point p = new Point();
                     mAppTransition.getStartingPoint(p);
                     appAnimator.thumbnailX = p.x;
@@ -10070,7 +10135,7 @@
                 mExitAnimId = mEnterAnimId = 0;
             }
             if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
-                    mTransitionAnimationScale, displayInfo.logicalWidth,
+                    getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
                         displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
                 scheduleAnimationLocked();
             } else {
@@ -10405,13 +10470,10 @@
 
     void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
         pw.println("WINDOW MANAGER SESSIONS (dumpsys window sessions)");
-        if (mSessions.size() > 0) {
-            Iterator<Session> it = mSessions.iterator();
-            while (it.hasNext()) {
-                Session s = it.next();
-                pw.print("  Session "); pw.print(s); pw.println(':');
-                s.dump(pw, "    ");
-            }
+        for (int i=0; i<mSessions.size(); i++) {
+            Session s = mSessions.valueAt(i);
+            pw.print("  Session "); pw.print(s); pw.println(':');
+            s.dump(pw, "    ");
         }
     }
 
@@ -10615,9 +10677,10 @@
             pw.print("  mLastWindowForcedOrientation="); pw.print(mLastWindowForcedOrientation);
                     pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation);
             pw.print("  mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
-            pw.print("  mWindowAnimationScale="); pw.print(mWindowAnimationScale);
-                    pw.print(" mTransitionWindowAnimationScale="); pw.print(mTransitionAnimationScale);
-                    pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale);
+            pw.print("  Animation settings: disabled="); pw.print(mAnimationsDisabled);
+                    pw.print(" window="); pw.print(mWindowAnimationScaleSetting);
+                    pw.print(" transition="); pw.print(mTransitionAnimationScaleSetting);
+                    pw.print(" animator="); pw.println(mAnimatorDurationScaleSetting);
             pw.print("  mTraversalScheduled="); pw.println(mTraversalScheduled);
             pw.print("  mStartingIconInTransition="); pw.print(mStartingIconInTransition);
                     pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e257ebc..bda10de 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -207,7 +207,7 @@
         mLocalAnimating = false;
         mAnimation = anim;
         mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
-        mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale);
+        mAnimation.scaleCurrentDuration(mService.getWindowAnimationScaleLocked());
         // Start out animation gone if window is gone, or visible if window is visible.
         mTransformation.clear();
         mTransformation.setAlpha(mLastHidden ? 0 : 1);
@@ -283,7 +283,7 @@
                         " @ " + currentTime + ": ww=" + mWin.mFrame.width() +
                         " wh=" + mWin.mFrame.height() +
                         " dw=" + mAnimDw + " dh=" + mAnimDh +
-                        " scale=" + mService.mWindowAnimationScale);
+                        " scale=" + mService.getWindowAnimationScaleLocked());
                     mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
                             mAnimDw, mAnimDh);
                     final DisplayInfo displayInfo = displayContent.getDisplayInfo();
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index cfe8e15..2f40003 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -195,8 +195,8 @@
     }
 
     @Override
-    public IWindowSession openSession(IInputMethodClient arg0, IInputContext arg1)
-            throws RemoteException {
+    public IWindowSession openSession(IWindowSessionCallback argn1, IInputMethodClient arg0,
+            IInputContext arg1) throws RemoteException {
         // TODO Auto-generated method stub
         return null;
     }
@@ -276,6 +276,11 @@
     }
 
     @Override
+    public float getCurrentAnimatorScale() throws RemoteException {
+        return 0;
+    }
+
+    @Override
     public void setAppGroupId(IBinder arg0, int arg1) throws RemoteException {
         // TODO Auto-generated method stub