Merge "AOD: Don't use doze states when unsupported"
diff --git a/Android.mk b/Android.mk
index b26543d..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 \
@@ -322,6 +323,7 @@
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IDockedStackListener.aidl \
core/java/android/view/IGraphicsStats.aidl \
+ core/java/android/view/IGraphicsStatsCallback.aidl \
core/java/android/view/IInputFilter.aidl \
core/java/android/view/IInputFilterHost.aidl \
core/java/android/view/IOnKeyguardExitResult.aidl \
@@ -708,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 \
@@ -1421,6 +1424,24 @@
include $(BUILD_JAVA_LIBRARY)
+# ==== c++ proto device library ==============================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libplatformprotos
+# b/34740546, work around clang-tidy segmentation fault.
+LOCAL_TIDY_CHECKS := -modernize*
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+LOCAL_PROTOC_FLAGS := \
+ --include_source_info \
+ -Iexternal/protobuf/src
+LOCAL_SRC_FILES := \
+ $(call all-proto-files-under, core/proto) \
+ $(call all-proto-files-under, libs/incident/proto)
+LOCAL_C_INCLUDES := \
+ $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
+LOCAL_EXPORT_C_INCLUDES := \
+ $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
+include $(BUILD_STATIC_LIBRARY)
+
# ==== c++ proto host library ==============================
include $(CLEAR_VARS)
LOCAL_MODULE := libplatformprotos
diff --git a/api/current.txt b/api/current.txt
index 85e153a..67cdc9c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31255,10 +31255,8 @@
method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
- method public long getCacheQuotaBytes();
- method public long getCacheSizeBytes();
- method public long getExternalCacheQuotaBytes();
- method public long getExternalCacheSizeBytes();
+ method public long getCacheQuotaBytes(java.io.File);
+ method public long getCacheSizeBytes(java.io.File);
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
@@ -45639,6 +45637,7 @@
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
method public abstract void setLongClickable(boolean);
+ method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
@@ -45647,7 +45646,6 @@
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
- field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
diff --git a/api/removed.txt b/api/removed.txt
index ab22b6e..e467811 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -180,6 +180,10 @@
package android.os.storage {
public class StorageManager {
+ method public deprecated long getCacheQuotaBytes();
+ method public deprecated long getCacheSizeBytes();
+ method public deprecated long getExternalCacheQuotaBytes();
+ method public deprecated long getExternalCacheSizeBytes();
method public android.os.storage.StorageVolume getPrimaryVolume();
method public android.os.storage.StorageVolume[] getVolumeList();
}
diff --git a/api/system-current.txt b/api/system-current.txt
index e0e2c35..dcd2103 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6942,14 +6942,22 @@
field public static final java.lang.String EXTRA_LOG_EVENT_ID = "android.app.backup.extra.LOG_EVENT_ID";
field public static final java.lang.String EXTRA_LOG_EVENT_PACKAGE_NAME = "android.app.backup.extra.LOG_EVENT_PACKAGE_NAME";
field public static final java.lang.String EXTRA_LOG_EVENT_PACKAGE_VERSION = "android.app.backup.extra.LOG_EVENT_PACKAGE_VERSION";
+ field public static final java.lang.String EXTRA_LOG_OLD_VERSION = "android.app.backup.extra.LOG_OLD_VERSION";
field public static final int LOG_EVENT_CATEGORY_AGENT = 2; // 0x2
field public static final int LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY = 3; // 0x3
field public static final int LOG_EVENT_CATEGORY_TRANSPORT = 1; // 0x1
+ field public static final int LOG_EVENT_ID_APP_HAS_NO_AGENT = 28; // 0x1c
+ field public static final int LOG_EVENT_ID_CANT_FIND_AGENT = 30; // 0x1e
field public static final int LOG_EVENT_ID_FULL_BACKUP_TIMEOUT = 4; // 0x4
field public static final int LOG_EVENT_ID_FULL_RESTORE_TIMEOUT = 45; // 0x2d
field public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT = 21; // 0x15
field public static final int LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT = 31; // 0x1f
field public static final int LOG_EVENT_ID_NO_PACKAGES = 49; // 0x31
+ field public static final int LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE = 22; // 0x16
+ field public static final int LOG_EVENT_ID_PACKAGE_NOT_FOUND = 12; // 0xc
+ field public static final int LOG_EVENT_ID_PACKAGE_NOT_PRESENT = 26; // 0x1a
+ field public static final int LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT = 15; // 0xf
+ field public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36; // 0x24
}
public abstract class BackupObserver {
@@ -7180,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();
@@ -34139,10 +34176,8 @@
method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
- method public long getCacheQuotaBytes();
- method public long getCacheSizeBytes();
- method public long getExternalCacheQuotaBytes();
- method public long getExternalCacheSizeBytes();
+ method public long getCacheQuotaBytes(java.io.File);
+ method public long getCacheSizeBytes(java.io.File);
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
@@ -49144,6 +49179,7 @@
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
method public abstract void setLongClickable(boolean);
+ method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
@@ -49152,7 +49188,6 @@
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
- field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 1ba26f5..6773112 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -174,6 +174,10 @@
package android.os.storage {
public class StorageManager {
+ method public deprecated long getCacheQuotaBytes();
+ method public deprecated long getCacheSizeBytes();
+ method public deprecated long getExternalCacheQuotaBytes();
+ method public deprecated long getExternalCacheSizeBytes();
method public android.os.storage.StorageVolume getPrimaryVolume();
method public android.os.storage.StorageVolume[] getVolumeList();
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 5e08f1a..319d9f3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31374,10 +31374,8 @@
method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
- method public long getCacheQuotaBytes();
- method public long getCacheSizeBytes();
- method public long getExternalCacheQuotaBytes();
- method public long getExternalCacheSizeBytes();
+ method public long getCacheQuotaBytes(java.io.File);
+ method public long getCacheSizeBytes(java.io.File);
method public java.lang.String getMountedObbPath(java.lang.String);
method public android.os.storage.StorageVolume getPrimaryStorageVolume();
method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
@@ -46001,6 +45999,7 @@
method public abstract void setHint(java.lang.CharSequence);
method public abstract void setId(int, java.lang.String, java.lang.String, java.lang.String);
method public abstract void setLongClickable(boolean);
+ method public abstract void setSanitized(boolean);
method public abstract void setSelected(boolean);
method public abstract void setText(java.lang.CharSequence);
method public abstract void setText(java.lang.CharSequence, int, int);
@@ -46009,7 +46008,6 @@
method public abstract void setTransformation(android.graphics.Matrix);
method public abstract void setUrl(java.lang.String);
method public abstract void setVisibility(int);
- field public static final int AUTO_FILL_FLAG_SANITIZED = 1; // 0x1
}
public final class ViewStub extends android.view.View {
diff --git a/api/test-removed.txt b/api/test-removed.txt
index ab22b6e..e467811 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -180,6 +180,10 @@
package android.os.storage {
public class StorageManager {
+ method public deprecated long getCacheQuotaBytes();
+ method public deprecated long getCacheSizeBytes();
+ method public deprecated long getExternalCacheQuotaBytes();
+ method public deprecated long getExternalCacheSizeBytes();
method public android.os.storage.StorageVolume getPrimaryVolume();
method public android.os.storage.StorageVolume[] getVolumeList();
}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 7965fc3..1aef363 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -65,6 +65,7 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.content.PackageHelper;
import com.android.internal.util.ArrayUtils;
@@ -402,6 +403,7 @@
* The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
*/
private int runInstall() throws RemoteException {
+ long startedTime = SystemClock.elapsedRealtime();
final InstallParams params = makeInstallParams();
final String inPath = nextArg();
if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
@@ -435,10 +437,12 @@
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
- if (doCommitSession(sessionId, false /*logSuccess*/)
- != PackageInstaller.STATUS_SUCCESS) {
+ Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
+ if (status.second != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
+ Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
+ - startedTime) + " ms");
System.out.println("Success");
return 0;
} finally {
@@ -456,7 +460,7 @@
private int runInstallCommit() throws RemoteException {
final int sessionId = Integer.parseInt(nextArg());
- return doCommitSession(sessionId, true /*logSuccess*/);
+ return doCommitSession(sessionId, true /*logSuccess*/).second;
}
private int runInstallCreate() throws RemoteException {
@@ -650,7 +654,8 @@
}
}
- private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
+ private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
+ throws RemoteException {
PackageInstaller.Session session = null;
try {
session = new PackageInstaller.Session(
@@ -670,7 +675,7 @@
System.err.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
}
- return status;
+ return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
} finally {
IoUtils.closeQuietly(session);
}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 739015f..8ad7810 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -2807,40 +2807,56 @@
public void dispatchCreate() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.CREATED, false);
+ mExecutingActions = false;
}
public void dispatchActivityCreated() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.ACTIVITY_CREATED, false);
+ mExecutingActions = false;
}
public void dispatchStart() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.STARTED, false);
+ mExecutingActions = false;
}
public void dispatchResume() {
mStateSaved = false;
+ mExecutingActions = true;
moveToState(Fragment.RESUMED, false);
+ mExecutingActions = false;
}
public void dispatchPause() {
+ mExecutingActions = true;
moveToState(Fragment.STARTED, false);
+ mExecutingActions = false;
}
public void dispatchStop() {
+ mExecutingActions = true;
moveToState(Fragment.STOPPED, false);
+ mExecutingActions = false;
}
public void dispatchDestroyView() {
+ mExecutingActions = true;
moveToState(Fragment.CREATED, false);
+ mExecutingActions = false;
}
public void dispatchDestroy() {
mDestroyed = true;
execPendingActions();
+ mExecutingActions = true;
moveToState(Fragment.INITIALIZING, false);
+ mExecutingActions = false;
mHost = null;
mContainer = null;
mParent = null;
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 8d385db..13d0f03 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -419,7 +419,7 @@
mDisplayId = root.getDisplayId();
mRoot = new ViewNode();
- ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false, 0);
+ ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false);
if ((root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
if (forAutoFill) {
// NOTE: flags are currently not supported, hence 0
@@ -1187,11 +1187,10 @@
final ViewNode mNode;
final boolean mAsync;
- ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async, int flags) {
+ ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) {
mAssist = assist;
mNode = node;
mAsync = async;
- mNode.mSanitized = (flags & AUTO_FILL_FLAG_SANITIZED) != 0;
}
@Override
@@ -1429,16 +1428,15 @@
ViewNode node = new ViewNode();
setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
- return new ViewNodeBuilder(mAssist, node, false, flags);
+ return new ViewNodeBuilder(mAssist, node, false);
}
- private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId,
- int flags) {
+ private ViewStructure asyncNewChild(int index, boolean forAutoFill, int virtualId) {
synchronized (mAssist) {
ViewNode node = new ViewNode();
setAutoFillId(node, forAutoFill, virtualId);
mNode.mChildren[index] = node;
- ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true, flags);
+ ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true);
mAssist.mPendingAsyncChildren.add(builder);
return builder;
}
@@ -1457,12 +1455,12 @@
@Override
public ViewStructure asyncNewChild(int index) {
- return asyncNewChild(index, false, 0, 0);
+ return asyncNewChild(index, false, 0);
}
@Override
public ViewStructure asyncNewChild(int index, int virtualId, int flags) {
- return asyncNewChild(index, true, virtualId, flags);
+ return asyncNewChild(index, true, virtualId);
}
@Override
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index 099878b..d2a623e 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -54,15 +54,31 @@
public static final String EXTRA_LOG_EVENT_CATEGORY =
"android.app.backup.extra.LOG_EVENT_CATEGORY";
+ /**
+ * string: when we have event of id LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER we send the version
+ * of the backup.
+ */
+ public static final String EXTRA_LOG_OLD_VERSION =
+ "android.app.backup.extra.LOG_OLD_VERSION";
+
// TODO complete this list with all log messages. And document properly.
public static final int LOG_EVENT_ID_FULL_BACKUP_TIMEOUT = 4;
+ public static final int LOG_EVENT_ID_PACKAGE_NOT_FOUND = 12;
+ public static final int LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT = 15;
public static final int LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT = 21;
+ public static final int LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE = 22;
+ public static final int LOG_EVENT_ID_PACKAGE_NOT_PRESENT = 26;
+ public static final int LOG_EVENT_ID_APP_HAS_NO_AGENT = 28;
+ public static final int LOG_EVENT_ID_CANT_FIND_AGENT = 30;
public static final int LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT = 31;
+ public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36;
public static final int LOG_EVENT_ID_FULL_RESTORE_TIMEOUT = 45;
public static final int LOG_EVENT_ID_NO_PACKAGES = 49;
+
+
/**
* This method will be called each time something important happens on BackupManager.
*
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/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl
index 62ebf60..f4c18dd 100644
--- a/core/java/android/app/usage/IStorageStatsManager.aidl
+++ b/core/java/android/app/usage/IStorageStatsManager.aidl
@@ -21,6 +21,7 @@
/** {@hide} */
interface IStorageStatsManager {
+ boolean isQuotaSupported(String volumeUuid, String callingPackage);
long getTotalBytes(String volumeUuid, String callingPackage);
long getFreeBytes(String volumeUuid, String callingPackage);
StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage);
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index 9d30771..7d4efb9 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -46,6 +46,15 @@
mService = Preconditions.checkNotNull(service);
}
+ /** {@hide} */
+ public boolean isQuotaSupported(String volumeUuid) {
+ try {
+ return mService.isQuotaSupported(volumeUuid, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Return the total space on the requested storage volume.
* <p>
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/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 7c015de..280fa2c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -432,14 +432,13 @@
*/
public static boolean contains(File dir, File file) {
if (dir == null || file == null) return false;
+ return contains(dir.getAbsolutePath(), file.getAbsolutePath());
+ }
- String dirPath = dir.getAbsolutePath();
- String filePath = file.getAbsolutePath();
-
+ public static boolean contains(String dirPath, String filePath) {
if (dirPath.equals(filePath)) {
return true;
}
-
if (!dirPath.endsWith("/")) {
dirPath += "/";
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7702c17..8882672 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -546,6 +546,25 @@
}
/**
+ * Return the filesystem path of the real file on disk that is represented
+ * by the given {@link FileDescriptor}.
+ *
+ * @hide
+ */
+ public static File getFile(FileDescriptor fd) throws IOException {
+ try {
+ final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
+ if (OsConstants.S_ISREG(Os.stat(path).st_mode)) {
+ return new File(path);
+ } else {
+ throw new IOException("Not a regular file: " + path);
+ }
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
* Retrieve the actual FileDescriptor associated with this object.
*
* @return Returns the FileDescriptor associated with this object.
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 9e35bf6..2cc4b71 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -293,6 +293,6 @@
ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
long getCacheSizeBytes(String volumeUuid, int uid) = 76;
- long getAllocatableBytes(String path, int flags) = 77;
- void allocateBytes(String path, long bytes, int flags) = 78;
+ long getAllocatableBytes(String volumeUuid, int flags) = 77;
+ void allocateBytes(String volumeUuid, long bytes, int flags) = 78;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2a3c03d..e070c6e 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,7 @@
package android.os.storage;
+import static android.net.TrafficStats.GB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
import android.annotation.IntDef;
@@ -675,6 +676,36 @@
}
/** {@hide} */
+ public @Nullable String findUuidForPath(File path) {
+ Preconditions.checkNotNull(path);
+ final String pathString = path.getAbsolutePath();
+ if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) {
+ return StorageManager.UUID_PRIVATE_INTERNAL;
+ }
+ try {
+ for (VolumeInfo vol : mStorageManager.getVolumes(0)) {
+ if (vol.path != null && FileUtils.contains(vol.path, pathString)) {
+ // TODO: verify that emulated adopted devices have UUID of
+ // underlying volume
+ return vol.fsUuid;
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ throw new IllegalStateException("Failed to find a storage device for " + path);
+ }
+
+ /** {@hide} */
+ public @Nullable File findPathForUuid(String volumeUuid) {
+ final VolumeInfo vol = findVolumeByQualifiedUuid(volumeUuid);
+ if (vol != null) {
+ return vol.getPath();
+ }
+ throw new IllegalStateException("Failed to find a storage device for " + volumeUuid);
+ }
+
+ /** {@hide} */
public @NonNull List<VolumeInfo> getVolumes() {
try {
return Arrays.asList(mStorageManager.getVolumes(0));
@@ -1069,9 +1100,12 @@
throw new IllegalStateException("Missing primary storage");
}
- /** {@hide} */
- private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
+ private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
+
+ private static final int DEFAULT_CACHE_PERCENTAGE = 10;
+ private static final long DEFAULT_CACHE_MAX_BYTES = 5 * GB_IN_BYTES;
+
private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
/**
@@ -1102,6 +1136,23 @@
}
/**
+ * Return the minimum number of bytes of storage on the device that should
+ * be reserved for cached data.
+ *
+ * @hide
+ */
+ public long getStorageCacheBytes(File path) {
+ final long cachePercent = Settings.Global.getInt(mResolver,
+ Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, DEFAULT_CACHE_PERCENTAGE);
+ final long cacheBytes = (path.getTotalSpace() * cachePercent) / 100;
+
+ final long maxCacheBytes = Settings.Global.getLong(mResolver,
+ Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, DEFAULT_CACHE_MAX_BYTES);
+
+ return Math.min(cacheBytes, maxCacheBytes);
+ }
+
+ /**
* Return the number of available bytes at which the given path is
* considered full.
*
@@ -1409,40 +1460,37 @@
}
/**
- * Return quota size in bytes for cached data belonging to the calling app.
+ * Return quota size in bytes for all cached data belonging to the calling
+ * app on the filesystem that hosts the given path.
* <p>
* If your app goes above this quota, your cached files will be some of the
* first to be deleted when additional disk space is needed. Conversely, if
* your app stays under this quota, your cached files will be some of the
* last to be deleted when additional disk space is needed.
* <p>
- * This quota may change over time depending on how frequently the user
+ * This quota will change over time depending on how frequently the user
* interacts with your app, and depending on how much disk space is used.
- * <p>
- * Cached data tracked by this method always includes
- * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
- * it also includes {@link Context#getExternalCacheDir()} if the primary
- * shared/external storage is hosted on the same storage device as your
- * private data.
* <p class="note">
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
* then cached data for all packages in your shared UID is tracked together
* as a single unit.
* </p>
*
- * @see #getCacheSizeBytes()
+ * @see #getCacheSizeBytes(File)
*/
- public long getCacheQuotaBytes() {
+ public long getCacheQuotaBytes(File path) {
try {
+ final String volumeUuid = findUuidForPath(path);
final ApplicationInfo app = mContext.getApplicationInfo();
- return mStorageManager.getCacheQuotaBytes(app.volumeUuid, app.uid);
+ return mStorageManager.getCacheQuotaBytes(volumeUuid, app.uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Return total size in bytes of cached data belonging to the calling app.
+ * Return total size in bytes of all cached data belonging to the calling
+ * app on the filesystem that hosts the given path.
* <p>
* Cached data tracked by this method always includes
* {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
@@ -1457,67 +1505,38 @@
*
* @see #getCacheQuotaBytes()
*/
- public long getCacheSizeBytes() {
+ public long getCacheSizeBytes(File path) {
try {
+ final String volumeUuid = findUuidForPath(path);
final ApplicationInfo app = mContext.getApplicationInfo();
- return mStorageManager.getCacheSizeBytes(app.volumeUuid, app.uid);
+ return mStorageManager.getCacheSizeBytes(volumeUuid, app.uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- /**
- * Return quota size in bytes for cached data on primary shared/external
- * storage belonging to the calling app.
- * <p>
- * If primary shared/external storage is hosted on the same storage device
- * as your private data, this method will return -1, since all data stored
- * under {@link Context#getExternalCacheDir()} will be counted under
- * {@link #getCacheQuotaBytes()}.
- * <p class="note">
- * Note: if your app uses the {@code android:sharedUserId} manifest feature,
- * then cached data for all packages in your shared UID is tracked together
- * as a single unit.
- * </p>
- */
+ /** @removed */
+ @Deprecated
+ public long getCacheQuotaBytes() {
+ return getCacheQuotaBytes(mContext.getCacheDir());
+ }
+
+ /** @removed */
+ @Deprecated
+ public long getCacheSizeBytes() {
+ return getCacheSizeBytes(mContext.getCacheDir());
+ }
+
+ /** @removed */
+ @Deprecated
public long getExternalCacheQuotaBytes() {
- final ApplicationInfo app = mContext.getApplicationInfo();
- final String primaryUuid = getPrimaryStorageUuid();
- if (Objects.equals(app.volumeUuid, primaryUuid)) {
- return -1;
- }
- try {
- return mStorageManager.getCacheQuotaBytes(primaryUuid, app.uid);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getCacheQuotaBytes(mContext.getExternalCacheDir());
}
- /**
- * Return total size in bytes of cached data on primary shared/external
- * storage belonging to the calling app.
- * <p>
- * If primary shared/external storage is hosted on the same storage device
- * as your private data, this method will return -1, since all data stored
- * under {@link Context#getExternalCacheDir()} will be counted under
- * {@link #getCacheQuotaBytes()}.
- * <p class="note">
- * Note: if your app uses the {@code android:sharedUserId} manifest feature,
- * then cached data for all packages in your shared UID is tracked together
- * as a single unit.
- * </p>
- */
+ /** @removed */
+ @Deprecated
public long getExternalCacheSizeBytes() {
- final ApplicationInfo app = mContext.getApplicationInfo();
- final String primaryUuid = getPrimaryStorageUuid();
- if (Objects.equals(app.volumeUuid, primaryUuid)) {
- return -1;
- }
- try {
- return mStorageManager.getCacheSizeBytes(primaryUuid, app.uid);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getCacheSizeBytes(mContext.getExternalCacheDir());
}
/**
@@ -1551,28 +1570,30 @@
* Return the maximum number of new bytes that your app can allocate for
* itself using {@link #allocateBytes(File, long, int)} at the given path.
* This value is typically larger than {@link File#getUsableSpace()}, since
- * the system may automatically delete cached files to satisfy your request.
+ * the system may be willing to delete cached files to satisfy an allocation
+ * request.
* <p>
* This method is best used as a pre-flight check, such as deciding if there
* is enough space to store an entire music album before you allocate space
* for each audio file in the album. Attempts to allocate disk space beyond
- * this value will fail.
+ * the returned value will fail.
* <p class="note">
* Note: if your app uses the {@code android:sharedUserId} manifest feature,
* then allocatable space for all packages in your shared UID is tracked
* together as a single unit.
* </p>
*
- * @param file the directory where you're considering allocating disk space,
+ * @param path the path where you're considering allocating disk space,
* since allocatable space can vary widely depending on the
* underlying storage device.
* @param flags to apply to the request.
* @return the maximum number of new bytes that the calling app can allocate
* using {@link #allocateBytes(File, long, int)}.
*/
- public long getAllocatableBytes(File file, @AllocateFlags int flags) throws IOException {
+ public long getAllocatableBytes(File path, @AllocateFlags int flags) throws IOException {
try {
- return mStorageManager.getAllocatableBytes(file.getAbsolutePath(), flags);
+ final String volumeUuid = findUuidForPath(path);
+ return mStorageManager.getAllocatableBytes(volumeUuid, flags);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
@@ -1594,14 +1615,15 @@
* {@link #allocateBytes(FileDescriptor, long, int)} which will guarantee
* that bytes are allocated to an opened file.
*
- * @param file the directory where you'd like to allocate disk space.
+ * @param path the path where you'd like to allocate disk space.
* @param bytes the number of bytes to allocate.
* @param flags to apply to the request.
* @see #getAllocatableBytes(File, int)
*/
- public void allocateBytes(File file, long bytes, @AllocateFlags int flags) throws IOException {
+ public void allocateBytes(File path, long bytes, @AllocateFlags int flags) throws IOException {
try {
- mStorageManager.allocateBytes(file.getAbsolutePath(), bytes, flags);
+ final String volumeUuid = findUuidForPath(path);
+ mStorageManager.allocateBytes(volumeUuid, bytes, flags);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
} catch (RemoteException e) {
@@ -1610,37 +1632,39 @@
}
/**
- * Allocate the requested number of bytes for your application to use at the
- * given path. This will cause the system to delete any cached files
+ * Allocate the requested number of bytes for your application to use in the
+ * given open file. This will cause the system to delete any cached files
* necessary to satisfy your request.
* <p>
* Attempts to allocate disk space beyond the value returned by
* {@link #getAllocatableBytes(File, int)} will fail.
* <p>
- * This method guarantees that bytes are allocated to the opened file,
- * otherwise it will throw if fast allocation not possible. Fast allocation
- * is typically only supported in private app data directories, and on
- * shared/external storage devices which are emulated.
+ * This method guarantees that bytes have been allocated to the opened file,
+ * otherwise it will throw if fast allocation is not possible. Fast
+ * allocation is typically only supported in private app data directories,
+ * and on shared/external storage devices which are emulated.
*
- * @param fd the directory where you'd like to allocate disk space.
- * @param bytes the number of bytes to allocate.
+ * @param fd the open file that you'd like to allocate disk space for.
+ * @param bytes the number of bytes to allocate. This is the desired final
+ * size of the open file.
* @param flags to apply to the request.
* @see #getAllocatableBytes(File, int)
* @see Environment#isExternalStorageEmulated(File)
*/
public void allocateBytes(FileDescriptor fd, long bytes, @AllocateFlags int flags)
throws IOException {
- final File file;
- try {
- file = new File(Os.readlink("/proc/self/fd/" + fd.getInt$()));
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
+ final File file = ParcelFileDescriptor.getFile(fd);
for (int i = 0; i < 3; i++) {
- allocateBytes(file, bytes, flags);
-
try {
+ final long haveBytes = Os.fstat(fd).st_blocks * 512;
+ final long needBytes = bytes - haveBytes;
+
+ if (needBytes > 0) {
+ allocateBytes(file, needBytes, flags);
+ }
+
Os.posix_fallocate(fd, 0, bytes);
+ return;
} catch (ErrnoException e) {
if (e.errno == OsConstants.ENOSPC) {
Log.w(TAG, "Odd, not enough space; let's try again?");
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c9b1c9c..ebc5b88 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8578,6 +8578,24 @@
SYS_STORAGE_FULL_THRESHOLD_BYTES = "sys_storage_full_threshold_bytes";
/**
+ * Minimum percentage of storage on the device that is reserved for
+ * cached data.
+ *
+ * @hide
+ */
+ public static final String
+ SYS_STORAGE_CACHE_PERCENTAGE = "sys_storage_cache_percentage";
+
+ /**
+ * Maximum bytes of storage on the device that is reserved for cached
+ * data.
+ *
+ * @hide
+ */
+ public static final String
+ SYS_STORAGE_CACHE_MAX_BYTES = "sys_storage_cache_max_bytes";
+
+ /**
* The maximum reconnect delay for short network outages or when the
* network is suspended due to phone use.
*
diff --git a/core/java/android/view/IGraphicsStats.aidl b/core/java/android/view/IGraphicsStats.aidl
index c235eb2..e6b750b 100644
--- a/core/java/android/view/IGraphicsStats.aidl
+++ b/core/java/android/view/IGraphicsStats.aidl
@@ -17,10 +17,11 @@
package android.view;
import android.os.ParcelFileDescriptor;
+import android.view.IGraphicsStatsCallback;
/**
* @hide
*/
interface IGraphicsStats {
- ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token);
+ ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback callback);
}
diff --git a/core/java/android/view/IGraphicsStatsCallback.aidl b/core/java/android/view/IGraphicsStatsCallback.aidl
new file mode 100644
index 0000000..f70e141
--- /dev/null
+++ b/core/java/android/view/IGraphicsStatsCallback.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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.view;
+
+/**
+ * @hide
+ */
+oneway interface IGraphicsStatsCallback {
+ void onRotateGraphicsStatsBuffer();
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 4ceb236..c66bf874 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -25,9 +25,9 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
-import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
import android.util.Log;
@@ -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:
@@ -248,10 +260,10 @@
*
* @return A threaded renderer backed by OpenGL.
*/
- public static ThreadedRenderer create(Context context, boolean translucent) {
+ public static ThreadedRenderer create(Context context, boolean translucent, String name) {
ThreadedRenderer renderer = null;
if (isAvailable()) {
- renderer = new ThreadedRenderer(context, translucent);
+ renderer = new ThreadedRenderer(context, translucent, name);
}
return renderer;
}
@@ -275,10 +287,6 @@
nOverrideProperty(name, value);
}
- public static void dumpProfileData(byte[] data, FileDescriptor fd) {
- nDumpProfileData(data, fd);
- }
-
// Keep in sync with DrawFrameTask.h SYNC_* flags
// Nothing interesting to report
private static final int SYNC_OK = 0;
@@ -334,7 +342,7 @@
private boolean mEnabled;
private boolean mRequested = true;
- ThreadedRenderer(Context context, boolean translucent) {
+ ThreadedRenderer(Context context, boolean translucent, String name) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
@@ -348,6 +356,7 @@
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
+ nSetName(mNativeProxy, name);
ProcessInitializer.sInstance.init(context, mNativeProxy);
@@ -815,15 +824,6 @@
}
/**
- * Optional, sets the name of the renderer. Useful for debugging purposes.
- *
- * @param name The name of this renderer, can be null
- */
- void setName(String name) {
- nSetName(mNativeProxy, name);
- }
-
- /**
* Blocks until all previously queued work has completed.
*/
void fence() {
@@ -884,20 +884,29 @@
private static class ProcessInitializer {
static ProcessInitializer sInstance = new ProcessInitializer();
- private static IBinder sProcToken;
private boolean mInitialized = false;
+ private Context mAppContext;
+ private IGraphicsStats mGraphicsStatsService;
+ private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
+ @Override
+ public void onRotateGraphicsStatsBuffer() throws RemoteException {
+ rotateBuffer();
+ }
+ };
+
private ProcessInitializer() {}
synchronized void init(Context context, long renderProxy) {
if (mInitialized) return;
mInitialized = true;
+ mAppContext = context.getApplicationContext();
initSched(context, renderProxy);
- initGraphicsStats(context, renderProxy);
+ initGraphicsStats();
}
- private static void initSched(Context context, long renderProxy) {
+ private void initSched(Context context, long renderProxy) {
try {
int tid = nGetRenderThreadTid(renderProxy);
ActivityManager.getService().setRenderThread(tid);
@@ -906,17 +915,28 @@
}
}
- private static void initGraphicsStats(Context context, long renderProxy) {
+ private void initGraphicsStats() {
try {
IBinder binder = ServiceManager.getService("graphicsstats");
if (binder == null) return;
- IGraphicsStats graphicsStatsService = IGraphicsStats.Stub
- .asInterface(binder);
- sProcToken = new Binder();
- final String pkg = context.getApplicationInfo().packageName;
- ParcelFileDescriptor pfd = graphicsStatsService.
- requestBufferForProcess(pkg, sProcToken);
- nSetProcessStatsBuffer(renderProxy, pfd.getFd());
+ mGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder);
+ requestBuffer();
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
+ }
+ }
+
+ private void rotateBuffer() {
+ nRotateProcessStatsBuffer();
+ requestBuffer();
+ }
+
+ private void requestBuffer() {
+ try {
+ final String pkg = mAppContext.getApplicationInfo().packageName;
+ ParcelFileDescriptor pfd = mGraphicsStatsService
+ .requestBufferForProcess(pkg, mGraphicsStatsCallback);
+ nSetProcessStatsBuffer(pfd.getFd());
pfd.close();
} catch (Throwable t) {
Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t);
@@ -936,7 +956,8 @@
static native void setupShadersDiskCache(String cacheFile);
- private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
+ private static native void nRotateProcessStatsBuffer();
+ private static native void nSetProcessStatsBuffer(int fd);
private static native int nGetRenderThreadTid(long nativeProxy);
private static native long nCreateRootRenderNode();
@@ -981,7 +1002,6 @@
private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
@DumpFlags int dumpFlags);
- private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
private static native void nAddRenderNode(long nativeProxy, long rootRenderNode,
boolean placeFront);
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 5d01b416..f16fcc9 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -35,7 +35,7 @@
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* dips
*/
- private static final int SCROLL_BAR_SIZE = 10;
+ private static final int SCROLL_BAR_SIZE = 4;
/**
* Duration of the fade when scrollbars fade away in milliseconds
@@ -346,7 +346,8 @@
mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
- mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
+ mScrollbarSize = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_scrollbarSize);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6cdd483..c81e938 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -887,9 +887,9 @@
final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0
|| insets.top != 0 || insets.bottom != 0;
final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;
- mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent);
+ mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
+ attrs.getTitle().toString());
if (mAttachInfo.mThreadedRenderer != null) {
- mAttachInfo.mThreadedRenderer.setName(attrs.getTitle().toString());
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 9ce23e6..bc2725f 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -31,16 +31,6 @@
public abstract class ViewStructure {
/**
- * Flag used when adding virtual views for auto-fill, it indicates the contents of the view
- * (such as * {@link android.app.assist.AssistStructure.ViewNode#getText()} and
- * {@link android.app.assist.AssistStructure.ViewNode#getAutoFillValue()})
- * can be passed to the {@link
- * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
- * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} call.
- */
- public static final int AUTO_FILL_FLAG_SANITIZED = 0x1;
-
- /**
* Set the identifier for this view.
*
* @param id The view's identifier, as per {@link View#getId View.getId()}.
@@ -278,7 +268,7 @@
*
* @param index child index
* @param virtualId id identifying the virtual child inside the custom view.
- * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
+ * @param flags currently {@code 0}.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
public abstract ViewStructure newChild(int index, int virtualId, int flags);
@@ -299,7 +289,7 @@
*
* @param index child index
* @param virtualId id identifying the virtual child inside the custom view.
- * @param flags currently {@code 0} or {@link #AUTO_FILL_FLAG_SANITIZED}.
+ * @param flags currently {@code 0}.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
public abstract ViewStructure asyncNewChild(int index, int virtualId, int flags);
@@ -317,12 +307,19 @@
public abstract void setAutoFillValue(AutoFillValue value);
/**
- * @hide
+ * Marks this node as sanitized so its content are sent on {@link
+ * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)}.
*
- * TODO(b/33197203, b/33269702): temporary set it as not sanitized until
- * AssistStructure automaticaly sets sanitization based on text coming from resources
+ * <p>Only nodes that does not have PII (Personally Identifiable Information - sensitive data
+ * such as email addresses, credit card numbers, passwords, etc...) should be marked
+ * as sanitized; a good rule of thumb is to mark as sanitized nodes whose value were statically
+ * set from resources.
+ *
+ * <p>Should only be set when the node is used for AutoFill purposes - it will be ignored
+ * when used for Assist.
*/
- public abstract void setSanitized(boolean sensitive);
+ public abstract void setSanitized(boolean sanitized);
/**
* Call when done populating a {@link ViewStructure} returned by
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 051ffee..bfaddaf 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -730,6 +730,10 @@
// Watcher used to notify changes to auto-fill manager.
private AutoFillChangeWatcher mAutoFillChangeWatcher;
+ // Indicates whether the text was set from resources or dynamically, so it can be used to
+ // sanitize auto-fill request.
+ private boolean mTextFromResource = false;
+
/**
* Kick-start the font cache for the zygote process (to pay the cost of
* initializing freetype for our default font only once).
@@ -948,6 +952,8 @@
attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
int n = a.getIndexCount();
+
+ boolean fromResourceId = false;
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
@@ -1089,6 +1095,7 @@
break;
case com.android.internal.R.styleable.TextView_text:
+ fromResourceId = true;
text = a.getText(attr);
break;
@@ -1567,6 +1574,10 @@
}
setText(text, bufferType);
+ if (fromResourceId) {
+ mTextFromResource = true;
+ }
+
if (hint != null) setHint(hint);
/*
@@ -5067,6 +5078,7 @@
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
+ mTextFromResource = false;
if (text == null) {
text = "";
}
@@ -5301,6 +5313,7 @@
@android.view.RemotableViewMethod
public final void setText(@StringRes int resid) {
setText(getContext().getResources().getText(resid));
+ mTextFromResource = true;
}
/**
@@ -5327,6 +5340,7 @@
*/
public final void setText(@StringRes int resid, BufferType type) {
setText(getContext().getResources().getText(resid), type);
+ mTextFromResource = true;
}
/**
@@ -9872,9 +9886,7 @@
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
if (forAutoFill) {
- // TODO(b/33197203, b/33269702): temporary set it as not sanitized until
- // AssistStructure automaticaly sets sanitization based on text coming from resources
- structure.setSanitized(!isPassword);
+ structure.setSanitized(mTextFromResource);
if (mAutoFillChangeWatcher == null && isTextEditable()) {
mAutoFillChangeWatcher = new AutoFillChangeWatcher();
addTextChangedListener(mAutoFillChangeWatcher);
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/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index d37f96a..37eae48a 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -43,7 +43,6 @@
#include <FrameInfo.h>
#include <FrameMetricsObserver.h>
#include <IContextFactory.h>
-#include <JankTracker.h>
#include <PropertyValuesAnimatorSet.h>
#include <RenderNode.h>
#include <renderthread/CanvasContext.h>
@@ -587,10 +586,13 @@
return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE;
}
+static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
+ RenderProxy::rotateProcessStatsBuffer();
+}
+
static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jint fd) {
- RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setProcessStatsBuffer(fd);
+ jint fd) {
+ RenderProxy::setProcessStatsBuffer(fd);
}
static jint android_view_ThreadedRenderer_getRenderThreadTid(JNIEnv* env, jobject clazz,
@@ -817,15 +819,6 @@
proxy->dumpProfileInfo(fd, dumpFlags);
}
-static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject clazz,
- jbyteArray jdata, jobject javaFileDescriptor) {
- int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
- ScopedByteArrayRO buffer(env, jdata);
- if (buffer.get()) {
- JankTracker::dumpBuffer(buffer.get(), buffer.size(), fd);
- }
-}
-
static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -910,7 +903,8 @@
static const JNINativeMethod gMethods[] = {
{ "nSupportsOpenGL", "()Z", (void*) android_view_ThreadedRenderer_supportsOpenGL },
- { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
+ { "nRotateProcessStatsBuffer", "()V", (void*) android_view_ThreadedRenderer_rotateProcessStatsBuffer },
+ { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
{ "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid },
{ "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
{ "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
@@ -943,7 +937,6 @@
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
{ "nSerializeDisplayListTree", "(J)V", (void*) android_view_ThreadedRenderer_serializeDisplayListTree },
{ "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
- { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
{ "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index ba1d664..a2f07d9 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -21,6 +21,7 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/service/appwidget.proto";
+import "frameworks/base/core/proto/android/service/graphicsstats.proto";
import "frameworks/base/core/proto/android/service/fingerprint.proto";
import "frameworks/base/core/proto/android/service/netstats.proto";
import "frameworks/base/core/proto/android/service/notification.proto";
@@ -57,4 +58,5 @@
android.providers.settings.SettingsServiceDumpProto settings = 3002;
android.service.appwidget.AppWidgetServiceDumpProto appwidget = 3003;
android.service.notification.NotificationServiceDumpProto notification = 3004;
+ android.service.GraphicsStatsServiceDumpProto graphicsstats = 3005;
}
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
new file mode 100644
index 0000000..6dbfe48
--- /dev/null
+++ b/core/proto/android/service/graphicsstats.proto
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package android.service;
+
+option java_multiple_files = true;
+option java_outer_classname = "GraphicsStatsServiceProto";
+
+message GraphicsStatsServiceDumpProto {
+ repeated GraphicsStatsProto stats = 1;
+}
+
+message GraphicsStatsProto {
+
+ // The package name of the app
+ string package_name = 1;
+
+ // The version code of the app
+ int32 version_code = 2;
+
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ // Compatible with java.util.Date#setTime()
+ int64 stats_start = 3;
+ int64 stats_end = 4;
+
+ // The aggregated statistics for the package
+ GraphicsStatsJankSummaryProto summary = 5;
+
+ // The frame time histogram for the package
+ repeated GraphicsStatsHistogramBucketProto histogram = 6;
+}
+
+message GraphicsStatsJankSummaryProto {
+ // Distinct frame count.
+ int32 total_frames = 1;
+
+ // Number of frames with slow render time. Frames are considered janky if
+ // they took more than a vsync interval (typically 16.667ms) to be rendered.
+ int32 janky_frames = 2;
+
+ // Number of "missed vsync" events.
+ int32 missed_vsync_count = 3;
+
+ // Number of "high input latency" events.
+ int32 high_input_latency_count = 4;
+
+ // Number of "slow UI thread" events.
+ int32 slow_ui_thread_count = 5;
+
+ // Number of "slow bitmap upload" events.
+ int32 slow_bitmap_upload_count = 6;
+
+ // Number of "slow draw" events.
+ int32 slow_draw_count = 7;
+}
+
+message GraphicsStatsHistogramBucketProto {
+ // Lower bound of render time in milliseconds.
+ int32 render_millis = 1;
+ // Number of frames in the bucket.
+ int32 frame_count = 2;
+}
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/core/res/res/drawable/scrollbar_handle_material.xml b/core/res/res/drawable/scrollbar_handle_material.xml
index 33efbba..f020112 100644
--- a/core/res/res/drawable/scrollbar_handle_material.xml
+++ b/core/res/res/drawable/scrollbar_handle_material.xml
@@ -19,7 +19,4 @@
android:shape="rectangle">
<solid
android:color="#84ffffff" />
- <size
- android:width="4dp"
- android:height="4dp" />
</shape>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ee7ea6a..d5ffdd0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1873,6 +1873,10 @@
<!-- Amount of time in ms the user needs to press the relevant key to bring up the global actions dialog -->
<integer name="config_globalActionsKeyTimeout">500</integer>
+ <!-- Default width of a vertical scrollbar and height of a horizontal scrollbar.
+ Takes effect only if the scrollbar drawables have no intrinsic size. -->
+ <dimen name="config_scrollbarSize">4dp</dimen>
+
<!-- Distance that should be scrolled in response to a {@link MotionEvent#ACTION_SCROLL event}
with an axis value of 1. -->
<dimen name="config_scrollFactor">64dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 89ded8d..b5a5125 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -428,6 +428,7 @@
<java-symbol type="dimen" name="config_viewConfigurationTouchSlop" />
<java-symbol type="dimen" name="config_viewMinFlingVelocity" />
<java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
+ <java-symbol type="dimen" name="config_scrollbarSize" />
<java-symbol type="dimen" name="config_scrollFactor" />
<java-symbol type="dimen" name="default_app_widget_padding_bottom" />
<java-symbol type="dimen" name="default_app_widget_padding_left" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index b063baf..400fb47 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -214,7 +214,7 @@
<!-- Scrollbar attributes -->
<item name="scrollbarFadeDuration">250</item>
<item name="scrollbarDefaultDelayBeforeFade">400</item>
- <item name="scrollbarSize">10dp</item>
+ <item name="scrollbarSize">@dimen/config_scrollbarSize</item>
<item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item>
<item name="scrollbarThumbVertical">@drawable/config_scrollbarThumbVertical</item>
<item name="scrollbarTrackHorizontal">@null</item>
@@ -583,7 +583,7 @@
<!-- Scrollbar attributes -->
<item name="scrollbarFadeDuration">250</item>
<item name="scrollbarDefaultDelayBeforeFade">400</item>
- <item name="scrollbarSize">10dp</item>
+ <item name="scrollbarSize">@dimen/config_scrollbarSize</item>
<item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item>
<item name="scrollbarThumbVertical">@drawable/config_scrollbarThumbVertical</item>
<item name="scrollbarTrackHorizontal">@null</item>
diff --git a/core/tests/coretests/src/android/provider/SettingsTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
similarity index 98%
rename from core/tests/coretests/src/android/provider/SettingsTest.java
rename to core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 019f837..76331a8 100644
--- a/core/tests/coretests/src/android/provider/SettingsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -24,8 +24,7 @@
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
-import android.annotation.TargetApi;
-
+import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -36,11 +35,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-/** Unit test for Settings. */
-@TargetApi(25)
+/** Tests that ensure appropriate settings are backed up. */
@RunWith(AndroidJUnit4.class)
+@Presubmit
@SmallTest
-public class SettingsTest {
+public class SettingsBackupTest {
/**
* The following blacklists contain settings that should *not* be backed up and restored to
@@ -64,7 +63,6 @@
Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
Settings.System.POINTER_LOCATION, // backup candidate?
Settings.System.RINGTONE_CACHE, // internal cache
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR, // bug?
Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
Settings.System.SHOW_TOUCHES, // bug?
@@ -246,6 +244,7 @@
Settings.Global.NETWORK_AVOID_BAD_WIFI,
Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
Settings.Global.NETWORK_PREFERENCE,
+ Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
Settings.Global.NETWORK_SCORER_APP,
Settings.Global.NETWORK_SCORING_PROVISIONED,
@@ -323,6 +322,7 @@
Settings.Global.USE_GOOGLE_MAIL,
Settings.Global.VT_IMS_ENABLED,
Settings.Global.WAIT_FOR_DEBUGGER,
+ Settings.Global.WAIT_FOR_NETWORK_TIMEOUT_MS,
Settings.Global.WARNING_TEMPERATURE,
Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
@@ -383,7 +383,6 @@
Settings.Secure.ASSIST_STRUCTURE_ENABLED,
Settings.Secure.AUTO_FILL_SERVICE,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
- Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
Settings.Secure.BACKUP_AUTO_RESTORE,
@@ -422,6 +421,7 @@
Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
Settings.Secure.MULTI_PRESS_TIMEOUT,
Settings.Secure.NFC_PAYMENT_FOREGROUND,
+ Settings.Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME,
Settings.Secure.PACKAGE_VERIFIER_STATE,
Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 166863c..5694115 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -98,9 +98,14 @@
if (dupAshmemFd < 0) {
result = -errno;
} else {
+ // the size of the ashmem descriptor can be modified between ashmem_get_size_region
+ // call and mmap, so we'll check again immediately after memory is mapped
void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
if (data == MAP_FAILED) {
result = -errno;
+ } else if (ashmem_get_size_region(dupAshmemFd) != size) {
+ ::munmap(data, size);
+ result = BAD_VALUE;
} else {
CursorWindow* window = new CursorWindow(name, dupAshmemFd,
data, size, true /*readOnly*/);
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index f543565..ad64b24 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -17,6 +17,7 @@
#ifndef _ANDROID__DATABASE_WINDOW_H
#define _ANDROID__DATABASE_WINDOW_H
+#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
@@ -128,12 +129,13 @@
inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
size_t* outSizeIncludingNull) {
*outSizeIncludingNull = fieldSlot->data.buffer.size;
- return static_cast<char*>(offsetToPtr(fieldSlot->data.buffer.offset));
+ return static_cast<char*>(offsetToPtr(
+ fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size));
}
inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
*outSize = fieldSlot->data.buffer.size;
- return offsetToPtr(fieldSlot->data.buffer.offset);
+ return offsetToPtr(fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size);
}
private:
@@ -166,7 +168,16 @@
bool mReadOnly;
Header* mHeader;
- inline void* offsetToPtr(uint32_t offset) {
+ inline void* offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) {
+ if (offset >= mSize) {
+ ALOGE("Offset %" PRIu32 " out of bounds, max value %zu", offset, mSize);
+ return NULL;
+ }
+ if (offset + bufferSize > mSize) {
+ ALOGE("End offset %" PRIu32 " out of bounds, max value %zu",
+ offset + bufferSize, mSize);
+ return NULL;
+ }
return static_cast<uint8_t*>(mData) + offset;
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index ff40c8a..9515b82 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -47,6 +47,7 @@
renderthread/RenderThread.cpp \
renderthread/TimeLord.cpp \
renderthread/Frame.cpp \
+ service/GraphicsStatsService.cpp \
thread/TaskManager.cpp \
utils/Blur.cpp \
utils/GLUtils.cpp \
@@ -293,6 +294,7 @@
tests/unit/GlopBuilderTests.cpp \
tests/unit/GpuMemoryTrackerTests.cpp \
tests/unit/GradientCacheTests.cpp \
+ tests/unit/GraphicsStatsServiceTests.cpp \
tests/unit/LayerUpdateQueueTests.cpp \
tests/unit/LeakCheckTests.cpp \
tests/unit/LinearAllocatorTests.cpp \
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 2132c2b..7be71ee 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -110,7 +110,7 @@
}
// Only called when dumping stats, less performance sensitive
-static uint32_t frameTimeForFrameCountIndex(uint32_t index) {
+int32_t JankTracker::frameTimeForFrameCountIndex(uint32_t index) {
index = index + kBucketMinThreshold;
if (index > kBucket2msIntervals) {
index += (index - kBucket2msIntervals);
@@ -123,6 +123,10 @@
return index;
}
+int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) {
+ return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+}
+
JankTracker::JankTracker(const DisplayInfo& displayInfo) {
// By default this will use malloc memory. It may be moved later to ashmem
// if there is shared space for it and a request comes in to do that.
@@ -161,8 +165,25 @@
mData = nullptr;
}
+void JankTracker::rotateStorage() {
+ // If we are mapped we want to stop using the ashmem backend and switch to malloc
+ // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
+ // If we aren't sitting on top of ashmem then just do a reset() as it's functionally
+ // equivalent do a free, malloc, reset.
+ if (mIsMapped) {
+ freeData();
+ mData = new ProfileData;
+ }
+ reset();
+}
+
void JankTracker::switchStorageToAshmem(int ashmemfd) {
int regionSize = ashmem_get_size_region(ashmemfd);
+ if (regionSize < 0) {
+ int err = errno;
+ ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err));
+ return;
+ }
if (regionSize < static_cast<int>(sizeof(ProfileData))) {
ALOGW("Ashmem region is too small! Received %d, required %u",
regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
@@ -279,15 +300,19 @@
}
}
-void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) {
- if (bufsize < sizeof(ProfileData)) {
- return;
+void JankTracker::dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data) {
+ if (description) {
+ switch (description->type) {
+ case JankTrackerType::Generic:
+ break;
+ case JankTrackerType::Package:
+ dprintf(fd, "\nPackage: %s", description->name.c_str());
+ break;
+ case JankTrackerType::Window:
+ dprintf(fd, "\nWindow: %s", description->name.c_str());
+ break;
+ }
}
- const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer);
- dumpData(data, fd);
-}
-
-void JankTracker::dumpData(const ProfileData* data, int fd) {
if (sFrameStart != FrameInfoIndex::IntendedVsync) {
dprintf(fd, "\nNote: Data has been filtered!");
}
@@ -308,7 +333,7 @@
data->frameCounts[i]);
}
for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
- dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs,
+ dprintf(fd, " %ums=%u", frameTimeForSlowFrameCountIndex(i),
data->slowFrameCounts[i]);
}
dprintf(fd, "\n");
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 8b482d5..6ff5d89 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -54,29 +54,50 @@
nsecs_t statStartTime;
};
+enum class JankTrackerType {
+ // The default, means there's no description set
+ Generic,
+ // The profile data represents a package
+ Package,
+ // The profile data is for a specific window
+ Window,
+};
+
+// Metadata about the ProfileData being collected
+struct ProfileDataDescription {
+ JankTrackerType type;
+ std::string name;
+};
+
// TODO: Replace DrawProfiler with this
class JankTracker {
public:
explicit JankTracker(const DisplayInfo& displayInfo);
~JankTracker();
+ void setDescription(JankTrackerType type, const std::string&& name) {
+ mDescription.type = type;
+ mDescription.name = name;
+ }
+
void addFrame(const FrameInfo& frame);
- void dump(int fd) { dumpData(mData, fd); }
+ void dump(int fd) { dumpData(fd, &mDescription, mData); }
void reset();
+ void rotateStorage();
void switchStorageToAshmem(int ashmemfd);
uint32_t findPercentile(int p) { return findPercentile(mData, p); }
-
- ANDROID_API static void dumpBuffer(const void* buffer, size_t bufsize, int fd);
+ static int32_t frameTimeForFrameCountIndex(uint32_t index);
+ static int32_t frameTimeForSlowFrameCountIndex(uint32_t index);
private:
void freeData();
void setFrameInterval(nsecs_t frameIntervalNanos);
static uint32_t findPercentile(const ProfileData* data, int p);
- static void dumpData(const ProfileData* data, int fd);
+ static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data);
std::array<int64_t, NUM_BUCKETS> mThresholds;
int64_t mFrameInterval;
@@ -90,6 +111,7 @@
nsecs_t mDequeueTimeForgiveness = 0;
ProfileData* mData;
bool mIsMapped = false;
+ ProfileDataDescription mDescription;
};
} /* namespace uirenderer */
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/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
index a75fd6a..8826cfc 100644
--- a/libs/hwui/hwui_static_deps.mk
+++ b/libs/hwui/hwui_static_deps.mk
@@ -22,11 +22,12 @@
libskia \
libui \
libgui \
- libprotobuf-cpp-lite \
+ libprotobuf-cpp-full \
libharfbuzz_ng \
libft2 \
libminikin \
- libandroidfw
+ libandroidfw \
+ libRScpp
-# enable RENDERSCRIPT
-LOCAL_SHARED_LIBRARIES += libRScpp
+LOCAL_STATIC_LIBRARIES += \
+ libplatformprotos
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index a53e5e0..02a9ffa 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -590,6 +590,7 @@
}
void CanvasContext::dumpFrames(int fd) {
+ mJankTracker.dump(fd);
FILE* file = fdopen(fd, "a");
fprintf(file, "\n\n---PROFILEDATA---\n");
for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
@@ -615,6 +616,10 @@
mRenderThread.jankTracker().reset();
}
+void CanvasContext::setName(const std::string&& name) {
+ mJankTracker.setDescription(JankTrackerType::Window, std::move(name));
+}
+
void CanvasContext::serializeDisplayListTree() {
#if ENABLE_RENDERNODE_SERIALIZATION
using namespace google::protobuf::io;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index aa01caa..738c091 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -155,8 +155,7 @@
void dumpFrames(int fd);
void resetFrameStats();
- void setName(const std::string&& name) { mName = name; }
- const std::string& name() { return mName; }
+ void setName(const std::string&& name);
void serializeDisplayListTree();
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/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 11614fa..f4a4773 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -487,9 +487,22 @@
void RenderProxy::setProcessStatsBuffer(int fd) {
SETUP_TASK(setProcessStatsBuffer);
- args->thread = &mRenderThread;
+ auto& rt = RenderThread::getInstance();
+ args->thread = &rt;
args->fd = dup(fd);
- post(task);
+ rt.queue(task);
+}
+
+CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) {
+ args->thread->jankTracker().rotateStorage();
+ return nullptr;
+}
+
+void RenderProxy::rotateProcessStatsBuffer() {
+ SETUP_TASK(rotateProcessStatsBuffer);
+ auto& rt = RenderThread::getInstance();
+ args->thread = &rt;
+ rt.queue(task);
}
int RenderProxy::getRenderThreadTid() {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 1629090..a60ed55 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -113,7 +113,8 @@
uint32_t frameTimePercentile(int p);
ANDROID_API static void dumpGraphicsMemory(int fd);
- ANDROID_API void setProcessStatsBuffer(int fd);
+ ANDROID_API static void rotateProcessStatsBuffer();
+ ANDROID_API static void setProcessStatsBuffer(int fd);
ANDROID_API int getRenderThreadTid();
ANDROID_API void serializeDisplayListTree();
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d121bcf..9bc5985 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -74,6 +74,7 @@
};
class ANDROID_API RenderThread : public Thread {
+ PREVENT_COPY_AND_ASSIGN(RenderThread);
public:
// RenderThread takes complete ownership of tasks that are queued
// and will delete them after they are run
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
new file mode 100644
index 0000000..ab6420e
--- /dev/null
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -0,0 +1,287 @@
+/*
+ * 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.
+ */
+
+#include "GraphicsStatsService.h"
+
+#include "JankTracker.h"
+
+#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <log/log.h>
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+namespace android {
+namespace uirenderer {
+
+using namespace google::protobuf;
+
+constexpr int32_t sCurrentFileVersion = 1;
+constexpr int32_t sHeaderSize = 4;
+static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
+
+constexpr int sHistogramSize =
+ std::tuple_size<decltype(ProfileData::frameCounts)>::value +
+ std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value;
+
+static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
+ const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
+ const ProfileData* data);
+static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd);
+
+bool GraphicsStatsService::parseFromFile(const std::string& path, service::GraphicsStatsProto* output) {
+
+ int fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ int err = errno;
+ // The file not existing is normal for addToDump(), so only log if
+ // we get an unexpected error
+ if (err != ENOENT) {
+ ALOGW("Failed to open '%s', errno=%d (%s)", path.c_str(), err, strerror(err));
+ }
+ return false;
+ }
+ uint32_t file_version;
+ ssize_t bytesRead = read(fd, &file_version, sHeaderSize);
+ if (bytesRead != sHeaderSize || file_version != sCurrentFileVersion) {
+ ALOGW("Failed to read '%s', bytesRead=%zd file_version=%d", path.c_str(), bytesRead,
+ file_version);
+ close(fd);
+ return false;
+ }
+
+ io::FileInputStream input(fd);
+ bool success = output->ParseFromZeroCopyStream(&input);
+ if (input.GetErrno() != 0) {
+ ALOGW("Error reading from fd=%d, path='%s' err=%d (%s)",
+ fd, path.c_str(), input.GetErrno(), strerror(input.GetErrno()));
+ success = false;
+ } else if (!success) {
+ ALOGW("Parse failed on '%s' error='%s'",
+ path.c_str(), output->InitializationErrorString().c_str());
+ }
+ close(fd);
+ return success;
+}
+
+void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
+ if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
+ proto->set_stats_start(startTime);
+ }
+ if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
+ proto->set_stats_end(endTime);
+ }
+ proto->set_package_name(package);
+ proto->set_version_code(versionCode);
+ auto summary = proto->mutable_summary();
+ summary->set_total_frames(summary->total_frames() + data->totalFrameCount);
+ summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount);
+ summary->set_missed_vsync_count(
+ summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]);
+ summary->set_high_input_latency_count(
+ summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]);
+ summary->set_slow_ui_thread_count(
+ summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]);
+ summary->set_slow_bitmap_upload_count(
+ summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]);
+ summary->set_slow_draw_count(
+ summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]);
+
+ bool creatingHistogram = false;
+ if (proto->histogram_size() == 0) {
+ proto->mutable_histogram()->Reserve(sHistogramSize);
+ creatingHistogram = true;
+ } else if (proto->histogram_size() != sHistogramSize) {
+ LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d",
+ proto->histogram_size(), sHistogramSize);
+ }
+ for (size_t i = 0; i < data->frameCounts.size(); i++) {
+ service::GraphicsStatsHistogramBucketProto* bucket;
+ int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i);
+ if (creatingHistogram) {
+ bucket = proto->add_histogram();
+ bucket->set_render_millis(renderTime);
+ } else {
+ bucket = proto->mutable_histogram(i);
+ LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
+ "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
+ }
+ bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]);
+ }
+ for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
+ service::GraphicsStatsHistogramBucketProto* bucket;
+ int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i);
+ if (creatingHistogram) {
+ bucket = proto->add_histogram();
+ bucket->set_render_millis(renderTime);
+ } else {
+ constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value;
+ bucket = proto->mutable_histogram(offset + i);
+ LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
+ "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
+ }
+ bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]);
+ }
+}
+
+static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
+ int32_t pos = percentile * proto->summary().total_frames() / 100;
+ int32_t remaining = proto->summary().total_frames() - pos;
+ for (auto it = proto->histogram().rbegin(); it != proto->histogram().rend(); ++it) {
+ remaining -= it->frame_count();
+ if (remaining <= 0) {
+ return it->render_millis();
+ }
+ }
+ return 0;
+}
+
+void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) {
+ // This isn't a full validation, just enough that we can deref at will
+ LOG_ALWAYS_FATAL_IF(proto->package_name().empty()
+ || !proto->has_summary(), "package_name() '%s' summary %d",
+ proto->package_name().c_str(), proto->has_summary());
+ dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
+ dprintf(fd, "\nVersion: %d", proto->version_code());
+ dprintf(fd, "\nStats since: %lldns", proto->stats_start());
+ dprintf(fd, "\nStats end: %lldns", proto->stats_end());
+ auto summary = proto->summary();
+ dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
+ dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
+ (float) summary.janky_frames() / (float) summary.total_frames() * 100.0f);
+ dprintf(fd, "\n50th percentile: %dms", findPercentile(proto, 50));
+ dprintf(fd, "\n90th percentile: %dms", findPercentile(proto, 90));
+ dprintf(fd, "\n95th percentile: %dms", findPercentile(proto, 95));
+ dprintf(fd, "\n99th percentile: %dms", findPercentile(proto, 99));
+ dprintf(fd, "\nNumber Missed Vsync: %d", summary.missed_vsync_count());
+ dprintf(fd, "\nNumber High input latency: %d", summary.high_input_latency_count());
+ dprintf(fd, "\nNumber Slow UI thread: %d", summary.slow_ui_thread_count());
+ dprintf(fd, "\nNumber Slow bitmap uploads: %d", summary.slow_bitmap_upload_count());
+ dprintf(fd, "\nNumber Slow issue draw commands: %d", summary.slow_draw_count());
+ dprintf(fd, "\nHISTOGRAM:");
+ for (const auto& it : proto->histogram()) {
+ dprintf(fd, " %dms=%d", it.render_millis(), it.frame_count());
+ }
+ dprintf(fd, "\n");
+}
+
+void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
+ service::GraphicsStatsProto statsProto;
+ if (!parseFromFile(path, &statsProto)) {
+ statsProto.Clear();
+ }
+ mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
+ // Although we might not have read any data from the file, merging the existing data
+ // should always fully-initialize the proto
+ LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s",
+ statsProto.InitializationErrorString().c_str());
+ LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty()
+ || !statsProto.has_summary(), "package_name() '%s' summary %d",
+ statsProto.package_name().c_str(), statsProto.has_summary());
+ int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660);
+ if (outFd <= 0) {
+ int err = errno;
+ ALOGW("Failed to open '%s', error=%d (%s)", path.c_str(), err, strerror(err));
+ return;
+ }
+ int wrote = write(outFd, &sCurrentFileVersion, sHeaderSize);
+ if (wrote != sHeaderSize) {
+ int err = errno;
+ ALOGW("Failed to write header to '%s', returned=%d errno=%d (%s)",
+ path.c_str(), wrote, err, strerror(err));
+ close(outFd);
+ return;
+ }
+ {
+ io::FileOutputStream output(outFd);
+ bool success = statsProto.SerializeToZeroCopyStream(&output) && output.Flush();
+ if (output.GetErrno() != 0) {
+ ALOGW("Error writing to fd=%d, path='%s' err=%d (%s)",
+ outFd, path.c_str(), output.GetErrno(), strerror(output.GetErrno()));
+ success = false;
+ } else if (!success) {
+ ALOGW("Serialize failed on '%s' unknown error", path.c_str());
+ }
+ }
+ close(outFd);
+}
+
+class GraphicsStatsService::Dump {
+public:
+ Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+ int fd() { return mFd; }
+ DumpType type() { return mType; }
+ service::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+private:
+ int mFd;
+ DumpType mType;
+ service::GraphicsStatsServiceDumpProto mProto;
+};
+
+GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
+ return new Dump(outFd, type);
+}
+
+void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) {
+ service::GraphicsStatsProto statsProto;
+ if (!path.empty() && !parseFromFile(path, &statsProto)) {
+ statsProto.Clear();
+ }
+ if (data) {
+ mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data);
+ }
+ if (!statsProto.IsInitialized()) {
+ ALOGW("Failed to load profile data from path '%s' and data %p",
+ path.empty() ? "<empty>" : path.c_str(), data);
+ return;
+ }
+
+ if (dump->type() == DumpType::Protobuf) {
+ dump->proto().add_stats()->CopyFrom(statsProto);
+ } else {
+ dumpAsTextToFd(&statsProto, dump->fd());
+ }
+}
+
+void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) {
+ service::GraphicsStatsProto statsProto;
+ if (!parseFromFile(path, &statsProto)) {
+ return;
+ }
+ if (dump->type() == DumpType::Protobuf) {
+ dump->proto().add_stats()->CopyFrom(statsProto);
+ } else {
+ dumpAsTextToFd(&statsProto, dump->fd());
+ }
+}
+
+void GraphicsStatsService::finishDump(Dump* dump) {
+ if (dump->type() == DumpType::Protobuf) {
+ io::FileOutputStream stream(dump->fd());
+ dump->proto().SerializeToZeroCopyStream(&stream);
+ }
+ delete dump;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
new file mode 100644
index 0000000..d0fd60e
--- /dev/null
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "JankTracker.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace service {
+class GraphicsStatsProto;
+}
+
+namespace uirenderer {
+
+/*
+ * The exported entry points used by GraphicsStatsService.java in f/b/services/core
+ *
+ * NOTE: Avoid exporting a requirement on the protobuf itself. Keep the usage
+ * of the generated protobuf classes internal to libhwui.so to minimize library
+ * bloat.
+ */
+class GraphicsStatsService {
+public:
+ class Dump;
+ enum class DumpType {
+ Text,
+ Protobuf,
+ };
+
+ ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data);
+
+ ANDROID_API static Dump* createDump(int outFd, DumpType type);
+ ANDROID_API static void addToDump(Dump* dump, const std::string& path, const std::string& package,
+ int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data);
+ ANDROID_API static void addToDump(Dump* dump, const std::string& path);
+ ANDROID_API static void finishDump(Dump* dump);
+
+ // Visible for testing
+ static bool parseFromFile(const std::string& path, service::GraphicsStatsProto* output);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
new file mode 100644
index 0000000..cfe1134
--- /dev/null
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "service/GraphicsStatsService.h"
+
+#include <frameworks/base/core/proto/android/service/graphicsstats.pb.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+std::string findRootPath() {
+ char path[1024];
+ ssize_t r = readlink("/proc/self/exe", path, 1024);
+ // < 1023 because we need room for the null terminator
+ if (r <= 0 || r > 1023) {
+ int err = errno;
+ fprintf(stderr, "Failed to read from /proc/self/exe; r=%zd, err=%d (%s)\n",
+ r, err, strerror(err));
+ exit(EXIT_FAILURE);
+ }
+ while (--r > 0) {
+ if (path[r] == '/') {
+ path[r] = '\0';
+ return std::string(path);
+ }
+ }
+ return std::string();
+}
+
+// No code left untested
+TEST(GraphicsStats, findRootPath) {
+ std::string expected = "/data/nativetest/hwui_unit_tests";
+ EXPECT_EQ(expected, findRootPath());
+}
+
+TEST(GraphicsStats, saveLoad) {
+ std::string path = findRootPath() + "/test_saveLoad";
+ std::string packageName = "com.test.saveLoad";
+ ProfileData mockData;
+ mockData.jankFrameCount = 20;
+ mockData.totalFrameCount = 100;
+ mockData.statStartTime = 10000;
+ // Fill with patterned data we can recognize but which won't map to a
+ // memset or basic for iteration count
+ for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
+ mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+ }
+ for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
+ mockData.slowFrameCounts[i] = (i % 5) + 1;
+ }
+ GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+ service::GraphicsStatsProto loadedProto;
+ EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
+ // Clean up the file
+ unlink(path.c_str());
+
+ EXPECT_EQ(packageName, loadedProto.package_name());
+ EXPECT_EQ(5, loadedProto.version_code());
+ EXPECT_EQ(3000, loadedProto.stats_start());
+ EXPECT_EQ(7000, loadedProto.stats_end());
+ // ASSERT here so we don't continue with a nullptr deref crash if this is false
+ ASSERT_TRUE(loadedProto.has_summary());
+ EXPECT_EQ(20, loadedProto.summary().janky_frames());
+ EXPECT_EQ(100, loadedProto.summary().total_frames());
+ EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+ (size_t) loadedProto.histogram_size());
+ for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
+ int expectedCount, expectedBucket;
+ if (i < mockData.frameCounts.size()) {
+ expectedCount = ((i % 10) + 1) * 2;
+ expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+ } else {
+ int temp = i - mockData.frameCounts.size();
+ expectedCount = (temp % 5) + 1;
+ expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+ }
+ EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
+ EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
+ }
+}
+
+TEST(GraphicsStats, merge) {
+ std::string path = findRootPath() + "/test_merge";
+ std::string packageName = "com.test.merge";
+ ProfileData mockData;
+ mockData.jankFrameCount = 20;
+ mockData.totalFrameCount = 100;
+ mockData.statStartTime = 10000;
+ // Fill with patterned data we can recognize but which won't map to a
+ // memset or basic for iteration count
+ for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
+ mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+ }
+ for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
+ mockData.slowFrameCounts[i] = (i % 5) + 1;
+ }
+ GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+ mockData.jankFrameCount = 50;
+ mockData.totalFrameCount = 500;
+ for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
+ mockData.frameCounts[i] = (i % 5) + 1;
+ }
+ for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
+ mockData.slowFrameCounts[i] = ((i % 10) + 1) * 2;
+ }
+ GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData);
+
+ service::GraphicsStatsProto loadedProto;
+ EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
+ // Clean up the file
+ unlink(path.c_str());
+
+ EXPECT_EQ(packageName, loadedProto.package_name());
+ EXPECT_EQ(5, loadedProto.version_code());
+ EXPECT_EQ(3000, loadedProto.stats_start());
+ EXPECT_EQ(10000, loadedProto.stats_end());
+ // ASSERT here so we don't continue with a nullptr deref crash if this is false
+ ASSERT_TRUE(loadedProto.has_summary());
+ EXPECT_EQ(20 + 50, loadedProto.summary().janky_frames());
+ EXPECT_EQ(100 + 500, loadedProto.summary().total_frames());
+ EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+ (size_t) loadedProto.histogram_size());
+ for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
+ int expectedCount, expectedBucket;
+ if (i < mockData.frameCounts.size()) {
+ expectedCount = ((i % 10) + 1) * 2;
+ expectedCount += (i % 5) + 1;
+ expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+ } else {
+ int temp = i - mockData.frameCounts.size();
+ expectedCount = (temp % 5) + 1;
+ expectedCount += ((temp % 10) + 1) * 2;
+ expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+ }
+ EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
+ EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
+ }
+}
\ No newline at end of file
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/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 0ad4fbf..2dcbf90 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -766,11 +766,7 @@
}
void loadConfig(WifiConfiguration config) {
- if (config.isPasspoint())
- ssid = config.providerFriendlyName;
- else
- ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
-
+ ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
bssid = config.BSSID;
security = getSecurity(config);
networkId = config.networkId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 9ac4d2d..11bcdca 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -469,29 +469,22 @@
}
AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
- if (config.isPasspoint() == false) {
- accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
- }
+ accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}
if (mIncludeSaved) {
- if (!config.isPasspoint() || mIncludePasspoints) {
- // If saved network not present in scan result then set its Rssi to MAX_VALUE
- boolean apFound = false;
- for (ScanResult result : results) {
- if (result.SSID.equals(accessPoint.getSsidStr())) {
- apFound = true;
- break;
- }
+ // If saved network not present in scan result then set its Rssi to MAX_VALUE
+ boolean apFound = false;
+ for (ScanResult result : results) {
+ if (result.SSID.equals(accessPoint.getSsidStr())) {
+ apFound = true;
+ break;
}
- if (!apFound) {
- accessPoint.setRssi(Integer.MAX_VALUE);
- }
- accessPoints.add(accessPoint);
}
-
- if (config.isPasspoint() == false) {
- apMap.put(accessPoint.getSsidStr(), accessPoint);
+ if (!apFound) {
+ accessPoint.setRssi(Integer.MAX_VALUE);
}
+ accessPoints.add(accessPoint);
+ apMap.put(accessPoint.getSsidStr(), accessPoint);
} else {
// If we aren't using saved networks, drop them into the cache so that
// we have access to their saved info.
@@ -528,20 +521,16 @@
}
if (result.isPasspointNetwork()) {
+ // Retrieve a WifiConfiguration for a Passpoint provider that matches
+ // the given ScanResult. This is used for showing that a given AP
+ // (ScanResult) is available via a Passpoint provider (provider friendly
+ // name).
WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
if (config != null) {
accessPoint.update(config);
}
}
- if (mLastInfo != null && mLastInfo.getBSSID() != null
- && mLastInfo.getBSSID().equals(result.BSSID)
- && connectionConfig != null && connectionConfig.isPasspoint()) {
- /* This network is connected via this passpoint config */
- /* SSID match is not going to work for it; so update explicitly */
- accessPoint.update(connectionConfig);
- }
-
accessPoints.add(accessPoint);
apMap.put(accessPoint.getSsidStr(), accessPoint);
}
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/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index c40797c..ca05240 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -19,9 +19,6 @@
<!-- thickness (width) of the navigation bar on phones that require it -->
<dimen name="navigation_bar_size">@*android:dimen/navigation_bar_width</dimen>
- <!-- Standard notification gravity -->
- <integer name="notification_panel_layout_gravity">@integer/standard_notification_panel_layout_gravity</integer>
-
<dimen name="docked_divider_handle_width">2dp</dimen>
<dimen name="docked_divider_handle_height">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7e63cbf..c8ffe8f 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -18,7 +18,6 @@
<resources>
<!-- Standard notification width + gravity -->
<dimen name="notification_panel_width">@dimen/standard_notification_panel_width</dimen>
- <integer name="notification_panel_layout_gravity">@integer/standard_notification_panel_layout_gravity</integer>
<!-- Diameter of outer shape drawable shown in navbar search-->
<dimen name="navbar_search_outerring_diameter">430dip</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6e399b4..728dde0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -201,8 +201,7 @@
<dimen name="volume_dialog_panel_width">@dimen/standard_notification_panel_width</dimen>
<!-- Gravity for the notification panel -->
- <integer name="standard_notification_panel_layout_gravity">0x31</integer><!-- top|center_horizontal -->
- <integer name="notification_panel_layout_gravity">0x37</integer><!-- fill_horizontal|top -->
+ <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
<!-- Height of the carrier/wifi name label -->
<dimen name="carrier_label_height">24dp</dimen>
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/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9a4b45a..2d47c7b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -21,6 +21,7 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.admin.DevicePolicyManager;
import android.app.Notification;
import android.app.Notification.BigPictureStyle;
import android.app.NotificationManager;
@@ -46,6 +47,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
+import android.os.UserHandle;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.view.Display;
@@ -866,6 +868,16 @@
.setAutoCancel(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
+ final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ final Intent intent = dpm.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ if (intent != null) {
+ final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ context, 0, intent, 0, null, UserHandle.CURRENT);
+ b.setContentIntent(pendingIntent);
+ }
+
SystemUI.overrideNotificationAppName(context, b);
Notification n = new Notification.BigTextStyle(b)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 6da9e90..f55699b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -292,7 +292,7 @@
int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
- if (lp.width != panelWidth) {
+ if (lp.width != panelWidth || lp.gravity != panelGravity) {
lp.width = panelWidth;
lp.gravity = panelGravity;
mQsFrame.setLayoutParams(lp);
@@ -300,7 +300,7 @@
}
lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
- if (lp.width != panelWidth) {
+ if (lp.width != panelWidth || lp.gravity != panelGravity) {
lp.width = panelWidth;
lp.gravity = panelGravity;
mNotificationStackScroller.setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index aebc2c9..9e93ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -509,7 +509,8 @@
if (!hasNotif(notifs, pkg, info.userId)) {
// TODO: Optimize by not always needing to get application info.
// Maybe cache non-ephemeral packages?
- ApplicationInfo appInfo = pm.getApplicationInfo(pkg, 0, info.userId);
+ ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, info.userId);
if (appInfo.isInstantApp()) {
postEphemeralNotif(pkg, info.userId, appInfo, noMan);
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 9b55c7a..b459f74 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -16,6 +16,9 @@
package com.android.server.backup;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
+import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
+import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_OLD_VERSION;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND;
import android.app.ActivityManager;
@@ -2340,7 +2343,7 @@
Slog.e(TAG, "No packages named for backup request");
sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
monitor = monitorEvent(monitor, BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES,
- null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT);
+ null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
throw new IllegalArgumentException("No packages are provided for backup");
}
@@ -3459,6 +3462,9 @@
// fail repeatedly (i.e. have proved themselves to be buggy).
Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
addBackupTrace(
"cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
errorCleanup();
@@ -4555,6 +4561,11 @@
mPackages.add(info);
} catch (NameNotFoundException e) {
Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
+ monitor = monitorEvent(monitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
}
}
}
@@ -4638,6 +4649,10 @@
if (mTransport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+ null);
return;
}
@@ -5148,7 +5163,7 @@
mMonitor = monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
mIsCancelled = true;
// Cancel tasks spun off by this task.
BackupManagerService.this.handleCancel(mEphemeralToken, cancelAll);
@@ -5525,6 +5540,8 @@
// Dedicated observer, if any
IFullBackupRestoreObserver mObserver;
+ IBackupManagerMonitor mMonitor;
+
// Where we're delivering the file data as we go
IBackupAgent mAgent;
@@ -5606,11 +5623,12 @@
}
public FullRestoreEngine(BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
- PackageInfo onlyPackage, boolean allowApks, boolean allowObbs,
- int ephemeralOpToken) {
+ IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
+ boolean allowObbs, int ephemeralOpToken) {
mEphemeralOpToken = ephemeralOpToken;
mMonitorTask = monitorTask;
mObserver = observer;
+ mMonitor = monitor;
mOnlyPackage = onlyPackage;
mAllowApks = allowApks;
mAllowObbs = allowObbs;
@@ -6326,6 +6344,16 @@
} else {
Slog.i(TAG, "Data requires newer version "
+ version + "; ignoring");
+ ArrayList<Pair<String, String>> list =
+ new ArrayList<>();
+ list.add(new Pair<String, String>(
+ EXTRA_LOG_OLD_VERSION,
+ Integer.toString(version)));
+ mMonitor = monitorEvent(mMonitor,
+ LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
+ pkgInfo,
+ LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ list);
policy = RestorePolicy.IGNORE;
}
}
@@ -8461,6 +8489,10 @@
RestoreDescription desc = mTransport.nextRestorePackage();
if (desc == null) {
Slog.e(TAG, "No restore metadata available; halting");
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
mStatus = BackupTransport.TRANSPORT_ERROR;
executeNextState(UnifiedRestoreState.FINAL);
return;
@@ -8562,6 +8594,11 @@
// Whoops, we thought we could restore this package but it
// turns out not to be present. Skip it.
Slog.e(TAG, "Package not present: " + pkgName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT,
+ mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+ null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
"Package missing on device");
nextState = UnifiedRestoreState.RUNNING_QUEUE;
@@ -8631,6 +8668,9 @@
Slog.i(TAG, "Data exists for package " + packageName
+ " but app has no agent; skipping");
}
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
"Package has no agent");
executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -8652,6 +8692,9 @@
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL);
if (mAgent == null) {
Slog.w(TAG, "Can't find backup agent for " + packageName);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT, mCurrentPackage,
+ BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
"Restore agent missing");
executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -8857,7 +8900,7 @@
EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
mCurrentPackage.packageName);
- mEngine = new FullRestoreEngine(this, null, mCurrentPackage, false, false, mEphemeralOpToken);
+ mEngine = new FullRestoreEngine(this, null, mMonitor, mCurrentPackage, false, false, mEphemeralOpToken);
mEngineThread = new EngineThread(mEngine, mEnginePipes[0]);
ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -9001,8 +9044,10 @@
if (DEBUG) {
Slog.w(TAG, "Full-data restore target timed out; shutting down");
}
- mMonitor = monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
mEngineThread.handleTimeout();
IoUtils.closeQuietly(mEnginePipes[1]);
@@ -9245,8 +9290,9 @@
public void handleCancel(boolean cancelAll) {
removeOperation(mEphemeralOpToken);
Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
- mMonitor = monitorEvent(mMonitor, BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
- mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT);
+ mMonitor = monitorEvent(mMonitor,
+ BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
+ mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
mCurrentPackage.packageName, "restore timeout");
// Handle like an agent that threw on invocation: wipe it and go on to the next
@@ -10913,7 +10959,7 @@
}
private static IBackupManagerMonitor monitorEvent(IBackupManagerMonitor monitor, int id,
- PackageInfo pkg, int category) {
+ PackageInfo pkg, int category, ArrayList<Pair<String, String>> extras) {
if (monitor != null) {
try {
Bundle bundle = new Bundle();
@@ -10925,6 +10971,11 @@
bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION,
pkg.versionCode);
}
+ if (extras != null) {
+ for (Pair<String,String> pair : extras) {
+ bundle.putString(pair.first, pair.second);
+ }
+ }
monitor.onEvent(bundle);
return monitor;
} catch(RemoteException e) {
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/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index ecbe1ca..bdd80e38 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -16,67 +16,145 @@
package com.android.server;
+import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.MemoryFile;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.Log;
import android.view.IGraphicsStats;
-import android.view.ThreadedRenderer;
+import android.view.IGraphicsStatsCallback;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.TimeZone;
/**
* This service's job is to collect aggregate rendering profile data. It
* does this by allowing rendering processes to request an ashmem buffer
- * to place their stats into. This buffer will be pre-initialized with historical
- * data for that process if it exists (if the userId & packageName match a buffer
- * in the historical log)
+ * to place their stats into.
*
- * This service does not itself attempt to understand the data in the buffer,
- * its primary job is merely to manage distributing these buffers. However,
- * it is assumed that this buffer is for ThreadedRenderer and delegates
- * directly to ThreadedRenderer for dumping buffers.
+ * Buffers are rotated on a daily (in UTC) basis and only the 3 most-recent days
+ * are kept.
*
- * MEMORY USAGE:
+ * The primary consumer of this is incident reports and automated metric checking. It is not
+ * intended for end-developer consumption, for that we have gfxinfo.
*
- * This class consumes UP TO:
- * 1) [active rendering processes] * (ASHMEM_SIZE * 2)
- * 2) ASHMEM_SIZE (for scratch space used during dumping)
- * 3) ASHMEM_SIZE * HISTORY_SIZE
- *
- * This is currently under 20KiB total memory in the worst case of
- * 20 processes in history + 10 unique active processes.
+ * Buffer rotation process:
+ * 1) Alarm fires
+ * 2) onRotateGraphicsStatsBuffer() is sent to all active processes
+ * 3) Upon receiving the callback, the process will stop using the previous ashmem buffer and
+ * request a new one.
+ * 4) When that request is received we now know that the ashmem region is no longer in use so
+ * it gets queued up for saving to disk and a new ashmem region is created and returned
+ * for the process to use.
*
* @hide */
public class GraphicsStatsService extends IGraphicsStats.Stub {
public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
private static final String TAG = "GraphicsStatsService";
- private static final int ASHMEM_SIZE = 464;
- private static final int HISTORY_SIZE = 20;
+
+ private static final int SAVE_BUFFER = 1;
+ private static final int DELETE_OLD = 2;
+
+ // This isn't static because we need this to happen after registerNativeMethods, however
+ // the class is loaded (and thus static ctor happens) before that occurs.
+ private final int ASHMEM_SIZE = nGetAshmemSize();
+ private final byte[] ZERO_DATA = new byte[ASHMEM_SIZE];
private final Context mContext;
private final AppOpsManager mAppOps;
+ private final AlarmManager mAlarmManager;
private final Object mLock = new Object();
private ArrayList<ActiveBuffer> mActive = new ArrayList<>();
- private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE];
- private int mNextHistoricalSlot = 0;
- private byte[] mTempBuffer = new byte[ASHMEM_SIZE];
+ private File mGraphicsStatsDir;
+ private final Object mFileAccessLock = new Object();
+ private Handler mWriteOutHandler;
+ private boolean mRotateIsScheduled = false;
public GraphicsStatsService(Context context) {
mContext = context;
mAppOps = context.getSystemService(AppOpsManager.class);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ File systemDataDir = new File(Environment.getDataDirectory(), "system");
+ mGraphicsStatsDir = new File(systemDataDir, "graphicsstats");
+ mGraphicsStatsDir.mkdirs();
+ if (!mGraphicsStatsDir.exists()) {
+ throw new IllegalStateException("Graphics stats directory does not exist: "
+ + mGraphicsStatsDir.getAbsolutePath());
+ }
+ HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", Process.THREAD_PRIORITY_BACKGROUND);
+ bgthread.start();
+
+ mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case SAVE_BUFFER:
+ saveBuffer((HistoricalBuffer) msg.obj);
+ break;
+ case DELETE_OLD:
+ deleteOldBuffers();
+ break;
+ }
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Current rotation policy is to rotate at midnight UTC. We don't specify RTC_WAKEUP because
+ * rotation can be delayed if there's otherwise no activity. However exact is used because
+ * we don't want the system to delay it by TOO much.
+ */
+ private void scheduleRotateLocked() {
+ if (mRotateIsScheduled) {
+ return;
+ }
+ mRotateIsScheduled = true;
+ Calendar calendar = normalizeDate(System.currentTimeMillis());
+ calendar.add(Calendar.DATE, 1);
+ mAlarmManager.setExact(AlarmManager.RTC, calendar.getTimeInMillis(), TAG, this::onAlarm,
+ mWriteOutHandler);
+ }
+
+ private void onAlarm() {
+ synchronized (mLock) {
+ mRotateIsScheduled = false;
+ scheduleRotateLocked();
+ for (ActiveBuffer active : mActive) {
+ try {
+ active.mCallback.onRotateGraphicsStatsBuffer();
+ } catch (RemoteException e) {
+ Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers",
+ active.mInfo.packageName, active.mPid), e);
+ }
+ }
+ }
+ // Give a few seconds for everyone to rotate before doing the cleanup
+ mWriteOutHandler.sendEmptyMessageDelayed(DELETE_OLD, 10000);
}
@Override
- public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token)
+ public ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback token)
throws RemoteException {
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
@@ -84,9 +162,12 @@
long callingIdentity = Binder.clearCallingIdentity();
try {
mAppOps.checkPackage(uid, packageName);
+ PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
synchronized (mLock) {
- pfd = requestBufferForProcessLocked(token, uid, pid, packageName);
+ pfd = requestBufferForProcessLocked(token, uid, pid, packageName, info.versionCode);
}
+ } catch (PackageManager.NameNotFoundException ex) {
+ throw new RemoteException("Unable to find package: '" + packageName + "'");
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -95,51 +176,130 @@
private ParcelFileDescriptor getPfd(MemoryFile file) {
try {
+ if (!file.getFileDescriptor().valid()) {
+ throw new IllegalStateException("Invalid file descriptor");
+ }
return new ParcelFileDescriptor(file.getFileDescriptor());
} catch (IOException ex) {
throw new IllegalStateException("Failed to get PFD from memory file", ex);
}
}
- private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token,
- int uid, int pid, String packageName) throws RemoteException {
- ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName);
+ private ParcelFileDescriptor requestBufferForProcessLocked(IGraphicsStatsCallback token,
+ int uid, int pid, String packageName, int versionCode) throws RemoteException {
+ ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode);
+ scheduleRotateLocked();
return getPfd(buffer.mProcessBuffer);
}
+ private Calendar normalizeDate(long timestamp) {
+ Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+ calendar.setTimeInMillis(timestamp);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar;
+ }
+
+ private File pathForApp(BufferInfo info) {
+ String subPath = String.format("%d/%s/%d/total",
+ normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode);
+ return new File(mGraphicsStatsDir, subPath);
+ }
+
+ private void saveBuffer(HistoricalBuffer buffer) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName);
+ }
+ synchronized (mFileAccessLock) {
+ File path = pathForApp(buffer.mInfo);
+ File parent = path.getParentFile();
+ parent.mkdirs();
+ if (!parent.exists()) {
+ Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
+ return;
+ }
+ nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode,
+ buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
+ private void deleteRecursiveLocked(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ deleteRecursiveLocked(child);
+ }
+ }
+ if (!file.delete()) {
+ Log.w(TAG, "Failed to delete '" + file.getAbsolutePath() + "'!");
+ }
+ }
+
+ private void deleteOldBuffers() {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "deleting old graphicsstats buffers");
+ synchronized (mFileAccessLock) {
+ File[] files = mGraphicsStatsDir.listFiles();
+ if (files == null || files.length <= 3) {
+ return;
+ }
+ long[] sortedDates = new long[files.length];
+ for (int i = 0; i < files.length; i++) {
+ try {
+ sortedDates[i] = Long.parseLong(files[i].getName());
+ } catch (NumberFormatException ex) {
+ // Skip unrecognized folders
+ }
+ }
+ if (sortedDates.length <= 3) {
+ return;
+ }
+ Arrays.sort(sortedDates);
+ for (int i = 0; i < sortedDates.length - 3; i++) {
+ deleteRecursiveLocked(new File(mGraphicsStatsDir, Long.toString(sortedDates[i])));
+ }
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
+ private void addToSaveQueue(ActiveBuffer buffer) {
+ try {
+ HistoricalBuffer data = new HistoricalBuffer(buffer);
+ Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e);
+ }
+ buffer.closeAllBuffers();
+ }
+
private void processDied(ActiveBuffer buffer) {
synchronized (mLock) {
mActive.remove(buffer);
- Log.d("GraphicsStats", "Buffer count: " + mActive.size());
}
- HistoricalData data = buffer.mPreviousData;
- buffer.mPreviousData = null;
- if (data == null) {
- data = mHistoricalLog[mNextHistoricalSlot];
- if (data == null) {
- data = new HistoricalData();
- }
- }
- data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer);
- buffer.closeAllBuffers();
-
- mHistoricalLog[mNextHistoricalSlot] = data;
- mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length;
+ addToSaveQueue(buffer);
}
- private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid,
- String packageName) throws RemoteException {
+ private ActiveBuffer fetchActiveBuffersLocked(IGraphicsStatsCallback token, int uid, int pid,
+ String packageName, int versionCode) throws RemoteException {
int size = mActive.size();
+ long today = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
for (int i = 0; i < size; i++) {
- ActiveBuffer buffers = mActive.get(i);
- if (buffers.mPid == pid
- && buffers.mUid == uid) {
- return buffers;
+ ActiveBuffer buffer = mActive.get(i);
+ if (buffer.mPid == pid
+ && buffer.mUid == uid) {
+ // If the buffer is too old we remove it and return a new one
+ if (buffer.mInfo.startTime < today) {
+ buffer.binderDied();
+ break;
+ } else {
+ return buffer;
+ }
}
}
// Didn't find one, need to create it
try {
- ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName);
+ ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName, versionCode);
mActive.add(buffers);
return buffers;
} catch (IOException ex) {
@@ -147,71 +307,106 @@
}
}
- private HistoricalData removeHistoricalDataLocked(int uid, String packageName) {
- for (int i = 0; i < mHistoricalLog.length; i++) {
- final HistoricalData data = mHistoricalLog[i];
- if (data != null && data.mUid == uid
- && data.mPackageName.equals(packageName)) {
- if (i == mNextHistoricalSlot) {
- mHistoricalLog[i] = null;
- } else {
- mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot];
- mHistoricalLog[mNextHistoricalSlot] = null;
+ private HashSet<File> dumpActiveLocked(long dump, ArrayList<HistoricalBuffer> buffers) {
+ HashSet<File> skipFiles = new HashSet<>(buffers.size());
+ for (int i = 0; i < buffers.size(); i++) {
+ HistoricalBuffer buffer = buffers.get(i);
+ File path = pathForApp(buffer.mInfo);
+ skipFiles.add(path);
+ nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName,
+ buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime,
+ buffer.mData);
+ }
+ return skipFiles;
+ }
+
+ private void dumpHistoricalLocked(long dump, HashSet<File> skipFiles) {
+ for (File date : mGraphicsStatsDir.listFiles()) {
+ for (File pkg : date.listFiles()) {
+ for (File version : pkg.listFiles()) {
+ File data = new File(version, "total");
+ if (skipFiles.contains(data)) {
+ continue;
+ }
+ nAddToDump(dump, data.getAbsolutePath());
}
- return data;
}
}
- return null;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ boolean dumpProto = false;
+ for (String str : args) {
+ if ("--proto".equals(str)) {
+ dumpProto = true;
+ break;
+ }
+ }
+ ArrayList<HistoricalBuffer> buffers;
synchronized (mLock) {
+ buffers = new ArrayList<>(mActive.size());
for (int i = 0; i < mActive.size(); i++) {
- final ActiveBuffer buffer = mActive.get(i);
- fout.print("Package: ");
- fout.print(buffer.mPackageName);
- fout.flush();
try {
- buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE);
- ThreadedRenderer.dumpProfileData(mTempBuffer, fd);
- } catch (IOException e) {
- fout.println("Failed to dump");
+ buffers.add(new HistoricalBuffer(mActive.get(i)));
+ } catch (IOException ex) {
+ // Ignore
}
- fout.println();
}
- for (HistoricalData buffer : mHistoricalLog) {
- if (buffer == null) continue;
- fout.print("Package: ");
- fout.print(buffer.mPackageName);
- fout.flush();
- ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd);
- fout.println();
+ }
+ long dump = nCreateDump(fd.getInt$(), dumpProto);
+ try {
+ synchronized (mFileAccessLock) {
+ HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+ buffers.clear();
+ dumpHistoricalLocked(dump, skipList);
}
+ } finally {
+ nFinishDump(dump);
+ }
+ }
+
+ private static native int nGetAshmemSize();
+ private static native long nCreateDump(int outFd, boolean isProto);
+ private static native void nAddToDump(long dump, String path, String packageName,
+ int versionCode, long startTime, long endTime, byte[] data);
+ private static native void nAddToDump(long dump, String path);
+ private static native void nFinishDump(long dump);
+ private static native void nSaveBuffer(String path, String packageName, int versionCode,
+ long startTime, long endTime, byte[] data);
+
+ private final class BufferInfo {
+ final String packageName;
+ final int versionCode;
+ long startTime;
+ long endTime;
+
+ BufferInfo(String packageName, int versionCode, long startTime) {
+ this.packageName = packageName;
+ this.versionCode = versionCode;
+ this.startTime = startTime;
}
}
private final class ActiveBuffer implements DeathRecipient {
+ final BufferInfo mInfo;
final int mUid;
final int mPid;
- final String mPackageName;
+ final IGraphicsStatsCallback mCallback;
final IBinder mToken;
MemoryFile mProcessBuffer;
- HistoricalData mPreviousData;
- ActiveBuffer(IBinder token, int uid, int pid, String packageName)
+ ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, int versionCode)
throws RemoteException, IOException {
+ mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
mUid = uid;
mPid = pid;
- mPackageName = packageName;
- mToken = token;
+ mCallback = token;
+ mToken = mCallback.asBinder();
mToken.linkToDeath(this, 0);
- mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE);
- mPreviousData = removeHistoricalDataLocked(mUid, mPackageName);
- if (mPreviousData != null) {
- mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE);
- }
+ mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE);
+ mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE);
}
@Override
@@ -228,17 +423,13 @@
}
}
- private final static class HistoricalData {
- final byte[] mBuffer = new byte[ASHMEM_SIZE];
- int mUid;
- String mPackageName;
-
- void update(String packageName, int uid, MemoryFile file) {
- mUid = uid;
- mPackageName = packageName;
- try {
- file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE);
- } catch (IOException e) {}
+ private final class HistoricalBuffer {
+ final BufferInfo mInfo;
+ final byte[] mData = new byte[ASHMEM_SIZE];
+ HistoricalBuffer(ActiveBuffer active) throws IOException {
+ mInfo = active.mInfo;
+ mInfo.endTime = System.currentTimeMillis();
+ active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE);
}
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0415971..32136bb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -70,13 +70,13 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.DiskInfo;
-import android.os.storage.IStorageEventListener;
-import android.os.storage.IStorageShutdownObserver;
import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
import android.os.storage.IStorageManager;
-import android.os.storage.StorageManagerInternal;
+import android.os.storage.IStorageShutdownObserver;
import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
@@ -109,6 +109,7 @@
import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.pm.PackageManagerService;
import com.android.server.storage.AppFuseBridge;
+
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -3293,14 +3294,69 @@
}
@Override
- public long getAllocatableBytes(String path, int flags) {
- return new File(path).getUsableSpace();
+ public long getAllocatableBytes(String volumeUuid, int flags) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
+
+ final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+ if (aggressive) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ALLOCATE_AGGRESSIVE, TAG);
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // In general, apps can allocate as much space as they want, except
+ // we never let them eat into either the minimum cache space or into
+ // the low disk warning space.
+ final File path = storage.findPathForUuid(volumeUuid);
+ if (stats.isQuotaSupported(volumeUuid)) {
+ if (aggressive) {
+ return Math.max(0,
+ stats.getFreeBytes(volumeUuid) - storage.getStorageFullBytes(path));
+ } else {
+ return Math.max(0,
+ stats.getFreeBytes(volumeUuid) - storage.getStorageLowBytes(path)
+ - storage.getStorageCacheBytes(path));
+ }
+ } else {
+ // When we don't have fast quota information, we ignore cached
+ // data and only consider unused bytes.
+ if (aggressive) {
+ return Math.max(0, path.getUsableSpace() - storage.getStorageFullBytes(path));
+ } else {
+ return Math.max(0, path.getUsableSpace() - storage.getStorageLowBytes(path));
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
- public void allocateBytes(String path, long bytes, int flags) {
- if (bytes > new File(path).getUsableSpace()) {
- throw new ParcelableException(new IOException("Not enough usable space"));
+ public void allocateBytes(String volumeUuid, long bytes, int flags) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+
+ // This method call will enforce FLAG_ALLOCATE_AGGRESSIVE permissions so
+ // we don't have to enforce them locally
+ final long allocatableBytes = getAllocatableBytes(volumeUuid, flags);
+ if (bytes > allocatableBytes) {
+ throw new ParcelableException(new IOException("Failed to allocate " + bytes
+ + " because only " + allocatableBytes + " allocatable"));
+ }
+
+ // Free up enough disk space to satisfy both the requested allocation
+ // and our low disk warning space.
+ final File path = storage.findPathForUuid(volumeUuid);
+ bytes += storage.getStorageLowBytes(path);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mPms.freeStorage(volumeUuid, bytes, flags);
+ } catch (IOException e) {
+ throw new ParcelableException(e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 5b480d0..1b2c75d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -182,7 +182,8 @@
static {
ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
- ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
}
private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
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/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 449d808..db04515 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -465,6 +465,15 @@
}
}
+ public boolean isQuotaSupported(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+ try {
+ return mInstalld.isQuotaSupported(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 463cfac..1c5675a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -24,6 +24,7 @@
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
+
import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
@@ -54,8 +55,8 @@
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SELinux;
import android.os.UserHandle;
+import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -66,9 +67,6 @@
import android.util.MathUtils;
import android.util.Slog;
-import libcore.io.IoUtils;
-import libcore.io.Libcore;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
@@ -78,6 +76,9 @@
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileFilter;
@@ -455,14 +456,9 @@
// If caller specified a total length, allocate it for them. Free up
// cache space to grow, if needed.
- if (lengthBytes > 0) {
- final StructStat stat = Libcore.os.fstat(targetFd);
- final long deltaBytes = lengthBytes - stat.st_size;
- // Only need to free up space when writing to internal stage
- if (stageDir != null && deltaBytes > 0) {
- mPm.freeStorage(params.volumeUuid, deltaBytes);
- }
- Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
+ if (stageDir != null && lengthBytes > 0) {
+ mContext.getSystemService(StorageManager.class).allocateBytes(targetFd,
+ lengthBytes, 0);
}
if (offsetBytes > 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f95ee8f..371a062 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3821,7 +3821,8 @@
});
}
- void freeStorage(String volumeUuid, long freeStorageSize) throws IOException {
+ public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags)
+ throws IOException {
synchronized (mInstallLock) {
try {
mInstaller.freeCache(volumeUuid, freeStorageSize, 0);
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..945a349 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);
}
@@ -3878,7 +3889,7 @@
}
private boolean forAllWindowBottomToTop(ToBooleanFunction<WindowState> callback) {
- // We want to consumer the negative sublayer children first because they need to appear
+ // We want to consume the negative sublayer children first because they need to appear
// below the parent, then this window (the parent), and then the positive sublayer children
// because they need to appear above the parent.
int i = 0;
@@ -3886,7 +3897,7 @@
WindowState child = mChildren.get(i);
while (i < count && child.mSubLayer < 0) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
i++;
@@ -3901,7 +3912,7 @@
}
while (i < count) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
i++;
@@ -3915,14 +3926,14 @@
}
private boolean forAllWindowTopToBottom(ToBooleanFunction<WindowState> callback) {
- // We want to consumer the positive sublayer children first because they need to appear
+ // We want to consume the positive sublayer children first because they need to appear
// above the parent, then this window (the parent), and then the negative sublayer children
// because they need to appear above the parent.
int i = mChildren.size() - 1;
WindowState child = mChildren.get(i);
while (i >= 0 && child.mSubLayer >= 0) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
--i;
@@ -3937,7 +3948,7 @@
}
while (i >= 0) {
- if (callback.apply(child)) {
+ if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
--i;
@@ -3980,10 +3991,43 @@
}
WindowState getWindow(Predicate<WindowState> callback) {
+ if (mChildren.isEmpty()) {
+ return callback.test(this) ? this : null;
+ }
+
+ // We want to consume the positive sublayer children first because they need to appear
+ // above the parent, then this window (the parent), and then the negative sublayer children
+ // because they need to appear above the parent.
+ int i = mChildren.size() - 1;
+ WindowState child = mChildren.get(i);
+
+ while (i >= 0 && child.mSubLayer >= 0) {
+ if (callback.test(child)) {
+ return child;
+ }
+ --i;
+ if (i < 0) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+
if (callback.test(this)) {
return this;
}
- return super.getWindow(callback);
+
+ while (i >= 0) {
+ if (callback.test(child)) {
+ return child;
+ }
+ --i;
+ if (i < 0) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+
+ return null;
}
boolean isWindowAnimationSet() {
@@ -4298,6 +4342,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/core/jni/Android.mk b/services/core/jni/Android.mk
index 2c3cda5..ac95db5 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -30,6 +30,7 @@
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
$(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_GraphicsStatsService.cpp \
$(LOCAL_REL_DIR)/onload.cpp
LOCAL_C_INCLUDES += \
@@ -37,7 +38,6 @@
external/scrypt/lib/crypto \
frameworks/base/services \
frameworks/base/libs \
- frameworks/base/libs/hwui \
frameworks/base/core/jni \
frameworks/native/services \
system/core/libappfuse/include \
@@ -76,6 +76,7 @@
libhidltransport \
libhwbinder \
libutils \
+ libhwui \
android.hardware.audio.common@2.0 \
android.hardware.contexthub@1.0 \
android.hardware.gnss@1.0 \
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
new file mode 100644
index 0000000..5d5728d
--- /dev/null
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GraphicsStatsService"
+
+#include <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <ScopedPrimitiveArray.h>
+#include <ScopedUtfChars.h>
+#include <JankTracker.h>
+#include <service/GraphicsStatsService.h>
+
+namespace android {
+
+using namespace android::uirenderer;
+
+static jint getAshmemSize(JNIEnv*, jobject) {
+ return sizeof(ProfileData);
+}
+
+static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) {
+ GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto
+ ? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text);
+ return reinterpret_cast<jlong>(dump);
+}
+
+static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
+ jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+ std::string path;
+ const ProfileData* data = nullptr;
+ LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null");
+ if (jdata != nullptr) {
+ ScopedByteArrayRO buffer(env, jdata);
+ LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
+ "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
+ data = reinterpret_cast<const ProfileData*>(buffer.get());
+ }
+ if (jpath != nullptr) {
+ ScopedUtfChars pathChars(env, jpath);
+ LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+ path.assign(pathChars.c_str(), pathChars.size());
+ }
+ ScopedUtfChars packageChars(env, jpackage);
+ LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
+
+ const std::string package(packageChars.c_str(), packageChars.size());
+ GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
+}
+
+static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) {
+ ScopedUtfChars pathChars(env, jpath);
+ LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+ const std::string path(pathChars.c_str(), pathChars.size());
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ GraphicsStatsService::addToDump(dump, path);
+}
+
+static void finishDump(JNIEnv*, jobject, jlong dumpPtr) {
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ GraphicsStatsService::finishDump(dump);
+}
+
+static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
+ jint versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+ ScopedByteArrayRO buffer(env, jdata);
+ LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
+ "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData));
+ ScopedUtfChars pathChars(env, jpath);
+ LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars");
+ ScopedUtfChars packageChars(env, jpackage);
+ LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars");
+
+ const std::string path(pathChars.c_str(), pathChars.size());
+ const std::string package(packageChars.c_str(), packageChars.size());
+ const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get());
+ GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
+}
+
+static const JNINativeMethod sMethods[] = {
+ { "nGetAshmemSize", "()I", (void*) getAshmemSize },
+ { "nCreateDump", "(IZ)J", (void*) createDump },
+ { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;IJJ[B)V", (void*) addToDump },
+ { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
+ { "nFinishDump", "(J)V", (void*) finishDump },
+ { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;IJJ[B)V", (void*) saveBuffer },
+};
+
+int register_android_server_GraphicsStatsService(JNIEnv* env)
+{
+ return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
+ sMethods, NELEM(sMethods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 899640e..f22b330 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -46,6 +46,7 @@
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
+int register_android_server_GraphicsStatsService(JNIEnv* env);
};
using namespace android;
@@ -87,6 +88,7 @@
register_android_server_HardwarePropertiesManagerService(env);
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
+ register_android_server_GraphicsStatsService(env);
return JNI_VERSION_1_4;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 30f99e5..bd3271b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -16,24 +16,25 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import android.content.res.Configuration;
-import android.hardware.display.DisplayManagerGlobal;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
-import android.view.DisplayInfo;
-import java.util.ArrayList;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-import static org.junit.Assert.assertEquals;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
/**
* Tests for the {@link DisplayContent} class.
@@ -54,38 +55,17 @@
exitingAppToken.mIsExiting = true;
exitingAppToken.mTask.mStack.mExitingAppTokens.add(exitingAppToken);
- final ArrayList<WindowState> windows = new ArrayList();
-
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(exitingAppWindow, windows.get(1));
- assertEquals(sChildAppWindowBelow, windows.get(2));
- assertEquals(sAppWindow, windows.get(3));
- assertEquals(sChildAppWindowAbove, windows.get(4));
- assertEquals(sDockedDividerWindow, windows.get(5));
- assertEquals(sStatusBarWindow, windows.get(6));
- assertEquals(sNavBarWindow, windows.get(7));
- assertEquals(sImeWindow, windows.get(8));
- assertEquals(sImeDialogWindow, windows.get(9));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(9));
- assertEquals(exitingAppWindow, windows.get(8));
- assertEquals(sChildAppWindowBelow, windows.get(7));
- assertEquals(sAppWindow, windows.get(6));
- assertEquals(sChildAppWindowAbove, windows.get(5));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(3));
- assertEquals(sNavBarWindow, windows.get(2));
- assertEquals(sImeWindow, windows.get(1));
- assertEquals(sImeDialogWindow, windows.get(0));
-
- exitingAppWindow.removeImmediately();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ exitingAppWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sNavBarWindow,
+ sImeWindow,
+ sImeDialogWindow));
}
@Test
@@ -95,78 +75,49 @@
sWm.mInputMethodTarget = imeAppTarget;
- final ArrayList<WindowState> windows = new ArrayList();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ imeAppTarget,
+ sImeWindow,
+ sImeDialogWindow,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sNavBarWindow));
+ }
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
+ @Test
+ public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
+ sWm.mInputMethodTarget = sChildAppWindowAbove;
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(sChildAppWindowBelow, windows.get(1));
- assertEquals(sAppWindow, windows.get(2));
- assertEquals(sChildAppWindowAbove, windows.get(3));
- assertEquals(imeAppTarget, windows.get(4));
- assertEquals(sImeWindow, windows.get(5));
- assertEquals(sImeDialogWindow, windows.get(6));
- assertEquals(sDockedDividerWindow, windows.get(7));
- assertEquals(sStatusBarWindow, windows.get(8));
- assertEquals(sNavBarWindow, windows.get(9));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(9));
- assertEquals(sChildAppWindowBelow, windows.get(8));
- assertEquals(sAppWindow, windows.get(7));
- assertEquals(sChildAppWindowAbove, windows.get(6));
- assertEquals(imeAppTarget, windows.get(5));
- assertEquals(sImeWindow, windows.get(4));
- assertEquals(sImeDialogWindow, windows.get(3));
- assertEquals(sDockedDividerWindow, windows.get(2));
- assertEquals(sStatusBarWindow, windows.get(1));
- assertEquals(sNavBarWindow, windows.get(0));
-
- // Clean-up
- sWm.mInputMethodTarget = null;
- imeAppTarget.removeImmediately();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sImeWindow,
+ sImeDialogWindow,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sNavBarWindow));
}
@Test
public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
-
sWm.mInputMethodTarget = sStatusBarWindow;
- final ArrayList<WindowState> windows = new ArrayList();
-
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(sChildAppWindowBelow, windows.get(1));
- assertEquals(sAppWindow, windows.get(2));
- assertEquals(sChildAppWindowAbove, windows.get(3));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(5));
- assertEquals(sImeWindow, windows.get(6));
- assertEquals(sImeDialogWindow, windows.get(7));
- assertEquals(sNavBarWindow, windows.get(8));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(8));
- assertEquals(sChildAppWindowBelow, windows.get(7));
- assertEquals(sAppWindow, windows.get(6));
- assertEquals(sChildAppWindowAbove, windows.get(5));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(3));
- assertEquals(sImeWindow, windows.get(2));
- assertEquals(sImeDialogWindow, windows.get(1));
- assertEquals(sNavBarWindow, windows.get(0));
-
- // Clean-up
- sWm.mInputMethodTarget = null;
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sDockedDividerWindow,
+ sStatusBarWindow,
+ sImeWindow,
+ sImeDialogWindow,
+ sNavBarWindow));
}
@Test
@@ -176,38 +127,35 @@
final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION,
sDisplayContent, "voiceInteractionWindow");
- final ArrayList<WindowState> windows = new ArrayList();
+ assertForAllWindowsOrder(Arrays.asList(
+ sWallpaperWindow,
+ sChildAppWindowBelow,
+ sAppWindow,
+ sChildAppWindowAbove,
+ sDockedDividerWindow,
+ voiceInteractionWindow,
+ sStatusBarWindow,
+ sNavBarWindow,
+ sImeWindow,
+ sImeDialogWindow));
+ }
- // Test forward traversal.
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
+ @Test
+ public void testComputeImeTarget() throws Exception {
+ // Verify that an app window can be an ime target.
+ final WindowState appWin = createWindow(null, TYPE_APPLICATION, sDisplayContent, "appWin");
+ appWin.setHasSurface(true);
+ assertTrue(appWin.canBeImeTarget());
+ WindowState imeTarget = sDisplayContent.computeImeTarget(false /* updateImeTarget */);
+ assertEquals(appWin, imeTarget);
- assertEquals(sWallpaperWindow, windows.get(0));
- assertEquals(sChildAppWindowBelow, windows.get(1));
- assertEquals(sAppWindow, windows.get(2));
- assertEquals(sChildAppWindowAbove, windows.get(3));
- assertEquals(sDockedDividerWindow, windows.get(4));
- assertEquals(voiceInteractionWindow, windows.get(5));
- assertEquals(sStatusBarWindow, windows.get(6));
- assertEquals(sNavBarWindow, windows.get(7));
- assertEquals(sImeWindow, windows.get(8));
- assertEquals(sImeDialogWindow, windows.get(9));
-
- // Test backward traversal.
- windows.clear();
- sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
-
- assertEquals(sWallpaperWindow, windows.get(9));
- assertEquals(sChildAppWindowBelow, windows.get(8));
- assertEquals(sAppWindow, windows.get(7));
- assertEquals(sChildAppWindowAbove, windows.get(6));
- assertEquals(sDockedDividerWindow, windows.get(5));
- assertEquals(voiceInteractionWindow, windows.get(4));
- assertEquals(sStatusBarWindow, windows.get(3));
- assertEquals(sNavBarWindow, windows.get(2));
- assertEquals(sImeWindow, windows.get(1));
- assertEquals(sImeDialogWindow, windows.get(0));
-
- voiceInteractionWindow.removeImmediately();
+ // Verify that an child window can be an ime target.
+ final WindowState childWin = createWindow(appWin,
+ TYPE_APPLICATION_ATTACHED_DIALOG, "childWin");
+ childWin.setHasSurface(true);
+ assertTrue(childWin.canBeImeTarget());
+ imeTarget = sDisplayContent.computeImeTarget(false /* updateImeTarget */);
+ assertEquals(childWin, imeTarget);
}
/**
@@ -284,4 +232,24 @@
assertEquals(currentOverrideConfig.densityDpi, globalConfig.densityDpi);
assertEquals(currentOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
+
+ private void assertForAllWindowsOrder(List<WindowState> expectedWindows) {
+ final LinkedList<WindowState> actualWindows = new LinkedList();
+
+ // Test forward traversal.
+ sDisplayContent.forAllWindows(actualWindows::addLast, false /* traverseTopToBottom */);
+ assertEquals(expectedWindows.size(), actualWindows.size());
+ for (WindowState w : expectedWindows) {
+ assertEquals(w, actualWindows.pollFirst());
+ }
+ assertTrue(actualWindows.isEmpty());
+
+ // Test backward traversal.
+ sDisplayContent.forAllWindows(actualWindows::addLast, true /* traverseTopToBottom */);
+ assertEquals(expectedWindows.size(), actualWindows.size());
+ for (WindowState w : expectedWindows) {
+ assertEquals(w, actualWindows.pollLast());
+ }
+ assertTrue(actualWindows.isEmpty());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index df35b7ee..5f51898 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -23,10 +23,17 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import java.util.LinkedList;
+
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -167,4 +174,34 @@
assertFalse(appWindow.canBeImeTarget());
assertFalse(imeWindow.canBeImeTarget());
}
+
+ @Test
+ public void testGetWindow() throws Exception {
+ final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
+ final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild");
+ final WindowState mediaOverlayChild = createWindow(root,
+ TYPE_APPLICATION_MEDIA_OVERLAY, "mediaOverlayChild");
+ final WindowState attachedDialogChild = createWindow(root,
+ TYPE_APPLICATION_ATTACHED_DIALOG, "attachedDialogChild");
+ final WindowState subPanelChild = createWindow(root,
+ TYPE_APPLICATION_SUB_PANEL, "subPanelChild");
+ final WindowState aboveSubPanelChild = createWindow(root,
+ TYPE_APPLICATION_ABOVE_SUB_PANEL, "aboveSubPanelChild");
+
+ final LinkedList<WindowState> windows = new LinkedList();
+
+ root.getWindow(w -> {
+ windows.addLast(w);
+ return false;
+ });
+
+ // getWindow should have returned candidate windows in z-order.
+ assertEquals(aboveSubPanelChild, windows.pollFirst());
+ assertEquals(subPanelChild, windows.pollFirst());
+ assertEquals(attachedDialogChild, windows.pollFirst());
+ assertEquals(root, windows.pollFirst());
+ assertEquals(mediaOverlayChild, windows.pollFirst());
+ assertEquals(mediaChild, windows.pollFirst());
+ assertTrue(windows.isEmpty());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index e5e3512..52e10a5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -44,6 +44,7 @@
import static android.content.res.Configuration.EMPTY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
@@ -60,6 +61,7 @@
import com.android.server.AttributeCache;
import java.util.HashSet;
+import java.util.LinkedList;
/**
* Common base class for window manager unit test classes.
@@ -120,6 +122,7 @@
sCommonWindows = new HashSet();
sWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
sImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "sImeWindow");
+ sWm.mInputMethodWindow = sImeWindow;
sImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG, "sImeDialogWindow");
sStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "sStatusBarWindow");
sNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "sNavBarWindow");
@@ -133,16 +136,25 @@
@After
public void tearDown() throws Exception {
+ final LinkedList<WindowState> nonCommonWindows = new LinkedList();
sWm.mRoot.forAllWindows(w -> {
if (!sCommonWindows.contains(w)) {
- w.removeImmediately();
+ nonCommonWindows.addLast(w);
}
}, true /* traverseTopToBottom */);
+
+ while (!nonCommonWindows.isEmpty()) {
+ nonCommonWindows.pollLast().removeImmediately();
+ }
+
+ sWm.mInputMethodTarget = null;
}
private static WindowState createCommonWindow(WindowState parent, int type, String name) {
final WindowState win = createWindow(parent, type, name);
sCommonWindows.add(win);
+ // Prevent common windows from been IMe targets
+ win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
return win;
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 6826975..a44860e 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) {
@@ -115,6 +130,17 @@
}
@Override
+ public boolean isQuotaSupported(String volumeUuid, String callingPackage) {
+ enforcePermission(Binder.getCallingUid(), callingPackage);
+
+ try {
+ return mInstaller.isQuotaSupported(volumeUuid);
+ } catch (InstallerException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
public long getTotalBytes(String volumeUuid, String callingPackage) {
enforcePermission(Binder.getCallingUid(), callingPackage);
@@ -274,4 +300,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);
+ }
}
}
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 0f865a8..fe8dbfb 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -128,23 +128,23 @@
}
@Override
- public boolean isConnected(int slotId, int featureType, int sessionId, int callSessionType,
- int callType) throws RemoteException {
+ public boolean isConnected(int slotId, int featureType, int callSessionType, int callType)
+ throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.isConnected(sessionId, callSessionType, callType);
+ return feature.isConnected(callSessionType, callType);
}
}
return false;
}
@Override
- public boolean isOpened(int slotId, int featureType, int sessionId) throws RemoteException {
+ public boolean isOpened(int slotId, int featureType) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.isOpened(sessionId);
+ return feature.isOpened();
}
}
return false;
@@ -166,23 +166,23 @@
}
@Override
- public void addRegistrationListener(int slotId, int featureType, int sessionId,
+ public void addRegistrationListener(int slotId, int featureType,
IImsRegistrationListener listener) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.addRegistrationListener(sessionId, listener);
+ feature.addRegistrationListener(listener);
}
}
}
@Override
- public void removeRegistrationListener(int slotId, int featureType, int sessionId,
+ public void removeRegistrationListener(int slotId, int featureType,
IImsRegistrationListener listener) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.removeRegistrationListener(sessionId, listener);
+ feature.removeRegistrationListener(listener);
}
}
}
@@ -224,79 +224,79 @@
}
@Override
- public IImsUt getUtInterface(int slotId, int featureType, int sessionId)
+ public IImsUt getUtInterface(int slotId, int featureType)
throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getUtInterface(sessionId);
+ return feature.getUtInterface();
}
}
return null;
}
@Override
- public IImsConfig getConfigInterface(int slotId, int featureType, int sessionId)
+ public IImsConfig getConfigInterface(int slotId, int featureType)
throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getConfigInterface(sessionId);
+ return feature.getConfigInterface();
}
}
return null;
}
@Override
- public void turnOnIms(int slotId, int featureType, int sessionId) throws RemoteException {
+ public void turnOnIms(int slotId, int featureType) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.turnOnIms(sessionId);
+ feature.turnOnIms();
}
}
}
@Override
- public void turnOffIms(int slotId, int featureType, int sessionId) throws RemoteException {
+ public void turnOffIms(int slotId, int featureType) throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.turnOffIms(sessionId);
+ feature.turnOffIms();
}
}
}
@Override
- public IImsEcbm getEcbmInterface(int slotId, int featureType, int sessionId)
+ public IImsEcbm getEcbmInterface(int slotId, int featureType)
throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getEcbmInterface(sessionId);
+ return feature.getEcbmInterface();
}
}
return null;
}
@Override
- public void setUiTTYMode(int slotId, int featureType, int sessionId, int uiTtyMode,
- Message onComplete) throws RemoteException {
+ public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete)
+ throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- feature.setUiTTYMode(sessionId, uiTtyMode, onComplete);
+ feature.setUiTTYMode(uiTtyMode, onComplete);
}
}
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType,
- int sessionId) throws RemoteException {
+ public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType)
+ throws RemoteException {
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
- return feature.getMultiEndpointInterface(sessionId);
+ return feature.getMultiEndpointInterface();
}
}
return null;
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index b2cdba2..38ea6e6f 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -135,40 +135,40 @@
}
@Override
- public boolean isConnected(int sessionId, int callServiceType, int callType)
+ public boolean isConnected(int callServiceType, int callType)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature, sessionId,
+ return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
callServiceType, callType);
}
}
@Override
- public boolean isOpened(int sessionId) throws RemoteException {
+ public boolean isOpened() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature, sessionId);
+ return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
}
}
@Override
- public void addRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
- sessionId, listener);
+ listener);
}
}
@Override
- public void removeRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
- sessionId, listener);
+ listener);
}
}
@@ -203,64 +203,61 @@
}
@Override
- public IImsUt getUtInterface(int sessionId) throws RemoteException {
+ public IImsUt getUtInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature,
- sessionId);
+ return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
}
}
@Override
- public IImsConfig getConfigInterface(int sessionId) throws RemoteException {
+ public IImsConfig getConfigInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature,
- sessionId);
+ return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
}
}
@Override
- public void turnOnIms(int sessionId) throws RemoteException {
+ public void turnOnIms() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature, sessionId);
+ getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
}
}
@Override
- public void turnOffIms(int sessionId) throws RemoteException {
+ public void turnOffIms() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature, sessionId);
+ getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
}
}
@Override
- public IImsEcbm getEcbmInterface(int sessionId) throws RemoteException {
+ public IImsEcbm getEcbmInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature,
- sessionId);
+ return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
}
}
@Override
- public void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete)
+ public void setUiTTYMode(int uiTtyMode, Message onComplete)
throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
- getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, sessionId,
- uiTtyMode, onComplete);
+ getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
+ onComplete);
}
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int sessionId) throws RemoteException {
+ public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
synchronized (mLock) {
checkBinderConnection();
return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
- mSupportedFeature, sessionId);
+ mSupportedFeature);
}
}
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
index ff53858..bbd5f02 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
@@ -42,6 +42,8 @@
public class ImsServiceProxyCompat implements IMMTelFeature {
+ private static final int SERVICE_ID = ImsFeature.MMTEL;
+
protected final int mSlotId;
protected IBinder mBinder;
@@ -65,29 +67,28 @@
}
@Override
- public boolean isConnected(int sessionId, int callServiceType, int callType)
+ public boolean isConnected(int callServiceType, int callType)
throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).isConnected(sessionId, callServiceType, callType);
+ return getServiceInterface(mBinder).isConnected(SERVICE_ID, callServiceType, callType);
}
@Override
- public boolean isOpened(int sessionId) throws RemoteException {
+ public boolean isOpened() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).isOpened(sessionId);
+ return getServiceInterface(mBinder).isOpened(SERVICE_ID);
}
@Override
- public void addRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
checkBinderConnection();
getServiceInterface(mBinder).addRegistrationListener(mSlotId, ImsFeature.MMTEL, listener);
}
@Override
- public void removeRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ public void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
- checkBinderConnection();
// Not Implemented in old ImsService. If the registration listener becomes invalid, the
// ImsService will remove.
}
@@ -114,46 +115,46 @@
}
@Override
- public IImsUt getUtInterface(int sessionId) throws RemoteException {
+ public IImsUt getUtInterface() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).getUtInterface(sessionId);
+ return getServiceInterface(mBinder).getUtInterface(SERVICE_ID);
}
@Override
- public IImsConfig getConfigInterface(int sessionId) throws RemoteException {
+ public IImsConfig getConfigInterface() throws RemoteException {
checkBinderConnection();
return getServiceInterface(mBinder).getConfigInterface(mSlotId);
}
@Override
- public void turnOnIms(int sessionId) throws RemoteException {
+ public void turnOnIms() throws RemoteException {
checkBinderConnection();
getServiceInterface(mBinder).turnOnIms(mSlotId);
}
@Override
- public void turnOffIms(int sessionId) throws RemoteException {
+ public void turnOffIms() throws RemoteException {
checkBinderConnection();
getServiceInterface(mBinder).turnOffIms(mSlotId);
}
@Override
- public IImsEcbm getEcbmInterface(int sessionId) throws RemoteException {
+ public IImsEcbm getEcbmInterface() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).getEcbmInterface(sessionId);
+ return getServiceInterface(mBinder).getEcbmInterface(SERVICE_ID);
}
@Override
- public void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete)
+ public void setUiTTYMode(int uiTtyMode, Message onComplete)
throws RemoteException {
checkBinderConnection();
- getServiceInterface(mBinder).setUiTTYMode(sessionId, uiTtyMode, onComplete);
+ getServiceInterface(mBinder).setUiTTYMode(SERVICE_ID, uiTtyMode, onComplete);
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int sessionId) throws RemoteException {
+ public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
checkBinderConnection();
- return getServiceInterface(mBinder).getMultiEndpointInterface(sessionId);
+ return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID);
}
/**
diff --git a/telephony/java/android/telephony/ims/feature/IMMTelFeature.java b/telephony/java/android/telephony/ims/feature/IMMTelFeature.java
index e180843..d65e27e 100644
--- a/telephony/java/android/telephony/ims/feature/IMMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/IMMTelFeature.java
@@ -68,7 +68,6 @@
* Checks if the IMS service has successfully registered to the IMS network with the specified
* service & call type.
*
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param callServiceType a service type that is specified in {@link ImsCallProfile}
* {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
* {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
@@ -80,31 +79,28 @@
* @return true if the specified service id is connected to the IMS network; false otherwise
* @throws RemoteException
*/
- boolean isConnected(int sessionId, int callServiceType, int callType) throws RemoteException;
+ boolean isConnected(int callServiceType, int callType) throws RemoteException;
/**
* Checks if the specified IMS service is opened.
*
- * @param sessionId a service id which is obtained from {@link #startSession}
* @return true if the specified service id is opened; false otherwise
*/
- boolean isOpened(int sessionId) throws RemoteException;
+ boolean isOpened() throws RemoteException;
/**
* Add a new registration listener for the client associated with the session Id.
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param listener An implementation of IImsRegistrationListener.
*/
- void addRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException;
/**
* Remove a previously registered listener using {@link #addRegistrationListener} for the client
* associated with the session Id.
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param listener A previously registered IImsRegistrationListener
*/
- void removeRegistrationListener(int sessionId, IImsRegistrationListener listener)
+ void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException;
/**
@@ -152,41 +148,40 @@
/**
* @return The Ut interface for the supplementary service configuration.
*/
- IImsUt getUtInterface(int sessionId) throws RemoteException;
+ IImsUt getUtInterface() throws RemoteException;
/**
* @return The config interface for IMS Configuration
*/
- IImsConfig getConfigInterface(int sessionId) throws RemoteException;
+ IImsConfig getConfigInterface() throws RemoteException;
/**
* Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
* @param sessionId a session id which is obtained from {@link #startSession}
*/
- void turnOnIms(int sessionId) throws RemoteException;
+ void turnOnIms() throws RemoteException;
/**
* Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
* @param sessionId a session id which is obtained from {@link #startSession}
*/
- void turnOffIms(int sessionId) throws RemoteException;
+ void turnOffIms() throws RemoteException;
/**
* @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
*/
- IImsEcbm getEcbmInterface(int sessionId) throws RemoteException;
+ IImsEcbm getEcbmInterface() throws RemoteException;
/**
* Sets the current UI TTY mode for the MMTelFeature.
- * @param sessionId a session id which is obtained from {@link #startSession}
* @param uiTtyMode An integer containing the new UI TTY Mode.
* @param onComplete A {@link Message} to be used when the mode has been set.
* @throws RemoteException
*/
- void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete) throws RemoteException;
+ void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException;
/**
* @return MultiEndpoint interface for DEP notifications
*/
- IImsMultiEndpoint getMultiEndpointInterface(int sessionId) throws RemoteException;
+ IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException;
}
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 570cd65..a71f0bf 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -50,21 +50,21 @@
}
@Override
- public boolean isConnected(int sessionId, int callSessionType, int callType) {
+ public boolean isConnected(int callSessionType, int callType) {
return false;
}
@Override
- public boolean isOpened(int sessionId) {
+ public boolean isOpened() {
return false;
}
@Override
- public void addRegistrationListener(int sessionId, IImsRegistrationListener listener) {
+ public void addRegistrationListener(IImsRegistrationListener listener) {
}
@Override
- public void removeRegistrationListener(int sessionId, IImsRegistrationListener listener) {
+ public void removeRegistrationListener(IImsRegistrationListener listener) {
}
@Override
@@ -84,34 +84,34 @@
}
@Override
- public IImsUt getUtInterface(int sessionId) {
+ public IImsUt getUtInterface() {
return null;
}
@Override
- public IImsConfig getConfigInterface(int sessionId) {
+ public IImsConfig getConfigInterface() {
return null;
}
@Override
- public void turnOnIms(int sessionId) {
+ public void turnOnIms() {
}
@Override
- public void turnOffIms(int sessionId) {
+ public void turnOffIms() {
}
@Override
- public IImsEcbm getEcbmInterface(int sessionId) {
+ public IImsEcbm getEcbmInterface() {
return null;
}
@Override
- public void setUiTTYMode(int sessionId, int uiTtyMode, Message onComplete) {
+ public void setUiTTYMode(int uiTtyMode, Message onComplete) {
}
@Override
- public IImsMultiEndpoint getMultiEndpointInterface(int sessionId) {
+ public IImsMultiEndpoint getMultiEndpointInterface() {
return null;
}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index b700f49..712816f 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -42,24 +42,23 @@
int startSession(int slotId, int featureType, in PendingIntent incomingCallIntent,
in IImsRegistrationListener listener);
void endSession(int slotId, int featureType, int sessionId);
- boolean isConnected(int slotId, int featureType, int sessionId, int callSessionType, int callType);
- boolean isOpened(int slotId, int featureType, int sessionId);
+ boolean isConnected(int slotId, int featureType, int callSessionType, int callType);
+ boolean isOpened(int slotId, int featureType);
int getFeatureStatus(int slotId, int featureType);
- void addRegistrationListener(int slotId, int featureType, int sessionId,
+ void addRegistrationListener(int slotId, int featureType, in IImsRegistrationListener listener);
+ void removeRegistrationListener(int slotId, int featureType,
in IImsRegistrationListener listener);
- void removeRegistrationListener(int slotId, int featureType, int sessionId,
- in IImsRegistrationListener listener);
- ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId, int callSessionType, int callType);
+ ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
+ int callSessionType, int callType);
IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
in ImsCallProfile profile, IImsCallSessionListener listener);
IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
String callId);
- IImsUt getUtInterface(int slotId, int featureType, int sessionId);
- IImsConfig getConfigInterface(int slotId, int featureType, int sessionId);
- void turnOnIms(int slotId, int featureType, int sessionId);
- void turnOffIms(int slotId, int featureType, int sessionId);
- IImsEcbm getEcbmInterface(int slotId, int featureType, int sessionId);
- void setUiTTYMode(int slotId, int featureType, int sessionId, int uiTtyMode,
- in Message onComplete);
- IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType, int sessionId);
+ IImsUt getUtInterface(int slotId, int featureType);
+ IImsConfig getConfigInterface(int slotId, int featureType);
+ void turnOnIms(int slotId, int featureType);
+ void turnOffIms(int slotId, int featureType);
+ IImsEcbm getEcbmInterface(int slotId, int featureType);
+ void setUiTTYMode(int slotId, int featureType, int uiTtyMode, in Message onComplete);
+ IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType);
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
index 91668af..e10f20d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java
@@ -100,7 +100,8 @@
mView.setText(text);
}
- private void setGravity(int gravity) {
+ @SuppressWarnings("WeakerAccess") // This method is used from Studio
+ public void setGravity(int gravity) {
mView.setGravity(gravity);
}
}