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);
+ }
}
}