Merge "Overhaul GraphicsStatsService"
diff --git a/Android.mk b/Android.mk
index e645ac1..b98d3bc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -109,6 +109,7 @@
 	core/java/android/app/backup/IRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreSession.aidl \
 	core/java/android/app/backup/ISelectBackupTransportCallback.aidl \
+	core/java/android/app/usage/ICacheQuotaService.aidl \
 	core/java/android/app/usage/IStorageStatsManager.aidl \
 	core/java/android/app/usage/IUsageStatsManager.aidl \
 	core/java/android/bluetooth/IBluetooth.aidl \
@@ -709,6 +710,7 @@
 	frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
 	frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
 	frameworks/base/core/java/android/speech/tts/Voice.aidl \
+	frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
 	frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
 	frameworks/base/core/java/android/app/usage/StorageStats.aidl \
 	frameworks/base/core/java/android/app/usage/UsageEvents.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index 3e7ef78..1d3ff9a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7188,6 +7188,35 @@
 
 package android.app.usage {
 
+  public final class CacheQuotaHint implements android.os.Parcelable {
+    ctor public CacheQuotaHint(android.app.usage.CacheQuotaHint.Builder);
+    method public int describeContents();
+    method public long getQuota();
+    method public int getUid();
+    method public android.app.usage.UsageStats getUsageStats();
+    method public java.lang.String getVolumeUuid();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.usage.CacheQuotaHint> CREATOR;
+    field public static final long QUOTA_NOT_SET = -1L; // 0xffffffffffffffffL
+  }
+
+  public static final class CacheQuotaHint.Builder {
+    ctor public CacheQuotaHint.Builder();
+    ctor public CacheQuotaHint.Builder(android.app.usage.CacheQuotaHint);
+    method public android.app.usage.CacheQuotaHint build();
+    method public android.app.usage.CacheQuotaHint.Builder setQuota(long);
+    method public android.app.usage.CacheQuotaHint.Builder setUid(int);
+    method public android.app.usage.CacheQuotaHint.Builder setUsageStats(android.app.usage.UsageStats);
+    method public android.app.usage.CacheQuotaHint.Builder setVolumeUuid(java.lang.String);
+  }
+
+  public abstract class CacheQuotaService extends android.app.Service {
+    ctor public CacheQuotaService();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public abstract java.util.List<android.app.usage.CacheQuotaHint> onComputeCacheQuotaHints(java.util.List<android.app.usage.CacheQuotaHint>);
+    field public static final java.lang.String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
+  }
+
   public final class ConfigurationStats implements android.os.Parcelable {
     ctor public ConfigurationStats(android.app.usage.ConfigurationStats);
     method public int describeContents();
diff --git a/core/java/android/app/usage/CacheQuotaHint.aidl b/core/java/android/app/usage/CacheQuotaHint.aidl
new file mode 100644
index 0000000..0470ea7
--- /dev/null
+++ b/core/java/android/app/usage/CacheQuotaHint.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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.app.usage;
+
+/** {@hide} */
+parcelable CacheQuotaHint;
\ No newline at end of file
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
new file mode 100644
index 0000000..4b6f99b
--- /dev/null
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 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.app.usage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * CacheQuotaRequest represents a triplet of a uid, the volume UUID it is stored upon, and
+ * its usage stats. When processed, it obtains a cache quota as defined by the system which
+ * allows apps to understand how much cache to use.
+ * {@hide}
+ */
+@SystemApi
+public final class CacheQuotaHint implements Parcelable {
+    public static final long QUOTA_NOT_SET = -1;
+    private final String mUuid;
+    private final int mUid;
+    private final UsageStats mUsageStats;
+    private final long mQuota;
+
+    /**
+     * Create a new request.
+     * @param builder A builder for this object.
+     */
+    public CacheQuotaHint(Builder builder) {
+        this.mUuid = builder.mUuid;
+        this.mUid = builder.mUid;
+        this.mUsageStats = builder.mUsageStats;
+        this.mQuota = builder.mQuota;
+    }
+
+    public String getVolumeUuid() {
+        return mUuid;
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public long getQuota() {
+        return mQuota;
+    }
+
+    public UsageStats getUsageStats() {
+        return mUsageStats;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mUuid);
+        dest.writeInt(mUid);
+        dest.writeLong(mQuota);
+        dest.writeParcelable(mUsageStats, 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final class Builder {
+        private String mUuid;
+        private int mUid;
+        private UsageStats mUsageStats;
+        private long mQuota;
+
+        public Builder() {
+        }
+
+        public Builder(CacheQuotaHint hint) {
+            setVolumeUuid(hint.getVolumeUuid());
+            setUid(hint.getUid());
+            setUsageStats(hint.getUsageStats());
+            setQuota(hint.getQuota());
+        }
+
+        public @NonNull Builder setVolumeUuid(@Nullable String uuid) {
+            mUuid = uuid;
+            return this;
+        }
+
+        public @NonNull Builder setUid(int uid) {
+            Preconditions.checkArgumentPositive(uid, "Proposed uid was not positive.");
+            mUid = uid;
+            return this;
+        }
+
+        public @NonNull Builder setUsageStats(@Nullable UsageStats stats) {
+            mUsageStats = stats;
+            return this;
+        }
+
+        public @NonNull Builder setQuota(long quota) {
+            Preconditions.checkArgument((quota >= QUOTA_NOT_SET));
+            mQuota = quota;
+            return this;
+        }
+
+        public @NonNull CacheQuotaHint build() {
+            Preconditions.checkNotNull(mUsageStats);
+            return new CacheQuotaHint(this);
+        }
+    }
+
+    public static final Parcelable.Creator<CacheQuotaHint> CREATOR =
+            new Creator<CacheQuotaHint>() {
+                @Override
+                public CacheQuotaHint createFromParcel(Parcel in) {
+                    final Builder builder = new Builder();
+                    return builder.setVolumeUuid(in.readString())
+                            .setUid(in.readInt())
+                            .setQuota(in.readLong())
+                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader()))
+                            .build();
+                }
+
+                @Override
+                public CacheQuotaHint[] newArray(int size) {
+                    return new CacheQuotaHint[size];
+                }
+            };
+}
diff --git a/core/java/android/app/usage/CacheQuotaService.java b/core/java/android/app/usage/CacheQuotaService.java
new file mode 100644
index 0000000..b9430ab
--- /dev/null
+++ b/core/java/android/app/usage/CacheQuotaService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.app.usage;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.usage.ICacheQuotaService;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallback;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.List;
+
+/**
+ * CacheQuoteService defines a service which accepts cache quota requests and processes them,
+ * thereby filling out how much quota each request deserves.
+ * {@hide}
+ */
+@SystemApi
+public abstract class CacheQuotaService extends Service {
+    private static final String TAG = "CacheQuotaService";
+
+    /**
+     * The {@link Intent} action that must be declared as handled by a service
+     * in its manifest for the system to recognize it as a quota providing service.
+     */
+    public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
+
+    /** {@hide} **/
+    public static final String REQUEST_LIST_KEY = "requests";
+
+    private CacheQuotaServiceWrapper mWrapper;
+    private Handler mHandler;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWrapper = new CacheQuotaServiceWrapper();
+        mHandler = new ServiceHandler(getMainLooper());
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mWrapper;
+    }
+
+    /**
+     * Processes the cache quota list upon receiving a list of requests.
+     * @param requests A list of cache quotas to fulfill.
+     * @return A completed list of cache quota requests.
+     */
+    public abstract List<CacheQuotaHint> onComputeCacheQuotaHints(
+            List<CacheQuotaHint> requests);
+
+    private final class CacheQuotaServiceWrapper extends ICacheQuotaService.Stub {
+        @Override
+        public void computeCacheQuotaHints(
+                RemoteCallback callback, List<CacheQuotaHint> requests) {
+            final Pair<RemoteCallback, List<CacheQuotaHint>> pair =
+                    Pair.create(callback, requests);
+            Message msg = mHandler.obtainMessage(ServiceHandler.MSG_SEND_LIST, pair);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private final class ServiceHandler extends Handler {
+        public static final int MSG_SEND_LIST = 1;
+
+        public ServiceHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final int action = msg.what;
+            switch (action) {
+                case MSG_SEND_LIST:
+                    final Pair<RemoteCallback, List<CacheQuotaHint>> pair =
+                            (Pair<RemoteCallback, List<CacheQuotaHint>>) msg.obj;
+                    List<CacheQuotaHint> processed = onComputeCacheQuotaHints(pair.second);
+                    final Bundle data = new Bundle();
+                    data.putParcelableList(REQUEST_LIST_KEY, processed);
+
+                    final RemoteCallback callback = pair.first;
+                    callback.sendResult(data);
+                    break;
+                default:
+                    Log.w(TAG, "Handling unknown message: " + action);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/usage/ICacheQuotaService.aidl b/core/java/android/app/usage/ICacheQuotaService.aidl
new file mode 100644
index 0000000..8d984e0
--- /dev/null
+++ b/core/java/android/app/usage/ICacheQuotaService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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.app.usage;
+
+import android.os.RemoteCallback;
+import android.app.usage.CacheQuotaHint;
+
+/** {@hide} */
+oneway interface ICacheQuotaService {
+    void computeCacheQuotaHints(in RemoteCallback callback, in List<CacheQuotaHint> requests);
+}
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index a6f91fe..08595dd 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -19,7 +19,7 @@
 import android.content.ComponentName;
 import android.content.res.Configuration;
 
-import java.io.IOException;
+import java.util.List;
 
 /**
  * UsageStatsManager local system service interface.
@@ -127,4 +127,7 @@
 
     public abstract void applyRestoredPayload(int user, String key, byte[] payload);
 
+    /* Cache Quota Service API */
+    public abstract List<UsageStats> queryUsageStatsForUser(
+            int userId, int interval, long beginTime, long endTime);
 }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 4bb7968..c66bf874 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -164,6 +164,18 @@
     public static final String OVERDRAW_PROPERTY_SHOW = "show";
 
     /**
+     * Defines the rendering pipeline to be used by the ThreadedRenderer.
+     *
+     * Possible values:
+     * "opengl", will use the existing OpenGL renderer
+     * "skiagl", will use Skia's OpenGL renderer
+     * "skiavk", will use Skia's Vulkan renderer
+     *
+     * @hide
+     */
+    public static final String DEBUG_RENDERER_PROPERTY = "debug.hwui.renderer";
+
+    /**
      * Turn on to debug non-rectangular clip operations.
      *
      * Possible values:
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index b57f2362..a03d3c5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -252,25 +252,26 @@
             if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
                 // This task wants to stay at background
                 // update its cpuset so it doesn't only run on bg core(s)
-#ifdef ENABLE_CPUSETS
-                int err = set_cpuset_policy(t_pid, sp);
-                if (err != NO_ERROR) {
-                    signalExceptionForGroupError(env, -err, t_pid);
-                    break;
+                if (cpusets_enabled()) {
+                    int err = set_cpuset_policy(t_pid, sp);
+                    if (err != NO_ERROR) {
+                        signalExceptionForGroupError(env, -err, t_pid);
+                        break;
+                    }
                 }
-#endif
                 continue;
             }
         }
         int err;
-#ifdef ENABLE_CPUSETS
-        // set both cpuset and cgroup for general threads
-        err = set_cpuset_policy(t_pid, sp);
-        if (err != NO_ERROR) {
-            signalExceptionForGroupError(env, -err, t_pid);
-            break;
+
+        if (cpusets_enabled()) {
+            // set both cpuset and cgroup for general threads
+            err = set_cpuset_policy(t_pid, sp);
+            if (err != NO_ERROR) {
+                signalExceptionForGroupError(env, -err, t_pid);
+                break;
+            }
         }
-#endif
 
         err = set_sched_policy(t_pid, sp);
         if (err != NO_ERROR) {
@@ -291,7 +292,6 @@
     return (int) sp;
 }
 
-#ifdef ENABLE_CPUSETS
 /** Sample CPUset list format:
  *  0-3,4,6-8
  */
@@ -367,7 +367,6 @@
     }
     return;
 }
-#endif
 
 
 /**
@@ -376,22 +375,21 @@
  * them in the passed in cpu_set_t
  */
 void get_exclusive_cpuset_cores(SchedPolicy policy, cpu_set_t *cpu_set) {
-#ifdef ENABLE_CPUSETS
-    int i;
-    cpu_set_t tmp_set;
-    get_cpuset_cores_for_policy(policy, cpu_set);
-    for (i = 0; i < SP_CNT; i++) {
-        if ((SchedPolicy) i == policy) continue;
-        get_cpuset_cores_for_policy((SchedPolicy)i, &tmp_set);
-        // First get cores exclusive to one set or the other
-        CPU_XOR(&tmp_set, cpu_set, &tmp_set);
-        // Then get the ones only in cpu_set
-        CPU_AND(cpu_set, cpu_set, &tmp_set);
+    if (cpusets_enabled()) {
+        int i;
+        cpu_set_t tmp_set;
+        get_cpuset_cores_for_policy(policy, cpu_set);
+        for (i = 0; i < SP_CNT; i++) {
+            if ((SchedPolicy) i == policy) continue;
+            get_cpuset_cores_for_policy((SchedPolicy)i, &tmp_set);
+            // First get cores exclusive to one set or the other
+            CPU_XOR(&tmp_set, cpu_set, &tmp_set);
+            // Then get the ones only in cpu_set
+            CPU_AND(cpu_set, cpu_set, &tmp_set);
+        }
+    } else {
+        CPU_ZERO(cpu_set);
     }
-#else
-    (void) policy;
-    CPU_ZERO(cpu_set);
-#endif
     return;
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ff70a69..9108f4a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3109,6 +3109,13 @@
     <permission android:name="android.permission.BIND_DREAM_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by an {@link android.app.usage.CacheQuotaService} to ensure that only the
+         system can bind to it.
+         @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
+    <permission android:name="android.permission.BIND_CACHE_QUOTA_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to call into a carrier setup flow. It is up to the
          carrier setup application to enforce that this permission is required
          @hide This is not a third-party API (intended for OEMs and system apps). -->
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 09e34bf..2931255 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -211,7 +211,7 @@
         return sRenderPipelineType;
     }
     char prop[PROPERTY_VALUE_MAX];
-    property_get(PROPERTY_DEFAULT_RENDERER, prop, "opengl");
+    property_get(PROPERTY_RENDERER, prop, "opengl");
     if (!strcmp(prop, "skiagl") ) {
         sRenderPipelineType = RenderPipelineType::SkiaGL;
     } else if (!strcmp(prop, "skiavk") ) {
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 6dc0cb3..9db6449 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -156,7 +156,7 @@
  * Allows to set rendering pipeline mode to OpenGL (default), Skia OpenGL
  * or Vulkan.
  */
-#define PROPERTY_DEFAULT_RENDERER "debug.hwui.default_renderer"
+#define PROPERTY_RENDERER "debug.hwui.renderer"
 
 ///////////////////////////////////////////////////////////////////////////////
 // Runtime configuration properties
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 7020be0..860725b 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -106,7 +106,10 @@
 
     // Now that extensions are loaded, pick a swap behavior
     if (Properties::enablePartialUpdates) {
-        if (Properties::useBufferAge && EglExtensions.bufferAge) {
+        // An Adreno driver bug is causing rendering problems for SkiaGL with
+        // buffer age swap behavior (b/31957043).  To temporarily workaround,
+        // we will use preserved swap behavior.
+        if (Properties::useBufferAge && EglExtensions.bufferAge && !Properties::isSkiaEnabled()) {
             mSwapBehavior = SwapBehavior::BufferAge;
         } else {
             mSwapBehavior = SwapBehavior::Preserved;
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 3b74ee7..35988d4 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -694,10 +694,16 @@
          *            {@link TvInputService}.
          */
         public Builder(Context context, ComponentName component) {
-            mContext = context;
+            if (context == null) {
+                throw new IllegalArgumentException("context cannot be null.");
+            }
             Intent intent = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
             mResolveInfo = context.getPackageManager().resolveService(intent,
                     PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+            if (mResolveInfo == null) {
+                throw new IllegalArgumentException("Invalid component. Can't find the service.");
+            }
+            mContext = context;
         }
 
         /**
diff --git a/packages/ExtServices/Android.mk b/packages/ExtServices/Android.mk
index e8a4007..d0c2b9f 100644
--- a/packages/ExtServices/Android.mk
+++ b/packages/ExtServices/Android.mk
@@ -34,7 +34,8 @@
 
 include $(BUILD_PACKAGE)
 
-
-
-
+# Use the following include to make our test apk.
+ifeq ($(strip $(LOCAL_PACKAGE_OVERRIDES)),)
+include $(call all-makefiles-under, $(LOCAL_PATH))
+endif
 
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index f442219..f3d8983 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -25,6 +25,13 @@
         android:defaultToDeviceProtectedStorage="true"
         android:directBootAware="true">
 
+        <service android:name=".storage.CacheQuotaServiceImpl"
+             android:permission="android.permission.BIND_CACHE_QUOTA_SERVICE">
+            <intent-filter>
+                <action android:name="android.app.usage.CacheQuotaService" />
+            </intent-filter>
+        </service>
+
         <library android:name="android.ext.services"/>
     </application>
 
diff --git a/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java b/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java
new file mode 100644
index 0000000..18863ca
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/storage/CacheQuotaServiceImpl.java
@@ -0,0 +1,144 @@
+
+/*
+ * Copyright (C) 2017 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.ext.services.storage;
+
+import android.app.usage.CacheQuotaHint;
+import android.app.usage.CacheQuotaService;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * CacheQuotaServiceImpl implements the CacheQuotaService with a strategy for populating the quota
+ * of {@link CacheQuotaHint}.
+ */
+public class CacheQuotaServiceImpl extends CacheQuotaService {
+    private static final double CACHE_RESERVE_RATIO = 0.15;
+
+    @Override
+    public List<CacheQuotaHint> onComputeCacheQuotaHints(List<CacheQuotaHint> requests) {
+        ArrayMap<String, List<CacheQuotaHint>> byUuid = new ArrayMap<>();
+        final int requestCount = requests.size();
+        for (int i = 0; i < requestCount; i++) {
+            CacheQuotaHint request = requests.get(i);
+            String uuid = request.getVolumeUuid();
+            List<CacheQuotaHint> listForUuid = byUuid.get(uuid);
+            if (listForUuid == null) {
+                listForUuid = new ArrayList<>();
+                byUuid.put(uuid, listForUuid);
+            }
+            listForUuid.add(request);
+        }
+
+        List<CacheQuotaHint> processed = new ArrayList<>();
+        byUuid.entrySet().forEach(
+                requestListEntry -> {
+                    // Collapse all usage stats to the same uid.
+                    Map<Integer, List<CacheQuotaHint>> byUid = requestListEntry.getValue()
+                            .stream()
+                            .collect(Collectors.groupingBy(CacheQuotaHint::getUid));
+                    byUid.values().forEach(uidGroupedList -> {
+                        int size = uidGroupedList.size();
+                        if (size < 2) {
+                            return;
+                        }
+                        CacheQuotaHint first = uidGroupedList.get(0);
+                        for (int i = 1; i < size; i++) {
+                            /* Note: We can't use the UsageStats built-in addition function because
+                                     UIDs may span multiple packages and usage stats adding has
+                                     matching package names as a precondition. */
+                            first.getUsageStats().mTotalTimeInForeground +=
+                                    uidGroupedList.get(i).getUsageStats().mTotalTimeInForeground;
+                        }
+                    });
+
+                    // Because the foreground stats have been added to the first element, we need
+                    // a list of only the first values (which contain the merged foreground time).
+                    List<CacheQuotaHint> flattenedRequests =
+                            byUid.values()
+                                 .stream()
+                                 .map(entryList -> entryList.get(0))
+                                 .filter(entry -> entry.getUsageStats().mTotalTimeInForeground != 0)
+                                 .sorted(sCacheQuotaRequestComparator)
+                                 .collect(Collectors.toList());
+
+                    // Because the elements are sorted, we can use the index to also be the sorted
+                    // index for cache quota calculation.
+                    double sum = getSumOfFairShares(flattenedRequests.size());
+                    String uuid = requestListEntry.getKey();
+                    long reservedSize = getReservedCacheSize(uuid);
+                    for (int count = 0; count < flattenedRequests.size(); count++) {
+                        double share = getFairShareForPosition(count) / sum;
+                        CacheQuotaHint entry = flattenedRequests.get(count);
+                        CacheQuotaHint.Builder builder = new CacheQuotaHint.Builder(entry);
+                        builder.setQuota(Math.round(share * reservedSize));
+                        processed.add(builder.build());
+                    }
+                }
+        );
+
+        return processed.stream()
+                .filter(request -> request.getQuota() > 0).collect(Collectors.toList());
+    }
+
+    private double getFairShareForPosition(int position) {
+        double value = 1.0 / Math.log(position + 3) - 0.285;
+        return (value > 0.01) ? value : 0.01;
+    }
+
+    private double getSumOfFairShares(int size) {
+        double sum = 0;
+        for (int i = 0; i < size; i++) {
+            sum += getFairShareForPosition(i);
+        }
+        return sum;
+    }
+
+    private long getReservedCacheSize(String uuid) {
+        // TODO: Revisit the cache size after running more storage tests.
+        // TODO: Figure out how to ensure ExtServices has the permissions to call
+        //       StorageStatsManager, because this is ignoring the cache...
+        StorageManager storageManager = getSystemService(StorageManager.class);
+        long freeBytes = 0;
+        if (uuid == StorageManager.UUID_PRIVATE_INTERNAL) { // regular equals because of null
+            freeBytes = Environment.getDataDirectory().getFreeSpace();
+        } else {
+            final VolumeInfo vol = storageManager.findVolumeByUuid(uuid);
+            freeBytes = vol.getPath().getFreeSpace();
+        }
+        return Math.round(freeBytes * CACHE_RESERVE_RATIO);
+    }
+
+    // Compares based upon foreground time.
+    private static Comparator<CacheQuotaHint> sCacheQuotaRequestComparator =
+            new Comparator<CacheQuotaHint>() {
+        @Override
+        public int compare(CacheQuotaHint o, CacheQuotaHint t1) {
+            long x = t1.getUsageStats().getTotalTimeInForeground();
+            long y = o.getUsageStats().getTotalTimeInForeground();
+            return (x < y) ? -1 : ((x == y) ? 0 : 1);
+        }
+    };
+}
diff --git a/packages/ExtServices/tests/Android.mk b/packages/ExtServices/tests/Android.mk
new file mode 100644
index 0000000..cb3c352
--- /dev/null
+++ b/packages/ExtServices/tests/Android.mk
@@ -0,0 +1,24 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-target-minus-junit4 \
+    espresso-core \
+    truth-prebuilt \
+    legacy-android-test
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ExtServicesUnitTests
+
+LOCAL_INSTRUMENTATION_FOR := ExtServices
+
+include $(BUILD_PACKAGE)
diff --git a/packages/ExtServices/tests/AndroidManifest.xml b/packages/ExtServices/tests/AndroidManifest.xml
new file mode 100644
index 0000000..e6c7b97
--- /dev/null
+++ b/packages/ExtServices/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.ext.services.tests.unit">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.ext.services"
+                     android:label="ExtServices Test Cases">
+    </instrumentation>
+
+</manifest>
\ No newline at end of file
diff --git a/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java b/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java
new file mode 100644
index 0000000..cc1699a
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/storage/CacheQuotaServiceImplTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 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.ext.services.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.app.usage.CacheQuotaHint;
+import android.app.usage.UsageStats;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.test.ServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CacheQuotaServiceImplTest extends ServiceTestCase<CacheQuotaServiceImpl> {
+    private static final String sTestVolUuid = "uuid";
+    private static final String sSecondTestVolUuid = "otherUuid";
+
+    @Mock private Context mContext;
+    @Mock private File mFile;
+    @Mock private VolumeInfo mVolume;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private StorageManager mStorageManager;
+
+    public CacheQuotaServiceImplTest() {
+        super(CacheQuotaServiceImpl.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mContext = Mockito.spy(new ContextWrapper(getSystemContext()));
+        setContext(mContext);
+        when(mContext.getSystemService(Context.STORAGE_SERVICE)).thenReturn(mStorageManager);
+
+        when(mFile.getFreeSpace()).thenReturn(10000L);
+        when(mVolume.getPath()).thenReturn(mFile);
+        when(mStorageManager.findVolumeByUuid(sTestVolUuid)).thenReturn(mVolume);
+        when(mStorageManager.findVolumeByUuid(sSecondTestVolUuid)).thenReturn(mVolume);
+
+        Intent intent = new Intent(getContext(), CacheQuotaServiceImpl.class);
+        startService(intent);
+    }
+
+    @Test
+    public void testNoApps() {
+        CacheQuotaServiceImpl service = getService();
+        assertEquals(service.onComputeCacheQuotaHints(new ArrayList()).size(), 0);
+    }
+
+    @Test
+    public void testOneApp() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        CacheQuotaHint request = makeNewRequest("com.test", sTestVolUuid, 1001, 100L);
+        requests.add(request);
+
+        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+        assertThat(output).hasSize(1);
+        assertThat(output.get(0).getQuota()).isEqualTo(1500L);
+    }
+
+    @Test
+    public void testTwoAppsOneVolume() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+        requests.add(makeNewRequest("com.test2", sTestVolUuid, 1002, 99L));
+
+        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+        // Note that the sizes are just the cache area split up.
+        assertThat(output).hasSize(2);
+        assertThat(output.get(0).getQuota()).isEqualTo(883);
+        assertThat(output.get(1).getQuota()).isEqualTo(1500 - 883);
+    }
+
+    @Test
+    public void testTwoAppsTwoVolumes() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+        requests.add(makeNewRequest("com.test2", sSecondTestVolUuid, 1002, 99L));
+
+        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+        assertThat(output).hasSize(2);
+        assertThat(output.get(0).getQuota()).isEqualTo(1500);
+        assertThat(output.get(1).getQuota()).isEqualTo(1500);
+    }
+
+    @Test
+    public void testMultipleAppsPerUidIsCollated() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+        requests.add(makeNewRequest("com.test2", sTestVolUuid, 1001, 99L));
+
+        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+        assertThat(output).hasSize(1);
+        assertThat(output.get(0).getQuota()).isEqualTo(1500);
+    }
+
+    @Test
+    public void testTwoAppsTwoVolumesTwoUuidsShouldBESeparate() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        requests.add(makeNewRequest("com.test", sTestVolUuid, 1001, 100L));
+        requests.add(makeNewRequest("com.test2", sSecondTestVolUuid, 1001, 99L));
+
+        List<CacheQuotaHint> output = getService().onComputeCacheQuotaHints(requests);
+
+        assertThat(output).hasSize(2);
+        assertThat(output.get(0).getQuota()).isEqualTo(1500);
+        assertThat(output.get(1).getQuota()).isEqualTo(1500);
+    }
+
+    private CacheQuotaHint makeNewRequest(String packageName, String uuid, int uid, long foregroundTime) {
+        UsageStats stats = new UsageStats();
+        stats.mPackageName = packageName;
+        stats.mTotalTimeInForeground = foregroundTime;
+        return new CacheQuotaHint.Builder()
+                .setVolumeUuid(uuid).setUid(uid).setUsageStats(stats).setQuota(-1).build();
+    }
+}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index eb64b3a..d207c35 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -444,6 +444,18 @@
         <item>show_deuteranomaly</item>
     </string-array>
 
+    <!-- Titles for debug renderer preference. [CHAR LIMIT=50] -->
+    <string-array name="debug_hw_renderer_entries">
+        <item>OpenGL (Default)</item>
+        <item>OpenGL (Skia)</item>
+    </string-array>
+
+    <!-- Values for debug renderer preference. -->
+    <string-array name="debug_hw_renderer_values" translatable="false" >
+        <item>opengl</item>
+        <item>skiagl</item>
+    </string-array>
+
     <!-- Titles for app process limit preference. [CHAR LIMIT=35] -->
     <string-array name="app_process_limit_entries">
         <item>Standard limit</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a4824ad..961d0e5 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -586,6 +586,9 @@
     <!-- UI debug setting: show the amount of overdraw in apps using the GPU [CHAR LIMIT=25] -->
     <string name="debug_hw_overdraw">Debug GPU overdraw</string>
 
+    <!-- UI debug setting: select the renderer to use by RenderThread [CHAR LIMIT=25] -->
+    <string name="debug_hw_renderer">Set GPU Renderer</string>
+
     <!-- UI debug setting: disable use of overlays? [CHAR LIMIT=25] -->
     <string name="disable_overlays">Disable HW overlays</string>
     <!-- UI debug setting: disable use of overlays summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
index fa1f91f..22f8856 100644
--- a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
@@ -38,6 +38,7 @@
     public long remainingTimeUs = 0;
     public String batteryPercentString;
     public String remainingLabel;
+    public String statusLabel;
     private BatteryStats mStats;
     private boolean mCharging;
     private long timePeriod;
@@ -135,6 +136,7 @@
         info.batteryPercentString = Utils.formatPercentage(info.mBatteryLevel);
         info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
         final Resources resources = context.getResources();
+        info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast, shortString);
         if (!info.mCharging) {
             final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs);
             if (drainTime > 0) {
@@ -155,8 +157,6 @@
             }
         } else {
             final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs);
-            final String statusLabel = Utils.getBatteryStatus(
-                    resources, batteryBroadcast, shortString);
             final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
                     BatteryManager.BATTERY_STATUS_UNKNOWN);
             if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
@@ -184,9 +184,9 @@
                 info.mChargeLabelString = resources.getString(
                         resId, info.batteryPercentString, timeString);
             } else {
-                info.remainingLabel = statusLabel;
+                info.remainingLabel = null;
                 info.mChargeLabelString = resources.getString(
-                        R.string.power_charging, info.batteryPercentString, statusLabel);
+                        R.string.power_charging, info.batteryPercentString, info.statusLabel);
             }
         }
         return info;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
new file mode 100644
index 0000000..1364958
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 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.settingslib;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.SystemClock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BatteryInfoTest {
+    private static final String STATUS_FULL = "Full";
+    private Intent mBatteryBroadcast;
+    @Mock
+    private BatteryStats mBatteryStats;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mBatteryBroadcast = new Intent();
+        mBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0);
+        mBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0);
+        mBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
+        mBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
+
+        when(mContext.getResources().getString(R.string.battery_info_status_full))
+                .thenReturn(STATUS_FULL);
+    }
+
+    @Test
+    public void testGetBatteryInfo_HasStatusLabel() {
+        BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mBatteryBroadcast, mBatteryStats,
+                SystemClock.elapsedRealtime() * 1000, true);
+
+        assertThat(info.statusLabel).isEqualTo(STATUS_FULL);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 8de4e58..33ad7fb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -375,6 +375,10 @@
         MutableBoolean isHomeStackVisible = new MutableBoolean(true);
         if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
             ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+            if (runningTask == null) {
+                return;
+            }
+
             RecentsTaskLoader loader = Recents.getTaskLoader();
             sInstanceLoadPlan = loader.createLoadPlan(mContext);
             sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4bc9bb1..0bc9d19 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -815,6 +815,8 @@
         intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+        mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.SYSTEM,
+                new IntentFilter(Intent.ACTION_USER_PRESENT), null, null);
 
         try {
             mNetd.registerObserver(mTethering);
@@ -4008,6 +4010,16 @@
         }
     };
 
+    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Try creating lockdown tracker, since user present usually means
+            // unlocked keystore.
+            updateLockdownVpn();
+            mContext.unregisterReceiver(this);
+        }
+    };
+
     private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
             new HashMap<Messenger, NetworkFactoryInfo>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 3c109ac..f49ad82 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2393,7 +2393,18 @@
         mWindowManager.deferSurfaceLayout();
         try {
             ActivityRecord r = stack.topRunningActivityLocked();
-            stack.resize(pinnedBounds, tempPinnedTaskBounds, null);
+            Rect insetBounds = null;
+            if (tempPinnedTaskBounds != null) {
+                // We always use 0,0 as the position for the inset rect because
+                // if we are getting insets at all in the pinned stack it must mean
+                // we are headed for fullscreen.
+                insetBounds = tempRect;
+                insetBounds.top = 0;
+                insetBounds.left = 0;
+                insetBounds.right = tempPinnedTaskBounds.width();
+                insetBounds.bottom = tempPinnedTaskBounds.height();
+            }
+            stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds);
             stack.ensureVisibleActivitiesConfigurationLocked(r, false);
         } finally {
             mWindowManager.continueSurfaceLayout();
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 017c5fb..63024db 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -183,11 +183,31 @@
             case CALLBACK_LISTEN_ALL:
                 break;
             case CALLBACK_TRACK_DEFAULT:
+                if (mDefaultNetworkCallback == null) {
+                    // The callback was unregistered in the interval between
+                    // ConnectivityService calling onAvailable() and our
+                    // handling of it here on the mTarget.getHandler() thread.
+                    // Clean-up of this network entry is deferred to the
+                    // handling of onLost() by other callbacks.
+                    // TODO: change to Log.wtf() after oag/331764 is merged.
+                    return;
+                }
+
                 cm().requestNetworkCapabilities(mDefaultNetworkCallback);
                 cm().requestLinkProperties(mDefaultNetworkCallback);
                 mCurrentDefault = network;
                 break;
             case CALLBACK_MOBILE_REQUEST:
+                if (mMobileNetworkCallback == null) {
+                    // The callback was unregistered in the interval between
+                    // ConnectivityService calling onAvailable() and our
+                    // handling of it here on the mTarget.getHandler() thread.
+                    // Clean-up of this network entry is deferred to the
+                    // handling of onLost() by other callbacks.
+                    // TODO: change to Log.wtf() after oag/331764 is merged.
+                    return;
+                }
+
                 cm().requestNetworkCapabilities(mMobileNetworkCallback);
                 cm().requestLinkProperties(mMobileNetworkCallback);
                 break;
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
new file mode 100644
index 0000000..10d30aa
--- /dev/null
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 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.storage;
+
+import android.annotation.MainThread;
+import android.app.usage.CacheQuotaHint;
+import android.app.usage.CacheQuotaService;
+import android.app.usage.ICacheQuotaService;
+import android.app.usage.UsageStats;
+import android.app.usage.UsageStatsManager;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.format.DateUtils;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.Installer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * CacheQuotaStrategy is a strategy for determining cache quotas using usage stats and foreground
+ * time using the calculation as defined in the refuel rocket.
+ */
+public class CacheQuotaStrategy implements RemoteCallback.OnResultListener {
+    private static final String TAG = "CacheQuotaStrategy";
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final UsageStatsManagerInternal mUsageStats;
+    private final Installer mInstaller;
+    private ServiceConnection mServiceConnection;
+    private ICacheQuotaService mRemoteService;
+
+    public CacheQuotaStrategy(
+            Context context, UsageStatsManagerInternal usageStatsManager, Installer installer) {
+        mContext = Preconditions.checkNotNull(context);
+        mUsageStats = Preconditions.checkNotNull(usageStatsManager);
+        mInstaller = Preconditions.checkNotNull(installer);
+    }
+
+    /**
+     * Recalculates the quotas and stores them to installd.
+     */
+    public void recalculateQuotas() {
+        createServiceConnection();
+
+        ComponentName component = getServiceComponentName();
+        if (component != null) {
+            Intent intent = new Intent();
+            intent.setComponent(component);
+            mContext.bindServiceAsUser(
+                    intent, mServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+        }
+    }
+
+    private void createServiceConnection() {
+        // If we're already connected, don't create a new connection.
+        if (mServiceConnection != null) {
+            return;
+        }
+
+        mServiceConnection = new ServiceConnection() {
+            @Override
+            @MainThread
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                Runnable runnable = new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (mLock) {
+                            mRemoteService = ICacheQuotaService.Stub.asInterface(service);
+                            List<CacheQuotaHint> requests = getUnfulfilledRequests();
+                            final RemoteCallback remoteCallback =
+                                    new RemoteCallback(CacheQuotaStrategy.this);
+                            try {
+                                mRemoteService.computeCacheQuotaHints(remoteCallback, requests);
+                            } catch (RemoteException ex) {
+                                Slog.w(TAG,
+                                        "Remote exception occurred while trying to get cache quota",
+                                        ex);
+                            }
+                        }
+                    }
+                };
+                AsyncTask.execute(runnable);
+            }
+
+            @Override
+            @MainThread
+            public void onServiceDisconnected(ComponentName name) {
+                synchronized (mLock) {
+                    mRemoteService = null;
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns a list of CacheQuotaRequests which do not have their quotas filled out for apps
+     * which have been used in the last year.
+     */
+    private List<CacheQuotaHint> getUnfulfilledRequests() {
+        long timeNow = System.currentTimeMillis();
+        long oneYearAgo = timeNow - DateUtils.YEAR_IN_MILLIS;
+
+        List<CacheQuotaHint> requests = new ArrayList<>();
+        UserManager um = mContext.getSystemService(UserManager.class);
+        final List<UserInfo> users = um.getUsers();
+        final int userCount = users.size();
+        final PackageManager packageManager = mContext.getPackageManager();
+        for (int i = 0; i < userCount; i++) {
+            UserInfo info = users.get(i);
+            List<UsageStats> stats =
+                    mUsageStats.queryUsageStatsForUser(info.id, UsageStatsManager.INTERVAL_BEST,
+                            oneYearAgo, timeNow);
+            if (stats == null) {
+                continue;
+            }
+
+            for (UsageStats stat : stats) {
+                String packageName = stat.getPackageName();
+                try {
+                    // We need the app info to determine the uid and the uuid of the volume
+                    // where the app is installed.
+                    ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+                    requests.add(
+                            new CacheQuotaHint.Builder()
+                                    .setVolumeUuid(appInfo.volumeUuid)
+                                    .setUid(appInfo.uid)
+                                    .setUsageStats(stat)
+                                    .setQuota(CacheQuotaHint.QUOTA_NOT_SET)
+                                    .build());
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.w(TAG, "Unable to find package for quota calculation", e);
+                    continue;
+                }
+            }
+        }
+        return requests;
+    }
+
+    @Override
+    public void onResult(Bundle data) {
+        final List<CacheQuotaHint> processedRequests =
+                data.getParcelableArrayList(
+                        CacheQuotaService.REQUEST_LIST_KEY);
+        final int requestSize = processedRequests.size();
+        for (int i = 0; i < requestSize; i++) {
+            CacheQuotaHint request = processedRequests.get(i);
+            long proposedQuota = request.getQuota();
+            if (proposedQuota == CacheQuotaHint.QUOTA_NOT_SET) {
+                continue;
+            }
+
+            try {
+                int uid = request.getUid();
+                mInstaller.setAppQuota(request.getVolumeUuid(),
+                        UserHandle.getUserId(uid),
+                        UserHandle.getAppId(uid), proposedQuota);
+            } catch (Installer.InstallerException ex) {
+                Slog.w(TAG,
+                        "Failed to set cache quota for " + request.getUid(),
+                        ex);
+            }
+        }
+
+        disconnectService();
+    }
+
+    private void disconnectService() {
+        mContext.unbindService(mServiceConnection);
+        mServiceConnection = null;
+    }
+
+    private ComponentName getServiceComponentName() {
+        String packageName =
+                mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+        if (packageName == null) {
+            Slog.w(TAG, "could not access the cache quota service: no package!");
+            return null;
+        }
+
+        Intent intent = new Intent(CacheQuotaService.SERVICE_INTERFACE);
+        intent.setPackage(packageName);
+        ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+            Slog.w(TAG, "No valid components found.");
+            return null;
+        }
+        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index cf5cecda..01bd86d 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -176,7 +176,7 @@
             // we trigger any size changes, so it can swap surfaces
             // in to appropriate modes, or do as it wishes otherwise.
             if (!mReplacement) {
-                mTarget.onAnimationStart();
+                mTarget.onAnimationStart(mMoveToFullScreen);
             }
 
             // Immediately update the task bounds if they have to become larger, but preserve
@@ -263,7 +263,7 @@
          */
         boolean setPinnedStackSize(Rect bounds, Rect taskBounds);
 
-        void onAnimationStart();
+        void onAnimationStart(boolean toFullscreen);
 
         /**
          * Callback for the target to inform it that the animation has ended, so it can do some
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ab9a378..da5fcf3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -592,8 +592,15 @@
         return mStack != null && mStack.mStackId == PINNED_STACK_ID;
     }
 
+    /**
+     * When we are in a floating stack (Freeform, Pinned, ...) we calculate
+     * insets differently. However if we are animating to the fullscreen stack
+     * we need to begin calculating insets as if we were fullscreen, otherwise
+     * we will have a jump at the end.
+     */
     boolean isFloating() {
-        return StackId.tasksAreFloating(mStack.mStackId);
+        return StackId.tasksAreFloating(mStack.mStackId)
+            && !mStack.isBoundsAnimatingToFullscreen();
     }
 
     WindowState getTopVisibleAppMainWindow() {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index b9429f4..5f8b694 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -125,6 +125,7 @@
     // perfectly fit the region it would have been cropped to. We may also avoid certain logic we
     // would otherwise apply while resizing, while resizing in the bounds animating mode.
     private boolean mBoundsAnimating = false;
+    private boolean mBoundsAnimatingToFullscreen = false;
     private Rect mBoundsAnimationTarget = new Rect();
 
     // Temporary storage for the new bounds that should be used after the configuration change.
@@ -1443,9 +1444,10 @@
     }
 
     @Override  // AnimatesBounds
-    public void onAnimationStart() {
+    public void onAnimationStart(boolean toFullscreen) {
         synchronized (mService.mWindowMap) {
             mBoundsAnimating = true;
+            mBoundsAnimatingToFullscreen = toFullscreen;
         }
     }
 
@@ -1486,7 +1488,7 @@
         return StackId.hasMovementAnimations(mStackId);
     }
 
-    public boolean getForceScaleToStack() {
+    public boolean isForceScaled() {
         return mBoundsAnimating;
     }
 
@@ -1494,6 +1496,10 @@
         return mBoundsAnimating;
     }
 
+    public boolean isBoundsAnimatingToFullscreen() {
+        return mBoundsAnimating && mBoundsAnimatingToFullscreen;
+    }
+
     /** Returns true if a removal action is still being deferred. */
     boolean checkCompleteDeferredRemoval() {
         if (isAnimating()) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 02c52a7..03ebf19 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2003,7 +2003,7 @@
             if (viewVisibility == View.VISIBLE &&
                     (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
                             || !win.mAppToken.clientHidden)) {
-                result = relayoutVisibleWindow(outConfig, result, win, winAnimator, attrChanges,
+                result = win.relayoutVisibleWindow(outConfig, result, attrChanges,
                         oldVisibility);
                 try {
                     result = createSurfaceControl(outSurface, result, win, winAnimator);
@@ -2221,69 +2221,6 @@
         return result;
     }
 
-    private int relayoutVisibleWindow(Configuration outConfig, int result, WindowState win,
-            WindowStateAnimator winAnimator, int attrChanges, int oldVisibility) {
-        result |= !win.isVisibleLw() ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0;
-        if (win.mAnimatingExit) {
-            Slog.d(TAG, "relayoutVisibleWindow: " + win + " mAnimatingExit=true, mRemoveOnExit="
-                    + win.mRemoveOnExit + ", mDestroying=" + win.mDestroying);
-
-            winAnimator.cancelExitAnimationForNextAnimationLocked();
-            win.mAnimatingExit = false;
-        }
-        if (win.mDestroying) {
-            win.mDestroying = false;
-            mDestroySurface.remove(win);
-        }
-        if (oldVisibility == View.GONE) {
-            winAnimator.mEnterAnimationPending = true;
-        }
-
-        win.mLastVisibleLayoutRotation = mRotation;
-
-        winAnimator.mEnteringAnimation = true;
-        if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
-            win.prepareWindowToDisplayDuringRelayout(outConfig);
-        }
-        if ((attrChanges & LayoutParams.FORMAT_CHANGED) != 0) {
-            // If the format can't be changed in place, preserve the old surface until the app draws
-            // on the new one. This prevents blinking when we change elevation of freeform and
-            // pinned windows.
-            if (!winAnimator.tryChangeFormatInPlaceLocked()) {
-                winAnimator.preserveSurfaceLocked();
-                result |= RELAYOUT_RES_SURFACE_CHANGED
-                        | WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
-            }
-        }
-
-        // If we're starting a drag-resize, we'll be changing the surface size as well as
-        // notifying the client to render to with an offset from the surface's top-left.
-        if (win.isDragResizeChanged() || win.isResizedWhileNotDragResizing()) {
-            win.setDragResizing();
-            win.setResizedWhileNotDragResizing(false);
-            // We can only change top level windows to the full-screen surface when
-            // resizing (as we only have one full-screen surface). So there is no need
-            // to preserve and destroy windows which are attached to another, they
-            // will keep their surface and its size may change over time.
-            if (win.mHasSurface && !win.isChildWindow()) {
-                winAnimator.preserveSurfaceLocked();
-                result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
-            }
-        }
-        final boolean freeformResizing = win.isDragResizing()
-                && win.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
-        final boolean dockedResizing = win.isDragResizing()
-                && win.getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-        result |= freeformResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
-        result |= dockedResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
-        if (win.isAnimatingWithSavedSurface()) {
-            // If we're animating with a saved surface now, request client to report draw.
-            // We still need to know when the real thing is drawn.
-            result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
-        }
-        return result;
-    }
-
     public void performDeferredDestroyWindow(Session session, IWindow client) {
         long origId = Binder.clearCallingIdentity();
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index fe7dc5a..5151fa4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -40,6 +40,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -56,6 +57,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static android.view.WindowManagerPolicy.TRANSIT_ENTER;
 import static android.view.WindowManagerPolicy.TRANSIT_EXIT;
@@ -540,6 +545,12 @@
     private static final Region sEmptyRegion = new Region();
 
     /**
+     * Surface insets from the previous call to relayout(), used to track
+     * if we are changing the Surface insets.
+     */
+    final Rect mLastSurfaceInsets = new Rect();
+
+    /**
      * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
      * of z-order and 1 otherwise.
      */
@@ -1581,7 +1592,7 @@
             // Anyway we don't need to synchronize position and content updates for these
             // windows since they aren't at the base layer and could be moved around anyway.
             if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION &&
-                    !getTask().mStack.getBoundsAnimating() && !isGoneForLayoutLw() &&
+                    !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() &&
                     !getTask().inPinnedWorkspace()) {
                 setResizedWhileNotDragResizing(true);
             }
@@ -4298,6 +4309,78 @@
                 -mAttrs.surfaceInsets.bottom);
     }
 
+    boolean surfaceInsetsChanging() {
+        return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets);
+    }
+
+    int relayoutVisibleWindow(Configuration outConfig, int result,
+            int attrChanges, int oldVisibility) {
+        result |= !isVisibleLw() ? RELAYOUT_RES_FIRST_TIME : 0;
+        if (mAnimatingExit) {
+            Slog.d(TAG, "relayoutVisibleWindow: " + this + " mAnimatingExit=true, mRemoveOnExit="
+                    + mRemoveOnExit + ", mDestroying=" + mDestroying);
+
+            mWinAnimator.cancelExitAnimationForNextAnimationLocked();
+            mAnimatingExit = false;
+        }
+        if (mDestroying) {
+            mDestroying = false;
+            mService.mDestroySurface.remove(this);
+        }
+        if (oldVisibility == View.GONE) {
+            mWinAnimator.mEnterAnimationPending = true;
+        }
+
+        mLastVisibleLayoutRotation = mService.mRotation;
+
+        mWinAnimator.mEnteringAnimation = true;
+        if ((result & RELAYOUT_RES_FIRST_TIME) != 0) {
+            prepareWindowToDisplayDuringRelayout(outConfig);
+        }
+        if ((attrChanges & FORMAT_CHANGED) != 0) {
+            // If the format can't be changed in place, preserve the old surface until the app draws
+            // on the new one. This prevents blinking when we change elevation of freeform and
+            // pinned windows.
+            if (!mWinAnimator.tryChangeFormatInPlaceLocked()) {
+                mWinAnimator.preserveSurfaceLocked();
+                result |= RELAYOUT_RES_SURFACE_CHANGED
+                        | RELAYOUT_RES_FIRST_TIME;
+            }
+        }
+
+        // When we change the Surface size, in scenarios which may require changing
+        // the surface position in sync with the resize, we use a preserved surface
+        // so we can freeze it while waiting for the client to report draw on the newly
+        // sized surface.
+        if (isDragResizeChanged() || isResizedWhileNotDragResizing()
+                || surfaceInsetsChanging()) {
+            mLastSurfaceInsets.set(mAttrs.surfaceInsets);
+
+            setDragResizing();
+            setResizedWhileNotDragResizing(false);
+            // We can only change top level windows to the full-screen surface when
+            // resizing (as we only have one full-screen surface). So there is no need
+            // to preserve and destroy windows which are attached to another, they
+            // will keep their surface and its size may change over time.
+            if (mHasSurface && !isChildWindow()) {
+                mWinAnimator.preserveSurfaceLocked();
+                result |= RELAYOUT_RES_FIRST_TIME;
+            }
+        }
+        final boolean freeformResizing = isDragResizing()
+                && getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
+        final boolean dockedResizing = isDragResizing()
+                && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+        result |= freeformResizing ? RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
+        result |= dockedResizing ? RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
+        if (isAnimatingWithSavedSurface()) {
+            // If we're animating with a saved surface now, request client to report draw.
+            // We still need to know when the real thing is drawn.
+            result |= RELAYOUT_RES_FIRST_TIME;
+        }
+        return result;
+    }
+
     // TODO: Hack to work around the number of states AppWindowToken needs to access without having
     // access to its windows children. Need to investigate re-writing
     // {@link AppWindowToken#updateReportedVisibilityLocked} so this can be removed.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d2ea64c8..c0929cb 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1318,7 +1318,7 @@
         float surfaceWidth = mSurfaceController.getWidth();
         float surfaceHeight = mSurfaceController.getHeight();
 
-        if ((task != null && task.mStack.getForceScaleToStack()) || mForceScaleUntilResize) {
+        if (isForceScaled()) {
             int hInsets = w.getAttrs().surfaceInsets.left + w.getAttrs().surfaceInsets.right;
             int vInsets = w.getAttrs().surfaceInsets.top + w.getAttrs().surfaceInsets.bottom;
             if (!mForceScaleUntilResize) {
@@ -1328,8 +1328,8 @@
             task.mStack.getDimBounds(mTmpStackBounds);
             // We want to calculate the scaling based on the content area, not based on
             // the entire surface, so that we scale in sync with windows that don't have insets.
-            mExtraHScale = (mTmpStackBounds.width() - hInsets) / (float)(surfaceWidth - hInsets);
-            mExtraVScale = (mTmpStackBounds.height() - vInsets) / (float)(surfaceHeight - vInsets);
+            mExtraHScale = mTmpStackBounds.width() / (float)(surfaceWidth - hInsets);
+            mExtraVScale = mTmpStackBounds.height() / (float)(surfaceHeight - vInsets);
 
             // In the case of ForceScaleToStack we scale entire tasks together,
             // and so we need to scale our offsets relative to the task bounds
@@ -1353,6 +1353,7 @@
             // expose the whole window in buffer space, and not risk extending
             // past where the system would have cropped us
             clipRect = null;
+            finalClipRect = null;
 
             // Various surfaces in the scaled stack may resize at different times.
             // We need to ensure for each surface, that we disable transformation matrix
@@ -1952,4 +1953,16 @@
             }
         }
     }
+
+    /** The force-scaled state for a given window can persist past
+     * the state for it's stack as the windows complete resizing
+     * independently of one another.
+     */
+    boolean isForceScaled() {
+        final Task task = mWin.getTask();
+        if (task != null && task.mStack.isForceScaled()) {
+            return true;
+        }
+        return mForceScaleUntilResize;
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 6826975..96907c3 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -20,6 +20,7 @@
 import android.app.usage.ExternalStorageStats;
 import android.app.usage.IStorageStatsManager;
 import android.app.usage.StorageStats;
+import android.app.usage.UsageStatsManagerInternal;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -28,25 +29,35 @@
 import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.StatFs;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageEventListener;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.IoThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.storage.CacheQuotaStrategy;
 
 public class StorageStatsService extends IStorageStatsManager.Stub {
     private static final String TAG = "StorageStatsService";
 
     private static final String PROP_VERIFY_STORAGE = "fw.verify_storage";
 
+    private static final long DELAY_IN_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
+
     public static class Lifecycle extends SystemService {
         private StorageStatsService mService;
 
@@ -68,6 +79,7 @@
     private final StorageManager mStorage;
 
     private final Installer mInstaller;
+    private final H mHandler;
 
     public StorageStatsService(Context context) {
         mContext = Preconditions.checkNotNull(context);
@@ -80,6 +92,9 @@
         mInstaller.onStart();
         invalidateMounts();
 
+        mHandler = new H(IoThread.get().getLooper());
+        mHandler.sendEmptyMessageDelayed(H.MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+
         mStorage.registerListener(new StorageEventListener() {
             @Override
             public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -274,4 +289,63 @@
         res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
         return res;
     }
+
+    private class H extends Handler {
+        private static final int MSG_CHECK_STORAGE_DELTA = 100;
+        /**
+         * By only triggering a re-calculation after the storage has changed sizes, we can avoid
+         * recalculating quotas too often. Minimum change delta defines the percentage of change
+         * we need to see before we recalculate.
+         */
+        private static final double MINIMUM_CHANGE_DELTA = 0.05;
+        private static final boolean DEBUG = false;
+
+        private final StatFs mStats;
+        private long mPreviousBytes;
+        private double mMinimumThresholdBytes;
+
+        public H(Looper looper) {
+            super(looper);
+            // TODO: Handle all private volumes.
+            mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
+            mPreviousBytes = mStats.getFreeBytes();
+            mMinimumThresholdBytes = mStats.getTotalBytes() * MINIMUM_CHANGE_DELTA;
+            // TODO: Load cache quotas from a file to avoid re-doing work.
+        }
+
+        public void handleMessage(Message msg) {
+            if (DEBUG) {
+                Slog.v(TAG, ">>> handling " + msg.what);
+            }
+            switch (msg.what) {
+                case MSG_CHECK_STORAGE_DELTA: {
+                    long bytesDelta = Math.abs(mPreviousBytes - mStats.getFreeBytes());
+                    if (bytesDelta > mMinimumThresholdBytes) {
+                        mPreviousBytes = mStats.getFreeBytes();
+                        recalculateQuotas();
+                    }
+                    sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+                    break;
+                }
+                default:
+                    if (DEBUG) {
+                        Slog.v(TAG, ">>> default message case ");
+                    }
+                    return;
+            }
+        }
+
+        private void recalculateQuotas() {
+            if (DEBUG) {
+                Slog.v(TAG, ">>> recalculating quotas ");
+            }
+
+            UsageStatsManagerInternal usageStatsManager =
+                    LocalServices.getService(UsageStatsManagerInternal.class);
+            CacheQuotaStrategy strategy = new CacheQuotaStrategy(
+                    mContext, usageStatsManager, mInstaller);
+            // TODO: Save cache quotas to an XML file.
+            strategy.recalculateQuotas();
+        }
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 7a69803..3c743b5 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1603,5 +1603,12 @@
                 userStats.applyRestoredPayload(key, payload);
             }
         }
+
+        @Override
+        public List<UsageStats> queryUsageStatsForUser(
+                int userId, int intervalType, long beginTime, long endTime) {
+            return UsageStatsService.this.queryUsageStats(
+                    userId, intervalType, beginTime, endTime);
+        }
     }
 }