Merge "Update measureText arguments and MinikinExtent member."
diff --git a/Android.bp b/Android.bp
index 9daeb37..93d1f7b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -853,6 +853,8 @@
"core/tests/utiltests/jni",
]
+// TODO(b/77285514): remove this once the last few hidl interfaces have been
+// updated to use hwbinder.stubs.
java_library {
name: "hwbinder",
no_framework_libs: true,
@@ -1072,3 +1074,58 @@
exact_api_filename: "test-exact.txt",
args: framework_docs_args + " -referenceonly -showAnnotation android.annotation.TestApi -nodocs",
}
+
+droiddoc {
+ name: "hwbinder-stubs-docs",
+ srcs: [
+ "core/java/android/os/HidlSupport.java",
+ "core/java/android/annotation/IntDef.java",
+ "core/java/android/annotation/NonNull.java",
+ "core/java/android/annotation/SystemApi.java",
+ "core/java/android/os/HwBinder.java",
+ "core/java/android/os/HwBlob.java",
+ "core/java/android/os/HwParcel.java",
+ "core/java/android/os/IHwBinder.java",
+ "core/java/android/os/IHwInterface.java",
+ "core/java/android/os/DeadObjectException.java",
+ "core/java/android/os/DeadSystemException.java",
+ "core/java/android/os/RemoteException.java",
+ "core/java/android/util/AndroidException.java",
+ ],
+ custom_template: "droiddoc-templates-sdk",
+ installable: false,
+ no_framework_libs: true,
+ args: "-showAnnotation android.annotation.SystemApi -nodocs -stubsourceonly",
+}
+
+java_library_static {
+ name: "hwbinder.stubs",
+ sdk_version: "core_current",
+ srcs: [
+ ":hwbinder-stubs-docs",
+ ],
+}
+
+filegroup {
+ name: "apache-http-stubs-sources",
+ srcs: [
+ "core/java/org/apache/http/conn/ConnectTimeoutException.java",
+ "core/java/org/apache/http/conn/scheme/HostNameResolver.java",
+ "core/java/org/apache/http/conn/scheme/LayeredSocketFactory.java",
+ "core/java/org/apache/http/conn/scheme/SocketFactory.java",
+ "core/java/org/apache/http/conn/ssl/AbstractVerifier.java",
+ "core/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java",
+ "core/java/org/apache/http/conn/ssl/AndroidDistinguishedNameParser.java",
+ "core/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java",
+ "core/java/org/apache/http/conn/ssl/SSLSocketFactory.java",
+ "core/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java",
+ "core/java/org/apache/http/conn/ssl/X509HostnameVerifier.java",
+ "core/java/org/apache/http/params/CoreConnectionPNames.java",
+ "core/java/org/apache/http/params/HttpConnectionParams.java",
+ "core/java/org/apache/http/params/HttpParams.java",
+ "core/java/android/net/http/HttpResponseCache.java",
+ "core/java/android/net/http/SslCertificate.java",
+ "core/java/android/net/http/SslError.java",
+ "core/java/com/android/internal/util/HexDump.java",
+ ],
+}
diff --git a/api/current.txt b/api/current.txt
index e5b3aee..deaf467 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6464,7 +6464,6 @@
method public int getLockTaskFeatures(android.content.ComponentName);
method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
- method public android.content.ComponentName getMandatoryBackupTransport();
method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
method public long getMaximumTimeToLock(android.content.ComponentName);
method public java.util.List<java.lang.String> getMeteredDataDisabledPackages(android.content.ComponentName);
@@ -6571,7 +6570,6 @@
method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
method public void setLogoutEnabled(android.content.ComponentName, boolean);
method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
- method public boolean setMandatoryBackupTransport(android.content.ComponentName, android.content.ComponentName);
method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int);
method public void setMaximumTimeToLock(android.content.ComponentName, long);
@@ -6732,7 +6730,6 @@
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
- field public static final java.lang.String POLICY_MANDATORY_BACKUPS = "policy_mandatory_backups";
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -7206,8 +7203,6 @@
public final class Slice implements android.os.Parcelable {
ctor protected Slice(android.os.Parcel);
- method public static deprecated android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
- method public static deprecated android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
method public int describeContents();
method public java.util.List<java.lang.String> getHints();
method public java.util.List<android.app.slice.SliceItem> getItems();
@@ -7297,15 +7292,18 @@
}
public class SliceManager {
- method public android.app.slice.Slice bindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
- method public android.app.slice.Slice bindSlice(android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
+ method public android.app.slice.Slice bindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+ method public deprecated android.app.slice.Slice bindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+ method public android.app.slice.Slice bindSlice(android.content.Intent, java.util.Set<android.app.slice.SliceSpec>);
+ method public deprecated android.app.slice.Slice bindSlice(android.content.Intent, java.util.List<android.app.slice.SliceSpec>);
method public int checkSlicePermission(android.net.Uri, int, int);
method public java.util.List<android.net.Uri> getPinnedSlices();
- method public java.util.List<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
+ method public java.util.Set<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri);
method public java.util.Collection<android.net.Uri> getSliceDescendants(android.net.Uri);
method public void grantSlicePermission(java.lang.String, android.net.Uri);
method public android.net.Uri mapIntentToUri(android.content.Intent);
- method public void pinSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+ method public void pinSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+ method public deprecated void pinSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
method public void revokeSlicePermission(java.lang.String, android.net.Uri);
method public void unpinSlice(android.net.Uri);
field public static final java.lang.String CATEGORY_SLICE = "android.app.slice.category.SLICE";
@@ -7325,7 +7323,8 @@
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public final java.lang.String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
- method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
+ method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.Set<android.app.slice.SliceSpec>);
+ method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>);
method public android.app.PendingIntent onCreatePermissionRequest(android.net.Uri);
method public java.util.Collection<android.net.Uri> onGetSliceDescendants(android.net.Uri);
method public android.net.Uri onMapIntentToUri(android.content.Intent);
@@ -7398,7 +7397,7 @@
public static class NetworkStats.Bucket {
ctor public NetworkStats.Bucket();
- method public int getDefaultNetwork();
+ method public int getDefaultNetworkStatus();
method public long getEndTimeStamp();
method public int getMetered();
method public int getRoaming();
@@ -7474,12 +7473,12 @@
public static final class UsageEvents.Event {
ctor public UsageEvents.Event();
+ method public int getAppStandbyBucket();
method public java.lang.String getClassName();
method public android.content.res.Configuration getConfiguration();
method public int getEventType();
method public java.lang.String getPackageName();
method public java.lang.String getShortcutId();
- method public int getStandbyBucket();
method public long getTimeStamp();
field public static final int CONFIGURATION_CHANGE = 5; // 0x5
field public static final int KEYGUARD_HIDDEN = 18; // 0x12
@@ -10954,7 +10953,7 @@
method public abstract void onPackageRemoved(java.lang.String, android.os.UserHandle);
method public abstract void onPackagesAvailable(java.lang.String[], android.os.UserHandle, boolean);
method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle);
- method public void onPackagesSuspended(java.lang.String[], android.os.Bundle, android.os.UserHandle);
+ method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle, android.os.Bundle);
method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean);
method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle);
method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle);
@@ -11021,7 +11020,7 @@
field public java.lang.String sharedUserId;
field public int sharedUserLabel;
field public deprecated android.content.pm.Signature[] signatures;
- field public android.content.pm.Signature[][] signingCertificateHistory;
+ field public android.content.pm.SigningInfo signingInfo;
field public java.lang.String[] splitNames;
field public int[] splitRevisionCodes;
field public deprecated int versionCode;
@@ -11649,6 +11648,18 @@
field public static final android.os.Parcelable.Creator<android.content.pm.Signature> CREATOR;
}
+ public final class SigningInfo implements android.os.Parcelable {
+ ctor public SigningInfo();
+ ctor public SigningInfo(android.content.pm.SigningInfo);
+ method public int describeContents();
+ method public android.content.pm.Signature[] getApkContentsSigners();
+ method public android.content.pm.Signature[] getSigningCertificateHistory();
+ method public boolean hasMultipleSigners();
+ method public boolean hasPastSigningCertificates();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.SigningInfo> CREATOR;
+ }
+
public final class VersionedPackage implements android.os.Parcelable {
ctor public VersionedPackage(java.lang.String, int);
ctor public VersionedPackage(java.lang.String, long);
diff --git a/api/system-current.txt b/api/system-current.txt
index 6186a55..d27a1bf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -383,12 +383,18 @@
}
public final class StatsManager {
+ method public void addConfig(long, byte[]) throws android.app.StatsManager.StatsUnavailableException;
method public boolean addConfiguration(long, byte[]);
method public byte[] getData(long);
method public byte[] getMetadata();
+ method public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException;
+ method public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
+ method public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
method public boolean removeConfiguration(long);
+ method public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException;
method public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
method public boolean setDataFetchOperation(long, android.app.PendingIntent);
+ method public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
field public static final java.lang.String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
field public static final java.lang.String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
field public static final java.lang.String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
@@ -398,6 +404,11 @@
field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
}
+ public static class StatsManager.StatsUnavailableException extends android.util.AndroidException {
+ ctor public StatsManager.StatsUnavailableException(java.lang.String);
+ ctor public StatsManager.StatsUnavailableException(java.lang.String, java.lang.Throwable);
+ }
+
public class VrManager {
method public void setAndBindVrCompositor(android.content.ComponentName);
method public void setPersistentVrModeEnabled(boolean);
@@ -1027,11 +1038,10 @@
method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
method public abstract int getIntentVerificationStatusAsUser(java.lang.String, int);
method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
- method public android.os.PersistableBundle getSuspendedPackageAppExtras(java.lang.String);
method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public boolean isPackageSuspended(java.lang.String);
+ method public boolean isPackageSuspended(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle);
method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
@@ -1039,7 +1049,6 @@
method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence);
method public java.lang.String[] setPackagesSuspended(java.lang.String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, java.lang.String);
- method public void setSuspendedPackageAppExtras(java.lang.String, android.os.PersistableBundle);
method public abstract void setUpdateAvailable(java.lang.String, boolean);
method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
@@ -4398,6 +4407,7 @@
method public java.security.Key importKey(java.lang.String, byte[]) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.security.keystore.recovery.LockScreenRequiredException;
method public deprecated void initRecoveryService(java.lang.String, byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
method public void initRecoveryService(java.lang.String, byte[], byte[]) throws java.security.cert.CertificateException, android.security.keystore.recovery.InternalRecoveryServiceException;
+ method public static boolean isRecoverableKeyStoreEnabled(android.content.Context);
method public void removeKey(java.lang.String) throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public void setRecoverySecretTypes(int[]) throws android.security.keystore.recovery.InternalRecoveryServiceException;
method public deprecated void setRecoveryStatus(java.lang.String, java.lang.String, int) throws android.security.keystore.recovery.InternalRecoveryServiceException, android.content.pm.PackageManager.NameNotFoundException;
diff --git a/api/test-current.txt b/api/test-current.txt
index d5b1399..6b52d10 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -492,6 +492,27 @@
method public int getProgramId();
}
+ public final class BufferingParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getInitialMarkMs();
+ method public int getResumePlaybackMarkMs();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+ }
+
+ public static class BufferingParams.Builder {
+ ctor public BufferingParams.Builder();
+ ctor public BufferingParams.Builder(android.media.BufferingParams);
+ method public android.media.BufferingParams build();
+ method public android.media.BufferingParams.Builder setInitialMarkMs(int);
+ method public android.media.BufferingParams.Builder setResumePlaybackMarkMs(int);
+ }
+
+ public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
+ method public android.media.BufferingParams getBufferingParams();
+ method public void setBufferingParams(android.media.BufferingParams);
+ }
+
public final class PlaybackParams implements android.os.Parcelable {
method public int getAudioStretchMode();
method public android.media.PlaybackParams setAudioStretchMode(int);
@@ -743,6 +764,15 @@
}
+package android.security {
+
+ public class KeyStoreException extends java.lang.Exception {
+ ctor public KeyStoreException(int, java.lang.String);
+ method public int getErrorCode();
+ }
+
+}
+
package android.security.keystore {
public abstract class AttestationUtils {
@@ -752,6 +782,18 @@
field public static final int ID_TYPE_SERIAL = 1; // 0x1
}
+ public static final class KeyGenParameterSpec.Builder {
+ method public android.security.keystore.KeyGenParameterSpec.Builder setUniqueIdIncluded(boolean);
+ }
+
+ public final class KeyProtection implements java.security.KeyStore.ProtectionParameter {
+ method public long getBoundToSpecificSecureUserId();
+ }
+
+ public static final class KeyProtection.Builder {
+ method public android.security.keystore.KeyProtection.Builder setBoundToSpecificSecureUserId(long);
+ }
+
}
package android.service.autofill {
@@ -920,6 +962,10 @@
package android.telephony.mbms {
+ public static class DownloadRequest.Builder {
+ method public android.telephony.mbms.DownloadRequest.Builder setServiceId(java.lang.String);
+ }
+
public final class FileInfo implements android.os.Parcelable {
ctor public FileInfo(android.net.Uri, java.lang.String);
}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 8e46714..e0222d9 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -205,6 +205,8 @@
tests/e2e/Alarm_e2e_test.cpp \
tests/e2e/Attribution_e2e_test.cpp \
tests/e2e/GaugeMetric_e2e_push_test.cpp \
+ tests/e2e/GaugeMetric_e2e_pull_test.cpp \
+ tests/e2e/ValueMetric_pull_e2e_test.cpp \
tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp \
tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp \
tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp \
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index 7d6c47b..e6272ed 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -365,7 +365,8 @@
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> periodicAlarmMonitor;
sp<StatsLogProcessor> processor = new StatsLogProcessor(
- uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, [](const ConfigKey&){});
+ uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec * NS_PER_SEC,
+ [](const ConfigKey&){});
processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
return processor;
}
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 90ce735..f2443e8 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -72,14 +72,15 @@
StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
const sp<AlarmMonitor>& periodicAlarmMonitor,
- const long timeBaseSec,
+ const int64_t timeBaseNs,
const std::function<void(const ConfigKey&)>& sendBroadcast)
: mUidMap(uidMap),
mAnomalyAlarmMonitor(anomalyAlarmMonitor),
mPeriodicAlarmMonitor(periodicAlarmMonitor),
mSendBroadcast(sendBroadcast),
- mTimeBaseSec(timeBaseSec),
- mLastLogTimestamp(0) {
+ mTimeBaseNs(timeBaseNs),
+ mLargestTimestampSeen(0),
+ mLastTimestampSeen(0) {
}
StatsLogProcessor::~StatsLogProcessor() {
@@ -156,18 +157,54 @@
}
void StatsLogProcessor::OnLogEvent(LogEvent* event) {
+ OnLogEvent(event, false);
+}
+
+void StatsLogProcessor::OnLogEvent(LogEvent* event, bool reconnected) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
const int64_t currentTimestampNs = event->GetElapsedTimestampNs();
- if (currentTimestampNs < mLastLogTimestamp) {
- StatsdStats::getInstance().noteLogEventSkipped(
- event->GetTagId(), event->GetElapsedTimestampNs());
- return;
+ if (reconnected && mLastTimestampSeen != 0) {
+ // LogReader tells us the connection has just been reset. Now we need
+ // to enter reconnection state to find the last CP.
+ mInReconnection = true;
+ }
+
+ if (mInReconnection) {
+ // We see the checkpoint
+ if (currentTimestampNs == mLastTimestampSeen) {
+ mInReconnection = false;
+ // Found the CP. ignore this event, and we will start to read from next event.
+ return;
+ }
+ if (currentTimestampNs > mLargestTimestampSeen) {
+ // We see a new log but CP has not been found yet. Give up now.
+ mLogLossCount++;
+ mInReconnection = false;
+ StatsdStats::getInstance().noteLogLost(currentTimestampNs);
+ // Persist the data before we reset. Do we want this?
+ WriteDataToDiskLocked();
+ // We see fresher event before we see the checkpoint. We might have lost data.
+ // The best we can do is to reset.
+ std::vector<ConfigKey> configKeys;
+ for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) {
+ configKeys.push_back(it->first);
+ }
+ resetConfigsLocked(currentTimestampNs, configKeys);
+ } else {
+ // Still in search of the CP. Keep going.
+ return;
+ }
+ }
+
+ mLogCount++;
+ mLastTimestampSeen = currentTimestampNs;
+ if (mLargestTimestampSeen < currentTimestampNs) {
+ mLargestTimestampSeen = currentTimestampNs;
}
resetIfConfigTtlExpiredLocked(currentTimestampNs);
- mLastLogTimestamp = currentTimestampNs;
StatsdStats::getInstance().noteAtomLogged(
event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC);
@@ -210,7 +247,7 @@
const int64_t timestampNs, const ConfigKey& key, const StatsdConfig& config) {
VLOG("Updated configuration for key %s", key.ToString().c_str());
sp<MetricsManager> newMetricsManager =
- new MetricsManager(key, config, mTimeBaseSec, (timestampNs - 1) / NS_PER_SEC + 1, mUidMap,
+ new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap,
mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
auto it = mMetricsManagers.find(key);
if (it != mMetricsManagers.end()) {
@@ -339,15 +376,9 @@
(long long)getWallClockNs());
}
-void StatsLogProcessor::resetIfConfigTtlExpiredLocked(const int64_t timestampNs) {
- std::vector<ConfigKey> configKeysTtlExpired;
- for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) {
- if (it->second != nullptr && !it->second->isInTtl(timestampNs)) {
- configKeysTtlExpired.push_back(it->first);
- }
- }
-
- for (const auto& key : configKeysTtlExpired) {
+void StatsLogProcessor::resetConfigsLocked(const int64_t timestampNs,
+ const std::vector<ConfigKey>& configs) {
+ for (const auto& key : configs) {
StatsdConfig config;
if (StorageManager::readConfigFromDisk(key, &config)) {
OnConfigUpdatedLocked(timestampNs, key, config);
@@ -362,6 +393,18 @@
}
}
+void StatsLogProcessor::resetIfConfigTtlExpiredLocked(const int64_t timestampNs) {
+ std::vector<ConfigKey> configKeysTtlExpired;
+ for (auto it = mMetricsManagers.begin(); it != mMetricsManagers.end(); it++) {
+ if (it->second != nullptr && !it->second->isInTtl(timestampNs)) {
+ configKeysTtlExpired.push_back(it->first);
+ }
+ }
+ if (configKeysTtlExpired.size() > 0) {
+ resetConfigsLocked(timestampNs, configKeysTtlExpired);
+ }
+}
+
void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
auto it = mMetricsManagers.find(key);
@@ -438,6 +481,10 @@
WriteDataToDiskLocked();
}
+void StatsLogProcessor::informPullAlarmFired(const int64_t timestampNs) {
+ mStatsPullerManager.OnAlarmFired(timestampNs);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 1e82b1e..6efdf8c 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -36,10 +36,13 @@
public:
StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor,
const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor,
- const long timeBaseSec,
+ const int64_t timeBaseNs,
const std::function<void(const ConfigKey&)>& sendBroadcast);
virtual ~StatsLogProcessor();
+ void OnLogEvent(LogEvent* event, bool reconnectionStarts);
+
+ // for testing only.
void OnLogEvent(LogEvent* event);
void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
@@ -70,6 +73,7 @@
void dumpStates(FILE* out, bool verbose);
+ void informPullAlarmFired(const int64_t timestampNs);
private:
// For testing only.
@@ -121,16 +125,30 @@
// Handler over the isolated uid change event.
void onIsolatedUidChangedEventLocked(const LogEvent& event);
+ void resetConfigsLocked(const int64_t timestampNs, const std::vector<ConfigKey>& configs);
+
// Function used to send a broadcast so that receiver for the config key can call getData
// to retrieve the stored data.
std::function<void(const ConfigKey& key)> mSendBroadcast;
- const long mTimeBaseSec;
+ const int64_t mTimeBaseNs;
- int64_t mLastLogTimestamp;
+ // Largest timestamp of the events that we have processed.
+ int64_t mLargestTimestampSeen = 0;
+
+ int64_t mLastTimestampSeen = 0;
+
+ bool mInReconnection = false;
+
+ // Processed log count
+ uint64_t mLogCount = 0;
+
+ // Log loss detected count
+ int mLogLossCount = 0;
long mLastPullerCacheClearTimeSec = 0;
+ FRIEND_TEST(StatsLogProcessorTest, TestOutOfOrderLogs);
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
@@ -145,6 +163,12 @@
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
+ FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
+ FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
+ FRIEND_TEST(GaugeMetricE2eTest, TestAllConditionChangesSamplePulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
+
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index d3cda63..f7cc00c 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -81,7 +81,7 @@
StatsPuller::SetUidMap(mUidMap);
mConfigManager = new ConfigManager();
mProcessor = new StatsLogProcessor(mUidMap, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor,
- getElapsedRealtimeSec(), [this](const ConfigKey& key) {
+ getElapsedRealtimeNs(), [this](const ConfigKey& key) {
sp<IStatsCompanionService> sc = getStatsCompanionService();
auto receiver = mConfigManager->GetConfigReceiver(key);
if (sc == nullptr) {
@@ -745,7 +745,7 @@
"Only system uid can call informPollAlarmFired");
}
- mStatsPullerManager.OnAlarmFired();
+ mProcessor->informPullAlarmFired(getElapsedRealtimeNs());
VLOG("StatsService::informPollAlarmFired succeeded");
@@ -780,8 +780,6 @@
}
void StatsService::sayHiToStatsCompanion() {
- // TODO: This method needs to be private. It is temporarily public and unsecured for testing
- // purposes.
sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
if (statsCompanion != nullptr) {
VLOG("Telling statsCompanion that statsd is ready");
@@ -818,49 +816,44 @@
mConfigManager->Startup();
}
-void StatsService::OnLogEvent(LogEvent* event) {
- mProcessor->OnLogEvent(event);
+void StatsService::OnLogEvent(LogEvent* event, bool reconnectionStarts) {
+ mProcessor->OnLogEvent(event, reconnectionStarts);
}
Status StatsService::getData(int64_t key, vector<uint8_t>* output) {
IPCThreadState* ipc = IPCThreadState::self();
VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
- if (checkCallingPermission(String16(kPermissionDump))) {
- ConfigKey configKey(ipc->getCallingUid(), key);
- mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(),
- false /* include_current_bucket*/, output);
- return Status::ok();
- } else {
+ if (!checkCallingPermission(String16(kPermissionDump))) {
return Status::fromExceptionCode(binder::Status::EX_SECURITY);
}
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(),
+ false /* include_current_bucket*/, output);
+ return Status::ok();
}
Status StatsService::getMetadata(vector<uint8_t>* output) {
IPCThreadState* ipc = IPCThreadState::self();
VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(),
ipc->getCallingUid());
- if (checkCallingPermission(String16(kPermissionDump))) {
- StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
- return Status::ok();
- } else {
+ if (!checkCallingPermission(String16(kPermissionDump))) {
return Status::fromExceptionCode(binder::Status::EX_SECURITY);
}
+ StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
+ return Status::ok();
}
-Status StatsService::addConfiguration(int64_t key,
- const vector <uint8_t>& config,
- bool* success) {
+Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config) {
+ if (!checkCallingPermission(String16(kPermissionDump))) {
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
IPCThreadState* ipc = IPCThreadState::self();
- if (checkCallingPermission(String16(kPermissionDump))) {
- if (addConfigurationChecked(ipc->getCallingUid(), key, config)) {
- *success = true;
- } else {
- *success = false;
- }
+ if (addConfigurationChecked(ipc->getCallingUid(), key, config)) {
return Status::ok();
} else {
- *success = false;
- return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ ALOGE("Could not parse malformatted StatsdConfig");
+ return Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT,
+ "config does not correspond to a StatsdConfig proto");
}
}
@@ -876,80 +869,62 @@
return true;
}
-Status StatsService::removeDataFetchOperation(int64_t key, bool* success) {
- IPCThreadState* ipc = IPCThreadState::self();
- if (checkCallingPermission(String16(kPermissionDump))) {
- ConfigKey configKey(ipc->getCallingUid(), key);
- mConfigManager->RemoveConfigReceiver(configKey);
- *success = true;
- return Status::ok();
- } else {
- *success = false;
+Status StatsService::removeDataFetchOperation(int64_t key) {
+ if (!checkCallingPermission(String16(kPermissionDump))) {
return Status::fromExceptionCode(binder::Status::EX_SECURITY);
}
+ IPCThreadState* ipc = IPCThreadState::self();
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mConfigManager->RemoveConfigReceiver(configKey);
+ return Status::ok();
}
-Status StatsService::setDataFetchOperation(int64_t key, const sp<android::IBinder>& intentSender,
- bool* success) {
- IPCThreadState* ipc = IPCThreadState::self();
- if (checkCallingPermission(String16(kPermissionDump))) {
- ConfigKey configKey(ipc->getCallingUid(), key);
- mConfigManager->SetConfigReceiver(configKey, intentSender);
- *success = true;
- return Status::ok();
- } else {
- *success = false;
+Status StatsService::setDataFetchOperation(int64_t key, const sp<android::IBinder>& intentSender) {
+ if (!checkCallingPermission(String16(kPermissionDump))) {
return Status::fromExceptionCode(binder::Status::EX_SECURITY);
}
+ IPCThreadState* ipc = IPCThreadState::self();
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mConfigManager->SetConfigReceiver(configKey, intentSender);
+ return Status::ok();
}
-Status StatsService::removeConfiguration(int64_t key, bool* success) {
- IPCThreadState* ipc = IPCThreadState::self();
- if (checkCallingPermission(String16(kPermissionDump))) {
- ConfigKey configKey(ipc->getCallingUid(), key);
- mConfigManager->RemoveConfig(configKey);
- SubscriberReporter::getInstance().removeConfig(configKey);
- *success = true;
- return Status::ok();
- } else {
- *success = false;
+Status StatsService::removeConfiguration(int64_t key) {
+ if (!checkCallingPermission(String16(kPermissionDump))) {
return Status::fromExceptionCode(binder::Status::EX_SECURITY);
}
+ IPCThreadState* ipc = IPCThreadState::self();
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mConfigManager->RemoveConfig(configKey);
+ SubscriberReporter::getInstance().removeConfig(configKey);
+ return Status::ok();
}
Status StatsService::setBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const sp<android::IBinder>& intentSender,
- bool* success) {
+ const sp<android::IBinder>& intentSender) {
VLOG("StatsService::setBroadcastSubscriber called.");
- IPCThreadState* ipc = IPCThreadState::self();
- if (checkCallingPermission(String16(kPermissionDump))) {
- ConfigKey configKey(ipc->getCallingUid(), configId);
- SubscriberReporter::getInstance()
- .setBroadcastSubscriber(configKey, subscriberId, intentSender);
- *success = true;
- return Status::ok();
- } else {
- *success = false;
+ if (!checkCallingPermission(String16(kPermissionDump))) {
return Status::fromExceptionCode(binder::Status::EX_SECURITY);
}
+ IPCThreadState* ipc = IPCThreadState::self();
+ ConfigKey configKey(ipc->getCallingUid(), configId);
+ SubscriberReporter::getInstance()
+ .setBroadcastSubscriber(configKey, subscriberId, intentSender);
+ return Status::ok();
}
Status StatsService::unsetBroadcastSubscriber(int64_t configId,
- int64_t subscriberId,
- bool* success) {
+ int64_t subscriberId) {
VLOG("StatsService::unsetBroadcastSubscriber called.");
- IPCThreadState* ipc = IPCThreadState::self();
- if (checkCallingPermission(String16(kPermissionDump))) {
- ConfigKey configKey(ipc->getCallingUid(), configId);
- SubscriberReporter::getInstance()
- .unsetBroadcastSubscriber(configKey, subscriberId);
- *success = true;
- return Status::ok();
- } else {
- *success = false;
+ if (!checkCallingPermission(String16(kPermissionDump))) {
return Status::fromExceptionCode(binder::Status::EX_SECURITY);
}
+ IPCThreadState* ipc = IPCThreadState::self();
+ ConfigKey configKey(ipc->getCallingUid(), configId);
+ SubscriberReporter::getInstance()
+ .unsetBroadcastSubscriber(configKey, subscriberId);
+ return Status::ok();
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 648e9c5..d502796 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -76,7 +76,7 @@
/**
* Called by LogReader when there's a log event to process.
*/
- virtual void OnLogEvent(LogEvent* event);
+ virtual void OnLogEvent(LogEvent* event, bool reconnectionStarts);
/**
* Binder call for clients to request data for this configuration key.
@@ -94,24 +94,23 @@
* Binder call to let clients send a configuration and indicate they're interested when they
* should requestData for this configuration.
*/
- virtual Status addConfiguration(int64_t key, const vector<uint8_t>& config,
- bool* success) override;
+ virtual Status addConfiguration(int64_t key, const vector<uint8_t>& config) override;
/**
* Binder call to let clients register the data fetch operation for a configuration.
*/
- virtual Status setDataFetchOperation(int64_t key, const sp<android::IBinder>& intentSender,
- bool* success) override;
+ virtual Status setDataFetchOperation(int64_t key,
+ const sp<android::IBinder>& intentSender) override;
/**
* Binder call to remove the data fetch operation for the specified config key.
*/
- virtual Status removeDataFetchOperation(int64_t key, bool* success) override;
+ virtual Status removeDataFetchOperation(int64_t key) override;
/**
* Binder call to allow clients to remove the specified configuration.
*/
- virtual Status removeConfiguration(int64_t key, bool* success) override;
+ virtual Status removeConfiguration(int64_t key) override;
/**
* Binder call to associate the given config's subscriberId with the given intentSender.
@@ -119,17 +118,13 @@
*/
virtual Status setBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const sp<android::IBinder>& intentSender,
- bool* success) override;
+ const sp<android::IBinder>& intentSender) override;
/**
* Binder call to unassociate the given config's subscriberId with any intentSender.
*/
- virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId,
- bool* success) override;
+ virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId) override;
- // TODO: public for testing since statsd doesn't run when system starts. Change to private
- // later.
/** Inform statsCompanion that statsd is ready. */
virtual void sayHiToStatsCompanion();
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 83d59c0..50ffe17 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -40,8 +40,8 @@
return mPullerManager.PullerForMatcherExists(tagId);
}
- void OnAlarmFired() {
- mPullerManager.OnAlarmFired();
+ void OnAlarmFired(const int64_t currentTimeNs) {
+ mPullerManager.OnAlarmFired(currentTimeNs);
}
virtual bool Pull(const int tagId, const int64_t timesNs,
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
index 2f0e885..610faad 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
@@ -275,11 +275,9 @@
}
}
-void StatsPullerManagerImpl::OnAlarmFired() {
+void StatsPullerManagerImpl::OnAlarmFired(const int64_t currentTimeNs) {
AutoMutex _l(mLock);
- int64_t currentTimeNs = getElapsedRealtimeNs();
-
int64_t minNextPullTimeNs = LONG_MAX;
vector<pair<int, vector<ReceiverInfo*>>> needToPull =
@@ -288,7 +286,7 @@
vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>();
if (pair.second.size() != 0) {
for (ReceiverInfo& receiverInfo : pair.second) {
- if (receiverInfo.nextPullTimeNs < currentTimeNs) {
+ if (receiverInfo.nextPullTimeNs <= currentTimeNs) {
receivers.push_back(&receiverInfo);
} else {
if (receiverInfo.nextPullTimeNs < minNextPullTimeNs) {
@@ -311,10 +309,9 @@
receiverPtr->onDataPulled(data);
// we may have just come out of a coma, compute next pull time
receiverInfo->nextPullTimeNs =
- ceil((double_t)(currentTimeNs - receiverInfo->nextPullTimeNs) /
- receiverInfo->intervalNs) *
- receiverInfo->intervalNs +
- receiverInfo->nextPullTimeNs;
+ (currentTimeNs - receiverInfo->nextPullTimeNs) /
+ receiverInfo->intervalNs * receiverInfo->intervalNs +
+ receiverInfo->intervalNs + receiverInfo->nextPullTimeNs;
if (receiverInfo->nextPullTimeNs < minNextPullTimeNs) {
minNextPullTimeNs = receiverInfo->nextPullTimeNs;
}
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h
index 8c771f3..56d04b4 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.h
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h
@@ -58,7 +58,7 @@
// Verify if we know how to pull for this matcher
bool PullerForMatcherExists(int tagId) const;
- void OnAlarmFired();
+ void OnAlarmFired(const int64_t timeNs);
bool Pull(const int tagId, const int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data);
@@ -90,6 +90,11 @@
void updateAlarmLocked();
int64_t mNextPullTimeNs;
+
+ FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
+ FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
};
} // namespace statsd
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index b589d0d..ee3ed23 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -50,7 +50,7 @@
// const int FIELD_ID_PULLED_ATOM_STATS = 10; // The proto is written in stats_log_util.cpp
const int FIELD_ID_LOGGER_ERROR_STATS = 11;
const int FIELD_ID_PERIODIC_ALARM_STATS = 12;
-const int FIELD_ID_SKIPPED_LOG_EVENT_STATS = 13;
+const int FIELD_ID_LOG_LOSS_STATS = 14;
const int FIELD_ID_ATOM_STATS_TAG = 1;
const int FIELD_ID_ATOM_STATS_COUNT = 2;
@@ -61,9 +61,6 @@
const int FIELD_ID_LOGGER_STATS_TIME = 1;
const int FIELD_ID_LOGGER_STATS_ERROR_CODE = 2;
-const int FIELD_ID_SKIPPED_LOG_EVENT_STATS_TAG = 1;
-const int FIELD_ID_SKIPPED_LOG_EVENT_STATS_TIMESTAMP = 2;
-
const int FIELD_ID_CONFIG_STATS_UID = 1;
const int FIELD_ID_CONFIG_STATS_ID = 2;
const int FIELD_ID_CONFIG_STATS_CREATION = 3;
@@ -182,6 +179,14 @@
noteConfigResetInternalLocked(key);
}
+void StatsdStats::noteLogLost(int64_t timestampNs) {
+ lock_guard<std::mutex> lock(mLock);
+ if (mLogLossTimestampNs.size() == kMaxLoggerErrors) {
+ mLogLossTimestampNs.pop_front();
+ }
+ mLogLossTimestampNs.push_back(timestampNs);
+}
+
void StatsdStats::noteBroadcastSent(const ConfigKey& key) {
noteBroadcastSent(key, getWallClockSec());
}
@@ -350,15 +355,6 @@
mPushedAtomStats[atomId]++;
}
-void StatsdStats::noteLogEventSkipped(int tag, int64_t timestamp) {
- lock_guard<std::mutex> lock(mLock);
- // grows strictly one at a time. so it won't > kMaxSkippedLogEvents
- if (mSkippedLogEvents.size() == kMaxSkippedLogEvents) {
- mSkippedLogEvents.pop_front();
- }
- mSkippedLogEvents.push_back(std::make_pair(tag, timestamp));
-}
-
void StatsdStats::noteLoggerError(int error) {
lock_guard<std::mutex> lock(mLock);
// grows strictly one at a time. so it won't > kMaxLoggerErrors
@@ -381,7 +377,7 @@
mAnomalyAlarmRegisteredStats = 0;
mPeriodicAlarmRegisteredStats = 0;
mLoggerErrors.clear();
- mSkippedLogEvents.clear();
+ mLogLossTimestampNs.clear();
for (auto& config : mConfigStats) {
config.second->broadcast_sent_time_sec.clear();
config.second->data_drop_time_sec.clear();
@@ -395,6 +391,14 @@
}
}
+string buildTimeString(int64_t timeSec) {
+ time_t t = timeSec;
+ struct tm* tm = localtime(&t);
+ char timeBuffer[80];
+ strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p\n", tm);
+ return string(timeBuffer);
+}
+
void StatsdStats::dumpStats(FILE* out) const {
lock_guard<std::mutex> lock(mLock);
time_t t = mStartTimeSec;
@@ -437,15 +441,19 @@
}
for (const auto& broadcastTime : configStats->broadcast_sent_time_sec) {
- fprintf(out, "\tbroadcast time: %d\n", broadcastTime);
+ fprintf(out, "\tbroadcast time: %s(%lld)\n",
+ buildTimeString(broadcastTime).c_str(), (long long)broadcastTime);
}
for (const auto& dataDropTime : configStats->data_drop_time_sec) {
- fprintf(out, "\tdata drop time: %d\n", dataDropTime);
+ fprintf(out, "\tdata drop time: %s(%lld)\n",
+ buildTimeString(dataDropTime).c_str(), (long long)dataDropTime);
}
for (const auto& dump : configStats->dump_report_stats) {
- fprintf(out, "\tdump report time: %d bytes: %lld\n", dump.first, (long long)dump.second);
+ fprintf(out, "\tdump report time: %s(%lld) bytes: %lld\n",
+ buildTimeString(dump.first).c_str(), (long long)dump.first,
+ (long long)dump.second);
}
for (const auto& stats : pair.second->matcher_stats) {
@@ -503,8 +511,8 @@
strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M%p\n", error_tm);
fprintf(out, "Logger error %d at %s\n", error.second, buffer);
}
- for (const auto& skipped : mSkippedLogEvents) {
- fprintf(out, "Log event (%d) skipped at %lld\n", skipped.first, (long long)skipped.second);
+ for (const auto& loss : mLogLossTimestampNs) {
+ fprintf(out, "Log loss detected at %lld (elapsedRealtimeNs)\n", (long long)loss);
}
}
@@ -660,13 +668,9 @@
proto.end(token);
}
- for (const auto& skipped : mSkippedLogEvents) {
- uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SKIPPED_LOG_EVENT_STATS |
- FIELD_COUNT_REPEATED);
- proto.write(FIELD_TYPE_INT32 | FIELD_ID_SKIPPED_LOG_EVENT_STATS_TAG, skipped.first);
- proto.write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_LOG_EVENT_STATS_TIMESTAMP,
- (long long)skipped.second);
- proto.end(token);
+ for (const auto& loss : mLogLossTimestampNs) {
+ proto.write(FIELD_TYPE_INT64 | FIELD_ID_LOG_LOSS_STATS | FIELD_COUNT_REPEATED,
+ (long long)loss);
}
output->clear();
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 123a703..2cbcca3 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -102,9 +102,7 @@
// The max number of old config stats we keep.
const static int kMaxIceBoxSize = 20;
- const static int kMaxLoggerErrors = 10;
-
- const static int kMaxSkippedLogEvents = 200;
+ const static int kMaxLoggerErrors = 20;
const static int kMaxTimestampCount = 20;
@@ -280,7 +278,7 @@
/**
* Records statsd skipped an event.
*/
- void noteLogEventSkipped(int tag, int64_t timestamp);
+ void noteLogLost(int64_t timestamp);
/**
* Reset the historical stats. Including all stats in icebox, and the tracked stats about
@@ -337,8 +335,8 @@
// Logd errors. Size capped by kMaxLoggerErrors.
std::list<const std::pair<int, int>> mLoggerErrors;
- // Skipped log events.
- std::list<const std::pair<int, int64_t>> mSkippedLogEvents;
+ // Timestamps when we detect log loss after logd reconnect.
+ std::list<int64_t> mLogLossTimestampNs;
// Stores the number of times statsd modified the anomaly alarm registered with
// StatsCompanionService.
diff --git a/cmds/statsd/src/logd/LogListener.h b/cmds/statsd/src/logd/LogListener.h
index 69ca571..f924040 100644
--- a/cmds/statsd/src/logd/LogListener.h
+++ b/cmds/statsd/src/logd/LogListener.h
@@ -33,7 +33,7 @@
LogListener();
virtual ~LogListener();
- virtual void OnLogEvent(LogEvent* msg) = 0;
+ virtual void OnLogEvent(LogEvent* msg, bool reconnectionStarts) = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/logd/LogReader.cpp b/cmds/statsd/src/logd/LogReader.cpp
index 0fe896b..26ae6a3 100644
--- a/cmds/statsd/src/logd/LogReader.cpp
+++ b/cmds/statsd/src/logd/LogReader.cpp
@@ -113,7 +113,8 @@
LogEvent event(msg);
// Call the listener
- mListener->OnLogEvent(&event);
+ mListener->OnLogEvent(&event,
+ lineCount == 1 /* indicate whether it's a new connection */);
}
}
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 4ce4768..8ce9ec7 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -116,11 +116,6 @@
ALOGE("Failed to add service");
return -1;
}
-
- // TODO: This line is temporary, since statsd doesn't start up automatically (and therefore
- // the call in StatsService::SystemRunning() won't ever be called right now).
- // TODO: Are you sure? Don't we need to reconnect to the system process if we get restarted?
- // --joeo
service->sayHiToStatsCompanion();
// Start the log reader thread
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index c77e07b..e21392c 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -92,7 +92,7 @@
mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
- (long long)mBucketSizeNs, (long long)mStartTimeNs);
+ (long long)mBucketSizeNs, (long long)mTimeBaseNs);
}
CountMetricProducer::~CountMetricProducer() {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 3125fa7..3661b31 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -122,7 +122,7 @@
}
}
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
- (long long)mBucketSizeNs, (long long)mStartTimeNs);
+ (long long)mBucketSizeNs, (long long)mTimeBaseNs);
}
DurationMetricProducer::~DurationMetricProducer() {
@@ -154,13 +154,13 @@
return make_unique<OringDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mStartTimeNs, mBucketSizeNs, mConditionSliced,
+ mTimeBaseNs, mBucketSizeNs, mConditionSliced,
mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
case DurationMetric_AggregationType_MAX_SPARSE:
return make_unique<MaxDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mStartTimeNs, mBucketSizeNs, mConditionSliced,
+ mTimeBaseNs, mBucketSizeNs, mConditionSliced,
mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
}
}
@@ -650,7 +650,7 @@
void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
const LogEvent& event) {
int64_t eventTimeNs = event.GetElapsedTimestampNs();
- if (eventTimeNs < mStartTimeNs) {
+ if (eventTimeNs < mTimeBaseNs) {
return;
}
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 33ab9aa..2f2679e 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -68,7 +68,7 @@
}
mProto = std::make_unique<ProtoOutputStream>();
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
- (long long)mBucketSizeNs, (long long)mStartTimeNs);
+ (long long)mBucketSizeNs, (long long)mTimeBaseNs);
}
EventMetricProducer::~EventMetricProducer() {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 3c77aae..6886f7c 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -61,9 +61,9 @@
GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric,
const int conditionIndex,
const sp<ConditionWizard>& wizard, const int pullTagId,
- const int64_t startTimeNs,
+ const int64_t timeBaseNs, const int64_t startTimeNs,
shared_ptr<StatsPullerManager> statsPullerManager)
- : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
+ : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard),
mStatsPullerManager(statsPullerManager),
mPullTagId(pullTagId),
mDimensionSoftLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) !=
@@ -110,14 +110,15 @@
}
mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
+ flushIfNeededLocked(startTimeNs);
// Kicks off the puller immediately.
if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
mStatsPullerManager->RegisterReceiver(
- mPullTagId, this, mCurrentBucketStartTimeNs + mBucketSizeNs, mBucketSizeNs);
+ mPullTagId, this, getCurrentBucketEndTimeNs(), mBucketSizeNs);
}
VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d",
- (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs,
+ (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs,
mConditionSliced);
}
@@ -125,14 +126,14 @@
GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric,
const int conditionIndex,
const sp<ConditionWizard>& wizard, const int pullTagId,
- const int64_t startTimeNs)
- : GaugeMetricProducer(key, metric, conditionIndex, wizard, pullTagId, startTimeNs,
+ const int64_t timeBaseNs, const int64_t startTimeNs)
+ : GaugeMetricProducer(key, metric, conditionIndex, wizard, pullTagId, timeBaseNs, startTimeNs,
make_shared<StatsPullerManager>()) {
}
GaugeMetricProducer::~GaugeMetricProducer() {
VLOG("~GaugeMetricProducer() called");
- if (mPullTagId != -1) {
+ if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
mStatsPullerManager->UnRegisterReceiver(mPullTagId, this);
}
}
@@ -214,18 +215,20 @@
android::util::AtomsInfo::kNotTruncatingTimestampAtomWhiteList.find(
mTagId) ==
android::util::AtomsInfo::kNotTruncatingTimestampAtomWhiteList.end();
- const int64_t wall_clock_ns = truncateTimestamp ?
- truncateTimestampNsToFiveMinutes(getWallClockNs()) : getWallClockNs();
for (const auto& atom : bucket.mGaugeAtoms) {
- int64_t timestampNs = truncateTimestamp ?
- truncateTimestampNsToFiveMinutes(atom.mTimestamps) : atom.mTimestamps;
+ const int64_t elapsedTimestampNs = truncateTimestamp ?
+ truncateTimestampNsToFiveMinutes(atom.mElapsedTimestamps) :
+ atom.mElapsedTimestamps;
+ const int64_t wallClockNs = truncateTimestamp ?
+ truncateTimestampNsToFiveMinutes(atom.mWallClockTimestampNs) :
+ atom.mWallClockTimestampNs;
protoOutput->write(
FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_ELAPSED_ATOM_TIMESTAMP,
- (long long)timestampNs);
+ (long long)elapsedTimestampNs);
protoOutput->write(
FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED |
FIELD_ID_WALL_CLOCK_ATOM_TIMESTAMP,
- (long long)wall_clock_ns);
+ (long long)wallClockNs);
}
}
protoOutput->end(bucketInfoToken);
@@ -241,7 +244,7 @@
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
-void GaugeMetricProducer::pullLocked() {
+void GaugeMetricProducer::pullLocked(const int64_t timestampNs) {
bool triggerPuller = false;
switch(mSamplingType) {
// When the metric wants to do random sampling and there is already one gauge atom for the
@@ -262,7 +265,7 @@
}
vector<std::shared_ptr<LogEvent>> allData;
- if (!mStatsPullerManager->Pull(mPullTagId, getElapsedRealtimeNs(), &allData)) {
+ if (!mStatsPullerManager->Pull(mPullTagId, timestampNs, &allData)) {
ALOGE("Gauge Stats puller failed for tag: %d", mPullTagId);
return;
}
@@ -273,26 +276,26 @@
}
void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet,
- const int64_t eventTime) {
+ const int64_t eventTimeNs) {
VLOG("GaugeMetric %lld onConditionChanged", (long long)mMetricId);
- flushIfNeededLocked(eventTime);
+ flushIfNeededLocked(eventTimeNs);
mCondition = conditionMet;
- if (mPullTagId != -1) {
- pullLocked();
+ if (mPullTagId != -1 && mCondition) {
+ pullLocked(eventTimeNs);
} // else: Push mode. No need to proactively pull the gauge data.
}
void GaugeMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
- const int64_t eventTime) {
+ const int64_t eventTimeNs) {
VLOG("GaugeMetric %lld onSlicedConditionMayChange overall condition %d", (long long)mMetricId,
overallCondition);
- flushIfNeededLocked(eventTime);
+ flushIfNeededLocked(eventTimeNs);
// If the condition is sliced, mCondition is true if any of the dimensions is true. And we will
// pull for every dimension.
mCondition = overallCondition;
if (mPullTagId != -1) {
- pullLocked();
+ pullLocked(eventTimeNs);
} // else: Push mode. No need to proactively pull the gauge data.
}
@@ -360,7 +363,7 @@
if (hitGuardRailLocked(eventKey)) {
return;
}
- GaugeAtom gaugeAtom(getGaugeFields(event), eventTimeNs);
+ GaugeAtom gaugeAtom(getGaugeFields(event), eventTimeNs, getWallClockNs());
(*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom);
// Anomaly detection on gauge metric only works when there is one numeric
// field specified.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 04b7df9..08765c2 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -33,11 +33,12 @@
namespace statsd {
struct GaugeAtom {
- GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t timeNs)
- : mFields(fields), mTimestamps(timeNs) {
+ GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t elapsedTimeNs, int wallClockNs)
+ : mFields(fields), mElapsedTimestamps(elapsedTimeNs), mWallClockTimestampNs(wallClockNs) {
}
std::shared_ptr<vector<FieldValue>> mFields;
- int64_t mTimestamps;
+ int64_t mElapsedTimestamps;
+ int64_t mWallClockTimestampNs;
};
struct GaugeBucket {
@@ -57,7 +58,7 @@
public:
GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int pullTagId, const int64_t startTimeNs);
+ const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs);
virtual ~GaugeMetricProducer();
@@ -76,7 +77,7 @@
flushCurrentBucketLocked(eventTimeNs);
mCurrentBucketStartTimeNs = eventTimeNs;
if (mPullTagId != -1) {
- pullLocked();
+ pullLocked(eventTimeNs);
}
};
@@ -94,7 +95,8 @@
// for testing
GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int pullTagId, const int64_t startTimeNs,
+ const int pullTagId,
+ const int64_t timeBaseNs, const int64_t startTimeNs,
std::shared_ptr<StatsPullerManager> statsPullerManager);
// Internal interface to handle condition change.
@@ -115,7 +117,7 @@
void flushCurrentBucketLocked(const int64_t& eventTimeNs) override;
- void pullLocked();
+ void pullLocked(const int64_t timestampNs);
int mTagId;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index bcf0e62..5ff8082 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -27,7 +27,7 @@
void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
int64_t eventTimeNs = event.GetElapsedTimestampNs();
// this is old event, maybe statsd restarted?
- if (eventTimeNs < mStartTimeNs) {
+ if (eventTimeNs < mTimeBaseNs) {
return;
}
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index f931e57..532ecbf 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -40,12 +40,12 @@
// be a no-op.
class MetricProducer : public virtual PackageInfoListener {
public:
- MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t startTimeNs,
+ MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
const int conditionIndex, const sp<ConditionWizard>& wizard)
: mMetricId(metricId),
mConfigKey(key),
- mStartTimeNs(startTimeNs),
- mCurrentBucketStartTimeNs(startTimeNs),
+ mTimeBaseNs(timeBaseNs),
+ mCurrentBucketStartTimeNs(timeBaseNs),
mCurrentBucketNum(0),
mCondition(conditionIndex >= 0 ? false : true),
mConditionSliced(false),
@@ -165,6 +165,11 @@
dropDataLocked(dropTimeNs);
}
+ // For test only.
+ inline int64_t getCurrentBucketNum() const {
+ return mCurrentBucketNum;
+ }
+
protected:
virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
@@ -204,7 +209,7 @@
// Convenience to compute the current bucket's end time, which is always aligned with the
// start time of the metric.
int64_t getCurrentBucketEndTimeNs() const {
- return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
+ return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
}
virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
@@ -215,7 +220,7 @@
// The time when this metric producer was first created. The end time for the current bucket
// can be computed from this based on mCurrentBucketNum.
- int64_t mStartTimeNs;
+ int64_t mTimeBaseNs;
// Start time may not be aligned with the start of statsd if there is an app upgrade in the
// middle of a bucket.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index b7f1bd5..47a1a86 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -54,21 +54,21 @@
const int FIELD_ID_ANNOTATIONS_INT32 = 2;
MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
- const long timeBaseSec, const long currentTimeSec,
+ const int64_t timeBaseNs, const int64_t currentTimeNs,
const sp<UidMap> &uidMap,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
const sp<AlarmMonitor>& periodicAlarmMonitor)
: mConfigKey(key), mUidMap(uidMap),
mTtlNs(config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1),
mTtlEndNs(-1),
- mLastReportTimeNs(timeBaseSec * NS_PER_SEC),
+ mLastReportTimeNs(timeBaseNs),
mLastReportWallClockNs(getWallClockNs()) {
// Init the ttl end timestamp.
- refreshTtl(timeBaseSec * NS_PER_SEC);
+ refreshTtl(timeBaseNs);
mConfigValid =
initStatsdConfig(key, config, *uidMap, anomalyAlarmMonitor, periodicAlarmMonitor,
- timeBaseSec, currentTimeSec, mTagIds, mAllAtomMatchers,
+ timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchers,
mAllConditionTrackers, mAllMetricProducers, mAllAnomalyTrackers,
mAllPeriodicAlarmTrackers, mConditionToMetricMap, mTrackerToMetricMap,
mTrackerToConditionMap, mNoReportMetricIds);
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 6aa260a..3d2c595 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -37,7 +37,7 @@
class MetricsManager : public PackageInfoListener {
public:
MetricsManager(const ConfigKey& configKey, const StatsdConfig& config,
- const long timeBaseSec, const long currentTimeSec,
+ const int64_t timeBaseNs, const int64_t currentTimeNs,
const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor,
const sp<AlarmMonitor>& periodicAlarmMonitor);
@@ -187,6 +187,11 @@
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
+ FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
+ FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
+ FRIEND_TEST(GaugeMetricE2eTest, TestAllConditionChangesSamplePulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 51fac8c..844c728 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -63,9 +63,9 @@
ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric& metric,
const int conditionIndex,
const sp<ConditionWizard>& wizard, const int pullTagId,
- const int64_t startTimeNs,
+ const int64_t timeBaseNs, const int64_t startTimestampNs,
shared_ptr<StatsPullerManager> statsPullerManager)
- : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard),
+ : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard),
mValueField(metric.value_field()),
mStatsPullerManager(statsPullerManager),
mPullTagId(pullTagId),
@@ -105,27 +105,28 @@
}
}
- if (mValueField.child_size()) {
+ if (mValueField.child_size() > 0) {
mField = mValueField.child(0).field();
}
mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
// Kicks off the puller immediately.
+ flushIfNeededLocked(startTimestampNs);
if (mPullTagId != -1) {
mStatsPullerManager->RegisterReceiver(
mPullTagId, this, mCurrentBucketStartTimeNs + mBucketSizeNs, mBucketSizeNs);
}
VLOG("value metric %lld created. bucket size %lld start_time: %lld",
- (long long)metric.id(), (long long)mBucketSizeNs, (long long)mStartTimeNs);
+ (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs);
}
// for testing
ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric& metric,
const int conditionIndex,
const sp<ConditionWizard>& wizard, const int pullTagId,
- const int64_t startTimeNs)
- : ValueMetricProducer(key, metric, conditionIndex, wizard, pullTagId, startTimeNs,
+ const int64_t timeBaseNs, const int64_t startTimeNs)
+ : ValueMetricProducer(key, metric, conditionIndex, wizard, pullTagId, timeBaseNs, startTimeNs,
make_shared<StatsPullerManager>()) {
}
@@ -198,7 +199,6 @@
VLOG("metric %lld dump report now...", (long long)mMetricId);
mPastBuckets.clear();
- // TODO: Clear mDimensionKeyMap once the report is dumped.
}
void ValueMetricProducer::onConditionChangedLocked(const bool condition,
@@ -237,8 +237,8 @@
// For scheduled pulled data, the effective event time is snap to the nearest
// bucket boundary to make bucket finalize.
int64_t realEventTime = allData.at(0)->GetElapsedTimestampNs();
- int64_t eventTime = mStartTimeNs +
- ((realEventTime - mStartTimeNs) / mBucketSizeNs) * mBucketSizeNs;
+ int64_t eventTime = mTimeBaseNs +
+ ((realEventTime - mTimeBaseNs) / mBucketSizeNs) * mBucketSizeNs;
mCondition = false;
for (const auto& data : allData) {
@@ -310,7 +310,7 @@
Interval& interval = mCurrentSlicedBucket[eventKey];
int error = 0;
- const long value = event.GetLong(mField, &error);
+ const int64_t value = event.GetLong(mField, &error);
if (error < 0) {
return;
}
@@ -334,14 +334,16 @@
} else {
interval.sum += value;
}
+ interval.hasValue = true;
interval.startUpdated = false;
} else {
- VLOG("No start for matching end %ld", value);
+ VLOG("No start for matching end %lld", (long long)value);
interval.tainted += 1;
}
}
} else { // for pushed events
interval.sum += value;
+ interval.hasValue = true;
}
long wholeBucketVal = interval.sum;
@@ -357,7 +359,7 @@
void ValueMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
- if (currentBucketEndTimeNs > eventTimeNs) {
+ if (eventTimeNs < currentBucketEndTimeNs) {
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
(long long)(currentBucketEndTimeNs));
return;
@@ -393,10 +395,12 @@
for (const auto& slice : mCurrentSlicedBucket) {
tainted += slice.second.tainted;
tainted += slice.second.startUpdated;
- info.mValue = slice.second.sum;
- // it will auto create new vector of ValuebucketInfo if the key is not found.
- auto& bucketList = mPastBuckets[slice.first];
- bucketList.push_back(info);
+ if (slice.second.hasValue) {
+ info.mValue = slice.second.sum;
+ // it will auto create new vector of ValuebucketInfo if the key is not found.
+ auto& bucketList = mPastBuckets[slice.first];
+ bucketList.push_back(info);
+ }
}
VLOG("%d tainted pairs in the bucket", tainted);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index b5f6429..9c5a56c 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -40,7 +40,7 @@
public:
ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int pullTagId, const int64_t startTimeNs);
+ const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs);
virtual ~ValueMetricProducer();
@@ -115,7 +115,7 @@
// for testing
ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int pullTagId, const int64_t startTimeNs,
+ const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs,
std::shared_ptr<StatsPullerManager> statsPullerManager);
// tagId for pulled data. -1 if this is not pulled
@@ -127,19 +127,22 @@
typedef struct {
// Pulled data always come in pair of <start, end>. This holds the value
// for start. The diff (end - start) is added to sum.
- long start;
+ int64_t start;
// Whether the start data point is updated
bool startUpdated;
// If end data point comes before the start, record this pair as tainted
// and the value is not added to the running sum.
int tainted;
// Running sum of known pairs in this bucket
- long sum;
+ int64_t sum;
+ // If this dimension has any non-tainted value. If not, don't report the
+ // dimension.
+ bool hasValue;
} Interval;
std::unordered_map<MetricDimensionKey, Interval> mCurrentSlicedBucket;
- std::unordered_map<MetricDimensionKey, long> mCurrentFullBucket;
+ std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket;
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 566d34e..811a00e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -262,7 +262,8 @@
return true;
}
-bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec,
+bool initMetrics(const ConfigKey& key, const StatsdConfig& config,
+ const int64_t timeBaseTimeNs, const int64_t currentTimeNs,
UidMap& uidMap, const unordered_map<int64_t, int>& logTrackerMap,
const unordered_map<int64_t, int>& conditionTrackerMap,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
@@ -277,8 +278,6 @@
allMetricProducers.reserve(allMetricsCount);
StatsPullerManager statsPullerManager;
- uint64_t startTimeNs = timeBaseSec * NS_PER_SEC;
-
// Build MetricProducers for each metric defined in config.
// build CountMetricProducer
for (int i = 0; i < config.count_metric_size(); i++) {
@@ -314,7 +313,7 @@
}
sp<MetricProducer> countProducer =
- new CountMetricProducer(key, metric, conditionIndex, wizard, startTimeNs);
+ new CountMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs);
allMetricProducers.push_back(countProducer);
}
@@ -384,7 +383,7 @@
sp<MetricProducer> durationMetric = new DurationMetricProducer(
key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
- trackerIndices[2], nesting, wizard, internalDimensions, startTimeNs);
+ trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs);
allMetricProducers.push_back(durationMetric);
}
@@ -420,7 +419,7 @@
}
sp<MetricProducer> eventMetric =
- new EventMetricProducer(key, metric, conditionIndex, wizard, startTimeNs);
+ new EventMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs);
allMetricProducers.push_back(eventMetric);
}
@@ -467,7 +466,8 @@
}
sp<MetricProducer> valueProducer = new ValueMetricProducer(key, metric, conditionIndex,
- wizard, pullTagId, startTimeNs);
+ wizard, pullTagId,
+ timeBaseTimeNs, currentTimeNs);
allMetricProducers.push_back(valueProducer);
}
@@ -526,7 +526,7 @@
}
sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
- key, metric, conditionIndex, wizard, pullTagId, startTimeNs);
+ key, metric, conditionIndex, wizard, pullTagId, timeBaseTimeNs, currentTimeNs);
allMetricProducers.push_back(gaugeProducer);
}
for (int i = 0; i < config.no_report_metric_size(); ++i) {
@@ -601,11 +601,11 @@
bool initAlarms(const StatsdConfig& config, const ConfigKey& key,
const sp<AlarmMonitor>& periodicAlarmMonitor,
- const long timeBaseSec, const long currentTimeSec,
+ const int64_t timeBaseNs, const int64_t currentTimeNs,
vector<sp<AlarmTracker>>& allAlarmTrackers) {
unordered_map<int64_t, int> alarmTrackerMap;
- uint64_t startMillis = (uint64_t)timeBaseSec * MS_PER_SEC;
- uint64_t currentTimeMillis = (uint64_t)currentTimeSec * MS_PER_SEC;
+ int64_t startMillis = timeBaseNs / 1000 / 1000;
+ int64_t currentTimeMillis = currentTimeNs / 1000 /1000;
for (int i = 0; i < config.alarm_size(); i++) {
const Alarm& alarm = config.alarm(i);
if (alarm.offset_millis() <= 0) {
@@ -646,8 +646,9 @@
bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
- const sp<AlarmMonitor>& periodicAlarmMonitor, const long timeBaseSec,
- const long currentTimeSec, set<int>& allTagIds,
+ const sp<AlarmMonitor>& periodicAlarmMonitor,
+ const int64_t timeBaseNs, const int64_t currentTimeNs,
+ set<int>& allTagIds,
vector<sp<LogMatchingTracker>>& allAtomMatchers,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
@@ -673,7 +674,8 @@
return false;
}
- if (!initMetrics(key, config, timeBaseSec, uidMap, logTrackerMap, conditionTrackerMap,
+ if (!initMetrics(key, config, timeBaseNs, currentTimeNs, uidMap,
+ logTrackerMap, conditionTrackerMap,
allAtomMatchers, allConditionTrackers, allMetricProducers,
conditionToMetricMap, trackerToMetricMap, metricProducerMap,
noReportMetricIds)) {
@@ -686,7 +688,7 @@
return false;
}
if (!initAlarms(config, key, periodicAlarmMonitor,
- timeBaseSec, currentTimeSec, allPeriodicAlarmTrackers)) {
+ timeBaseNs, currentTimeNs, allPeriodicAlarmTrackers)) {
ALOGE("initAlarms failed");
return false;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 0ebdcf9..d749bf4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -81,7 +81,9 @@
// the list of MetricProducer index
// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
bool initMetrics(
- const ConfigKey& key, const StatsdConfig& config, const long timeBaseSec, UidMap& uidMap,
+ const ConfigKey& key, const StatsdConfig& config,
+ const int64_t timeBaseTimeNs, const int64_t currentTimeNs,
+ UidMap& uidMap,
const std::unordered_map<int64_t, int>& logTrackerMap,
const std::unordered_map<int64_t, int>& conditionTrackerMap,
const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
@@ -96,8 +98,9 @@
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
- const sp<AlarmMonitor>& periodicAlarmMonitor, const long timeBaseSec,
- const long currentTimeSec, std::set<int>& allTagIds,
+ const sp<AlarmMonitor>& periodicAlarmMonitor,
+ const int64_t timeBaseNs, const int64_t currentTimeNs,
+ std::set<int>& allTagIds,
std::vector<sp<LogMatchingTracker>>& allAtomMatchers,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 36b24c8..eaa7bf1 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -97,9 +97,9 @@
}
message GaugeBucketInfo {
- optional int64 start_bucket_nanos = 1;
+ optional int64 start_bucket_elapsed_nanos = 1;
- optional int64 end_bucket_nanos = 2;
+ optional int64 end_bucket_elapsed_nanos = 2;
repeated Atom atom = 3;
@@ -305,4 +305,6 @@
optional int64 elapsed_timestamp_nanos = 2;
}
repeated SkippedLogEventStats skipped_log_event_stats = 13;
+
+ repeated int64 log_loss_stats = 14;
}
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index fb8877a..91a40e3 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -178,6 +178,128 @@
EXPECT_EQ(2, report.annotation(0).field_int32());
}
+TEST(StatsLogProcessorTest, TestOutOfOrderLogs) {
+ // Setup simple config key corresponding to empty config.
+ sp<UidMap> m = new UidMap();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> subscriberAlarmMonitor;
+ int broadcastCount = 0;
+ StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+
+ LogEvent event1(0, 1 /*logd timestamp*/, 1001 /*elapsedRealtime*/);
+ event1.init();
+
+ LogEvent event2(0, 2, 1002);
+ event2.init();
+
+ LogEvent event3(0, 3, 1005);
+ event3.init();
+
+ LogEvent event4(0, 4, 1004);
+ event4.init();
+
+ // <----- Reconnection happens
+
+ LogEvent event5(0, 5, 999);
+ event5.init();
+
+ LogEvent event6(0, 6, 2000);
+ event6.init();
+
+ // <----- Reconnection happens
+
+ LogEvent event7(0, 7, 3000);
+ event7.init();
+
+ // first event ever
+ p.OnLogEvent(&event1, true);
+ EXPECT_EQ(1UL, p.mLogCount);
+ EXPECT_EQ(1001LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(1001LL, p.mLastTimestampSeen);
+
+ p.OnLogEvent(&event2, false);
+ EXPECT_EQ(2UL, p.mLogCount);
+ EXPECT_EQ(1002LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(1002LL, p.mLastTimestampSeen);
+
+ p.OnLogEvent(&event3, false);
+ EXPECT_EQ(3UL, p.mLogCount);
+ EXPECT_EQ(1005LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(1005LL, p.mLastTimestampSeen);
+
+ p.OnLogEvent(&event4, false);
+ EXPECT_EQ(4UL, p.mLogCount);
+ EXPECT_EQ(1005LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(1004LL, p.mLastTimestampSeen);
+ EXPECT_FALSE(p.mInReconnection);
+
+ // Reconnect happens, event1 out of buffer. Read event2
+ p.OnLogEvent(&event2, true);
+ EXPECT_EQ(4UL, p.mLogCount);
+ EXPECT_EQ(1005LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(1004LL, p.mLastTimestampSeen);
+ EXPECT_TRUE(p.mInReconnection);
+
+ p.OnLogEvent(&event3, false);
+ EXPECT_EQ(4UL, p.mLogCount);
+ EXPECT_EQ(1005LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(1004LL, p.mLastTimestampSeen);
+ EXPECT_TRUE(p.mInReconnection);
+
+ p.OnLogEvent(&event4, false);
+ EXPECT_EQ(4UL, p.mLogCount);
+ EXPECT_EQ(1005LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(1004LL, p.mLastTimestampSeen);
+ EXPECT_FALSE(p.mInReconnection);
+
+ // Fresh event comes.
+ p.OnLogEvent(&event5, false);
+ EXPECT_EQ(5UL, p.mLogCount);
+ EXPECT_EQ(1005LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(999LL, p.mLastTimestampSeen);
+
+ p.OnLogEvent(&event6, false);
+ EXPECT_EQ(6UL, p.mLogCount);
+ EXPECT_EQ(2000LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(2000LL, p.mLastTimestampSeen);
+
+ // Reconnect happens, read from event4
+ p.OnLogEvent(&event4, true);
+ EXPECT_EQ(6UL, p.mLogCount);
+ EXPECT_EQ(2000LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(2000LL, p.mLastTimestampSeen);
+ EXPECT_TRUE(p.mInReconnection);
+
+ p.OnLogEvent(&event5, false);
+ EXPECT_EQ(6UL, p.mLogCount);
+ EXPECT_EQ(2000LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(2000LL, p.mLastTimestampSeen);
+ EXPECT_TRUE(p.mInReconnection);
+
+ // Before we get out of reconnection state, it reconnects again.
+ p.OnLogEvent(&event5, true);
+ EXPECT_EQ(6UL, p.mLogCount);
+ EXPECT_EQ(2000LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(2000LL, p.mLastTimestampSeen);
+ EXPECT_TRUE(p.mInReconnection);
+
+ p.OnLogEvent(&event6, false);
+ EXPECT_EQ(6UL, p.mLogCount);
+ EXPECT_EQ(2000LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(2000LL, p.mLastTimestampSeen);
+ EXPECT_FALSE(p.mInReconnection);
+ EXPECT_EQ(0, p.mLogLossCount);
+
+ // it reconnects again. All old events are gone. We lose CP.
+ p.OnLogEvent(&event7, true);
+ EXPECT_EQ(7UL, p.mLogCount);
+ EXPECT_EQ(3000LL, p.mLargestTimestampSeen);
+ EXPECT_EQ(3000LL, p.mLastTimestampSeen);
+ EXPECT_EQ(1, p.mLogLossCount);
+ EXPECT_FALSE(p.mInReconnection);
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp b/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp
index 73c4e7b..9ea0b81 100644
--- a/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Alarm_e2e_test.cpp
@@ -51,7 +51,7 @@
int64_t bucketStartTimeNs = 10000000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
EXPECT_EQ(2u, processor->mMetricsManagers.begin()->second->mAllPeriodicAlarmTrackers.size());
diff --git a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
index 93ecde5..c78d99e 100644
--- a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
@@ -65,7 +65,7 @@
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
@@ -168,7 +168,7 @@
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
diff --git a/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
index e924b03..50da9e2 100644
--- a/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
@@ -101,7 +101,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
@@ -278,7 +278,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
@@ -391,7 +391,7 @@
config.mutable_alert(0)->set_refractory_period_secs(refractory_period_sec);
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
index 4dd0da8..4f035be 100644
--- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -62,7 +62,7 @@
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -204,7 +204,7 @@
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
index bb3ad64..b98dc60 100644
--- a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
@@ -68,7 +68,7 @@
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
index eb57d470..0758fd0 100644
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
@@ -90,7 +90,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
auto processor = CreateStatsLogProcessor(
- bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -404,7 +404,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
auto processor = CreateStatsLogProcessor(
- bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -648,7 +648,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
auto processor = CreateStatsLogProcessor(
- bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
index 9729a2e..e74be1e 100644
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
@@ -75,7 +75,7 @@
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -283,7 +283,7 @@
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1"),
@@ -465,7 +465,8 @@
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -666,7 +667,8 @@
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
index 4e2c36e..c32048b 100644
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
@@ -80,7 +80,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
auto processor = CreateStatsLogProcessor(
- bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -363,7 +363,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
auto processor = CreateStatsLogProcessor(
- bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -582,7 +582,7 @@
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
auto processor = CreateStatsLogProcessor(
- bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
new file mode 100644
index 0000000..9561fcf
--- /dev/null
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -0,0 +1,396 @@
+// 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 <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type) {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ auto temperatureAtomMatcher = CreateTemperatureAtomMatcher();
+ *config.add_atom_matcher() = temperatureAtomMatcher;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+ *config.add_predicate() = screenIsOffPredicate;
+
+ auto gaugeMetric = config.add_gauge_metric();
+ gaugeMetric->set_id(123456);
+ gaugeMetric->set_what(temperatureAtomMatcher.id());
+ gaugeMetric->set_condition(screenIsOffPredicate.id());
+ gaugeMetric->set_sampling_type(sampling_type);
+ gaugeMetric->mutable_gauge_fields_filter()->set_include_all(true);
+ *gaugeMetric->mutable_dimensions_in_what() =
+ CreateDimensions(android::util::TEMPERATURE, {2/* sensor name field */ });
+ gaugeMetric->set_bucket(FIVE_MINUTES);
+
+ return config;
+}
+
+} // namespace
+
+TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents) {
+ auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
+ int64_t baseTimeNs = 10 * NS_PER_SEC;
+ int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ auto processor = CreateStatsLogProcessor(
+ baseTimeNs, configAddedTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ int startBucketNum = processor->mMetricsManagers.begin()->second->
+ mAllMetricProducers[0]->getCurrentBucketNum();
+ EXPECT_GT(startBucketNum, (int64_t)0);
+
+ // When creating the config, the gauge metric producer should register the alarm at the
+ // end of the current bucket.
+ EXPECT_EQ((size_t)1, StatsPullerManagerImpl::GetInstance().mReceivers.size());
+ EXPECT_EQ(bucketSizeNs,
+ StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().intervalNs);
+ int64_t& nextPullTimeNs = StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().nextPullTimeNs;
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs);
+
+ auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 55);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ // Pulling alarm arrives on time and reset the sequential pulling alarm.
+ processor->informPullAlarmFired(nextPullTimeNs + 1);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, nextPullTimeNs);
+
+ auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + bucketSizeNs + 10);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + bucketSizeNs + 100);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ processor->informPullAlarmFired(nextPullTimeNs + 1);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs,
+ nextPullTimeNs);
+
+ processor->informPullAlarmFired(nextPullTimeNs + 1);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, nextPullTimeNs);
+
+ screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + 3 * bucketSizeNs + 2);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ processor->informPullAlarmFired(nextPullTimeNs + 3);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs);
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 5 * bucketSizeNs + 1);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ processor->informPullAlarmFired(nextPullTimeNs + 2);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, nextPullTimeNs);
+
+ processor->informPullAlarmFired(nextPullTimeNs + 2);
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+ EXPECT_GT((int)gaugeMetrics.data_size(), 1);
+
+ auto data = gaugeMetrics.data(0);
+ EXPECT_EQ(android::util::TEMPERATURE, data.dimensions_in_what().field());
+ EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+ EXPECT_EQ(2 /* sensor name field */,
+ data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+ EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
+ EXPECT_EQ(6, data.bucket_info_size());
+
+ EXPECT_EQ(1, data.bucket_info(0).atom_size());
+ EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(1, data.bucket_info(0).wall_clock_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(0).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(0).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(1).atom_size());
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 1,
+ data.bucket_info(1).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(1).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(1).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(2).atom_size());
+ EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs + 1,
+ data.bucket_info(2).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(2).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(2).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(3).atom_size());
+ EXPECT_EQ(1, data.bucket_info(3).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs + 1,
+ data.bucket_info(3).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(3).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(3).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(4).atom_size());
+ EXPECT_EQ(1, data.bucket_info(4).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1,
+ data.bucket_info(4).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(4).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(4).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(4).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(4).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(5).atom_size());
+ EXPECT_EQ(1, data.bucket_info(5).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs + 2,
+ data.bucket_info(5).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(5).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(5).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(5).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(5).atom(0).temperature().temperature_dc(), 0);
+}
+
+TEST(GaugeMetricE2eTest, TestAllConditionChangesSamplePulledEvents) {
+ auto config = CreateStatsdConfig(GaugeMetric::ALL_CONDITION_CHANGES);
+ int64_t baseTimeNs = 10 * NS_PER_SEC;
+ int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ auto processor = CreateStatsLogProcessor(
+ baseTimeNs, configAddedTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ int startBucketNum = processor->mMetricsManagers.begin()->second->
+ mAllMetricProducers[0]->getCurrentBucketNum();
+ EXPECT_GT(startBucketNum, (int64_t)0);
+
+ auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 55);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + bucketSizeNs + 10);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + bucketSizeNs + 100);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + 3 * bucketSizeNs + 2);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 5 * bucketSizeNs + 1);
+ processor->OnLogEvent(screenOffEvent.get());
+ screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + 5 * bucketSizeNs + 3);
+ processor->OnLogEvent(screenOnEvent.get());
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 5 * bucketSizeNs + 10);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, configAddedTimeNs + 8 * bucketSizeNs + 10, false, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+ EXPECT_GT((int)gaugeMetrics.data_size(), 1);
+
+ auto data = gaugeMetrics.data(0);
+ EXPECT_EQ(android::util::TEMPERATURE, data.dimensions_in_what().field());
+ EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+ EXPECT_EQ(2 /* sensor name field */,
+ data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+ EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
+ EXPECT_EQ(3, data.bucket_info_size());
+
+ EXPECT_EQ(1, data.bucket_info(0).atom_size());
+ EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(1, data.bucket_info(0).wall_clock_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(0).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(0).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(1).atom_size());
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs + 100,
+ data.bucket_info(1).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(1).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(1).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(2, data.bucket_info(2).atom_size());
+ EXPECT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 1,
+ data.bucket_info(2).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs + 10,
+ data.bucket_info(2).elapsed_timestamp_nanos(1));
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(2).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(2).atom(0).temperature().temperature_dc(), 0);
+ EXPECT_FALSE(data.bucket_info(2).atom(1).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(2).atom(1).temperature().temperature_dc(), 0);
+}
+
+
+TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm) {
+ auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
+ int64_t baseTimeNs = 10 * NS_PER_SEC;
+ int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ auto processor = CreateStatsLogProcessor(
+ baseTimeNs, configAddedTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ int startBucketNum = processor->mMetricsManagers.begin()->second->
+ mAllMetricProducers[0]->getCurrentBucketNum();
+ EXPECT_GT(startBucketNum, (int64_t)0);
+
+ // When creating the config, the gauge metric producer should register the alarm at the
+ // end of the current bucket.
+ EXPECT_EQ((size_t)1, StatsPullerManagerImpl::GetInstance().mReceivers.size());
+ EXPECT_EQ(bucketSizeNs,
+ StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().intervalNs);
+ int64_t& nextPullTimeNs = StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().nextPullTimeNs;
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, nextPullTimeNs);
+
+ auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 55);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + bucketSizeNs + 10);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ // Pulling alarm arrives one bucket size late.
+ processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 3 * bucketSizeNs, nextPullTimeNs);
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 3 * bucketSizeNs + 11);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ // Pulling alarm arrives more than one bucket size late.
+ processor->informPullAlarmFired(nextPullTimeNs + bucketSizeNs + 12);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 5 * bucketSizeNs, nextPullTimeNs);
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+ EXPECT_GT((int)gaugeMetrics.data_size(), 1);
+
+ auto data = gaugeMetrics.data(0);
+ EXPECT_EQ(android::util::TEMPERATURE, data.dimensions_in_what().field());
+ EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+ EXPECT_EQ(2 /* sensor name field */,
+ data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+ EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
+ EXPECT_EQ(3, data.bucket_info_size());
+
+ EXPECT_EQ(1, data.bucket_info(0).atom_size());
+ EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(1, data.bucket_info(0).wall_clock_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(0).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(0).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(1).atom_size());
+ EXPECT_EQ(configAddedTimeNs + 3 * bucketSizeNs + 11,
+ data.bucket_info(1).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(configAddedTimeNs + 55, data.bucket_info(0).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(1).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(1).atom(0).temperature().temperature_dc(), 0);
+
+ EXPECT_EQ(1, data.bucket_info(2).atom_size());
+ EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
+ EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs + 12,
+ data.bucket_info(2).elapsed_timestamp_nanos(0));
+ EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
+ EXPECT_FALSE(data.bucket_info(2).atom(0).temperature().sensor_name().empty());
+ EXPECT_GT(data.bucket_info(2).atom(0).temperature().temperature_dc(), 0);
+
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index 18a0485..d79cb33 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -96,7 +96,8 @@
TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -169,9 +170,11 @@
EXPECT_EQ(2, data.bucket_info(0).atom_size());
EXPECT_EQ(2, data.bucket_info(0).elapsed_timestamp_nanos_size());
EXPECT_EQ(2, data.bucket_info(0).wall_clock_timestamp_nanos_size());
- EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_nanos());
- EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_nanos());
- EXPECT_EQ(AppStartOccurred::HOT, data.bucket_info(0).atom(0).app_start_occurred().type());
+ EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+ data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(AppStartOccurred::HOT,
+ data.bucket_info(0).atom(0).app_start_occurred().type());
EXPECT_EQ("activity_name2",
data.bucket_info(0).atom(0).app_start_occurred().activity_name());
EXPECT_EQ(102L,
@@ -186,8 +189,10 @@
EXPECT_EQ(1, data.bucket_info(1).atom_size());
EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
EXPECT_EQ(1, data.bucket_info(1).wall_clock_timestamp_nanos_size());
- EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(1).start_bucket_nanos());
- EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(1).end_bucket_nanos());
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+ data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+ data.bucket_info(1).end_bucket_elapsed_nanos());
EXPECT_EQ(AppStartOccurred::WARM,
data.bucket_info(1).atom(0).app_start_occurred().type());
EXPECT_EQ("activity_name4",
@@ -199,9 +204,9 @@
EXPECT_EQ(2, data.bucket_info(2).elapsed_timestamp_nanos_size());
EXPECT_EQ(2, data.bucket_info(2).wall_clock_timestamp_nanos_size());
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
- data.bucket_info(2).start_bucket_nanos());
+ data.bucket_info(2).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
- data.bucket_info(2).end_bucket_nanos());
+ data.bucket_info(2).end_bucket_elapsed_nanos());
EXPECT_EQ(AppStartOccurred::COLD,
data.bucket_info(2).atom(0).app_start_occurred().type());
EXPECT_EQ("activity_name5",
@@ -218,9 +223,11 @@
EXPECT_EQ(1, data.bucket_info(0).atom_size());
EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
EXPECT_EQ(1, data.bucket_info(0).wall_clock_timestamp_nanos_size());
- EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_nanos());
- EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_nanos());
- EXPECT_EQ(AppStartOccurred::HOT, data.bucket_info(0).atom(0).app_start_occurred().type());
+ EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+ data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(AppStartOccurred::HOT,
+ data.bucket_info(0).atom(0).app_start_occurred().type());
EXPECT_EQ("activity_name2",
data.bucket_info(0).atom(0).app_start_occurred().activity_name());
EXPECT_EQ(102L,
@@ -229,8 +236,10 @@
EXPECT_EQ(1, data.bucket_info(1).atom_size());
EXPECT_EQ(1, data.bucket_info(1).elapsed_timestamp_nanos_size());
EXPECT_EQ(1, data.bucket_info(1).wall_clock_timestamp_nanos_size());
- EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(1).start_bucket_nanos());
- EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(1).end_bucket_nanos());
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+ data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+ data.bucket_info(1).end_bucket_elapsed_nanos());
EXPECT_EQ(AppStartOccurred::WARM,
data.bucket_info(1).atom(0).app_start_occurred().type());
EXPECT_EQ("activity_name4",
@@ -242,9 +251,9 @@
EXPECT_EQ(1, data.bucket_info(2).elapsed_timestamp_nanos_size());
EXPECT_EQ(1, data.bucket_info(2).wall_clock_timestamp_nanos_size());
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
- data.bucket_info(2).start_bucket_nanos());
+ data.bucket_info(2).start_bucket_elapsed_nanos());
EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
- data.bucket_info(2).end_bucket_nanos());
+ data.bucket_info(2).end_bucket_elapsed_nanos());
EXPECT_EQ(AppStartOccurred::COLD,
data.bucket_info(2).atom(0).app_start_occurred().type());
EXPECT_EQ("activity_name5",
@@ -264,8 +273,10 @@
EXPECT_EQ(1, data.bucket_info(0).atom_size());
EXPECT_EQ(1, data.bucket_info(0).elapsed_timestamp_nanos_size());
EXPECT_EQ(1, data.bucket_info(0).wall_clock_timestamp_nanos_size());
- EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_nanos());
- EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_nanos());
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+ data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
+ data.bucket_info(0).end_bucket_elapsed_nanos());
EXPECT_EQ(AppStartOccurred::COLD, data.bucket_info(0).atom(0).app_start_occurred().type());
EXPECT_EQ("activity_name7",
data.bucket_info(0).atom(0).app_start_occurred().activity_name());
diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
index 1952a6f..4ace382 100644
--- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -107,7 +107,7 @@
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
@@ -224,7 +224,8 @@
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
ConfigKey cfgKey;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index d4892ed..5499ee3 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -35,7 +35,7 @@
config.SerializeToString(&str);
std::vector<uint8_t> configAsVec(str.begin(), str.end());
bool success;
- service.addConfiguration(kConfigKey, configAsVec, &success);
+ service.addConfiguration(kConfigKey, configAsVec);
}
ConfigMetricsReport GetReports(StatsService& service) {
diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
new file mode 100644
index 0000000..62b6fcc
--- /dev/null
+++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -0,0 +1,260 @@
+// 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 <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateStatsdConfig() {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ auto temperatureAtomMatcher = CreateTemperatureAtomMatcher();
+ *config.add_atom_matcher() = temperatureAtomMatcher;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+ *config.add_predicate() = screenIsOffPredicate;
+
+ auto valueMetric = config.add_value_metric();
+ valueMetric->set_id(123456);
+ valueMetric->set_what(temperatureAtomMatcher.id());
+ valueMetric->set_condition(screenIsOffPredicate.id());
+ *valueMetric->mutable_value_field() =
+ CreateDimensions(android::util::TEMPERATURE, {3/* temperature degree field */ });
+ *valueMetric->mutable_dimensions_in_what() =
+ CreateDimensions(android::util::TEMPERATURE, {2/* sensor name field */ });
+ valueMetric->set_bucket(FIVE_MINUTES);
+
+ return config;
+}
+
+} // namespace
+
+TEST(ValueMetricE2eTest, TestPulledEvents) {
+ auto config = CreateStatsdConfig();
+ int64_t baseTimeNs = 10 * NS_PER_SEC;
+ int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ auto processor = CreateStatsLogProcessor(
+ baseTimeNs, configAddedTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ int startBucketNum = processor->mMetricsManagers.begin()->second->
+ mAllMetricProducers[0]->getCurrentBucketNum();
+ EXPECT_GT(startBucketNum, (int64_t)0);
+
+ // When creating the config, the gauge metric producer should register the alarm at the
+ // end of the current bucket.
+ EXPECT_EQ((size_t)1, StatsPullerManagerImpl::GetInstance().mReceivers.size());
+ EXPECT_EQ(bucketSizeNs,
+ StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().intervalNs);
+ int64_t& expectedPullTimeNs = StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().nextPullTimeNs;
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs);
+
+ auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 55);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + 65);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 75);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ // Pulling alarm arrives on time and reset the sequential pulling alarm.
+ processor->informPullAlarmFired(expectedPullTimeNs + 1);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 2 * bucketSizeNs, expectedPullTimeNs);
+
+ processor->informPullAlarmFired(expectedPullTimeNs + 1);
+
+ screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + 2 * bucketSizeNs + 15);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ processor->informPullAlarmFired(expectedPullTimeNs + 1);
+
+ processor->informPullAlarmFired(expectedPullTimeNs + 1);
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 4 * bucketSizeNs + 11);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ processor->informPullAlarmFired(expectedPullTimeNs + 1);
+
+ processor->informPullAlarmFired(expectedPullTimeNs + 1);
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ StatsLogReport::ValueMetricDataWrapper valueMetrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
+ EXPECT_GT((int)valueMetrics.data_size(), 1);
+
+ auto data = valueMetrics.data(0);
+ EXPECT_EQ(android::util::TEMPERATURE, data.dimensions_in_what().field());
+ EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+ EXPECT_EQ(2 /* sensor name field */,
+ data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+ EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
+ EXPECT_EQ(5, data.bucket_info_size());
+
+ EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(0).has_value());
+
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(1).has_value());
+
+ EXPECT_EQ(baseTimeNs + 4 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 5 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(2).has_value());
+
+ EXPECT_EQ(baseTimeNs + 6 * bucketSizeNs, data.bucket_info(3).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(3).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(3).has_value());
+
+ EXPECT_EQ(baseTimeNs + 7 * bucketSizeNs, data.bucket_info(4).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(4).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(4).has_value());
+}
+
+TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) {
+ auto config = CreateStatsdConfig();
+ int64_t baseTimeNs = 10 * NS_PER_SEC;
+ int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ auto processor = CreateStatsLogProcessor(
+ baseTimeNs, configAddedTimeNs, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ int startBucketNum = processor->mMetricsManagers.begin()->second->
+ mAllMetricProducers[0]->getCurrentBucketNum();
+ EXPECT_GT(startBucketNum, (int64_t)0);
+
+ // When creating the config, the gauge metric producer should register the alarm at the
+ // end of the current bucket.
+ EXPECT_EQ((size_t)1, StatsPullerManagerImpl::GetInstance().mReceivers.size());
+ EXPECT_EQ(bucketSizeNs,
+ StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().intervalNs);
+ int64_t& expectedPullTimeNs = StatsPullerManagerImpl::GetInstance().mReceivers.begin()->
+ second.front().nextPullTimeNs;
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + bucketSizeNs, expectedPullTimeNs);
+
+ // Screen off/on/off events.
+ auto screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 55);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ auto screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + 65);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 75);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ // Pulling alarm arrives late by 2 buckets and 1 ns.
+ processor->informPullAlarmFired(expectedPullTimeNs + 2 * bucketSizeNs + 1);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 4 * bucketSizeNs, expectedPullTimeNs);
+
+ screenOnEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ configAddedTimeNs + 4 * bucketSizeNs + 65);
+ processor->OnLogEvent(screenOnEvent.get());
+
+ // Pulling alarm arrives late by one bucket size + 21ns.
+ processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 6 * bucketSizeNs, expectedPullTimeNs);
+
+ screenOffEvent = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ configAddedTimeNs + 6 * bucketSizeNs + 31);
+ processor->OnLogEvent(screenOffEvent.get());
+
+ processor->informPullAlarmFired(expectedPullTimeNs + bucketSizeNs + 21);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 8 * bucketSizeNs, expectedPullTimeNs);
+
+ processor->informPullAlarmFired(expectedPullTimeNs + 1);
+ EXPECT_EQ(baseTimeNs + startBucketNum * bucketSizeNs + 9 * bucketSizeNs, expectedPullTimeNs);
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, configAddedTimeNs + 9 * bucketSizeNs + 10, false, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ StatsLogReport::ValueMetricDataWrapper valueMetrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
+ EXPECT_GT((int)valueMetrics.data_size(), 1);
+
+ auto data = valueMetrics.data(0);
+ EXPECT_EQ(android::util::TEMPERATURE, data.dimensions_in_what().field());
+ EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+ EXPECT_EQ(2 /* sensor name field */,
+ data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+ EXPECT_FALSE(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str().empty());
+ EXPECT_EQ(3, data.bucket_info_size());
+
+ EXPECT_EQ(baseTimeNs + 2 * bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 3 * bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(0).has_value());
+
+ EXPECT_EQ(baseTimeNs + 8 * bucketSizeNs, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(1).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(1).has_value());
+
+ EXPECT_EQ(baseTimeNs + 9 * bucketSizeNs, data.bucket_info(2).start_bucket_elapsed_nanos());
+ EXPECT_EQ(baseTimeNs + 10 * bucketSizeNs, data.bucket_info(2).end_bucket_elapsed_nanos());
+ EXPECT_TRUE(data.bucket_info(2).has_value());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index f2d47c7..55bf4be 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -121,7 +121,7 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
FeedEvents(config, processor);
@@ -154,7 +154,7 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
FeedEvents(config, processor);
@@ -188,7 +188,7 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
FeedEvents(config, processor);
@@ -231,7 +231,7 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
FeedEvents(config, processor);
@@ -256,7 +256,7 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
FeedEvents(config, processor);
@@ -284,7 +284,7 @@
uint64_t bucketStartTimeNs = 10000000000;
uint64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
- auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
FeedEvents(config, processor);
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index a1f865d..698ce72 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -67,7 +67,7 @@
EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- tagId, bucketStartTimeNs, pullerManager);
+ tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
gaugeProducer.setBucketSize(60 * NS_PER_SEC);
vector<shared_ptr<LogEvent>> allData;
@@ -144,7 +144,7 @@
make_shared<StrictMock<MockStatsPullerManager>>();
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
-1 /* -1 means no pulling */, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
gaugeProducer.setBucketSize(60 * NS_PER_SEC);
sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor);
@@ -228,7 +228,7 @@
}));
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- tagId, bucketStartTimeNs, pullerManager);
+ tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
gaugeProducer.setBucketSize(60 * NS_PER_SEC);
vector<shared_ptr<LogEvent>> allData;
@@ -297,7 +297,7 @@
}));
GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, tagId,
- bucketStartTimeNs, pullerManager);
+ bucketStartTimeNs, bucketStartTimeNs, pullerManager);
gaugeProducer.setBucketSize(60 * NS_PER_SEC);
gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8);
@@ -389,7 +389,7 @@
}));
GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
gaugeProducer.setBucketSize(60 * NS_PER_SEC);
gaugeProducer.onSlicedConditionMayChange(true, bucketStartTimeNs + 8);
@@ -433,7 +433,7 @@
gaugeFieldMatcher->set_field(tagId);
gaugeFieldMatcher->add_child()->set_field(2);
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- tagId, bucketStartTimeNs, pullerManager);
+ tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
gaugeProducer.setBucketSize(60 * NS_PER_SEC);
Alert alert;
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index a2bb734..81d8892 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -68,7 +68,7 @@
EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- tagId, bucketStartTimeNs, pullerManager);
+ tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
vector<shared_ptr<LogEvent>> allData;
@@ -90,8 +90,7 @@
EXPECT_EQ(0, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
EXPECT_EQ(11, curInterval.start);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second.back().mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
allData.clear();
event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
@@ -108,7 +107,7 @@
EXPECT_EQ(0, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
EXPECT_EQ(12, valueProducer.mPastBuckets.begin()->second.back().mValue);
allData.clear();
@@ -125,7 +124,7 @@
EXPECT_EQ(0, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(3UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size());
EXPECT_EQ(13, valueProducer.mPastBuckets.begin()->second.back().mValue);
}
@@ -169,7 +168,7 @@
}));
ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
valueProducer.onConditionChanged(true, bucketStartTimeNs + 8);
@@ -222,7 +221,7 @@
shared_ptr<MockStatsPullerManager> pullerManager =
make_shared<StrictMock<MockStatsPullerManager>>();
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
@@ -278,7 +277,7 @@
return true;
}));
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, tagId, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
vector<shared_ptr<LogEvent>> allData;
@@ -321,7 +320,7 @@
make_shared<StrictMock<MockStatsPullerManager>>();
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
@@ -369,7 +368,7 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- -1 /*not pulled*/, bucketStartTimeNs);
+ -1 /*not pulled*/, bucketStartTimeNs, bucketStartTimeNs);
valueProducer.setBucketSize(60 * NS_PER_SEC);
sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor);
@@ -448,7 +447,7 @@
EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- tagId, bucketStartTimeNs, pullerManager);
+ tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
vector<shared_ptr<LogEvent>> allData;
@@ -464,15 +463,13 @@
// has one slice
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
- valueProducer.setBucketSize(60 * NS_PER_SEC);
// startUpdated:true tainted:0 sum:0 start:11
EXPECT_EQ(true, curInterval.startUpdated);
EXPECT_EQ(0, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
EXPECT_EQ(11, curInterval.start);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second.back().mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
// pull 2 at correct time
allData.clear();
@@ -490,7 +487,7 @@
EXPECT_EQ(0, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
EXPECT_EQ(12, valueProducer.mPastBuckets.begin()->second.back().mValue);
// pull 3 come late.
@@ -512,11 +509,8 @@
EXPECT_EQ(36, curInterval.start);
EXPECT_EQ(0, curInterval.sum);
EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(4UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
- EXPECT_EQ(12, valueProducer.mPastBuckets.begin()->second[1].mValue);
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[2].mValue);
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[3].mValue);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(12, valueProducer.mPastBuckets.begin()->second.back().mValue);
}
/*
@@ -562,7 +556,7 @@
}));
ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
valueProducer.onConditionChanged(true, bucketStartTimeNs + 8);
@@ -582,9 +576,7 @@
EXPECT_EQ(false, curInterval.startUpdated);
EXPECT_EQ(1, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
// Now the alarm is delivered.
// since the condition turned to off before this pull finish, it has no effect
@@ -601,9 +593,7 @@
EXPECT_EQ(false, curInterval.startUpdated);
EXPECT_EQ(1, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
}
/*
@@ -660,7 +650,7 @@
}));
ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
valueProducer.onConditionChanged(true, bucketStartTimeNs + 8);
@@ -680,9 +670,7 @@
EXPECT_EQ(false, curInterval.startUpdated);
EXPECT_EQ(1, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
// condition changed to true again, before the pull alarm is delivered
valueProducer.onConditionChanged(true, bucket2StartTimeNs + 25);
@@ -691,9 +679,7 @@
EXPECT_EQ(130, curInterval.start);
EXPECT_EQ(1, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
// Now the alarm is delivered, but it is considered late, it has no effect
vector<shared_ptr<LogEvent>> allData;
@@ -710,9 +696,7 @@
EXPECT_EQ(130, curInterval.start);
EXPECT_EQ(1, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
}
/*
@@ -758,7 +742,7 @@
}));
ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs,
- pullerManager);
+ bucketStartTimeNs, pullerManager);
valueProducer.setBucketSize(60 * NS_PER_SEC);
valueProducer.onConditionChanged(true, bucketStartTimeNs + 8);
@@ -779,9 +763,7 @@
EXPECT_EQ(false, curInterval.startUpdated);
EXPECT_EQ(1, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
// Alarm is delivered in time, but the pull is very slow, and pullers are called in order,
// so this one comes even later
@@ -798,9 +780,7 @@
EXPECT_EQ(false, curInterval.startUpdated);
EXPECT_EQ(1, curInterval.tainted);
EXPECT_EQ(0, curInterval.sum);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].mValue);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
}
} // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 649c399..1264909 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -18,6 +18,7 @@
namespace os {
namespace statsd {
+
AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
AtomMatcher atom_matcher;
atom_matcher.set_id(StringToId(name));
@@ -26,6 +27,10 @@
return atom_matcher;
}
+AtomMatcher CreateTemperatureAtomMatcher() {
+ return CreateSimpleAtomMatcher("TemperatureMatcher", android::util::TEMPERATURE);
+}
+
AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name,
ScheduledJobStateChanged::State state) {
AtomMatcher atom_matcher;
@@ -444,8 +449,8 @@
return logEvent;
}
-sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
- const ConfigKey& key) {
+sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
+ const StatsdConfig& config, const ConfigKey& key) {
sp<UidMap> uidMap = new UidMap();
sp<AlarmMonitor> anomalyAlarmMonitor =
new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){},
@@ -454,8 +459,8 @@
new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){},
[](const sp<IStatsCompanionService>&){});
sp<StatsLogProcessor> processor = new StatsLogProcessor(
- uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, [](const ConfigKey&){});
- processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
+ uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&){});
+ processor->OnConfigUpdated(currentTimeNs, key, config);
return processor;
}
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 1ac630c..6ecca46 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -28,6 +28,9 @@
// Create AtomMatcher proto to simply match a specific atom type.
AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
+// Create AtomMatcher proto for temperature atom.
+AtomMatcher CreateTemperatureAtomMatcher();
+
// Create AtomMatcher proto for scheduled job state changed.
AtomMatcher CreateScheduledJobStateChangedAtomMatcher();
@@ -172,8 +175,9 @@
AttributionNodeInternal CreateAttribution(const int& uid, const string& tag);
// Create a statsd log event processor upon the start time in seconds, config and key.
-sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
- const ConfigKey& key);
+sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs,
+ const int64_t currentTimeNs,
+ const StatsdConfig& config, const ConfigKey& key);
// Util function to sort the log events by timestamp.
void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index 0e6c933..2b7da6a 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -19,6 +19,7 @@
import android.app.PendingIntent;
import android.app.IntentService;
import android.app.StatsManager;
+import android.app.StatsManager.StatsUnavailableException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -171,12 +172,16 @@
return;
}
if (mStatsManager != null) {
- byte[] data = mStatsManager.getData(CONFIG_ID);
- if (data != null) {
- displayData(data);
- } else {
- mReportText.setText("Failed!");
+ try {
+ byte[] data = mStatsManager.getReports(CONFIG_ID);
+ if (data != null) {
+ displayData(data);
+ return;
+ }
+ } catch (StatsUnavailableException e) {
+ Log.e(TAG, "Failed to get data from statsd", e);
}
+ mReportText.setText("Failed!");
}
}
});
@@ -194,10 +199,11 @@
byte[] config = new byte[inputStream.available()];
inputStream.read(config);
if (mStatsManager != null) {
- if (mStatsManager.addConfiguration(CONFIG_ID, config)) {
+ try {
+ mStatsManager.addConfig(CONFIG_ID, config);
Toast.makeText(
MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
- } else {
+ } catch (StatsUnavailableException | IllegalArgumentException e) {
Toast.makeText(MainActivity.this, "Config push FAILED!",
Toast.LENGTH_LONG).show();
}
@@ -218,11 +224,12 @@
return;
}
if (mStatsManager != null) {
- if (mStatsManager.setDataFetchOperation(CONFIG_ID, pi)) {
+ try {
+ mStatsManager.setFetchReportsOperation(pi, CONFIG_ID);
Toast.makeText(MainActivity.this,
"Receiver specified to pending intent", Toast.LENGTH_LONG)
.show();
- } else {
+ } catch (StatsUnavailableException e) {
Toast.makeText(MainActivity.this, "Statsd did not set receiver",
Toast.LENGTH_LONG)
.show();
@@ -241,10 +248,11 @@
return;
}
if (mStatsManager != null) {
- if (mStatsManager.setDataFetchOperation(CONFIG_ID, null)) {
+ try {
+ mStatsManager.setFetchReportsOperation(null, CONFIG_ID);
Toast.makeText(MainActivity.this, "Receiver remove", Toast.LENGTH_LONG)
.show();
- } else {
+ } catch (StatsUnavailableException e) {
Toast.makeText(MainActivity.this, "Statsd did not remove receiver",
Toast.LENGTH_LONG)
.show();
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index bed4d98..769f78c 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -355,7 +355,13 @@
return null;
}
if (mStatsManager != null) {
- byte[] data = mStatsManager.getMetadata();
+ byte[] data;
+ try {
+ data = mStatsManager.getStatsMetadata();
+ } catch (StatsManager.StatsUnavailableException e) {
+ Log.e(TAG, "Failed to get data from statsd", e);
+ return null;
+ }
if (data != null) {
StatsdStatsReport report = null;
boolean good = false;
@@ -375,7 +381,13 @@
return null;
}
if (mStatsManager != null) {
- byte[] data = mStatsManager.getData(ConfigFactory.CONFIG_ID);
+ byte[] data;
+ try {
+ data = mStatsManager.getReports(ConfigFactory.CONFIG_ID);
+ } catch (StatsManager.StatsUnavailableException e) {
+ Log.e(TAG, "Failed to get data from statsd", e);
+ return null;
+ }
if (data != null) {
ConfigMetricsReportList reports = null;
try {
@@ -563,10 +575,11 @@
// TODO: Clear all configs instead of specific ones.
if (mStatsManager != null) {
if (mStarted) {
- if (!mStatsManager.removeConfiguration(ConfigFactory.CONFIG_ID)) {
+ try {
+ mStatsManager.removeConfig(ConfigFactory.CONFIG_ID);
Log.d(TAG, "Removed loadtest statsd configs.");
- } else {
- Log.d(TAG, "Failed to remove loadtest configs.");
+ } catch (StatsManager.StatsUnavailableException e) {
+ Log.e(TAG, "Failed to remove loadtest configs.", e);
}
}
}
@@ -574,12 +587,13 @@
private boolean setConfig(ConfigFactory.ConfigMetadata configData) {
if (mStatsManager != null) {
- if (mStatsManager.addConfiguration(ConfigFactory.CONFIG_ID, configData.bytes)) {
+ try {
+ mStatsManager.addConfig(ConfigFactory.CONFIG_ID, configData.bytes);
mNumMetrics = configData.numMetrics;
Log.d(TAG, "Config pushed to statsd");
return true;
- } else {
- Log.d(TAG, "Failed to push config to statsd");
+ } catch (StatsManager.StatsUnavailableException | IllegalArgumentException e) {
+ Log.e(TAG, "Failed to push config to statsd", e);
}
}
return false;
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 87fb998..7177336 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -32326,7 +32326,7 @@
HSPLandroid/view/IWindowSession$Stub$Proxy;->getDisplayFrame(Landroid/view/IWindow;Landroid/graphics/Rect;)V
HSPLandroid/view/IWindowSession$Stub$Proxy;->getInTouchMode()Z
HSPLandroid/view/IWindowSession$Stub$Proxy;->onRectangleOnScreenRequested(Landroid/os/IBinder;Landroid/graphics/Rect;)V
-HSPLandroid/view/IWindowSession$Stub$Proxy;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I
+HSPLandroid/view/IWindowSession$Stub$Proxy;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIIJLandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I
HSPLandroid/view/IWindowSession$Stub$Proxy;->setInsets(Landroid/view/IWindow;ILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Region;)V
HSPLandroid/view/IWindowSession$Stub$Proxy;->setTransparentRegion(Landroid/view/IWindow;Landroid/graphics/Region;)V
HSPLandroid/view/IWindowSession$Stub$Proxy;->setWallpaperPosition(Landroid/os/IBinder;FFFF)V
@@ -32349,7 +32349,7 @@
HSPLandroid/view/IWindowSession;->pokeDrawLock(Landroid/os/IBinder;)V
HSPLandroid/view/IWindowSession;->prepareDrag(Landroid/view/IWindow;IIILandroid/view/Surface;)Landroid/os/IBinder;
HSPLandroid/view/IWindowSession;->prepareToReplaceWindows(Landroid/os/IBinder;Z)V
-HSPLandroid/view/IWindowSession;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I
+HSPLandroid/view/IWindowSession;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIIJLandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I
HSPLandroid/view/IWindowSession;->remove(Landroid/view/IWindow;)V
HSPLandroid/view/IWindowSession;->reportDropResult(Landroid/view/IWindow;Z)V
HSPLandroid/view/IWindowSession;->sendWallpaperCommand(Landroid/os/IBinder;Ljava/lang/String;IIILandroid/os/Bundle;Z)Landroid/os/Bundle;
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index a1e71f4..942a9dd 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -2,8 +2,19 @@
Landroid/accounts/IAccountAuthenticatorResponse$Stub;-><init>()V
Landroid/accounts/IAccountManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/accounts/IAccountManager;
Landroid/accounts/IAccountManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/animation/KeyframeSet;-><init>([Landroid/animation/Keyframe;)V
+Landroid/animation/KeyframeSet;->ofFloat([F)Landroid/animation/KeyframeSet;
+Landroid/animation/KeyframeSet;->ofInt([I)Landroid/animation/KeyframeSet;
+Landroid/animation/KeyframeSet;->ofKeyframe([Landroid/animation/Keyframe;)Landroid/animation/KeyframeSet;
+Landroid/animation/KeyframeSet;->ofObject([Ljava/lang/Object;)Landroid/animation/KeyframeSet;
Landroid/animation/LayoutTransition;->cancel(I)V
Landroid/animation/LayoutTransition;->cancel()V
+Landroid/animation/PropertyValuesHolder$FloatPropertyValuesHolder;-><init>(Landroid/util/Property;[F)V
+Landroid/animation/PropertyValuesHolder$FloatPropertyValuesHolder;-><init>(Ljava/lang/String;[F)V
+Landroid/animation/PropertyValuesHolder$IntPropertyValuesHolder;-><init>(Landroid/util/Property;[I)V
+Landroid/animation/PropertyValuesHolder$IntPropertyValuesHolder;-><init>(Ljava/lang/String;[I)V
+Landroid/animation/PropertyValuesHolder$MultiFloatValuesHolder;-><init>(Ljava/lang/String;Landroid/animation/TypeConverter;Landroid/animation/TypeEvaluator;[Ljava/lang/Object;)V
+Landroid/animation/PropertyValuesHolder$MultiIntValuesHolder;-><init>(Ljava/lang/String;Landroid/animation/TypeConverter;Landroid/animation/TypeEvaluator;[Ljava/lang/Object;)V
Landroid/animation/ValueAnimator;->animateValue(F)V
Landroid/animation/ValueAnimator;->sDurationScale:F
Landroid/app/Activity;->getActivityOptions()Landroid/app/ActivityOptions;
@@ -51,6 +62,7 @@
Landroid/app/Activity;->mWindow:Landroid/view/Window;
Landroid/app/Activity;->mWindowManager:Landroid/view/WindowManager;
Landroid/app/ActivityOptions;->makeMultiThumbFutureAspectScaleAnimation(Landroid/content/Context;Landroid/os/Handler;Landroid/view/IAppTransitionAnimationSpecsFuture;Landroid/app/ActivityOptions$OnAnimationStartedListener;Z)Landroid/app/ActivityOptions;
+Landroid/app/ActivityOptions;->startSharedElementAnimation(Landroid/view/Window;[Landroid/util/Pair;)Landroid/app/ActivityOptions;
Landroid/app/Activity;->setDisablePreviewScreenshots(Z)V
Landroid/app/Activity;->setPersistent(Z)V
Landroid/app/ActivityThread$ActivityClientRecord;->activityInfo:Landroid/content/pm/ActivityInfo;
@@ -124,6 +136,7 @@
Landroid/app/ActivityThread;->mServices:Landroid/util/ArrayMap;
Landroid/app/ActivityThread;->performNewIntents(Landroid/os/IBinder;Ljava/util/List;Z)V
Landroid/app/ActivityThread;->performStopActivity(Landroid/os/IBinder;ZLjava/lang/String;)V
+Landroid/app/ActivityThread;->printRow(Ljava/io/PrintWriter;Ljava/lang/String;[Ljava/lang/Object;)V
Landroid/app/ActivityThread$ProviderClientRecord;->mHolder:Landroid/app/ContentProviderHolder;
Landroid/app/ActivityThread$ProviderClientRecord;->mLocalProvider:Landroid/content/ContentProvider;
Landroid/app/ActivityThread$ProviderClientRecord;->mProvider:Landroid/content/IContentProvider;
@@ -136,6 +149,7 @@
Landroid/app/ActivityThread$ServiceArgsData;->token:Landroid/os/IBinder;
Landroid/app/ActivityThread;->sPackageManager:Landroid/content/pm/IPackageManager;
Landroid/app/ActivityThread;->startActivityNow(Landroid/app/Activity;Ljava/lang/String;Landroid/content/Intent;Landroid/content/pm/ActivityInfo;Landroid/os/IBinder;Landroid/os/Bundle;Landroid/app/Activity$NonConfigurationInstances;)Landroid/app/Activity;
+Landroid/app/admin/DevicePolicyManager;->getProfileOwnerAsUser(I)Landroid/content/ComponentName;
Landroid/app/admin/DevicePolicyManager;->getTrustAgentConfiguration(Landroid/content/ComponentName;Landroid/content/ComponentName;I)Ljava/util/List;
Landroid/app/admin/DevicePolicyManager;->packageHasActiveAdmins(Ljava/lang/String;I)Z
Landroid/app/admin/DevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;ZI)V
@@ -145,6 +159,7 @@
Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_packageHasActiveAdmins:I
Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_removeActiveAdmin:I
Landroid/app/admin/SecurityLog$SecurityEvent;-><init>([B)V
+Landroid/app/admin/SecurityLog;->writeEvent(I[Ljava/lang/Object;)I
Landroid/app/AlarmManager;->FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED:I
Landroid/app/AlarmManager;->FLAG_IDLE_UNTIL:I
Landroid/app/AlarmManager;->FLAG_STANDALONE:I
@@ -196,12 +211,14 @@
Landroid/app/AppOpsManager;->OP_READ_CONTACTS:I
Landroid/app/AppOpsManager;->OP_READ_PHONE_STATE:I
Landroid/app/AppOpsManager;->OP_READ_SMS:I
+Landroid/app/AppOpsManager;->OP_RUN_IN_BACKGROUND:I
Landroid/app/AppOpsManager;->OP_VIBRATE:I
Landroid/app/AppOpsManager;->OP_WIFI_SCAN:I
Landroid/app/AppOpsManager;->OP_WRITE_CONTACTS:I
Landroid/app/AppOpsManager;->OP_WRITE_SMS:I
Landroid/app/AppOpsManager;->permissionToOpCode(Ljava/lang/String;)I
Landroid/app/AppOpsManager;->strOpToOp(Ljava/lang/String;)I
+Landroid/app/backup/AbsoluteFileBackupHelper;-><init>(Landroid/content/Context;[Ljava/lang/String;)V
Landroid/app/backup/BackupDataInput$EntityHeader;->dataSize:I
Landroid/app/backup/BackupDataInput$EntityHeader;->key:Ljava/lang/String;
Landroid/app/backup/BackupDataInputStream;->dataSize:I
@@ -209,6 +226,7 @@
Landroid/app/backup/BackupDataOutput;->mBackupWriter:J
Landroid/app/backup/BackupHelperDispatcher$Header;->chunkSize:I
Landroid/app/backup/BackupHelperDispatcher$Header;->keyPrefix:Ljava/lang/String;
+Landroid/app/backup/BlobBackupHelper;-><init>(I[Ljava/lang/String;)V
Landroid/app/backup/FileBackupHelperBase;->writeNewStateDescription(Landroid/os/ParcelFileDescriptor;)V
Landroid/app/backup/FullBackup;->backupToTar(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/app/backup/FullBackupDataOutput;)I
Landroid/app/backup/FullBackupDataOutput;->addSize(J)V
@@ -245,7 +263,10 @@
Landroid/app/Dialog;->mListenersHandler:Landroid/os/Handler;
Landroid/app/Dialog;->mOwnerActivity:Landroid/app/Activity;
Landroid/app/Dialog;->mShowMessage:Landroid/os/Message;
+Landroid/app/DownloadManager;->forceDownload([J)V
+Landroid/app/DownloadManager;->markRowDeleted([J)I
Landroid/app/DownloadManager$Request;->mUri:Landroid/net/Uri;
+Landroid/app/DownloadManager;->restartDownload([J)V
Landroid/app/FragmentManagerImpl;->mAdded:Ljava/util/ArrayList;
Landroid/app/FragmentManagerImpl;->noteStateNotSaved()V
Landroid/app/Fragment;->mChildFragmentManager:Landroid/app/FragmentManagerImpl;
@@ -425,14 +446,24 @@
Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
Landroid/bluetooth/BluetoothMapClient;->sendMessage(Landroid/bluetooth/BluetoothDevice;[Landroid/net/Uri;Ljava/lang/String;Landroid/app/PendingIntent;Landroid/app/PendingIntent;)Z
+Landroid/bluetooth/BluetoothPan;->close()V
+Landroid/bluetooth/BluetoothPan;->connect(Landroid/bluetooth/BluetoothDevice;)Z
+Landroid/bluetooth/BluetoothPan;->disconnect(Landroid/bluetooth/BluetoothDevice;)Z
+Landroid/bluetooth/BluetoothPan;->doBind()Z
+Landroid/bluetooth/BluetoothPan;-><init>(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;)V
+Landroid/bluetooth/BluetoothPan;->isEnabled()Z
Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z
+Landroid/bluetooth/BluetoothPan;->isValidDevice(Landroid/bluetooth/BluetoothDevice;)Z
+Landroid/bluetooth/BluetoothPan;->log(Ljava/lang/String;)V
Landroid/bluetooth/BluetoothPan;->setBluetoothTethering(Z)V
+Landroid/bluetooth/BluetoothProfile;->PAN:I
Landroid/bluetooth/BluetoothUuid;->RESERVED_UUIDS:[Landroid/os/ParcelUuid;
Landroid/bluetooth/IBluetooth;->getAddress()Ljava/lang/String;
Landroid/bluetooth/IBluetoothManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
Landroid/bluetooth/IBluetooth$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetooth;
Landroid/bluetooth/IBluetooth$Stub$Proxy;->getAddress()Ljava/lang/String;
Landroid/bluetooth/le/ScanRecord;->parseFromBytes([B)Landroid/bluetooth/le/ScanRecord;
+Landroid/content/AsyncTaskLoader$LoadTask;->doInBackground([Ljava/lang/Void;)Ljava/lang/Object;
Landroid/content/AsyncTaskLoader;->mExecutor:Ljava/util/concurrent/Executor;
Landroid/content/BroadcastReceiver$PendingResult;-><init>(ILjava/lang/String;Landroid/os/Bundle;IZZLandroid/os/IBinder;II)V
Landroid/content/BroadcastReceiver;->setPendingResult(Landroid/content/BroadcastReceiver$PendingResult;)V
@@ -499,6 +530,7 @@
Landroid/content/pm/ApplicationInfo;->scanSourceDir:Ljava/lang/String;
Landroid/content/pm/ApplicationInfo;->secondaryCpuAbi:Ljava/lang/String;
Landroid/content/pm/ApplicationInfo;->secondaryNativeLibraryDir:Ljava/lang/String;
+Landroid/content/pm/ApplicationInfo;->versionCode:I
Landroid/content/pm/ComponentInfo;->getComponentName()Landroid/content/ComponentName;
Landroid/content/pm/IPackageDataObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageDataObserver;
Landroid/content/pm/IPackageManager;->addPermissionAsync(Landroid/content/pm/PermissionInfo;)Z
@@ -621,6 +653,7 @@
Landroid/content/res/ColorStateList;->mDefaultColor:I
Landroid/content/res/ColorStateList;->mFactory:Landroid/content/res/ColorStateList$ColorStateListFactory;
Landroid/content/res/ColorStateList;->mStateSpecs:[[I
+Landroid/content/res/ColorStateList;->onColorsChanged()V
Landroid/content/res/CompatibilityInfo;->applicationScale:F
Landroid/content/res/CompatibilityInfo;->DEFAULT_COMPATIBILITY_INFO:Landroid/content/res/CompatibilityInfo;
Landroid/content/res/DrawableCache;->getInstance(JLandroid/content/res/Resources;Landroid/content/res/Resources$Theme;)Landroid/graphics/drawable/Drawable;
@@ -678,6 +711,7 @@
Landroid/database/AbstractCursor;->mNotifyUri:Landroid/net/Uri;
Landroid/database/AbstractCursor;->mRowIdColumnIndex:I
Landroid/database/AbstractWindowedCursor;->clearOrCreateWindow(Ljava/lang/String;)V
+Landroid/database/CursorJoiner;->compareStrings([Ljava/lang/String;)I
Landroid/database/CursorWindow;->mWindowPtr:J
Landroid/database/CursorWindow;->sCursorWindowSize:I
Landroid/database/CursorWindow;->sWindowToPidMap:Landroid/util/LongSparseArray;
@@ -689,12 +723,21 @@
Landroid/database/sqlite/SQLiteDatabase;->CONFLICT_VALUES:[Ljava/lang/String;
Landroid/database/sqlite/SQLiteDatabase;->mConfigurationLocked:Landroid/database/sqlite/SQLiteDatabaseConfiguration;
Landroid/database/sqlite/SQLiteDatabase;->mConnectionPoolLocked:Landroid/database/sqlite/SQLiteConnectionPool;
+Landroid/database/sqlite/SQLiteDatabase;->reopenReadWrite()V
Landroid/database/sqlite/SQLiteDebug$PagerStats;->largestMemAlloc:I
Landroid/database/sqlite/SQLiteDebug$PagerStats;->memoryUsed:I
Landroid/database/sqlite/SQLiteDebug$PagerStats;->pageCacheOverflow:I
Landroid/database/sqlite/SQLiteOpenHelper;->mName:Ljava/lang/String;
Landroid/database/sqlite/SQLiteStatement;-><init>(Landroid/database/sqlite/SQLiteDatabase;Ljava/lang/String;[Ljava/lang/Object;)V
Landroid/ddm/DdmHandleAppName;->getAppName()Ljava/lang/String;
+Landroid/filterfw/core/AsyncRunner$AsyncRunnerTask;->doInBackground([Landroid/filterfw/core/SyncRunner;)Landroid/filterfw/core/AsyncRunner$RunnerResult;
+Landroid/filterfw/core/FilterFunction;->executeWithArgList([Ljava/lang/Object;)Landroid/filterfw/core/Frame;
+Landroid/filterfw/core/Filter;->initWithAssignmentList([Ljava/lang/Object;)V
+Landroid/filterfw/core/KeyValueMap;->fromKeyValues([Ljava/lang/Object;)Landroid/filterfw/core/KeyValueMap;
+Landroid/filterfw/core/KeyValueMap;->setKeyValues([Ljava/lang/Object;)V
+Landroid/filterfw/FilterFunctionEnvironment;->createFunction(Ljava/lang/Class;[Ljava/lang/Object;)Landroid/filterfw/core/FilterFunction;
+Landroid/filterfw/GraphEnvironment;->addReferences([Ljava/lang/Object;)V
+Landroid/filterfw/io/GraphReader;->addReferencesByKeysAndValues([Ljava/lang/Object;)V
Landroid/graphics/AvoidXfermode$Mode;->AVOID:Landroid/graphics/AvoidXfermode$Mode;
Landroid/graphics/AvoidXfermode$Mode;->TARGET:Landroid/graphics/AvoidXfermode$Mode;
Landroid/graphics/BaseCanvas;->mNativeCanvasWrapper:J
@@ -736,6 +779,7 @@
Landroid/graphics/drawable/Drawable;->getOpticalInsets()Landroid/graphics/Insets;
Landroid/graphics/drawable/Drawable;->inflateWithAttributes(Landroid/content/res/Resources;Lorg/xmlpull/v1/XmlPullParser;Landroid/content/res/TypedArray;I)V
Landroid/graphics/drawable/Drawable;->mCallback:Ljava/lang/ref/WeakReference;
+Landroid/graphics/drawable/Drawable;->parseTintMode(ILandroid/graphics/PorterDuff$Mode;)Landroid/graphics/PorterDuff$Mode;
Landroid/graphics/drawable/GradientDrawable$GradientState;->mAngle:I
Landroid/graphics/drawable/GradientDrawable$GradientState;->mGradientColors:[I
Landroid/graphics/drawable/GradientDrawable$GradientState;->mGradient:I
@@ -748,12 +792,14 @@
Landroid/graphics/drawable/GradientDrawable$GradientState;->mRadiusArray:[F
Landroid/graphics/drawable/GradientDrawable$GradientState;->mRadius:F
Landroid/graphics/drawable/GradientDrawable$GradientState;->mShape:I
+Landroid/graphics/drawable/GradientDrawable$GradientState;->mSolidColors:Landroid/content/res/ColorStateList;
Landroid/graphics/drawable/GradientDrawable$GradientState;->mStrokeDashGap:F
Landroid/graphics/drawable/GradientDrawable$GradientState;->mStrokeDashWidth:F
Landroid/graphics/drawable/GradientDrawable$GradientState;->mStrokeWidth:I
Landroid/graphics/drawable/GradientDrawable$GradientState;->mThickness:I
Landroid/graphics/drawable/GradientDrawable$GradientState;->mThicknessRatio:F
Landroid/graphics/drawable/GradientDrawable$GradientState;->mWidth:I
+Landroid/graphics/drawable/GradientDrawable;->mGradientState:Landroid/graphics/drawable/GradientDrawable$GradientState;
Landroid/graphics/drawable/GradientDrawable;->mPadding:Landroid/graphics/Rect;
Landroid/graphics/drawable/Icon;->getBitmap()Landroid/graphics/Bitmap;
Landroid/graphics/drawable/Icon;->getDataBytes()[B
@@ -763,9 +809,13 @@
Landroid/graphics/drawable/Icon;->hasTint()Z
Landroid/graphics/drawable/Icon;->mType:I
Landroid/graphics/drawable/InsetDrawable;->mState:Landroid/graphics/drawable/InsetDrawable$InsetState;
+Landroid/graphics/drawable/LayerDrawable$ChildDrawable;->mDrawable:Landroid/graphics/drawable/Drawable;
+Landroid/graphics/drawable/LayerDrawable$LayerState;->mChildren:[Landroid/graphics/drawable/LayerDrawable$ChildDrawable;
Landroid/graphics/drawable/LayerDrawable;->mLayerState:Landroid/graphics/drawable/LayerDrawable$LayerState;
Landroid/graphics/drawable/NinePatchDrawable;->mNinePatchState:Landroid/graphics/drawable/NinePatchDrawable$NinePatchState;
Landroid/graphics/drawable/NinePatchDrawable$NinePatchState;->mNinePatch:Landroid/graphics/NinePatch;
+Landroid/graphics/drawable/RippleDrawable;->mState:Landroid/graphics/drawable/RippleDrawable$RippleState;
+Landroid/graphics/drawable/RippleDrawable$RippleState;->mColor:Landroid/content/res/ColorStateList;
Landroid/graphics/drawable/StateListDrawable;->extractStateSet(Landroid/util/AttributeSet;)[I
Landroid/graphics/drawable/StateListDrawable;->getStateCount()I
Landroid/graphics/drawable/StateListDrawable;->getStateDrawable(I)Landroid/graphics/drawable/Drawable;
@@ -785,6 +835,8 @@
Landroid/graphics/GraphicBuffer;-><init>(IIIIJ)V
Landroid/graphics/GraphicBuffer;->mNativeObject:J
Landroid/graphics/ImageDecoder;->postProcessAndRelease(Landroid/graphics/Canvas;)I
+Landroid/graphics/Insets;->left:I
+Landroid/graphics/Insets;->right:I
Landroid/graphics/LinearGradient;->mColors:[I
Landroid/graphics/Matrix;->native_instance:J
Landroid/graphics/Movie;-><init>(J)V
@@ -793,8 +845,6 @@
Landroid/graphics/NinePatch;->mBitmap:Landroid/graphics/Bitmap;
Landroid/graphics/Picture;->mNativePicture:J
Landroid/graphics/PixelXorXfermode;-><init>(I)V
-Landroid/graphics/PorterDuffColorFilter;->setColor(I)V
-Landroid/graphics/PorterDuffColorFilter;->setMode(Landroid/graphics/PorterDuff$Mode;)V
Landroid/graphics/Rect;->scale(F)V
Landroid/graphics/Region;-><init>(JI)V
Landroid/graphics/Region;->mNativeRegion:J
@@ -867,7 +917,11 @@
Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_BLUE:Landroid/hardware/camera2/CaptureResult$Key;
Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_GREEN:Landroid/hardware/camera2/CaptureResult$Key;
Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_RED:Landroid/hardware/camera2/CaptureResult$Key;
+Landroid/hardware/camera2/impl/CameraMetadataNative;->areValuesAllNull([Ljava/lang/Object;)Z
Landroid/hardware/camera2/impl/CameraMetadataNative;->mMetadataPtr:J
+Landroid/hardware/camera2/utils/HashCodeHelpers;->hashCode([F)I
+Landroid/hardware/camera2/utils/HashCodeHelpers;->hashCodeGeneric([Ljava/lang/Object;)I
+Landroid/hardware/camera2/utils/HashCodeHelpers;->hashCode([I)I
Landroid/hardware/Camera;->addCallbackBuffer([BI)V
Landroid/hardware/Camera;->mNativeContext:J
Landroid/hardware/Camera;->openLegacy(II)Landroid/hardware/Camera;
@@ -934,15 +988,25 @@
Landroid/hardware/usb/UsbRequest;->mLength:I
Landroid/hardware/usb/UsbRequest;->mNativeContext:J
Landroid/icu/impl/CurrencyData;-><init>()V
+Landroid/icu/impl/locale/XLocaleDistance$RegionMapper$Builder;->addParadigms([Ljava/lang/String;)Landroid/icu/impl/locale/XLocaleDistance$RegionMapper$Builder;
+Landroid/icu/impl/TimeZoneGenericNames;->formatPattern(Landroid/icu/impl/TimeZoneGenericNames$Pattern;[Ljava/lang/String;)Ljava/lang/String;
+Landroid/icu/impl/TimeZoneGenericNames$GenericNameType;-><init>([Ljava/lang/String;)V
Landroid/icu/text/ArabicShaping;->isAlefMaksouraChar(C)Z
Landroid/icu/text/ArabicShaping;->isSeenTailFamilyChar(C)I
Landroid/icu/text/ArabicShaping;->isTailChar(C)Z
Landroid/icu/text/ArabicShaping;->isYehHamzaChar(C)Z
+Landroid/icu/text/Collator;->getIntValue(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)I
Landroid/icu/text/DateFormatSymbols;->getLocale(Landroid/icu/util/ULocale$Type;)Landroid/icu/util/ULocale;
Landroid/icu/text/DateIntervalFormat;-><init>()V
Landroid/icu/text/DateTimePatternGenerator$DistanceInfo;-><init>()V
Landroid/icu/text/DecimalFormatSymbols;->getLocale(Landroid/icu/util/ULocale$Type;)Landroid/icu/util/ULocale;
+Landroid/icu/text/DictionaryBreakEngine;-><init>([Ljava/lang/Integer;)V
+Landroid/icu/text/LocaleDisplayNames$LastResortLocaleDisplayNames;-><init>(Landroid/icu/util/ULocale;[Landroid/icu/text/DisplayContext;)V
+Landroid/icu/text/MeasureFormat;->formatMeasuresSlowTrack(Landroid/icu/text/ListFormatter;Ljava/lang/StringBuilder;Ljava/text/FieldPosition;[Landroid/icu/util/Measure;)Ljava/lang/StringBuilder;
Landroid/icu/text/RuleBasedCollator;->getLocale(Landroid/icu/util/ULocale$Type;)Landroid/icu/util/ULocale;
+Landroid/icu/text/SimpleFormatter;->formatAndAppend(Ljava/lang/StringBuilder;[I[Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;
+Landroid/icu/text/SimpleFormatter;->formatAndReplace(Ljava/lang/StringBuilder;[I[Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;
+Landroid/icu/text/SimpleFormatter;->format([Ljava/lang/CharSequence;)Ljava/lang/String;
Landroid/icu/text/SpoofChecker$ScriptSet;->and(I)V
Landroid/icu/text/SpoofChecker$ScriptSet;-><init>()V
Landroid/icu/text/SpoofChecker$ScriptSet;->isFull()Z
@@ -952,6 +1016,11 @@
Landroid/icu/text/Transliterator;->transliterate(Ljava/lang/String;)Ljava/lang/String;
Landroid/icu/text/UFormat;->getLocale(Landroid/icu/util/ULocale$Type;)Landroid/icu/util/ULocale;
Landroid/icu/util/Calendar;->getLocale(Landroid/icu/util/ULocale$Type;)Landroid/icu/util/ULocale;
+Landroid/icu/util/Currency$EquivalenceRelation;->add([Ljava/lang/Object;)Landroid/icu/util/Currency$EquivalenceRelation;
+Landroid/icu/util/GenderInfo;->getListGender([Landroid/icu/util/GenderInfo$Gender;)Landroid/icu/util/GenderInfo$Gender;
+Landroid/icu/util/LocaleMatcher;->getBestMatch([Landroid/icu/util/ULocale;)Landroid/icu/util/ULocale;
+Landroid/icu/util/LocalePriorityList;->add([Landroid/icu/util/ULocale;)Landroid/icu/util/LocalePriorityList$Builder;
+Landroid/icu/util/LocalePriorityList$Builder;->add([Landroid/icu/util/ULocale;)Landroid/icu/util/LocalePriorityList$Builder;
Landroid/inputmethodservice/InputMethodService;->mExtractEditText:Landroid/inputmethodservice/ExtractEditText;
Landroid/inputmethodservice/InputMethodService;->mRootView:Landroid/view/View;
Landroid/inputmethodservice/InputMethodService;->mSettingsObserver:Landroid/inputmethodservice/InputMethodService$SettingsObserver;
@@ -978,6 +1047,7 @@
Landroid/media/AudioFormat;->mEncoding:I
Landroid/media/AudioFormat;->mSampleRate:I
Landroid/media/audiofx/AudioEffect;->command(I[B[B)I
+Landroid/media/audiofx/AudioEffect;->concatArrays([[B)[B
Landroid/media/audiofx/AudioEffect;->getParameter([I[B)I
Landroid/media/audiofx/AudioEffect;->getParameter([I[I)I
Landroid/media/audiofx/AudioEffect;-><init>(Ljava/util/UUID;Ljava/util/UUID;II)V
@@ -1053,6 +1123,8 @@
Landroid/media/AudioTrack;->mStreamType:I
Landroid/media/AudioTrack;->native_release()V
Landroid/media/AudioTrack;->postEventFromNative(Ljava/lang/Object;IIILjava/lang/Object;)V
+Landroid/media/effect/SingleFilterEffect;-><init>(Landroid/media/effect/EffectContext;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/media/effect/SizeChangeEffect;-><init>(Landroid/media/effect/EffectContext;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
Landroid/media/ExifInterface;->getDateTime()J
Landroid/media/IAudioService;->getStreamMaxVolume(I)I
Landroid/media/IAudioService;->getStreamVolume(I)I
@@ -1137,6 +1209,7 @@
Landroid/media/SubtitleTrack$RenderingWidget;->setSize(II)V
Landroid/media/ThumbnailUtils;->createImageThumbnail(Ljava/lang/String;I)Landroid/graphics/Bitmap;
Landroid/media/ToneGenerator;->mNativeContext:J
+Landroid/media/tv/TvInputService$OverlayViewCleanUpTask;->doInBackground([Landroid/view/View;)Ljava/lang/Void;
Landroid/media/VolumeShaper$Configuration;-><init>(IIIDI[F[F)V
Landroid/media/VolumeShaper$Configuration;->mDurationMs:D
Landroid/media/VolumeShaper$Configuration;->mId:I
@@ -1190,6 +1263,7 @@
Landroid/net/LinkProperties;->setHttpProxy(Landroid/net/ProxyInfo;)V
Landroid/net/LocalSocketImpl;->inboundFileDescriptors:[Ljava/io/FileDescriptor;
Landroid/net/LocalSocketImpl;->outboundFileDescriptors:[Ljava/io/FileDescriptor;
+Landroid/net/MacAddress;->addr([I)[B
Landroid/net/NetworkCapabilities;->getCapabilities()[I
Landroid/net/NetworkCapabilities;->getTransportTypes()[I
Landroid/net/NetworkPolicyManager;->mService:Landroid/net/INetworkPolicyManager;
@@ -1234,6 +1308,7 @@
Landroid/net/SSLCertificateSocketFactory;->setChannelIdPrivateKey(Ljava/security/PrivateKey;)V
Landroid/net/SSLCertificateSocketFactory;->setSoWriteTimeout(Ljava/net/Socket;I)V
Landroid/net/SSLCertificateSocketFactory;->TAG:Ljava/lang/String;
+Landroid/net/SSLCertificateSocketFactory;->toLengthPrefixedList([[B)[B
Landroid/net/SSLCertificateSocketFactory;->verifyHostname(Ljava/net/Socket;Ljava/lang/String;)V
Landroid/net/SSLSessionCache;->mSessionCache:Lcom/android/org/conscrypt/SSLClientSessionCache;
Landroid/net/StaticIpConfiguration;->gateway:Ljava/net/InetAddress;
@@ -1305,10 +1380,12 @@
Landroid/net/wifi/WifiSsid;->NONE:Ljava/lang/String;
Landroid/nfc/NfcAdapter;->getDefaultAdapter()Landroid/nfc/NfcAdapter;
Landroid/nfc/NfcAdapter;->setNdefPushMessageCallback(Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;Landroid/app/Activity;I)V
+Landroid/nfc/TechListParcel;-><init>([[Ljava/lang/String;)V
Landroid/opengl/GLSurfaceView$EglHelper;->mEglContext:Ljavax/microedition/khronos/egl/EGLContext;
Landroid/opengl/GLSurfaceView$GLThread;->mEglHelper:Landroid/opengl/GLSurfaceView$EglHelper;
Landroid/opengl/GLSurfaceView;->mGLThread:Landroid/opengl/GLSurfaceView$GLThread;
Landroid/opengl/GLSurfaceView;->mRenderer:Landroid/opengl/GLSurfaceView$Renderer;
+Landroid/os/AsyncTask$AsyncTaskResult;-><init>(Landroid/os/AsyncTask;[Ljava/lang/Object;)V
Landroid/os/AsyncTask;->mFuture:Ljava/util/concurrent/FutureTask;
Landroid/os/AsyncTask;->mStatus:Landroid/os/AsyncTask$Status;
Landroid/os/AsyncTask;->mTaskInvoked:Ljava/util/concurrent/atomic/AtomicBoolean;
@@ -1316,6 +1393,7 @@
Landroid/os/AsyncTask;->sDefaultExecutor:Ljava/util/concurrent/Executor;
Landroid/os/AsyncTask;->setDefaultExecutor(Ljava/util/concurrent/Executor;)V
Landroid/os/BatteryStats$Counter;->getCountLocked(I)I
+Landroid/os/BatteryStats;->dumpLine(Ljava/io/PrintWriter;ILjava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
Landroid/os/BatteryStats;->getUidStats()Landroid/util/SparseArray;
Landroid/os/BatteryStats$HistoryItem;->CMD_UPDATE:B
Landroid/os/BatteryStats$HistoryItem;->states2:I
@@ -1345,6 +1423,7 @@
Landroid/os/BatteryStats$Uid$Proc;->getUserTime(I)J
Landroid/os/BatteryStats$Uid$Sensor;->getHandle()I
Landroid/os/BatteryStats$Uid$Sensor;->getSensorTime()Landroid/os/BatteryStats$Timer;
+Landroid/os/BestClock;-><init>(Ljava/time/ZoneId;[Ljava/time/Clock;)V
Landroid/os/Binder;->execTransact(IJJI)Z
Landroid/os/Binder;->mObject:J
Landroid/os/Build;->getString(Ljava/lang/String;)Ljava/lang/String;
@@ -1385,6 +1464,7 @@
Landroid/os/Debug$MemoryInfo;->otherSwappedOut:I
Landroid/os/Debug$MemoryInfo;->otherSwappedOutPss:I
Landroid/os/Environment;->buildExternalStorageAppDataDirs(Ljava/lang/String;)[Ljava/io/File;
+Landroid/os/Environment;->buildPaths([Ljava/io/File;[Ljava/lang/String;)[Ljava/io/File;
Landroid/os/Environment;->getVendorDirectory()Ljava/io/File;
Landroid/os/Environment;->maybeTranslateEmulatedPathToInternal(Ljava/io/File;)Ljava/io/File;
Landroid/os/FileObserver$ObserverThread;->onEvent(IILjava/lang/String;)V
@@ -1458,6 +1538,7 @@
Landroid/os/Process;->readProcFile(Ljava/lang/String;[I[Ljava/lang/String;[J[F)Z
Landroid/os/Process;->readProcLines(Ljava/lang/String;[Ljava/lang/String;[J)V
Landroid/os/Process;->setArgV0(Ljava/lang/String;)V
+Landroid/os/RecoverySystem;->bootCommand(Landroid/content/Context;[Ljava/lang/String;)V
Landroid/os/SELinux;->isSELinuxEnabled()Z
Landroid/os/SELinux;->isSELinuxEnforced()Z
Landroid/os/ServiceManager;->addService(Ljava/lang/String;Landroid/os/IBinder;)V
@@ -1477,6 +1558,7 @@
Landroid/os/storage/StorageManager;->getBestVolumeDescription(Landroid/os/storage/VolumeInfo;)Ljava/lang/String;
Landroid/os/storage/StorageManager;->getDisks()Ljava/util/List;
Landroid/os/storage/StorageManager;->getStorageBytesUntilLow(Ljava/io/File;)J
+Landroid/os/storage/StorageManager;->getStorageFullBytes(Ljava/io/File;)J
Landroid/os/storage/StorageManager;->getStorageLowBytes(Ljava/io/File;)J
Landroid/os/storage/StorageManager;->getVolumeList(II)[Landroid/os/storage/StorageVolume;
Landroid/os/storage/StorageManager;->getVolumeList()[Landroid/os/storage/StorageVolume;
@@ -1496,8 +1578,10 @@
Landroid/os/storage/VolumeInfo;->isPrimary()Z
Landroid/os/storage/VolumeInfo;->isVisible()Z
Landroid/os/StrictMode;->disableDeathOnFileUriExposure()V
+Landroid/os/StrictMode;->enterCriticalSpan(Ljava/lang/String;)Landroid/os/StrictMode$Span;
Landroid/os/StrictMode;->getThreadPolicyMask()I
Landroid/os/StrictMode;->onBinderStrictModePolicyChange(I)V
+Landroid/os/StrictMode$Span;->finish()V
Landroid/os/StrictMode$ThreadPolicy$Builder;->penaltyListener(Landroid/os/StrictMode$OnThreadViolationListener;Ljava/util/concurrent/Executor;)Landroid/os/StrictMode$ThreadPolicy$Builder;
Landroid/os/StrictMode;->violationsBeingTimed:Ljava/lang/ThreadLocal;
Landroid/os/SystemProperties;->addChangeCallback(Ljava/lang/Runnable;)V
@@ -1505,6 +1589,7 @@
Landroid/os/SystemProperties;->native_get(Ljava/lang/String;)Ljava/lang/String;
Landroid/os/SystemProperties;->PROP_NAME_MAX:I
Landroid/os/SystemProperties;->set(Ljava/lang/String;Ljava/lang/String;)V
+Landroid/os/SystemService;->waitForAnyStopped([Ljava/lang/String;)V
Landroid/os/Trace;->asyncTraceBegin(JLjava/lang/String;I)V
Landroid/os/Trace;->asyncTraceEnd(JLjava/lang/String;I)V
Landroid/os/Trace;->isTagEnabled(J)Z
@@ -1616,6 +1701,7 @@
Landroid/preference/Preference;->performClick(Landroid/preference/PreferenceScreen;)V
Landroid/preference/PreferenceScreen;->mRootAdapter:Landroid/widget/ListAdapter;
Landroid/print/PrinterId;->getServiceName()Landroid/content/ComponentName;
+Landroid/print/PrintFileDocumentAdapter$WriteFileAsyncTask;->doInBackground([Ljava/lang/Void;)Ljava/lang/Void;
Landroid/print/PrintJobInfo;->getAdvancedOptions()Landroid/os/Bundle;
Landroid/print/PrintJobInfo;->getDocumentInfo()Landroid/print/PrintDocumentInfo;
Landroid/provider/Browser$BookmarkColumns;->DATE:Ljava/lang/String;
@@ -1631,6 +1717,7 @@
Landroid/provider/Browser;->updateVisitedHistory(Landroid/content/ContentResolver;Ljava/lang/String;Z)V
Landroid/provider/CalendarContract$CalendarAlerts;->findNextAlarmTime(Landroid/content/ContentResolver;J)J
Landroid/provider/CalendarContract$CalendarAlerts;->rescheduleMissedAlarms(Landroid/content/ContentResolver;Landroid/content/Context;Landroid/app/AlarmManager;)V
+Landroid/provider/Downloads$Impl;->CONTENT_URI:Landroid/net/Uri;
Landroid/provider/Settings$ContentProviderHolder;->mContentProvider:Landroid/content/IContentProvider;
Landroid/provider/Settings$Global;->ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED:Ljava/lang/String;
Landroid/provider/Settings$Global;->PACKAGE_VERIFIER_ENABLE:Ljava/lang/String;
@@ -1856,6 +1943,8 @@
Landroid/R$styleable;->Window:[I
Landroid/R$styleable;->Window_windowBackground:I
Landroid/R$styleable;->Window_windowFrame:I
+Landroid/security/Credentials;->convertToPem([Ljava/security/cert/Certificate;)[B
+Landroid/security/keymaster/KeymasterArguments;->addEnums(I[I)V
Landroid/security/keystore/AndroidKeyStoreProvider;->getKeyStoreOperationHandle(Ljava/lang/Object;)J
Landroid/security/KeyStore;->getInstance()Landroid/security/KeyStore;
Landroid/security/net/config/RootTrustManager;->checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
@@ -1868,10 +1957,13 @@
Landroid/service/media/MediaBrowserService$Result;->mFlags:I
Landroid/service/notification/NotificationListenerService;->registerAsSystemService(Landroid/content/Context;Landroid/content/ComponentName;I)V
Landroid/service/notification/NotificationListenerService;->unregisterAsSystemService()V
+Landroid/service/notification/StatusBarNotification;->getUid()I
Landroid/service/voice/AlwaysOnHotwordDetector$EventPayload;->getCaptureSession()Ljava/lang/Integer;
+Landroid/service/voice/AlwaysOnHotwordDetector$RefreshAvailabiltyTask;->doInBackground([Ljava/lang/Void;)Ljava/lang/Void;
Landroid/service/voice/VoiceInteractionService;->isKeyphraseAndLocaleSupportedForHotword(Ljava/lang/String;Ljava/util/Locale;)Z
Landroid/service/vr/IVrManager;->getVr2dDisplayId()I
Landroid/service/wallpaper/WallpaperService$Engine;->setFixedSizeAllowed(Z)V
+Landroid/speech/tts/TextToSpeech$Connection$SetupConnectionAsyncTask;->doInBackground([Ljava/lang/Void;)Ljava/lang/Integer;
Landroid/speech/tts/TextToSpeech;->getCurrentEngine()Ljava/lang/String;
Landroid/system/Int32Ref;->value:I
Landroid/system/OsConstants;->AF_NETLINK:I
@@ -1930,6 +2022,24 @@
Landroid/system/OsConstants;->XATTR_REPLACE:I
Landroid/system/StructTimeval;->fromMillis(J)Landroid/system/StructTimeval;
Landroid/telecom/AudioState;->isMuted:Z
+Landroid/telecom/Log;->addEvent(Landroid/telecom/Logging/EventManager$Loggable;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->buildMessage(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
+Landroid/telecom/Log;->d(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->d(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->e(Ljava/lang/Object;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->e(Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Logging/EventManager;->event(Landroid/telecom/Logging/EventManager$Loggable;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->i(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->i(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->v(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->v(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->w(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->w(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->wtf(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->wtf(Ljava/lang/Object;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->wtf(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Log;->wtf(Ljava/lang/String;Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
+Landroid/telecom/Response;->onResult(Ljava/lang/Object;[Ljava/lang/Object;)V
Landroid/telecom/TelecomManager;->EXTRA_IS_HANDOVER:Ljava/lang/String;
Landroid/telecom/TelecomManager;->getUserSelectedOutgoingPhoneAccount()Landroid/telecom/PhoneAccountHandle;
Landroid/telecom/TelecomManager;->setUserSelectedOutgoingPhoneAccount(Landroid/telecom/PhoneAccountHandle;)V
@@ -1978,6 +2088,7 @@
Landroid/telephony/SubscriptionManager;->getPhoneId(I)I
Landroid/telephony/SubscriptionManager;->getSlotIndex(I)I
Landroid/telephony/SubscriptionManager;->getSubId(I)[I
+Landroid/telephony/SubscriptionManager;->setDefaultDataSubId(I)V
Landroid/telephony/SubscriptionManager;->setDefaultSmsSubId(I)V
Landroid/telephony/TelephonyManager;->from(Landroid/content/Context;)Landroid/telephony/TelephonyManager;
Landroid/telephony/TelephonyManager;->getCallState(I)I
@@ -2014,6 +2125,7 @@
Landroid/telephony/TelephonyManager;->isWifiCallingAvailable()Z
Landroid/telephony/TelephonyManager;->mSubscriptionManager:Landroid/telephony/SubscriptionManager;
Landroid/text/AndroidBidi;->bidi(I[C[B)I
+Landroid/text/BoringLayout;->isBoring(Ljava/lang/CharSequence;Landroid/text/TextPaint;Landroid/text/TextDirectionHeuristic;Landroid/text/BoringLayout$Metrics;)Landroid/text/BoringLayout$Metrics;
Landroid/text/DynamicLayout;-><init>(Ljava/lang/CharSequence;Ljava/lang/CharSequence;Landroid/text/TextPaint;ILandroid/text/Layout$Alignment;Landroid/text/TextDirectionHeuristic;FFZIIILandroid/text/TextUtils$TruncateAt;I)V
Landroid/text/DynamicLayout;->sStaticLayout:Landroid/text/StaticLayout;
Landroid/text/Html;->withinStyle(Ljava/lang/StringBuilder;Ljava/lang/CharSequence;II)V
@@ -2079,9 +2191,11 @@
Landroid/text/TextLine;->obtain()Landroid/text/TextLine;
Landroid/text/TextLine;->sCached:[Landroid/text/TextLine;
Landroid/text/TextPaint;->setUnderlineText(IF)V
+Landroid/text/TextUtils$TruncateAt;->END_SMALL:Landroid/text/TextUtils$TruncateAt;
Landroid/transition/ChangeBounds;->BOTTOM_RIGHT_ONLY_PROPERTY:Landroid/util/Property;
Landroid/transition/ChangeBounds;->POSITION_PROPERTY:Landroid/util/Property;
Landroid/transition/TransitionManager;->sRunningTransitions:Ljava/lang/ThreadLocal;
+Landroid/transition/TransitionUtils;->mergeTransitions([Landroid/transition/Transition;)Landroid/transition/Transition;
Landroid/util/ArrayMap;->append(Ljava/lang/Object;Ljava/lang/Object;)V
Landroid/util/ArrayMap;->mBaseCacheSize:I
Landroid/util/ArrayMap;->mTwiceBaseCacheSize:I
@@ -2092,6 +2206,7 @@
Landroid/util/LongSparseLongArray;->mKeys:[J
Landroid/util/LongSparseLongArray;->mSize:I
Landroid/util/LongSparseLongArray;->mValues:[J
+Landroid/util/MathUtils;->constrain(III)I
Landroid/util/NtpTrustedTime;->forceRefresh()Z
Landroid/util/NtpTrustedTime;->getCachedNtpTime()J
Landroid/util/NtpTrustedTime;->getCachedNtpTimeReference()J
@@ -2100,6 +2215,7 @@
Landroid/util/Pools$SimplePool;->mPool:[Ljava/lang/Object;
Landroid/util/Pools$SynchronizedPool;->acquire()Ljava/lang/Object;
Landroid/util/Pools$SynchronizedPool;-><init>(I)V
+Landroid/util/Pools$SynchronizedPool;->release(Ljava/lang/Object;)Z
Landroid/util/Rational;->mDenominator:I
Landroid/util/Rational;->mNumerator:I
Landroid/util/Singleton;->get()Ljava/lang/Object;
@@ -2127,6 +2243,7 @@
Landroid/view/accessibility/IAccessibilityManager;->getEnabledAccessibilityServiceList(II)Ljava/util/List;
Landroid/view/accessibility/IAccessibilityManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/view/accessibility/IAccessibilityManager;
Landroid/view/accessibility/IAccessibilityManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/view/animation/Animation;->detach()V
Landroid/view/animation/Animation;->initializeInvalidateRegion(IIII)V
Landroid/view/animation/Animation;->mListener:Landroid/view/animation/Animation$AnimationListener;
Landroid/view/autofill/IAutoFillManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -2171,10 +2288,12 @@
Landroid/view/inputmethod/InputMethodManager;->focusIn(Landroid/view/View;)V
Landroid/view/inputmethod/InputMethodManager;->focusOut(Landroid/view/View;)V
Landroid/view/inputmethod/InputMethodManager;->getInputMethodWindowVisibleHeight()I
+Landroid/view/inputmethod/InputMethodManager;->getInstance()Landroid/view/inputmethod/InputMethodManager;
Landroid/view/inputmethod/InputMethodManager;->mCurId:Ljava/lang/String;
Landroid/view/inputmethod/InputMethodManager;->mCurRootView:Landroid/view/View;
Landroid/view/inputmethod/InputMethodManager;->mH:Landroid/view/inputmethod/InputMethodManager$H;
Landroid/view/inputmethod/InputMethodManager;->mNextServedView:Landroid/view/View;
+Landroid/view/inputmethod/InputMethodManager;->mServedInputConnectionWrapper:Landroid/view/inputmethod/InputMethodManager$ControlledInputConnectionWrapper;
Landroid/view/inputmethod/InputMethodManager;->mServedView:Landroid/view/View;
Landroid/view/inputmethod/InputMethodManager;->mService:Lcom/android/internal/view/IInputMethodManager;
Landroid/view/inputmethod/InputMethodManager;->notifyUserAction()V
@@ -2196,10 +2315,11 @@
Landroid/view/IWindowManager$Stub$Proxy;->getInitialDisplayDensity(I)I
Landroid/view/IWindowManager$Stub$Proxy;->hasNavigationBar()Z
Landroid/view/IWindowManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/view/IWindowSession$Stub$Proxy;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/view/DisplayCutout$ParcelableWrapper;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I
+Landroid/view/IWindowSession$Stub$Proxy;->relayout(Landroid/view/IWindow;ILandroid/view/WindowManager$LayoutParams;IIIIJLandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/Rect;Landroid/view/DisplayCutout$ParcelableWrapper;Landroid/util/MergedConfiguration;Landroid/view/Surface;)I
Landroid/view/KeyCharacterMap$FallbackAction;->keyCode:I
Landroid/view/KeyCharacterMap$FallbackAction;->metaState:I
Landroid/view/KeyCharacterMap;-><init>(J)V
+Landroid/view/KeyEvent;->isConfirmKey(I)Z
Landroid/view/KeyEvent;->mAction:I
Landroid/view/KeyEvent;->mCharacters:Ljava/lang/String;
Landroid/view/KeyEvent;->mDeviceId:I
@@ -2252,6 +2372,9 @@
Landroid/view/RemoteAnimationTarget;->taskId:I
Landroid/view/RemoteAnimationTarget;->windowConfiguration:Landroid/app/WindowConfiguration;
Landroid/view/RenderNodeAnimator;->callOnFinished(Landroid/view/RenderNodeAnimator;)V
+Landroid/view/RenderNodeAnimator;-><init>(IF)V
+Landroid/view/RenderNodeAnimator;->mapViewPropertyToRenderProperty(I)I
+Landroid/view/RenderNodeAnimator;->setTarget(Landroid/view/View;)V
Landroid/view/RenderNode;->discardDisplayList()V
Landroid/view/RenderNode;->output()V
Landroid/view/ScaleGestureDetector;->mListener:Landroid/view/ScaleGestureDetector$OnScaleGestureListener;
@@ -2288,6 +2411,9 @@
Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;->selectionModified(IILandroid/view/textclassifier/TextSelection;)Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;
Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;->selectionStarted(I)Landroid/view/textclassifier/logging/SmartSelectionEventTracker$SelectionEvent;
Landroid/view/textclassifier/TextClassificationManager;->getTextClassifier(I)Landroid/view/textclassifier/TextClassifier;
+Landroid/view/textclassifier/TextClassifier;->classifyText(Ljava/lang/CharSequence;IILandroid/view/textclassifier/TextClassification$Options;)Landroid/view/textclassifier/TextClassification;
+Landroid/view/textclassifier/TextClassifier;->generateLinks(Ljava/lang/CharSequence;Landroid/view/textclassifier/TextLinks$Options;)Landroid/view/textclassifier/TextLinks;
+Landroid/view/textclassifier/TextClassifier;->suggestSelection(Ljava/lang/CharSequence;IILandroid/view/textclassifier/TextSelection$Options;)Landroid/view/textclassifier/TextSelection;
Landroid/view/textservice/TextServicesManager;->isSpellCheckerEnabled()Z
Landroid/view/TextureView;->destroyHardwareLayer()V
Landroid/view/TextureView;->destroyHardwareResources()V
@@ -2368,6 +2494,7 @@
Landroid/view/View$ListenerInfo;->mOnTouchListener:Landroid/view/View$OnTouchListener;
Landroid/view/View;->mAccessibilityDelegate:Landroid/view/View$AccessibilityDelegate;
Landroid/view/View;->mAttachInfo:Landroid/view/View$AttachInfo;
+Landroid/view/View;->mBackground:Landroid/graphics/drawable/Drawable;
Landroid/view/View;->mBottom:I
Landroid/view/View;->mContext:Landroid/content/Context;
Landroid/view/View;->mDrawingCache:Landroid/graphics/Bitmap;
@@ -2409,7 +2536,10 @@
Landroid/view/View;->resetResolvedTextDirection()V
Landroid/view/View;->resetRtlProperties()V
Landroid/view/ViewRootImpl;->detachFunctor(J)V
+Landroid/view/ViewRootImpl;->dispatchInputEvent(Landroid/view/InputEvent;Landroid/view/InputEventReceiver;)V
+Landroid/view/ViewRootImpl;->dispatchInputEvent(Landroid/view/InputEvent;)V
Landroid/view/ViewRootImpl;->enqueueInputEvent(Landroid/view/InputEvent;)V
+Landroid/view/ViewRootImpl;->getWindowFlags()I
Landroid/view/ViewRootImpl;->invokeFunctor(JZ)V
Landroid/view/ViewRootImpl;->mStopped:Z
Landroid/view/ViewRootImpl;->mSurface:Landroid/view/Surface;
@@ -2545,6 +2675,7 @@
Landroid/widget/AbsSeekBar;->mThumb:Landroid/graphics/drawable/Drawable;
Landroid/widget/AbsSeekBar;->mTouchProgressOffset:F
Landroid/widget/ActivityChooserModel;->get(Landroid/content/Context;Ljava/lang/String;)Landroid/widget/ActivityChooserModel;
+Landroid/widget/ActivityChooserModel$PersistHistoryAsyncTask;->doInBackground([Ljava/lang/Object;)Ljava/lang/Void;
Landroid/widget/ActivityChooserView;->setExpandActivityOverflowButtonDrawable(Landroid/graphics/drawable/Drawable;)V
Landroid/widget/AdapterView;->mDataChanged:Z
Landroid/widget/AdapterView;->mFirstPosition:I
@@ -2573,6 +2704,7 @@
Landroid/widget/Editor;->mShowSoftInputOnFocus:Z
Landroid/widget/ExpandableListView;->mChildDivider:Landroid/graphics/drawable/Drawable;
Landroid/widget/ExpandableListView;->mGroupIndicator:Landroid/graphics/drawable/Drawable;
+Landroid/widget/FastScroller;->groupAnimatorOfFloat(Landroid/util/Property;F[Landroid/view/View;)Landroid/animation/Animator;
Landroid/widget/FastScroller;->mContainerRect:Landroid/graphics/Rect;
Landroid/widget/FastScroller;->mHeaderCount:I
Landroid/widget/FastScroller;->mLongList:Z
@@ -2628,6 +2760,7 @@
Landroid/widget/NumberPicker;->mInputText:Landroid/widget/EditText;
Landroid/widget/NumberPicker;->mSelectionDivider:Landroid/graphics/drawable/Drawable;
Landroid/widget/NumberPicker;->mSelectorWheelPaint:Landroid/graphics/Paint;
+Landroid/widget/OverScroller;->isScrollingInDirection(FF)Z
Landroid/widget/OverScroller;->mScrollerY:Landroid/widget/OverScroller$SplineOverScroller;
Landroid/widget/OverScroller$SplineOverScroller;->mCurrVelocity:F
Landroid/widget/PopupMenu;->mPopup:Lcom/android/internal/view/menu/MenuPopupHelper;
@@ -2655,12 +2788,14 @@
Landroid/widget/PopupWindow;->setLayoutInScreenEnabled(Z)V
Landroid/widget/PopupWindow;->setLayoutInsetDecor(Z)V
Landroid/widget/PopupWindow;->setTouchModal(Z)V
+Landroid/widget/PopupWindow;->showAtLocation(Landroid/os/IBinder;III)V
Landroid/widget/ProgressBar;->mCurrentDrawable:Landroid/graphics/drawable/Drawable;
Landroid/widget/ProgressBar;->mDuration:I
Landroid/widget/ProgressBar;->mIndeterminate:Z
Landroid/widget/ProgressBar;->mMaxHeight:I
Landroid/widget/ProgressBar;->mMinHeight:I
Landroid/widget/ProgressBar;->mOnlyIndeterminate:Z
+Landroid/widget/RelativeLayout$DependencyGraph;->getSortedViews([Landroid/view/View;[I)V
Landroid/widget/RelativeLayout$LayoutParams;->mBottom:I
Landroid/widget/RelativeLayout$LayoutParams;->mLeft:I
Landroid/widget/RelativeLayout$LayoutParams;->mRight:I
@@ -2670,6 +2805,7 @@
Landroid/widget/RemoteViews$Action;->viewId:I
Landroid/widget/RemoteViewsAdapter;->mCache:Landroid/widget/RemoteViewsAdapter$FixedSizeRemoteViewsCache;
Landroid/widget/RemoteViewsAdapter;->mWorkerThread:Landroid/os/HandlerThread;
+Landroid/widget/RemoteViews$AsyncApplyTask;->doInBackground([Ljava/lang/Void;)Landroid/widget/RemoteViews$ViewTree;
Landroid/widget/RemoteViews$BitmapCache;->mBitmaps:Ljava/util/ArrayList;
Landroid/widget/RemoteViews$BitmapReflectionAction;->bitmap:Landroid/graphics/Bitmap;
Landroid/widget/RemoteViews$BitmapReflectionAction;->methodName:Ljava/lang/String;
@@ -2700,6 +2836,7 @@
Landroid/widget/SearchView;->mSearchSrcTextView:Landroid/widget/SearchView$SearchAutoComplete;
Landroid/widget/SearchView;->onCloseClicked()V
Landroid/widget/SearchView;->setQuery(Ljava/lang/CharSequence;)V
+Landroid/widget/SelectionActionModeHelper$TextClassificationAsyncTask;->doInBackground([Ljava/lang/Void;)Landroid/widget/SelectionActionModeHelper$SelectionResult;
Landroid/widget/SlidingDrawer;->mTopOffset:I
Landroid/widget/Spinner;->mPopup:Landroid/widget/Spinner$SpinnerPopup;
Landroid/widget/Switch;->mThumbDrawable:Landroid/graphics/drawable/Drawable;
@@ -3004,6 +3141,7 @@
Lcom/android/internal/R$styleable;->DialogPreference:[I
Lcom/android/internal/R$styleable;->EdgeEffect_colorEdgeEffect:I
Lcom/android/internal/R$styleable;->EdgeEffect:[I
+Lcom/android/internal/R$styleable;->GridView:[I
Lcom/android/internal/R$styleable;->IconMenuView:[I
Lcom/android/internal/R$styleable;->ImageView:[I
Lcom/android/internal/R$styleable;->ImageView_src:I
@@ -3047,6 +3185,7 @@
Lcom/android/internal/R$styleable;->SyncAdapter_userVisible:I
Lcom/android/internal/R$styleable;->TabWidget:[I
Lcom/android/internal/R$styleable;->TextAppearance:[I
+Lcom/android/internal/R$styleable;->TextViewAppearance:[I
Lcom/android/internal/R$styleable;->TextView_drawableBottom:I
Lcom/android/internal/R$styleable;->TextView_drawableLeft:I
Lcom/android/internal/R$styleable;->TextView_drawableRight:I
@@ -3093,6 +3232,11 @@
Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_call:I
Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_endCall:I
Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_getDeviceId:I
+Lcom/android/internal/telephony/SmsHeader;->concatRef:Lcom/android/internal/telephony/SmsHeader$ConcatRef;
+Lcom/android/internal/telephony/SmsHeader$ConcatRef;->msgCount:I
+Lcom/android/internal/telephony/SmsHeader$ConcatRef;->refNumber:I
+Lcom/android/internal/telephony/SmsHeader$ConcatRef;->seqNumber:I
+Lcom/android/internal/telephony/SmsMessageBase;->mUserDataHeader:Lcom/android/internal/telephony/SmsHeader;
Lcom/android/internal/telephony/SmsRawData;->CREATOR:Landroid/os/Parcelable$Creator;
Lcom/android/internal/telephony/SmsRawData;-><init>([B)V
Lcom/android/internal/textservice/ITextServicesManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -3101,6 +3245,8 @@
Lcom/android/internal/util/XmlUtils;->readMapXml(Ljava/io/InputStream;)Ljava/util/HashMap;
Lcom/android/internal/util/XmlUtils;->skipCurrentTag(Lorg/xmlpull/v1/XmlPullParser;)V
Lcom/android/internal/util/XmlUtils;->writeMapXml(Ljava/util/Map;Ljava/io/OutputStream;)V
+Lcom/android/internal/view/IInputConnectionWrapper;->mInputConnection:Landroid/view/inputmethod/InputConnection;
+Lcom/android/internal/view/IInputConnectionWrapper;->mLock:Ljava/lang/Object;
Lcom/android/internal/view/IInputMethodManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethodManager;
Lcom/android/internal/view/IInputMethodManager$Stub$Proxy;->getEnabledInputMethodList()Ljava/util/List;
Lcom/android/internal/view/IInputMethodManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -3111,11 +3257,19 @@
Lcom/android/internal/view/menu/MenuBuilder;->setOptionalIconsVisible(Z)V
Lcom/android/internal/view/menu/MenuItemImpl;->mIconResId:I
Lcom/android/internal/view/menu/MenuItemImpl;->setMenuInfo(Landroid/view/ContextMenu$ContextMenuInfo;)V
+Lcom/android/internal/view/menu/MenuPopupHelper;->mForceShowIcon:Z
Lcom/android/internal/view/menu/MenuPopupHelper;->setForceShowIcon(Z)V
Lcom/android/internal/view/menu/MenuView$ItemView;->getItemData()Lcom/android/internal/view/menu/MenuItemImpl;
+Lcom/android/okhttp/CertificatePinner$Builder;->add(Ljava/lang/String;[Ljava/lang/String;)Lcom/android/okhttp/CertificatePinner$Builder;
+Lcom/android/okhttp/CertificatePinner;->check(Ljava/lang/String;[Ljava/security/cert/Certificate;)V
Lcom/android/okhttp/ConnectionPool;->keepAliveDurationNs:J
Lcom/android/okhttp/ConnectionPool;->maxIdleConnections:I
Lcom/android/okhttp/ConnectionPool;->systemDefault:Lcom/android/okhttp/ConnectionPool;
+Lcom/android/okhttp/ConnectionSpec$Builder;->cipherSuites([Lcom/android/okhttp/CipherSuite;)Lcom/android/okhttp/ConnectionSpec$Builder;
+Lcom/android/okhttp/ConnectionSpec$Builder;->cipherSuites([Ljava/lang/String;)Lcom/android/okhttp/ConnectionSpec$Builder;
+Lcom/android/okhttp/ConnectionSpec$Builder;->tlsVersions([Lcom/android/okhttp/TlsVersion;)Lcom/android/okhttp/ConnectionSpec$Builder;
+Lcom/android/okhttp/ConnectionSpec$Builder;->tlsVersions([Ljava/lang/String;)Lcom/android/okhttp/ConnectionSpec$Builder;
+Lcom/android/okhttp/Headers;->of([Ljava/lang/String;)Lcom/android/okhttp/Headers;
Lcom/android/okhttp/HttpUrl;->encodedPath()Ljava/lang/String;
Lcom/android/okhttp/HttpUrl;->query()Ljava/lang/String;
Lcom/android/okhttp/internal/http/HttpEngine;->httpStream:Lcom/android/okhttp/internal/http/HttpStream;
@@ -3123,11 +3277,13 @@
Lcom/android/okhttp/internal/http/HttpEngine;->networkRequest(Lcom/android/okhttp/Request;)Lcom/android/okhttp/Request;
Lcom/android/okhttp/internal/http/HttpEngine;->priorResponse:Lcom/android/okhttp/Response;
Lcom/android/okhttp/internal/http/HttpEngine;->userResponse:Lcom/android/okhttp/Response;
+Lcom/android/okhttp/internal/NamedRunnable;-><init>(Ljava/lang/String;[Ljava/lang/Object;)V
Lcom/android/okhttp/OkHttpClient;->connectionPool:Lcom/android/okhttp/ConnectionPool;
Lcom/android/okhttp/OkHttpClient;->DEFAULT_PROTOCOLS:Ljava/util/List;
Lcom/android/okhttp/OkHttpClient;->dns:Lcom/android/okhttp/Dns;
Lcom/android/okhttp/OkHttpClient;->setProtocols(Ljava/util/List;)Lcom/android/okhttp/OkHttpClient;
Lcom/android/okhttp/OkHttpClient;->setRetryOnConnectionFailure(Z)V
+Lcom/android/okhttp/okio/ByteString;->of([B)Lcom/android/okhttp/okio/ByteString;
Lcom/android/okhttp/Request;->headers:Lcom/android/okhttp/Headers;
Lcom/android/okhttp/Request;->method:Ljava/lang/String;
Lcom/android/okhttp/Request;->url:Lcom/android/okhttp/HttpUrl;
@@ -3248,6 +3404,7 @@
Ljava/lang/AbstractStringBuilder;->value:[C
Ljava/lang/Boolean;->value:Z
Ljava/lang/Byte;->value:B
+Ljava/lang/Character$UnicodeBlock;-><init>(Ljava/lang/String;[Ljava/lang/String;)V
Ljava/lang/Character;->value:C
Ljava/lang/Class;->accessFlags:I
Ljava/lang/Class;->dexCache:Ljava/lang/Object;
@@ -3276,9 +3433,12 @@
Ljava/lang/ref/FinalizerReference;->next:Ljava/lang/ref/FinalizerReference;
Ljava/lang/ref/FinalizerReference;->queue:Ljava/lang/ref/ReferenceQueue;
Ljava/lang/ref/FinalizerReference;->remove(Ljava/lang/ref/FinalizerReference;)V
+Ljava/lang/reflect/Constructor;->newInstance0([Ljava/lang/Object;)Ljava/lang/Object;
Ljava/lang/reflect/Executable;->artMethod:J
Ljava/lang/reflect/Parameter;-><init>(Ljava/lang/String;ILjava/lang/reflect/Executable;I)V
+Ljava/lang/reflect/Proxy;->getProxyClass0(Ljava/lang/ClassLoader;[Ljava/lang/Class;)Ljava/lang/Class;
Ljava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
+Ljava/lang/ref/Reference;->getReferent()Ljava/lang/Object;
Ljava/lang/ref/ReferenceQueue;->add(Ljava/lang/ref/Reference;)V
Ljava/lang/ref/Reference;->referent:Ljava/lang/Object;
Ljava/lang/Runtime;->loadLibrary(Ljava/lang/String;Ljava/lang/ClassLoader;)V
@@ -3286,7 +3446,9 @@
Ljava/lang/Runtime;->mLibPaths:[Ljava/lang/String;
Ljava/lang/Runtime;->nativeLoad(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/String;
Ljava/lang/Short;->value:S
+Ljava/lang/String;->getCharsNoCheck(II[CI)V
Ljava/lang/String;-><init>(II[C)V
+Ljava/lang/System;->arraycopy([CI[CII)V
Ljava/lang/System;->arraycopy([II[III)V
Ljava/lang/System;-><init>()V
Ljava/lang/Thread;->daemon:Z
@@ -3355,6 +3517,9 @@
Ljava/nio/ByteBuffer;->offset:I
Ljava/nio/charset/CharsetEncoder;->canEncode(Ljava/nio/CharBuffer;)Z
Ljava/nio/DirectByteBuffer;-><init>(JI)V
+Ljava/nio/file/Files;->createAndCheckIsDirectory(Ljava/nio/file/Path;[Ljava/nio/file/attribute/FileAttribute;)V
+Ljava/nio/file/Files;->followLinks([Ljava/nio/file/LinkOption;)Z
+Ljava/nio/file/Files;->isAccessible(Ljava/nio/file/Path;[Ljava/nio/file/AccessMode;)Z
Ljava/nio/NIOAccess;->getBaseArray(Ljava/nio/Buffer;)Ljava/lang/Object;
Ljava/nio/NIOAccess;->getBaseArrayOffset(Ljava/nio/Buffer;)I
Ljava/nio/NIOAccess;->getBasePointer(Ljava/nio/Buffer;)J
@@ -3395,6 +3560,7 @@
Ljava/util/concurrent/ThreadPoolExecutor;->allowCoreThreadTimeOut:Z
Ljava/util/EnumMap;->keyType:Ljava/lang/Class;
Ljava/util/EnumSet;->elementType:Ljava/lang/Class;
+Ljava/util/Formatter$FormatSpecifier;->checkBadFlags([Ljava/util/Formatter$Flags;)V
Ljava/util/HashMap$HashIterator;->hasNext()Z
Ljava/util/HashMap;->modCount:I
Ljava/util/HashMap;->table:[Ljava/util/HashMap$Node;
@@ -3404,6 +3570,8 @@
Ljava/util/LinkedHashMap$LinkedHashIterator;->hasNext()Z
Ljava/util/LinkedList;->size:I
Ljava/util/Locale;->createConstant(Ljava/lang/String;Ljava/lang/String;)Ljava/util/Locale;
+Ljava/util/logging/LogManager$Beans;->getConstructor(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;
+Ljava/util/logging/LogManager$Beans;->getMethod(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
Ljava/util/PriorityQueue;->modCount:I
Ljava/util/PriorityQueue;->size:I
Ljava/util/Random;->seedUniquifier()J
@@ -3438,3 +3606,8 @@
Lorg/json/JSONObject;->writeTo(Lorg/json/JSONStringer;)V
Lorg/w3c/dom/traversal/NodeIterator;->nextNode()Lorg/w3c/dom/Node;
Lsun/misc/Unsafe;->theUnsafe:Lsun/misc/Unsafe;
+Lsun/security/x509/AlgorithmId;->oid([I)Lsun/security/util/ObjectIdentifier;
+Lsun/util/logging/PlatformLogger$DefaultLoggerProxy;->doLog(Lsun/util/logging/PlatformLogger$Level;Ljava/lang/String;[Ljava/lang/Object;)V
+Lsun/util/logging/PlatformLogger$DefaultLoggerProxy;->formatMessage(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
+Lsun/util/logging/PlatformLogger$JavaLoggerProxy;->doLog(Lsun/util/logging/PlatformLogger$Level;Ljava/lang/String;[Ljava/lang/Object;)V
+Lsun/util/logging/PlatformLogger$LoggerProxy;->doLog(Lsun/util/logging/PlatformLogger$Level;Ljava/lang/String;[Ljava/lang/Object;)V
diff --git a/config/hiddenapi-vendor-list.txt b/config/hiddenapi-vendor-list.txt
index e3d2e77..88f8794 100644
--- a/config/hiddenapi-vendor-list.txt
+++ b/config/hiddenapi-vendor-list.txt
@@ -501,7 +501,6 @@
Landroid/telephony/SubscriptionManager;->isValidSubscriptionId(I)Z
Landroid/telephony/SubscriptionManager;->putPhoneIdAndSubIdExtra(Landroid/content/Intent;II)V
Landroid/telephony/SubscriptionManager;->putPhoneIdAndSubIdExtra(Landroid/content/Intent;I)V
-Landroid/telephony/SubscriptionManager;->setDefaultDataSubId(I)V
Landroid/telephony/SubscriptionManager;->setDisplayName(Ljava/lang/String;IJ)I
Landroid/telephony/SubscriptionManager;->setIconTint(II)I
Landroid/telephony/TelephonyManager;->getIntAtIndex(Landroid/content/ContentResolver;Ljava/lang/String;I)I
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 27bbc4b..1084b42 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2165,30 +2165,18 @@
}
@Override
- public PersistableBundle getSuspendedPackageAppExtras(String packageName) {
+ public Bundle getSuspendedPackageAppExtras() {
+ final PersistableBundle extras;
try {
- return mPM.getSuspendedPackageAppExtras(packageName, mContext.getUserId());
+ extras = mPM.getSuspendedPackageAppExtras(mContext.getOpPackageName(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- }
-
- @Override
- public Bundle getSuspendedPackageAppExtras() {
- final PersistableBundle extras = getSuspendedPackageAppExtras(mContext.getOpPackageName());
return extras != null ? new Bundle(extras.deepCopy()) : null;
}
@Override
- public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras) {
- try {
- mPM.setSuspendedPackageAppExtras(packageName, appExtras, mContext.getUserId());
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
- @Override
public boolean isPackageSuspendedForUser(String packageName, int userId) {
try {
return mPM.isPackageSuspendedForUser(packageName, userId);
@@ -2199,8 +2187,12 @@
/** @hide */
@Override
- public boolean isPackageSuspended(String packageName) {
- return isPackageSuspendedForUser(packageName, mContext.getUserId());
+ public boolean isPackageSuspended(String packageName) throws NameNotFoundException {
+ try {
+ return isPackageSuspendedForUser(packageName, mContext.getUserId());
+ } catch (IllegalArgumentException ie) {
+ throw new NameNotFoundException(packageName);
+ }
}
@Override
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4ab6724..2b4f420 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4588,10 +4588,18 @@
bindHeaderChronometerAndTime(contentView);
bindProfileBadge(contentView);
}
+ bindActivePermissions(contentView);
bindExpandButton(contentView);
mN.mUsesStandardHeader = true;
}
+ private void bindActivePermissions(RemoteViews contentView) {
+ int color = isColorized() ? getPrimaryTextColor() : getSecondaryTextColor();
+ contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP);
+ contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP);
+ contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP);
+ }
+
private void bindExpandButton(RemoteViews contentView) {
int color = isColorized() ? getPrimaryTextColor() : getSecondaryTextColor();
contentView.setDrawableTint(R.id.expand_button, false, color,
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 4a7cf62..9e47ced 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -328,7 +328,8 @@
* Group information is only used for presentation, not for behavior.
*
* Only modifiable before the channel is submitted to
- * {@link NotificationManager#notify(String, int, Notification)}.
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}, unless the
+ * channel is not currently part of a group.
*
* @param groupId the id of a group created by
* {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.
@@ -341,6 +342,9 @@
* Sets whether notifications posted to this channel can appear as application icon badges
* in a Launcher.
*
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
+ *
* @param showBadge true if badges should be allowed to be shown.
*/
public void setShowBadge(boolean showBadge) {
@@ -353,7 +357,7 @@
* least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
*
* Only modifiable before the channel is submitted to
- * {@link NotificationManager#notify(String, int, Notification)}.
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*/
public void setSound(Uri sound, AudioAttributes audioAttributes) {
this.mSound = sound;
@@ -365,7 +369,7 @@
* on devices that support that feature.
*
* Only modifiable before the channel is submitted to
- * {@link NotificationManager#notify(String, int, Notification)}.
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*/
public void enableLights(boolean lights) {
this.mLights = lights;
@@ -376,7 +380,7 @@
* {@link #enableLights(boolean) enabled} on this channel and the device supports that feature.
*
* Only modifiable before the channel is submitted to
- * {@link NotificationManager#notify(String, int, Notification)}.
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*/
public void setLightColor(int argb) {
this.mLightColor = argb;
@@ -387,7 +391,7 @@
* be set with {@link #setVibrationPattern(long[])}.
*
* Only modifiable before the channel is submitted to
- * {@link NotificationManager#notify(String, int, Notification)}.
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*/
public void enableVibration(boolean vibration) {
this.mVibrationEnabled = vibration;
@@ -399,7 +403,7 @@
* vibration} as well. Otherwise, vibration will be disabled.
*
* Only modifiable before the channel is submitted to
- * {@link NotificationManager#notify(String, int, Notification)}.
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*/
public void setVibrationPattern(long[] vibrationPattern) {
this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0;
@@ -407,9 +411,10 @@
}
/**
- * Sets the level of interruption of this notification channel. Only
- * modifiable before the channel is submitted to
- * {@link NotificationManager#notify(String, int, Notification)}.
+ * Sets the level of interruption of this notification channel.
+ *
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*
* @param importance the amount the user should be interrupted by
* notifications from this channel.
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 4a6fa8c..8783d94 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -23,6 +23,7 @@
import android.os.IStatsManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.AndroidException;
import android.util.Slog;
/**
@@ -82,55 +83,74 @@
}
/**
- * Clients can send a configuration and simultaneously registers the name of a broadcast
- * receiver that listens for when it should request data.
+ * Adds the given configuration and associates it with the given configKey. If a config with the
+ * given configKey already exists for the caller's uid, it is replaced with the new one.
*
* @param configKey An arbitrary integer that allows clients to track the configuration.
- * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
+ * @param config Wire-encoded StatsdConfig proto that specifies metrics (and all
* dependencies eg, conditions and matchers).
- * @return true if successful
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+ * @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean addConfiguration(long configKey, byte[] config) {
+ public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.e(TAG, "Failed to find statsd when adding configuration");
- return false;
- }
- return service.addConfiguration(configKey, config);
+ service.addConfiguration(configKey, config); // can throw IllegalArgumentException
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect to statsd when adding configuration");
- return false;
+ throw new StatsUnavailableException("could not connect", e);
}
}
}
/**
+ * TODO: Temporary for backwards compatibility. Remove.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean addConfiguration(long configKey, byte[] config) {
+ try {
+ addConfig(configKey, config);
+ return true;
+ } catch (StatsUnavailableException | IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ /**
* Remove a configuration from logging.
*
* @param configKey Configuration key to remove.
- * @return true if successful
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean removeConfiguration(long configKey) {
+ public void removeConfig(long configKey) throws StatsUnavailableException {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.e(TAG, "Failed to find statsd when removing configuration");
- return false;
- }
- return service.removeConfiguration(configKey);
+ service.removeConfiguration(configKey);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect to statsd when removing configuration");
- return false;
+ throw new StatsUnavailableException("could not connect", e);
}
}
}
/**
+ * TODO: Temporary for backwards compatibility. Remove.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean removeConfiguration(long configKey) {
+ try {
+ removeConfig(configKey);
+ return true;
+ } catch (StatsUnavailableException e) {
+ return false;
+ }
+ }
+
+ /**
* Set the PendingIntent to be used when broadcasting subscriber information to the given
* subscriberId within the given config.
* <p>
@@ -150,123 +170,165 @@
* {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
* <p>
* This function can only be called by the owner (uid) of the config. It must be called each
- * time statsd starts. The config must have been added first (via addConfiguration()).
+ * time statsd starts. The config must have been added first (via {@link #addConfig}).
*
- * @param configKey The integer naming the config to which this subscriber is attached.
- * @param subscriberId ID of the subscriber, as used in the config.
* @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
* associated with the given subscriberId. May be null, in which case
* it undoes any previous setting of this subscriberId.
- * @return true if successful
+ * @param configKey The integer naming the config to which this subscriber is attached.
+ * @param subscriberId ID of the subscriber, as used in the config.
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public void setBroadcastSubscriber(
+ PendingIntent pendingIntent, long configKey, long subscriberId)
+ throws StatsUnavailableException {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (pendingIntent != null) {
+ // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+ IBinder intentSender = pendingIntent.getTarget().asBinder();
+ service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
+ } else {
+ service.unsetBroadcastSubscriber(configKey, subscriberId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+ throw new StatsUnavailableException("could not connect", e);
+ }
+ }
+ }
+
+ /**
+ * TODO: Temporary for backwards compatibility. Remove.
*/
@RequiresPermission(Manifest.permission.DUMP)
public boolean setBroadcastSubscriber(
long configKey, long subscriberId, PendingIntent pendingIntent) {
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.e(TAG, "Failed to find statsd when adding broadcast subscriber");
- return false;
- }
- if (pendingIntent != null) {
- // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
- IBinder intentSender = pendingIntent.getTarget().asBinder();
- return service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
- } else {
- return service.unsetBroadcastSubscriber(configKey, subscriberId);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
- return false;
- }
+ try {
+ setBroadcastSubscriber(pendingIntent, configKey, subscriberId);
+ return true;
+ } catch (StatsUnavailableException e) {
+ return false;
}
}
/**
* Registers the operation that is called to retrieve the metrics data. This must be called
- * each time statsd starts. The config must have been added first (via addConfiguration(),
- * although addConfiguration could have been called on a previous boot). This operation allows
+ * each time statsd starts. The config must have been added first (via {@link #addConfig},
+ * although addConfig could have been called on a previous boot). This operation allows
* statsd to send metrics data whenever statsd determines that the metrics in memory are
- * approaching the memory limits. The fetch operation should call {@link #getData} to fetch the
- * data, which also deletes the retrieved metrics from statsd's memory.
+ * approaching the memory limits. The fetch operation should call {@link #getReports} to fetch
+ * the data, which also deletes the retrieved metrics from statsd's memory.
*
- * @param configKey The integer naming the config to which this operation is attached.
* @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
* associated with the given subscriberId. May be null, in which case
* it removes any associated pending intent with this configKey.
- * @return true if successful
+ * @param configKey The integer naming the config to which this operation is attached.
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
+ public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey)
+ throws StatsUnavailableException {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.e(TAG, "Failed to find statsd when registering data listener.");
- return false;
- }
if (pendingIntent == null) {
- return service.removeDataFetchOperation(configKey);
+ service.removeDataFetchOperation(configKey);
} else {
// Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
IBinder intentSender = pendingIntent.getTarget().asBinder();
- return service.setDataFetchOperation(configKey, intentSender);
+ service.setDataFetchOperation(configKey, intentSender);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect to statsd when registering data listener.");
- return false;
+ throw new StatsUnavailableException("could not connect", e);
}
}
}
/**
- * Clients can request data with a binder call. This getter is destructive and also clears
- * the retrieved metrics from statsd memory.
- *
- * @param configKey Configuration key to retrieve data from.
- * @return Serialized ConfigMetricsReportList proto. Returns null on failure (eg, if statsd
- * crashed).
+ * TODO: Temporary for backwards compatibility. Remove.
*/
@RequiresPermission(Manifest.permission.DUMP)
- public @Nullable byte[] getData(long configKey) {
+ public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
+ try {
+ setFetchReportsOperation(pendingIntent, configKey);
+ return true;
+ } catch (StatsUnavailableException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Request the data collected for the given configKey.
+ * This getter is destructive - it also clears the retrieved metrics from statsd's memory.
+ *
+ * @param configKey Configuration key to retrieve data from.
+ * @return Serialized ConfigMetricsReportList proto.
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getReports(long configKey) throws StatsUnavailableException {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.e(TAG, "Failed to find statsd when getting data");
- return null;
- }
return service.getData(configKey);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect to statsd when getting data");
- return null;
+ throw new StatsUnavailableException("could not connect", e);
+ }
+ }
+ }
+
+ /**
+ * TODO: Temporary for backwards compatibility. Remove.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public @Nullable byte[] getData(long configKey) {
+ try {
+ return getReports(configKey);
+ } catch (StatsUnavailableException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Clients can request metadata for statsd. Will contain stats across all configurations but not
+ * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}.
+ * This getter is not destructive and will not reset any metrics/counters.
+ *
+ * @return Serialized StatsdStatsReport proto.
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getStatsMetadata() throws StatsUnavailableException {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ return service.getMetadata();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to connect to statsd when getting metadata");
+ throw new StatsUnavailableException("could not connect", e);
}
}
}
/**
* Clients can request metadata for statsd. Will contain stats across all configurations but not
- * the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
+ * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}.
* This getter is not destructive and will not reset any metrics/counters.
*
* @return Serialized StatsdStatsReport proto. Returns null on failure (eg, if statsd crashed).
*/
@RequiresPermission(Manifest.permission.DUMP)
public @Nullable byte[] getMetadata() {
- synchronized (this) {
- try {
- IStatsManager service = getIStatsManagerLocked();
- if (service == null) {
- Slog.e(TAG, "Failed to find statsd when getting metadata");
- return null;
- }
- return service.getMetadata();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when getting metadata");
- return null;
- }
+ try {
+ return getStatsMetadata();
+ } catch (StatsUnavailableException e) {
+ return null;
}
}
@@ -279,14 +341,33 @@
}
}
- private IStatsManager getIStatsManagerLocked() throws RemoteException {
+ private IStatsManager getIStatsManagerLocked() throws StatsUnavailableException {
if (mService != null) {
return mService;
}
mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
- if (mService != null) {
+ if (mService == null) {
+ throw new StatsUnavailableException("could not be found");
+ }
+ try {
mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+ } catch (RemoteException e) {
+ throw new StatsUnavailableException("could not connect when linkToDeath", e);
}
return mService;
}
+
+ /**
+ * Exception thrown when communication with the stats service fails (eg if it is not available).
+ * This might be thrown early during boot before the stats service has started or if it crashed.
+ */
+ public static class StatsUnavailableException extends AndroidException {
+ public StatsUnavailableException(String reason) {
+ super("Failed to connect to statsd: " + reason);
+ }
+
+ public StatsUnavailableException(String reason, Throwable e) {
+ super("Failed to connect to statsd: " + reason, e);
+ }
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 990147b..c491dcc 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1169,6 +1169,7 @@
* Constant to indicate the feature of mandatory backups. Used as argument to
* {@link #createAdminSupportIntent(String)}.
* @see #setMandatoryBackupTransport(ComponentName, ComponentName)
+ * @hide
*/
public static final String POLICY_MANDATORY_BACKUPS = "policy_mandatory_backups";
@@ -6843,8 +6844,7 @@
* @param restriction Indicates for which feature the dialog should be displayed. Can be a
* user restriction from {@link UserManager}, e.g.
* {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the constants
- * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or
- * {@link #POLICY_MANDATORY_BACKUPS}.
+ * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
* @return Intent An intent to be used to start the dialog-activity if the restriction is
* set by an admin, or null if the restriction does not exist or no admin set it.
*/
@@ -8791,13 +8791,6 @@
*
* <p> Backup service is off by default when device owner is present.
*
- * <p> If backups are made mandatory by specifying a non-null mandatory backup transport using
- * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is
- * automatically enabled.
- *
- * <p> If the backup service is disabled using this method after the mandatory backup transport
- * has been set, the mandatory backup transport is cleared.
- *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled {@code true} to enable the backup service, {@code false} to disable it.
* @throws SecurityException if {@code admin} is not a device owner.
@@ -8835,6 +8828,8 @@
* <p>Only device owner can call this method.
* <p>If backups were disabled and a non-null backup transport {@link ComponentName} is
* specified, backups will be enabled.
+ * <p> If the backup service is disabled after the mandatory backup transport has been set, the
+ * mandatory backup transport is cleared.
*
* <p>NOTE: The method shouldn't be called on the main thread.
*
@@ -8842,6 +8837,7 @@
* @param backupTransportComponent The backup transport layer to be used for mandatory backups.
* @return {@code true} if the backup transport was successfully set; {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner.
+ * @hide
*/
@WorkerThread
public boolean setMandatoryBackupTransport(
@@ -8861,6 +8857,7 @@
*
* @return a {@link ComponentName} of the backup transport layer to be used if backups are
* mandatory or {@code null} if backups are not mandatory.
+ * @hide
*/
public ComponentName getMandatoryBackupTransport() {
throwIfParentInstance("getMandatoryBackupTransport");
diff --git a/core/java/android/app/slice/ISliceManager.aidl b/core/java/android/app/slice/ISliceManager.aidl
index a2aaf12..69852f3 100644
--- a/core/java/android/app/slice/ISliceManager.aidl
+++ b/core/java/android/app/slice/ISliceManager.aidl
@@ -25,11 +25,15 @@
void unpinSlice(String pkg, in Uri uri, in IBinder token);
boolean hasSliceAccess(String pkg);
SliceSpec[] getPinnedSpecs(in Uri uri, String pkg);
- int checkSlicePermission(in Uri uri, String pkg, int pid, int uid,
- in String[] autoGrantPermissions);
- void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
Uri[] getPinnedSlices(String pkg);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
+
+ // Perms.
+ void grantSlicePermission(String callingPkg, String toPkg, in Uri uri);
+ void revokeSlicePermission(String callingPkg, String toPkg, in Uri uri);
+ int checkSlicePermission(in Uri uri, String pkg, int pid, int uid,
+ in String[] autoGrantPermissions);
+ void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
}
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index bf3398a..4336f18 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -21,19 +21,13 @@
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.IContentProvider;
-import android.content.Intent;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -575,45 +569,4 @@
}
return sb.toString();
}
-
- /**
- * @deprecated TO BE REMOVED.
- */
- @Deprecated
- public static @Nullable Slice bindSlice(ContentResolver resolver,
- @NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
- Preconditions.checkNotNull(uri, "uri");
- IContentProvider provider = resolver.acquireProvider(uri);
- if (provider == null) {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- try {
- Bundle extras = new Bundle();
- extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
- extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
- new ArrayList<>(supportedSpecs));
- final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
- null, extras);
- Bundle.setDefusable(res, true);
- if (res == null) {
- return null;
- }
- return res.getParcelable(SliceProvider.EXTRA_SLICE);
- } catch (RemoteException e) {
- // Arbitrary and not worth documenting, as Activity
- // Manager will kill this process shortly anyway.
- return null;
- } finally {
- resolver.releaseProvider(provider);
- }
- }
-
- /**
- * @deprecated TO BE REMOVED.
- */
- @Deprecated
- public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
- @NonNull List<SliceSpec> supportedSpecs) {
- return context.getSystemService(SliceManager.class).bindSlice(intent, supportedSpecs);
- }
}
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 0285e9f..ad49437 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -16,6 +16,8 @@
package android.app.slice;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -38,6 +40,7 @@
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -47,6 +50,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* Class to handle interactions with {@link Slice}s.
@@ -101,22 +105,6 @@
private final IBinder mToken = new Binder();
/**
- * Permission denied.
- * @hide
- */
- public static final int PERMISSION_DENIED = -1;
- /**
- * Permission granted.
- * @hide
- */
- public static final int PERMISSION_GRANTED = 0;
- /**
- * Permission just granted by the user, and should be granted uri permission as well.
- * @hide
- */
- public static final int PERMISSION_USER_GRANTED = 1;
-
- /**
* @hide
*/
public SliceManager(Context context, Handler handler) throws ServiceNotFoundException {
@@ -140,7 +128,7 @@
* @see Intent#ACTION_ASSIST
* @see Intent#CATEGORY_HOME
*/
- public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) {
+ public void pinSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> specs) {
try {
mService.pinSlice(mContext.getPackageName(), uri,
specs.toArray(new SliceSpec[specs.size()]), mToken);
@@ -150,6 +138,14 @@
}
/**
+ * @deprecated TO BE REMOVED
+ */
+ @Deprecated
+ public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) {
+ pinSlice(uri, new ArraySet<>(specs));
+ }
+
+ /**
* Remove a pin for a slice.
* <p>
* If the slice has no other pins/callbacks then the slice will be unpinned.
@@ -189,9 +185,10 @@
* into account all clients and returns only specs supported by all.
* @see SliceSpec
*/
- public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) {
+ public @NonNull Set<SliceSpec> getPinnedSpecs(Uri uri) {
try {
- return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName()));
+ return new ArraySet<>(Arrays.asList(mService.getPinnedSpecs(uri,
+ mContext.getPackageName())));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -240,7 +237,7 @@
* @return The Slice provided by the app or null if none is given.
* @see Slice
*/
- public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
+ public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(uri, "uri");
ContentResolver resolver = mContext.getContentResolver();
try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
@@ -265,6 +262,14 @@
}
/**
+ * @deprecated TO BE REMOVED
+ */
+ @Deprecated
+ public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
+ return bindSlice(uri, new ArraySet<>(supportedSpecs));
+ }
+
+ /**
* Turns a slice intent into a slice uri. Expects an explicit intent.
* <p>
* This goes through a several stage resolution process to determine if any slice
@@ -272,12 +277,12 @@
* <ol>
* <li> If the intent contains data that {@link ContentResolver#getType} is
* {@link SliceProvider#SLICE_TYPE} then the data will be returned.</li>
- * <li>If the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then
- * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result
- * will be returned.</li>
- * <li>Lastly, if the intent explicitly points at an activity, and that activity has
+ * <li>If the intent explicitly points at an activity, and that activity has
* meta-data for key {@link #SLICE_METADATA_KEY}, then the Uri specified there will be
* returned.</li>
+ * <li>Lastly, if the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then
+ * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result
+ * will be returned.</li>
* <li>If no slice is found, then {@code null} is returned.</li>
* </ol>
* @param intent The intent associated with a slice.
@@ -287,37 +292,12 @@
* @see Intent
*/
public @Nullable Uri mapIntentToUri(@NonNull Intent intent) {
- Preconditions.checkNotNull(intent, "intent");
- Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
- || intent.getData() != null,
- "Slice intent must be explicit %s", intent);
ContentResolver resolver = mContext.getContentResolver();
-
- // Check if the intent has data for the slice uri on it and use that
- final Uri intentData = intent.getData();
- if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
- return intentData;
- }
+ final Uri staticUri = resolveStatic(intent, resolver);
+ if (staticUri != null) return staticUri;
// Otherwise ask the app
- Intent queryIntent = new Intent(intent);
- if (!queryIntent.hasCategory(CATEGORY_SLICE)) {
- queryIntent.addCategory(CATEGORY_SLICE);
- }
- List<ResolveInfo> providers =
- mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0);
- if (providers == null || providers.isEmpty()) {
- // There are no providers, see if this activity has a direct link.
- ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent,
- PackageManager.GET_META_DATA);
- if (resolve != null && resolve.activityInfo != null
- && resolve.activityInfo.metaData != null
- && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) {
- return Uri.parse(
- resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY));
- }
- return null;
- }
- String authority = providers.get(0).providerInfo.authority;
+ String authority = getAuthority(intent);
+ if (authority == null) return null;
Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority).build();
try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
@@ -338,10 +318,43 @@
}
}
+ private String getAuthority(Intent intent) {
+ Intent queryIntent = new Intent(intent);
+ if (!queryIntent.hasCategory(CATEGORY_SLICE)) {
+ queryIntent.addCategory(CATEGORY_SLICE);
+ }
+ List<ResolveInfo> providers =
+ mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0);
+ return providers != null && !providers.isEmpty() ? providers.get(0).providerInfo.authority
+ : null;
+ }
+
+ private Uri resolveStatic(@NonNull Intent intent, ContentResolver resolver) {
+ Preconditions.checkNotNull(intent, "intent");
+ Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
+ || intent.getData() != null,
+ "Slice intent must be explicit %s", intent);
+
+ // Check if the intent has data for the slice uri on it and use that
+ final Uri intentData = intent.getData();
+ if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
+ return intentData;
+ }
+ // There are no providers, see if this activity has a direct link.
+ ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent,
+ PackageManager.GET_META_DATA);
+ if (resolve != null && resolve.activityInfo != null
+ && resolve.activityInfo.metaData != null
+ && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) {
+ return Uri.parse(
+ resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY));
+ }
+ return null;
+ }
+
/**
- * Turns a slice intent into slice content. Expects an explicit intent. If there is no
- * {@link android.content.ContentProvider} associated with the given intent this will throw
- * {@link IllegalArgumentException}.
+ * Turns a slice intent into slice content. Is a shortcut to perform the action
+ * of both {@link #mapIntentToUri(Intent)} and {@link #bindSlice(Uri, List)} at once.
*
* @param intent The intent associated with a slice.
* @param supportedSpecs List of supported specs.
@@ -351,34 +364,17 @@
* @see Intent
*/
public @Nullable Slice bindSlice(@NonNull Intent intent,
- @NonNull List<SliceSpec> supportedSpecs) {
+ @NonNull Set<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(intent, "intent");
Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
|| intent.getData() != null,
"Slice intent must be explicit %s", intent);
ContentResolver resolver = mContext.getContentResolver();
-
- // Check if the intent has data for the slice uri on it and use that
- final Uri intentData = intent.getData();
- if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
- return bindSlice(intentData, supportedSpecs);
- }
+ final Uri staticUri = resolveStatic(intent, resolver);
+ if (staticUri != null) return bindSlice(staticUri, supportedSpecs);
// Otherwise ask the app
- List<ResolveInfo> providers =
- mContext.getPackageManager().queryIntentContentProviders(intent, 0);
- if (providers == null || providers.isEmpty()) {
- // There are no providers, see if this activity has a direct link.
- ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent,
- PackageManager.GET_META_DATA);
- if (resolve != null && resolve.activityInfo != null
- && resolve.activityInfo.metaData != null
- && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) {
- return bindSlice(Uri.parse(resolve.activityInfo.metaData
- .getString(SLICE_METADATA_KEY)), supportedSpecs);
- }
- return null;
- }
- String authority = providers.get(0).providerInfo.authority;
+ String authority = getAuthority(intent);
+ if (authority == null) return null;
Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority).build();
try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
@@ -387,8 +383,6 @@
}
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
- extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
- new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
return null;
@@ -402,6 +396,16 @@
}
/**
+ * @deprecated TO BE REMOVED.
+ */
+ @Deprecated
+ @Nullable
+ public Slice bindSlice(@NonNull Intent intent,
+ @NonNull List<SliceSpec> supportedSpecs) {
+ return bindSlice(intent, new ArraySet<>(supportedSpecs));
+ }
+
+ /**
* Determine whether a particular process and user ID has been granted
* permission to access a specific slice URI.
*
@@ -417,9 +421,11 @@
* @see #grantSlicePermission(String, Uri)
*/
public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) {
- // TODO: Switch off Uri permissions.
- return mContext.checkUriPermission(uri, pid, uid,
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ try {
+ return mService.checkSlicePermission(uri, null, pid, uid, null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -431,11 +437,11 @@
* @see #revokeSlicePermission
*/
public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
- // TODO: Switch off Uri permissions.
- mContext.grantUriPermission(toPackage, uri,
- Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ try {
+ mService.grantSlicePermission(mContext.getPackageName(), toPackage, uri);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -453,11 +459,11 @@
* @see #grantSlicePermission
*/
public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
- // TODO: Switch off Uri permissions.
- mContext.revokeUriPermission(toPackage, uri,
- Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ try {
+ mService.revokeSlicePermission(mContext.getPackageName(), toPackage, uri);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -478,16 +484,6 @@
throw new SecurityException("User " + uid + " does not have slice permission for "
+ uri + ".");
}
- if (result == PERMISSION_USER_GRANTED) {
- // We just had a user grant of this permission and need to grant this to the app
- // permanently.
- mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(),
- Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- // Notify a change has happened because we just granted a permission.
- mContext.getContentResolver().notifyChange(uri, null);
- }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index fe5742d..d369272 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -44,6 +44,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* A SliceProvider allows an app to provide content to be displayed in system spaces. This content
@@ -197,6 +198,14 @@
* @see {@link Slice}.
* @see {@link Slice#HINT_PARTIAL}
*/
+ public Slice onBindSlice(Uri sliceUri, Set<SliceSpec> supportedSpecs) {
+ return onBindSlice(sliceUri, new ArrayList<>(supportedSpecs));
+ }
+
+ /**
+ * @deprecated TO BE REMOVED
+ */
+ @Deprecated
public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
return null;
}
diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java
index 7252f02..216a4a0 100644
--- a/core/java/android/app/usage/NetworkStats.java
+++ b/core/java/android/app/usage/NetworkStats.java
@@ -237,20 +237,26 @@
DEFAULT_NETWORK_YES
})
@Retention(RetentionPolicy.SOURCE)
- public @interface DefaultNetwork {}
+ public @interface DefaultNetworkStatus {}
/**
- * Combined usage for this network regardless of whether it was the active default network.
+ * Combined usage for this network regardless of default network status.
*/
public static final int DEFAULT_NETWORK_ALL = -1;
/**
- * Usage that occurs while this network is not the active default network.
+ * Usage that occurs while this network is not a default network.
+ *
+ * <p>This implies that the app responsible for this usage requested that it occur on a
+ * specific network different from the one(s) the system would have selected for it.
*/
public static final int DEFAULT_NETWORK_NO = 0x1;
/**
- * Usage that occurs while this network is the active default network.
+ * Usage that occurs while this network is a default network.
+ *
+ * <p>This implies that the app either did not select a specific network for this usage,
+ * or it selected a network that the system could have selected for app traffic.
*/
public static final int DEFAULT_NETWORK_YES = 0x2;
@@ -262,7 +268,7 @@
private int mUid;
private int mTag;
private int mState;
- private int mDefaultNetwork;
+ private int mDefaultNetworkStatus;
private int mMetered;
private int mRoaming;
private long mBeginTimeStamp;
@@ -323,8 +329,9 @@
return 0;
}
- private static @DefaultNetwork int convertDefaultNetwork(int defaultNetwork) {
- switch (defaultNetwork) {
+ private static @DefaultNetworkStatus int convertDefaultNetworkStatus(
+ int defaultNetworkStatus) {
+ switch (defaultNetworkStatus) {
case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL;
case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO;
case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES;
@@ -397,18 +404,15 @@
}
/**
- * Default network state. One of the following values:<p/>
+ * Default network status. One of the following values:<p/>
* <ul>
* <li>{@link #DEFAULT_NETWORK_ALL}</li>
* <li>{@link #DEFAULT_NETWORK_NO}</li>
* <li>{@link #DEFAULT_NETWORK_YES}</li>
* </ul>
- * <p>Indicates whether the network usage occurred on the system default network for this
- * type of traffic, or whether the application chose to send this traffic on a network that
- * was not the one selected by the system.
*/
- public @DefaultNetwork int getDefaultNetwork() {
- return mDefaultNetwork;
+ public @DefaultNetworkStatus int getDefaultNetworkStatus() {
+ return mDefaultNetworkStatus;
}
/**
@@ -605,7 +609,7 @@
bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag);
bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
- bucketOut.mDefaultNetwork = Bucket.convertDefaultNetwork(
+ bucketOut.mDefaultNetworkStatus = Bucket.convertDefaultNetworkStatus(
mRecycledSummaryEntry.defaultNetwork);
bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered);
bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming);
@@ -657,7 +661,7 @@
bucketOut.mUid = Bucket.convertUid(getUid());
bucketOut.mTag = Bucket.convertTag(mTag);
bucketOut.mState = mState;
- bucketOut.mDefaultNetwork = Bucket.DEFAULT_NETWORK_ALL;
+ bucketOut.mDefaultNetworkStatus = Bucket.DEFAULT_NETWORK_ALL;
bucketOut.mMetered = Bucket.METERED_ALL;
bucketOut.mRoaming = Bucket.ROAMING_ALL;
bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 85f4efc..0b21196 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -35,6 +35,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.DataUnit;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -95,6 +96,15 @@
/** @hide */
public static final int CALLBACK_RELEASED = 1;
+ /**
+ * Minimum data usage threshold for registering usage callbacks.
+ *
+ * Requests registered with a threshold lower than this will only be triggered once this minimum
+ * is reached.
+ * @hide
+ */
+ public static final long MIN_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(2);
+
private final Context mContext;
private final INetworkStatsService mService;
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 84f57a3..503ca6c 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -111,7 +111,7 @@
/**
* An event type denoting a change in App Standby Bucket. The new bucket can be
- * retrieved by calling {@link #getStandbyBucket()}.
+ * retrieved by calling {@link #getAppStandbyBucket()}.
*
* @see UsageStatsManager#getAppStandbyBucket()
*/
@@ -326,13 +326,23 @@
* Returns the standby bucket of the app, if the event is of type
* {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0.
* @return the standby bucket associated with the event.
- *
+ * @hide
*/
public int getStandbyBucket() {
return (mBucketAndReason & 0xFFFF0000) >>> 16;
}
/**
+ * Returns the standby bucket of the app, if the event is of type
+ * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0.
+ * @return the standby bucket associated with the event.
+ *
+ */
+ public int getAppStandbyBucket() {
+ return (mBucketAndReason & 0xFFFF0000) >>> 16;
+ }
+
+ /**
* Returns the reason for the bucketing, if the event is of type
* {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include
* the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 01ee671..f608fcb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -40,6 +40,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCommand;
@@ -1814,8 +1815,12 @@
public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
/**
- * Intent extra: A {@link Bundle} of extras for a package being suspended. Will be sent with
- * {@link #ACTION_MY_PACKAGE_SUSPENDED}.
+ * Intent extra: A {@link Bundle} of extras for a package being suspended. Will be sent as an
+ * extra with {@link #ACTION_MY_PACKAGE_SUSPENDED}.
+ *
+ * <p>The contents of this {@link Bundle} are a contract between the suspended app and the
+ * suspending app, i.e. any app with the permission {@code android.permission.SUSPEND_APPS}.
+ * This is meant to enable the suspended app to better handle the state of being suspended.
*
* @see #ACTION_MY_PACKAGE_SUSPENDED
* @see #ACTION_MY_PACKAGE_UNSUSPENDED
@@ -2284,6 +2289,10 @@
/**
* Activity Action: Started to show more details about why an application was suspended.
*
+ * <p>Whenever the system detects an activity launch for a suspended app, it shows a dialog to
+ * the user to inform them of the state and present them an affordance to start this activity
+ * action to show more details about the reason for suspension.
+ *
* <p>Apps holding {@link android.Manifest.permission#SUSPEND_APPS} must declare an activity
* handling this intent and protect it with
* {@link android.Manifest.permission#SEND_SHOW_SUSPENDED_APP_DETAILS}.
@@ -2293,6 +2302,8 @@
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
+ * @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
+ * PersistableBundle, String)
* @see PackageManager#isPackageSuspended()
* @see #ACTION_PACKAGES_SUSPENDED
*
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 02ce47b8..2be33e9 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -280,9 +280,6 @@
PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId);
- void setSuspendedPackageAppExtras(String packageName, in PersistableBundle appExtras,
- int userId);
-
/**
* Backup/restore support - only the system uid may use these.
*/
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 9aace2e..8223363 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -212,7 +212,7 @@
* an applicaton.
*
* <p>Note: On devices running {@link android.os.Build.VERSION_CODES#P Android P} or higher,
- * any apps that override {@link #onPackagesSuspended(String[], Bundle, UserHandle)} will
+ * any apps that override {@link #onPackagesSuspended(String[], UserHandle, Bundle)} will
* not receive this callback.
*
* @param packageNames The names of the packages that have just been
@@ -226,15 +226,20 @@
* Indicates that one or more packages have been suspended. A device administrator or an app
* with {@code android.permission.SUSPEND_APPS} can do this.
*
- * @param packageNames The names of the packages that have just been suspended.
- * @param launcherExtras A {@link Bundle} of extras for the launcher.
- * @param user the user for which the given packages were suspended.
+ * <p>A suspending app with the permission {@code android.permission.SUSPEND_APPS} can
+ * optionally provide a {@link Bundle} of extra information that it deems helpful for the
+ * launcher to handle the suspended state of these packages. The contents of this
+ * {@link Bundle} supposed to be a contract between the suspending app and the launcher.
*
+ * @param packageNames The names of the packages that have just been suspended.
+ * @param user the user for which the given packages were suspended.
+ * @param launcherExtras A {@link Bundle} of extras for the launcher, if provided to the
+ * system, {@code null} otherwise.
* @see PackageManager#isPackageSuspended()
* @see #getSuspendedPackageLauncherExtras(String, UserHandle)
*/
- public void onPackagesSuspended(String[] packageNames, @Nullable Bundle launcherExtras,
- UserHandle user) {
+ public void onPackagesSuspended(String[] packageNames, UserHandle user,
+ @Nullable Bundle launcherExtras) {
onPackagesSuspended(packageNames, user);
}
@@ -662,6 +667,9 @@
* {@code PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
* PersistableBundle, String)}.
*
+ * <p>The contents of this {@link Bundle} are supposed to be a contract between the suspending
+ * app and the launcher.
+ *
* <p>Note: This just returns whatever extras were provided to the system, <em>which might
* even be {@code null}.</em>
*
@@ -670,7 +678,7 @@
* @return A {@link Bundle} of launcher extras. Or {@code null} if the package is not currently
* suspended.
*
- * @see Callback#onPackagesSuspended(String[], Bundle, UserHandle)
+ * @see Callback#onPackagesSuspended(String[], UserHandle, Bundle)
* @see PackageManager#isPackageSuspended()
*/
public @Nullable Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) {
@@ -1298,8 +1306,8 @@
mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing);
break;
case MSG_SUSPENDED:
- mCallback.onPackagesSuspended(info.packageNames, info.launcherExtras,
- info.user);
+ mCallback.onPackagesSuspended(info.packageNames, info.user, info.launcherExtras
+ );
break;
case MSG_UNSUSPENDED:
mCallback.onPackagesUnsuspended(info.packageNames, info.user);
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 627ceb7..5f9f8f1 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -244,7 +244,7 @@
* the first position to be the same across updates.
*
* <strong>Deprecated</strong> This has been replaced by the
- * {@link PackageInfo#signingCertificateHistory} field, which takes into
+ * {@link PackageInfo#signingInfo} field, which takes into
* account signing certificate rotation. For backwards compatibility in
* the event of signing certificate rotation, this will return the oldest
* reported signing certificate, so that an application will appear to
@@ -256,29 +256,15 @@
public Signature[] signatures;
/**
- * Array of all signatures arrays read from the package file, potentially
+ * Signing information read from the package file, potentially
* including past signing certificates no longer used after signing
- * certificate rotation. Though signing certificate rotation is only
- * available for apps with a single signing certificate, this provides an
- * array of arrays so that packages signed with multiple signing
- * certificates can still return all signers. This is only filled in if
+ * certificate rotation. This is only filled in if
* the flag {@link PackageManager#GET_SIGNING_CERTIFICATES} was set.
*
- * A package must be singed with at least one certificate, which is at
- * position zero in the array. An application may be signed by multiple
- * certificates, which would be in the array at position zero in an
- * indeterminate order. A package may also have a history of certificates
- * due to signing certificate rotation. In this case, the array will be
- * populated by a series of single-entry arrays corresponding to a signing
- * certificate of the package.
- *
- * <strong>Note:</strong> Signature ordering is not guaranteed to be
- * stable which means that a package signed with certificates A and B is
- * equivalent to being signed with certificates B and A. This means that
- * in case multiple signatures are reported you cannot assume the one at
- * the first position will be the same across updates.
+ * Use this field instead of the deprecated {@code signatures} field.
+ * See {@link SigningInfo} for more information on its contents.
*/
- public Signature[][] signingCertificateHistory;
+ public SigningInfo signingInfo;
/**
* Application specified preferred configuration
@@ -476,17 +462,11 @@
dest.writeBoolean(mOverlayIsStatic);
dest.writeInt(compileSdkVersion);
dest.writeString(compileSdkVersionCodename);
- writeSigningCertificateHistoryToParcel(dest, parcelableFlags);
- }
-
- private void writeSigningCertificateHistoryToParcel(Parcel dest, int parcelableFlags) {
- if (signingCertificateHistory != null) {
- dest.writeInt(signingCertificateHistory.length);
- for (int i = 0; i < signingCertificateHistory.length; i++) {
- dest.writeTypedArray(signingCertificateHistory[i], parcelableFlags);
- }
+ if (signingInfo != null) {
+ dest.writeInt(1);
+ signingInfo.writeToParcel(dest, parcelableFlags);
} else {
- dest.writeInt(-1);
+ dest.writeInt(0);
}
}
@@ -544,7 +524,10 @@
mOverlayIsStatic = source.readBoolean();
compileSdkVersion = source.readInt();
compileSdkVersionCodename = source.readString();
- readSigningCertificateHistoryFromParcel(source);
+ int hasSigningInfo = source.readInt();
+ if (hasSigningInfo != 0) {
+ signingInfo = SigningInfo.CREATOR.createFromParcel(source);
+ }
// The component lists were flattened with the redundant ApplicationInfo
// instances omitted. Distribute the canonical one here as appropriate.
@@ -556,16 +539,6 @@
}
}
- private void readSigningCertificateHistoryFromParcel(Parcel source) {
- int len = source.readInt();
- if (len != -1) {
- signingCertificateHistory = new Signature[len][];
- for (int i = 0; i < len; i++) {
- signingCertificateHistory[i] = source.createTypedArray(Signature.CREATOR);
- }
- }
- }
-
private void propagateApplicationInfo(ApplicationInfo appInfo, ComponentInfo[] components) {
if (components != null) {
for (ComponentInfo ci : components) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6e952c0..c68f253 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5577,7 +5577,8 @@
* @param packageName The name of the package to get the suspended status of.
* @param userId The user id.
* @return {@code true} if the package is suspended or {@code false} if the package is not
- * suspended or could not be found.
+ * suspended.
+ * @throws IllegalArgumentException if the package was not found.
* @hide
*/
public abstract boolean isPackageSuspendedForUser(String packageName, int userId);
@@ -5586,12 +5587,13 @@
* Query if an app is currently suspended.
*
* @return {@code true} if the given package is suspended, {@code false} otherwise
+ * @throws NameNotFoundException if the package could not be found.
*
* @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
* @hide
*/
@SystemApi
- public boolean isPackageSuspended(String packageName) {
+ public boolean isPackageSuspended(String packageName) throws NameNotFoundException {
throw new UnsupportedOperationException("isPackageSuspended not implemented");
}
@@ -5622,51 +5624,16 @@
}
/**
- * Retrieve the {@link PersistableBundle} that was passed as {@code appExtras} when the given
- * package was suspended.
+ * Returns a {@link Bundle} of extras that was meant to be sent to the calling app when it was
+ * suspended. An app with the permission {@code android.permission.SUSPEND_APPS} can supply this
+ * to the system at the time of suspending an app.
*
- * <p> The caller must hold permission {@link Manifest.permission#SUSPEND_APPS} to use this
- * api.</p>
+ * <p>This is the same {@link Bundle} that is sent along with the broadcast
+ * {@link Intent#ACTION_MY_PACKAGE_SUSPENDED}, whenever the app is suspended. The contents of
+ * this {@link Bundle} are a contract between the suspended app and the suspending app.
*
- * @param packageName The package to retrieve extras for.
- * @return The {@code appExtras} for the suspended package.
- *
- * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.SUSPEND_APPS)
- public @Nullable PersistableBundle getSuspendedPackageAppExtras(String packageName) {
- throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented");
- }
-
- /**
- * Set the app extras for a suspended package. This method can be used to update the appExtras
- * for a package that was earlier suspended using
- * {@link #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
- * String)}
- * Does nothing if the given package is not already in a suspended state.
- *
- * @param packageName The package for which the appExtras need to be updated
- * @param appExtras The new appExtras for the given package
- *
- * @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(Manifest.permission.SUSPEND_APPS)
- public void setSuspendedPackageAppExtras(String packageName,
- @Nullable PersistableBundle appExtras) {
- throw new UnsupportedOperationException("setSuspendedPackageAppExtras not implemented");
- }
-
- /**
- * Returns any extra information supplied as {@code appExtras} to the system when the calling
- * app was suspended.
- *
- * <p>Note: If no extras were supplied to the system, this method will return {@code null}, even
- * when the calling app has been suspended.</p>
+ * <p>Note: These extras are optional, so if no extras were supplied to the system, this method
+ * will return {@code null}, even when the calling app has been suspended.
*
* @return A {@link Bundle} containing the extras for the app, or {@code null} if the
* package is not currently suspended.
@@ -5674,6 +5641,7 @@
* @see #isPackageSuspended()
* @see Intent#ACTION_MY_PACKAGE_UNSUSPENDED
* @see Intent#ACTION_MY_PACKAGE_SUSPENDED
+ * @see Intent#EXTRA_SUSPENDED_PACKAGE_EXTRAS
*/
public @Nullable Bundle getSuspendedPackageAppExtras() {
throw new UnsupportedOperationException("getSuspendedPackageAppExtras not implemented");
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 699e81b..a9d0911 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -521,11 +521,6 @@
public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
/**
- * Returns a {@link com.android.server.pm.PackageSetting} for a given package name.
- */
- public abstract @Nullable Object getPackageSetting(String packageName);
-
- /**
* Returns a list without a change observer.
*
* {@see #getPackageList(PackageListObserver)}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3e0db60..7159f77 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -810,21 +810,11 @@
// replacement for GET_SIGNATURES
if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
- if (p.mSigningDetails.hasPastSigningCertificates()) {
- // Package has included signing certificate rotation information. Convert each
- // entry to an array
- int numberOfSigs = p.mSigningDetails.pastSigningCertificates.length;
- pi.signingCertificateHistory = new Signature[numberOfSigs][];
- for (int i = 0; i < numberOfSigs; i++) {
- pi.signingCertificateHistory[i] =
- new Signature[] { p.mSigningDetails.pastSigningCertificates[i] };
- }
- } else if (p.mSigningDetails.hasSignatures()) {
- // otherwise keep old behavior
- int numberOfSigs = p.mSigningDetails.signatures.length;
- pi.signingCertificateHistory = new Signature[1][numberOfSigs];
- System.arraycopy(p.mSigningDetails.signatures, 0,
- pi.signingCertificateHistory[0], 0, numberOfSigs);
+ if (p.mSigningDetails != SigningDetails.UNKNOWN) {
+ // only return a valid SigningInfo if there is signing information to report
+ pi.signingInfo = new SigningInfo(p.mSigningDetails);
+ } else {
+ pi.signingInfo = null;
}
}
return pi;
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
new file mode 100644
index 0000000..ef87403
--- /dev/null
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2018 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.content.pm;
+
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information pertaining to the signing certificates used to sign a package.
+ */
+public final class SigningInfo implements Parcelable {
+
+ @NonNull
+ private final PackageParser.SigningDetails mSigningDetails;
+
+ public SigningInfo() {
+ mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
+ }
+
+ /**
+ * @hide only packagemanager should be populating this
+ */
+ public SigningInfo(PackageParser.SigningDetails signingDetails) {
+ mSigningDetails = new PackageParser.SigningDetails(signingDetails);
+ }
+
+ public SigningInfo(SigningInfo orig) {
+ mSigningDetails = new PackageParser.SigningDetails(orig.mSigningDetails);
+ }
+
+ private SigningInfo(Parcel source) {
+ mSigningDetails = PackageParser.SigningDetails.CREATOR.createFromParcel(source);
+ }
+
+ /**
+ * Although relatively uncommon, packages may be signed by more than one signer, in which case
+ * their identity is viewed as being the set of all signers, not just any one.
+ */
+ public boolean hasMultipleSigners() {
+ return mSigningDetails.signatures != null && mSigningDetails.signatures.length > 1;
+ }
+
+ /**
+ * APK Signature Scheme v3 enables packages to provide a proof-of-rotation record that the
+ * platform verifies, and uses, to allow the use of new signing certificates. This is only
+ * available to packages that are not signed by multiple signers. In the event of a change to a
+ * new signing certificate, the package's past signing certificates are presented as well. Any
+ * check of a package's signing certificate should also include a search through its entire
+ * signing history, since it could change to a new signing certificate at any time.
+ */
+ public boolean hasPastSigningCertificates() {
+ return mSigningDetails.signatures != null
+ && mSigningDetails.pastSigningCertificates != null;
+ }
+
+ /**
+ * Returns the signing certificates this package has proven it is authorized to use. This
+ * includes both the signing certificate associated with the signer of the package and the past
+ * signing certificates it included as its proof of signing certificate rotation. This method
+ * is the preferred replacement for the {@code GET_SIGNATURES} flag used with {@link
+ * PackageManager#getPackageInfo(String, int)}. When determining if a package is signed by a
+ * desired certificate, the returned array should be checked to determine if it is one of the
+ * entries.
+ *
+ * <note>
+ * This method returns null if the package is signed by multiple signing certificates, as
+ * opposed to being signed by one current signer and also providing the history of past
+ * signing certificates. {@link #hasMultipleSigners()} may be used to determine if this
+ * package is signed by multiple signers. Packages which are signed by multiple signers
+ * cannot change their signing certificates and their {@code Signature} array should be
+ * checked to make sure that every entry matches the looked-for signing certificates.
+ * </note>
+ */
+ public Signature[] getSigningCertificateHistory() {
+ if (hasMultipleSigners()) {
+ return null;
+ } else if (!hasPastSigningCertificates()) {
+
+ // this package is only signed by one signer with no history, return it
+ return mSigningDetails.signatures;
+ } else {
+
+ // this package has provided proof of past signing certificates, include them
+ return mSigningDetails.pastSigningCertificates;
+ }
+ }
+
+ /**
+ * Returns the signing certificates used to sign the APK contents of this application. Not
+ * including any past signing certificates the package proved it is authorized to use.
+ * <note>
+ * This method should not be used unless {@link #hasMultipleSigners()} returns true,
+ * indicating that {@link #getSigningCertificateHistory()} cannot be used, otherwise {@link
+ * #getSigningCertificateHistory()} should be preferred.
+ * </note>
+ */
+ public Signature[] getApkContentsSigners() {
+ return mSigningDetails.signatures;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ mSigningDetails.writeToParcel(dest, parcelableFlags);
+ }
+
+ public static final Parcelable.Creator<SigningInfo> CREATOR =
+ new Parcelable.Creator<SigningInfo>() {
+ @Override
+ public SigningInfo createFromParcel(Parcel source) {
+ return new SigningInfo(source);
+ }
+
+ @Override
+ public SigningInfo[] newArray(int size) {
+ return new SigningInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 6f812ac..bd4a27c 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -168,11 +168,6 @@
* the requested network's required capabilities. Note that when searching
* for a network to satisfy a request, all capabilities requested must be
* satisfied.
- * <p>
- * If the given capability was previously added to the list of unwanted capabilities
- * then the capability will also be removed from the list of unwanted capabilities.
- *
- * @see #addUnwantedCapability(int)
*
* @param capability The capability to add.
* @return The builder to facilitate chaining
@@ -184,8 +179,7 @@
}
/**
- * Removes (if found) the given capability from this builder instance from both required
- * and unwanted capabilities lists.
+ * Removes (if found) the given capability from this builder instance.
*
* @param capability The capability to remove.
* @return The builder to facilitate chaining.
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 2a68714..3b32c52 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -77,45 +77,51 @@
/**
* Fetches data for the specified configuration key. Returns a byte array representing proto
* wire-encoded of ConfigMetricsReportList.
+ *
+ * Requires Manifest.permission.DUMP.
*/
byte[] getData(in long key);
/**
* Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
+ *
+ * Requires Manifest.permission.DUMP.
*/
byte[] getMetadata();
/**
* Sets a configuration with the specified config key and subscribes to updates for this
* configuration key. Broadcasts will be sent if this configuration needs to be collected.
- * The configuration must be a wire-encoded StatsDConfig. The receiver for this data is
+ * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
* registered in a separate function.
*
- * Returns if this configuration was correctly registered.
+ * Requires Manifest.permission.DUMP.
*/
- boolean addConfiguration(in long configKey, in byte[] config);
+ void addConfiguration(in long configKey, in byte[] config);
/**
* Registers the given pending intent for this config key. This intent is invoked when the
* memory consumed by the metrics for this configuration approach the pre-defined limits. There
* can be at most one listener per config key.
*
- * Returns if this listener was correctly registered.
+ * Requires Manifest.permission.DUMP.
*/
- boolean setDataFetchOperation(long configKey, in IBinder intentSender);
+ void setDataFetchOperation(long configKey, in IBinder intentSender);
/**
* Removes the data fetch operation for the specified configuration.
+ *
+ * Requires Manifest.permission.DUMP.
*/
- boolean removeDataFetchOperation(long configKey);
+ void removeDataFetchOperation(long configKey);
/**
* Removes the configuration with the matching config key. No-op if this config key does not
* exist.
*
- * Returns if this configuration key was removed.
+ * Requires Manifest.permission.DUMP.
*/
- boolean removeConfiguration(in long configKey);
+ void removeConfiguration(in long configKey);
/**
* Set the IIntentSender (i.e. PendingIntent) to be used when broadcasting subscriber
@@ -133,16 +139,16 @@
* intentSender must be convertible into an IntentSender using IntentSender(IBinder)
* and cannot be null.
*
- * Returns true if successful.
+ * Requires Manifest.permission.DUMP.
*/
- boolean setBroadcastSubscriber(long configKey, long subscriberId, in IBinder intentSender);
+ void setBroadcastSubscriber(long configKey, long subscriberId, in IBinder intentSender);
/**
* Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair.
* Any broadcasts associated with subscriberId will henceforth not be sent.
* No-op if this (configKey, subsriberId) pair was not associated with an IntentSender.
*
- * Returns true if successful.
+ * Requires Manifest.permission.DUMP.
*/
- boolean unsetBroadcastSubscriber(long configKey, long subscriberId);
+ void unsetBroadcastSubscriber(long configKey, long subscriberId);
}
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 17d83db..3270719 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -924,13 +924,17 @@
/** @hide */
@VisibleForTesting
public int[] getUids() {
- return mUids;
+ int[] uids = new int[mSize];
+ System.arraycopy(mUids, 0, uids, 0, mSize);
+ return uids;
}
/** @hide */
@VisibleForTesting
public String[] getTags() {
- return mTags;
+ String[] tags = new String[mSize];
+ System.arraycopy(mTags, 0, tags, 0, mSize);
+ return tags;
}
/** @hide */
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index d1d5d8e..673da50 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -166,6 +166,11 @@
private List<String> mApiBlacklistExemptions = Collections.emptyList();
/**
+ * Proportion of hidden API accesses that should be logged to the event log; 0 - 0x10000.
+ */
+ private int mHiddenApiAccessLogSampleRate;
+
+ /**
* The state of the connection to the primary zygote.
*/
private ZygoteState primaryZygoteState;
@@ -478,6 +483,21 @@
}
}
+ /**
+ * Set the precentage of detected hidden API accesses that are logged to the event log.
+ *
+ * <p>This rate will take affect for all new processes forked from the zygote after this call.
+ *
+ * @param rate An integer between 0 and 0x10000 inclusive. 0 means no event logging.
+ */
+ public void setHiddenApiAccessLogSampleRate(int rate) {
+ synchronized (mLock) {
+ mHiddenApiAccessLogSampleRate = rate;
+ maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
+ maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
+ }
+ }
+
@GuardedBy("mLock")
private void maybeSetApiBlacklistExemptions(ZygoteState state, boolean sendIfEmpty) {
if (state == null || state.isClosed()) {
@@ -505,6 +525,29 @@
}
}
+ private void maybeSetHiddenApiAccessLogSampleRate(ZygoteState state) {
+ if (state == null || state.isClosed()) {
+ return;
+ }
+ if (mHiddenApiAccessLogSampleRate == -1) {
+ return;
+ }
+ try {
+ state.writer.write(Integer.toString(1));
+ state.writer.newLine();
+ state.writer.write("--hidden-api-log-sampling-rate="
+ + Integer.toString(mHiddenApiAccessLogSampleRate));
+ state.writer.newLine();
+ state.writer.flush();
+ int status = state.inputStream.readInt();
+ if (status != 0) {
+ Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate; status " + status);
+ }
+ } catch (IOException ioe) {
+ Slog.e(LOG_TAG, "Failed to set hidden API log sampling rate", ioe);
+ }
+ }
+
/**
* Tries to open socket to Zygote process if not already open. If
* already open, does nothing. May block and retry. Requires that mLock be held.
@@ -520,6 +563,7 @@
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
maybeSetApiBlacklistExemptions(primaryZygoteState, false);
+ maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
}
if (primaryZygoteState.matches(abi)) {
return primaryZygoteState;
@@ -533,6 +577,7 @@
throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
}
maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
+ maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
}
if (secondaryZygoteState.matches(abi)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 38de1e4..ef87752 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11774,6 +11774,15 @@
"hidden_api_blacklist_exemptions";
/**
+ * Sampling rate for hidden API access event logs, as an integer in the range 0 to 0x10000
+ * inclusive.
+ *
+ * @hide
+ */
+ public static final String HIDDEN_API_ACCESS_LOG_SAMPLING_RATE =
+ "hidden_api_access_log_sampling_rate";
+
+ /**
* Hidden API enforcement policy for apps targeting SDK versions prior to the latest
* version.
*
diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java
index f351c5a..b84843b 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -288,6 +289,18 @@
}
/**
+ * Checks whether the recoverable key store is currently available.
+ *
+ * <p>If it returns true, the device must currently be using a screen lock that is supported for
+ * use with the recoverable key store, i.e. AOSP PIN, pattern or password.
+ */
+ @RequiresPermission(android.Manifest.permission.RECOVER_KEYSTORE)
+ public static boolean isRecoverableKeyStoreEnabled(@NonNull Context context) {
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ return keyguardManager != null && keyguardManager.isDeviceSecure();
+ }
+
+ /**
* @deprecated Use {@link #initRecoveryService(String, byte[], byte[])} instead.
*/
@Deprecated
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index daecea7..7b01f7a 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -83,7 +83,8 @@
private static final int DAY_MINUTES = 24 * 60;
private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
- // Default allow categories set in readXml() from default_zen_mode_config.xml, fallback values:
+ // Default allow categories set in readXml() from default_zen_mode_config.xml,
+ // fallback/upgrade values:
private static final boolean DEFAULT_ALLOW_ALARMS = true;
private static final boolean DEFAULT_ALLOW_MEDIA = true;
private static final boolean DEFAULT_ALLOW_SYSTEM = false;
@@ -97,7 +98,7 @@
private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS =
Policy.getAllSuppressedVisualEffects();
- public static final int XML_VERSION = 6;
+ public static final int XML_VERSION = 7;
public static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ZEN_ATT_USER = "user";
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f1fd812..c48b6d1 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -813,7 +813,7 @@
}
final int relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
+ View.VISIBLE, 0, -1, mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
mDisplayCutout, mMergedConfiguration, mSurfaceHolder.mSurface);
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 66a9c6c..f59c0b5 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -31,6 +31,7 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.PathParser;
import android.util.proto.ProtoOutputStream;
@@ -75,15 +76,19 @@
false /* copyArguments */);
+ private static final Pair<Path, DisplayCutout> NULL_PAIR = new Pair<>(null, null);
private static final Object CACHE_LOCK = new Object();
+
@GuardedBy("CACHE_LOCK")
private static String sCachedSpec;
@GuardedBy("CACHE_LOCK")
private static int sCachedDisplayWidth;
@GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayHeight;
+ @GuardedBy("CACHE_LOCK")
private static float sCachedDensity;
@GuardedBy("CACHE_LOCK")
- private static DisplayCutout sCachedCutout;
+ private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR;
private final Rect mSafeInsets;
private final Region mBounds;
@@ -347,7 +352,7 @@
}
/**
- * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout.
+ * Creates the bounding path according to @android:string/config_mainBuiltInDisplayCutout.
*
* @hide
*/
@@ -357,6 +362,16 @@
}
/**
+ * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout.
+ *
+ * @hide
+ */
+ public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
+ return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
+ displayWidth, displayHeight, res.getDisplayMetrics().density).first;
+ }
+
+ /**
* Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec.
*
* @hide
@@ -364,11 +379,17 @@
@VisibleForTesting(visibility = PRIVATE)
public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
float density) {
+ return pathAndDisplayCutoutFromSpec(spec, displayWidth, displayHeight, density).second;
+ }
+
+ private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec,
+ int displayWidth, int displayHeight, float density) {
if (TextUtils.isEmpty(spec)) {
- return null;
+ return NULL_PAIR;
}
synchronized (CACHE_LOCK) {
if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
+ && sCachedDisplayHeight == displayHeight
&& sCachedDensity == density) {
return sCachedCutout;
}
@@ -398,7 +419,7 @@
p = PathParser.createPathFromPathData(spec);
} catch (Throwable e) {
Log.wtf(TAG, "Could not inflate cutout: ", e);
- return null;
+ return NULL_PAIR;
}
final Matrix m = new Matrix();
@@ -414,7 +435,7 @@
bottomPath = PathParser.createPathFromPathData(bottomSpec);
} catch (Throwable e) {
Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
- return null;
+ return NULL_PAIR;
}
// Keep top transform
m.postTranslate(0, displayHeight);
@@ -422,10 +443,11 @@
p.addPath(bottomPath);
}
- final DisplayCutout result = fromBounds(p);
+ final Pair<Path, DisplayCutout> result = new Pair<>(p, fromBounds(p));
synchronized (CACHE_LOCK) {
sCachedSpec = spec;
sCachedDisplayWidth = displayWidth;
+ sCachedDisplayHeight = displayHeight;
sCachedDensity = density;
sCachedCutout = result;
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d8a5609..f868a00 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -66,6 +66,7 @@
* @param viewVisibility Window root view's visibility.
* @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
* {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
+ * @param frameNumber A frame number in which changes requested in this layout will be rendered.
* @param outFrame Rect in which is placed the new position/size on
* screen.
* @param outOverscanInsets Rect in which is placed the offsets from
@@ -96,7 +97,7 @@
*/
int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
- int flags, out Rect outFrame, out Rect outOverscanInsets,
+ int flags, long frameNumber, out Rect outFrame, out Rect outOverscanInsets,
out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets,
out Rect outOutsets, out Rect outBackdropFrame,
out DisplayCutout.ParcelableWrapper displayCutout,
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6002fe5..2ec42c0 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -5692,6 +5692,7 @@
}
dispatchVisibilityAggregated(isAttachedToWindow() && getWindowVisibility() == VISIBLE
&& isShown());
+ notifySubtreeAccessibilityStateChangedIfNeeded();
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 70054717..2c17bdf 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6479,17 +6479,17 @@
params.type = mOrigWindowType;
}
}
-
- if (mSurface.isValid()) {
- params.frameNumber = mSurface.getNextFrameNumber();
- }
}
- int relayoutResult = mWindowSession.relayout(
- mWindow, mSeq, params,
+ long frameNumber = -1;
+ if (mSurface.isValid()) {
+ frameNumber = mSurface.getNextFrameNumber();
+ }
+
+ int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
- (int) (mView.getMeasuredHeight() * appScale + 0.5f),
- viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
+ (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurface);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f6181d7..0f5c23f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2438,13 +2438,6 @@
public long hideTimeoutMilliseconds = -1;
/**
- * A frame number in which changes requested in this layout will be rendered.
- *
- * @hide
- */
- public long frameNumber = -1;
-
- /**
* The color mode requested by this window. The target display may
* not be able to honor the request. When the color mode is not set
* to {@link ActivityInfo#COLOR_MODE_DEFAULT}, it might override the
@@ -2617,7 +2610,6 @@
TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
out.writeInt(mColorMode);
out.writeLong(hideTimeoutMilliseconds);
- out.writeLong(frameNumber);
}
public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -2674,7 +2666,6 @@
accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mColorMode = in.readInt();
hideTimeoutMilliseconds = in.readLong();
- frameNumber = in.readLong();
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -2875,10 +2866,6 @@
changes |= SURFACE_INSETS_CHANGED;
}
- // The frame number changing is only relevant in the context of other
- // changes, and so we don't need to track it with a flag.
- frameNumber = o.frameNumber;
-
if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) {
hasManualSurfaceInsets = o.hasManualSurfaceInsets;
changes |= SURFACE_INSETS_CHANGED;
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 95a83da..7946e9e 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -201,6 +201,7 @@
* <em>Properties:</em></br>
* <ul>
* <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getContentChangeTypes()} - The type of state changes.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
@@ -863,16 +864,17 @@
}
/**
- * Gets the bit mask of change types signaled by an
- * {@link #TYPE_WINDOW_CONTENT_CHANGED} event. A single event may represent
- * multiple change types.
+ * Gets the bit mask of change types signaled by a
+ * {@link #TYPE_WINDOW_CONTENT_CHANGED} event or {@link #TYPE_WINDOW_STATE_CHANGED}. A single
+ * event may represent multiple change types.
*
* @return The bit mask of change types. One or more of:
* <ul>
- * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
- * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
- * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
- * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
+ * <li>{@link #CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+ * <li>{@link #CONTENT_CHANGE_TYPE_SUBTREE}
+ * <li>{@link #CONTENT_CHANGE_TYPE_TEXT}
+ * <li>{@link #CONTENT_CHANGE_TYPE_PANE_TITLE}
+ * <li>{@link #CONTENT_CHANGE_TYPE_UNDEFINED}
* </ul>
*/
@ContentChangeTypes
@@ -891,6 +893,7 @@
}
case CONTENT_CHANGE_TYPE_SUBTREE: return "CONTENT_CHANGE_TYPE_SUBTREE";
case CONTENT_CHANGE_TYPE_TEXT: return "CONTENT_CHANGE_TYPE_TEXT";
+ case CONTENT_CHANGE_TYPE_PANE_TITLE: return "CONTENT_CHANGE_TYPE_PANE_TITLE";
case CONTENT_CHANGE_TYPE_UNDEFINED: return "CONTENT_CHANGE_TYPE_UNDEFINED";
default: return Integer.toHexString(type);
}
@@ -1324,7 +1327,7 @@
}
if (!DEBUG_CONCISE_TOSTRING || mWindowChangeTypes != 0) {
builder.append("; WindowChangeTypes: ").append(
- contentChangeTypesToString(mWindowChangeTypes));
+ windowChangeTypesToString(mWindowChangeTypes));
}
super.appendTo(builder);
if (DEBUG || DEBUG_CONCISE_TOSTRING) {
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index f80625f..96016b4 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -375,13 +375,13 @@
*/
public static final class Builder {
- @NonNull private String mText;
@NonNull private List<RemoteAction> mActions = new ArrayList<>();
@NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
- @Nullable Drawable mLegacyIcon;
- @Nullable String mLegacyLabel;
- @Nullable Intent mLegacyIntent;
- @Nullable OnClickListener mLegacyOnClickListener;
+ @Nullable private String mText;
+ @Nullable private Drawable mLegacyIcon;
+ @Nullable private String mLegacyLabel;
+ @Nullable private Intent mLegacyIntent;
+ @Nullable private OnClickListener mLegacyOnClickListener;
@Nullable private String mId;
/**
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 3d503e2..851b2c9 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -339,7 +339,7 @@
/**
* @return The config representing the set of entities to look for
- * @see #setEntityConfig(TextClassifier.EntityConfig)
+ * @see Builder#setEntityConfig(TextClassifier.EntityConfig)
*/
@Nullable
public TextClassifier.EntityConfig getEntityConfig() {
diff --git a/core/java/android/webkit/TracingConfig.java b/core/java/android/webkit/TracingConfig.java
index d95ca61..2080168 100644
--- a/core/java/android/webkit/TracingConfig.java
+++ b/core/java/android/webkit/TracingConfig.java
@@ -54,37 +54,37 @@
/**
* Predefined set of categories typically useful for analyzing WebViews.
- * Typically includes android_webview and Java.
+ * Typically includes "android_webview" and "Java" categories.
*/
public static final int CATEGORIES_ANDROID_WEBVIEW = 1 << 1;
/**
* Predefined set of categories typically useful for web developers.
- * Typically includes blink, compositor, renderer.scheduler and v8 categories.
+ * Typically includes "blink", "compositor", "renderer.scheduler" and "v8" categories.
*/
public static final int CATEGORIES_WEB_DEVELOPER = 1 << 2;
/**
* Predefined set of categories for analyzing input latency issues.
- * Typically includes input, renderer.scheduler categories.
+ * Typically includes "input", "renderer.scheduler" categories.
*/
public static final int CATEGORIES_INPUT_LATENCY = 1 << 3;
/**
* Predefined set of categories for analyzing rendering issues.
- * Typically includes blink, compositor and gpu categories.
+ * Typically includes "blink", "compositor" and "gpu" categories.
*/
public static final int CATEGORIES_RENDERING = 1 << 4;
/**
* Predefined set of categories for analyzing javascript and rendering issues.
- * Typically includes blink, compositor, gpu, renderer.scheduler and v8 categories.
+ * Typically includes "blink", "compositor", "gpu", "renderer.scheduler" and "v8" categories.
*/
public static final int CATEGORIES_JAVASCRIPT_AND_RENDERING = 1 << 5;
/**
* Predefined set of categories for studying difficult rendering performance problems.
- * Typically includes blink, compositor, gpu, renderer.scheduler, v8 and
+ * Typically includes "blink", "compositor", "gpu", "renderer.scheduler", "v8" and
* some other compositor categories which are disabled by default.
*/
public static final int CATEGORIES_FRAME_VIEWER = 1 << 6;
@@ -123,7 +123,9 @@
}
/**
- * Returns a bitmask of the predefined categories values of this configuration.
+ * Returns a bitmask of the predefined category sets of this configuration.
+ *
+ * @return Bitmask of predefined category sets.
*/
@PredefinedCategories
public int getPredefinedCategories() {
@@ -133,7 +135,7 @@
/**
* Returns the list of included custom category patterns for this configuration.
*
- * @return empty list if no custom category patterns are specified.
+ * @return Empty list if no custom category patterns are specified.
*/
@NonNull
public List<String> getCustomIncludedCategories() {
@@ -142,6 +144,8 @@
/**
* Returns the tracing mode of this configuration.
+ *
+ * @return The tracing mode of this configuration.
*/
@TracingMode
public int getTracingMode() {
@@ -150,28 +154,37 @@
/**
* Builder used to create {@link TracingConfig} objects.
- *
+ * <p>
* Examples:
- * new TracingConfig.Builder().build()
- * -- creates a configuration with default options: {@link #CATEGORIES_NONE},
- * {@link #RECORD_UNTIL_FULL}.
- * new TracingConfig.Builder().addCategories(CATEGORIES_WEB_DEVELOPER).build()
- * -- records trace events from the "web developer" predefined category sets.
- * new TracingConfig.Builder().addCategories(CATEGORIES_RENDERING,
- * CATEGORIES_INPUT_LATENCY).build()
- * -- records trace events from the "rendering" and "input latency" predefined
- * category sets.
- * new TracingConfig.Builder().addCategories("browser").build()
- * -- records only the trace events from the "browser" category.
- * new TracingConfig.Builder().addCategories("blink*","renderer*").build()
- * -- records only the trace events matching the "blink*" and "renderer*" patterns
- * (e.g. "blink.animations", "renderer_host" and "renderer.scheduler" categories).
- * new TracingConfig.Builder().addCategories(CATEGORIES_WEB_DEVELOPER)
+ * <pre class="prettyprint">
+ * // Create a configuration with default options: {@link #CATEGORIES_NONE},
+ * // {@link #RECORD_CONTINUOUSLY}.
+ * <code>new TracingConfig.Builder().build()</code>
+ *
+ * // Record trace events from the "web developer" predefined category sets.
+ * // Uses a ring buffer (the default {@link #RECORD_CONTINUOUSLY} mode) for
+ * // internal storage during tracing.
+ * <code>new TracingConfig.Builder().addCategories(CATEGORIES_WEB_DEVELOPER).build()</code>
+ *
+ * // Record trace events from the "rendering" and "input latency" predefined
+ * // category sets.
+ * <code>new TracingConfig.Builder().addCategories(CATEGORIES_RENDERING,
+ * CATEGORIES_INPUT_LATENCY).build()</code>
+ *
+ * // Record only the trace events from the "browser" category.
+ * <code>new TracingConfig.Builder().addCategories("browser").build()</code>
+ *
+ * // Record only the trace events matching the "blink*" and "renderer*" patterns
+ * // (e.g. "blink.animations", "renderer_host" and "renderer.scheduler" categories).
+ * <code>new TracingConfig.Builder().addCategories("blink*","renderer*").build()</code>
+ *
+ * // Record events from the "web developer" predefined category set and events from
+ * // the "disabled-by-default-v8.gc" category to understand where garbage collection
+ * // is being triggered. Uses a limited size buffer for internal storage during tracing.
+ * <code>new TracingConfig.Builder().addCategories(CATEGORIES_WEB_DEVELOPER)
* .addCategories("disabled-by-default-v8.gc")
- * .setTracingMode(RECORD_CONTINUOUSLY).build()
- * -- records events from the "web developer" predefined category set and events from
- * the "disabled-by-default-v8.gc" category to understand where garbage collection
- * is being triggered. Uses a ring buffer for internal storage during tracing.
+ * .setTracingMode(RECORD_UNTIL_FULL).build()</code>
+ * </pre>
*/
public static class Builder {
private @PredefinedCategories int mPredefinedCategories = CATEGORIES_NONE;
@@ -185,6 +198,8 @@
/**
* Build {@link TracingConfig} using the current settings.
+ *
+ * @return The {@link TracingConfig} with the current settings.
*/
public TracingConfig build() {
return new TracingConfig(mPredefinedCategories, mCustomIncludedCategories,
@@ -192,16 +207,15 @@
}
/**
- * Adds categories from a predefined set of categories to be included in the trace output.
+ * Adds predefined sets of categories to be included in the trace output.
*
- * @param predefinedCategories list or bitmask of predefined category sets to use:
- * {@link #CATEGORIES_NONE}, {@link #CATEGORIES_ALL},
- * {@link #CATEGORIES_ANDROID_WEBVIEW},
- * {@link #CATEGORIES_WEB_DEVELOPER},
- * {@link #CATEGORIES_INPUT_LATENCY},
- * {@link #CATEGORIES_RENDERING},
- * {@link #CATEGORIES_JAVASCRIPT_AND_RENDERING} or
- * {@link #CATEGORIES_FRAME_VIEWER}.
+ * A predefined category set can be one of {@link #CATEGORIES_NONE},
+ * {@link #CATEGORIES_ALL}, {@link #CATEGORIES_ANDROID_WEBVIEW},
+ * {@link #CATEGORIES_WEB_DEVELOPER}, {@link #CATEGORIES_INPUT_LATENCY},
+ * {@link #CATEGORIES_RENDERING}, {@link #CATEGORIES_JAVASCRIPT_AND_RENDERING} or
+ * {@link #CATEGORIES_FRAME_VIEWER}.
+ *
+ * @param predefinedCategories A list or bitmask of predefined category sets.
* @return The builder to facilitate chaining.
*/
public Builder addCategories(@PredefinedCategories int... predefinedCategories) {
@@ -215,11 +229,11 @@
* Adds custom categories to be included in trace output.
*
* Note that the categories are defined by the currently-in-use version of WebView. They
- * live in chromium code and are not part of the Android API. See
+ * live in chromium code and are not part of the Android API.
* See <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">
* chromium documentation on tracing</a> for more details.
*
- * @param categories a list of category patterns. A category pattern can contain wilcards,
+ * @param categories A list of category patterns. A category pattern can contain wildcards,
* e.g. "blink*" or full category name e.g. "renderer.scheduler".
* @return The builder to facilitate chaining.
*/
@@ -235,7 +249,7 @@
*
* Same as {@link #addCategories(String...)} but allows to pass a Collection as a parameter.
*
- * @param categories a list of category patters.
+ * @param categories A list of category patterns.
* @return The builder to facilitate chaining.
*/
public Builder addCategories(Collection<String> categories) {
@@ -245,8 +259,9 @@
/**
* Sets the tracing mode for this configuration.
+ * When tracingMode is not set explicitly, the default is {@link #RECORD_CONTINUOUSLY}.
*
- * @param tracingMode tracing mode to use, one of {@link #RECORD_UNTIL_FULL} or
+ * @param tracingMode The tracing mode to use, one of {@link #RECORD_UNTIL_FULL} or
* {@link #RECORD_CONTINUOUSLY}.
* @return The builder to facilitate chaining.
*/
diff --git a/core/java/android/webkit/TracingController.java b/core/java/android/webkit/TracingController.java
index 50068f5..05c0304 100644
--- a/core/java/android/webkit/TracingController.java
+++ b/core/java/android/webkit/TracingController.java
@@ -35,9 +35,9 @@
* Example usage:
* <pre class="prettyprint">
* TracingController tracingController = TracingController.getInstance();
- * tracingController.start(new TraceConfig.Builder()
+ * tracingController.start(new TracingConfig.Builder()
* .addCategories(CATEGORIES_WEB_DEVELOPER).build());
- * [..]
+ * ...
* tracingController.stop(new FileOutputStream("trace.json"),
* Executors.newSingleThreadExecutor());
* </pre></p>
@@ -49,7 +49,7 @@
* only one TracingController instance for all WebView instances,
* however this restriction may be relaxed in a future Android release.
*
- * @return the default TracingController instance
+ * @return The default TracingController instance.
*/
@NonNull
public static TracingController getInstance() {
@@ -65,8 +65,10 @@
* using an internal buffer and flushed to the outputStream when
* {@link #stop(OutputStream, Executor)} is called.
*
- * @param tracingConfig configuration options to use for tracing
- * @throws IllegalStateException if the system is already tracing.
+ * @param tracingConfig Configuration options to use for tracing.
+ * @throws IllegalStateException If the system is already tracing.
+ * @throws IllegalArgumentException If the configuration is invalid (e.g.
+ * invalid category pattern or invalid tracing mode).
*/
public abstract void start(@NonNull TracingConfig tracingConfig);
@@ -77,17 +79,22 @@
* in chunks by invoking {@link java.io.OutputStream#write(byte[])}. On completion
* the {@link java.io.OutputStream#close()} method is called.
*
- * @param outputStream the output steam the tracing data will be sent to. If null
+ * @param outputStream The output stream the tracing data will be sent to. If null
* the tracing data will be discarded.
- * @param executor the {@link java.util.concurrent.Executor} on which the
- * outputStream #write and #close methods will be invoked.
- * @return false if the system was not tracing at the time of the call, true
- * otherwise.
+ * @param executor The {@link java.util.concurrent.Executor} on which the
+ * outputStream {@link java.io.OutputStream#write(byte[])} and
+ * {@link java.io.OutputStream#close()} methods will be invoked.
+ * @return False if the WebView framework was not tracing at the time of the call,
+ * true otherwise.
*/
public abstract boolean stop(@Nullable OutputStream outputStream,
@NonNull @CallbackExecutor Executor executor);
- /** True if the system is tracing */
+ /**
+ * Returns whether the WebView framework is tracing.
+ *
+ * @return True if tracing is enabled.
+ */
public abstract boolean isTracing();
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 6af678b..dac100a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -39,6 +39,7 @@
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -4837,14 +4838,48 @@
return true;
}
- private boolean handleOverlapsMagnifier() {
- final int handleY = mContainer.getDecorViewLayoutParams().y;
- final int magnifierBottomWhenAtWindowTop =
- mTextView.getRootWindowInsets().getSystemWindowInsetTop()
- + mMagnifierAnimator.mMagnifier.getHeight();
- return handleY <= magnifierBottomWhenAtWindowTop;
+ private boolean handleOverlapsMagnifier(@NonNull final HandleView handle,
+ @NonNull final Rect magnifierRect) {
+ final PopupWindow window = handle.mContainer;
+ if (!window.hasDecorView()) {
+ return false;
+ }
+ final Rect handleRect = new Rect(
+ window.getDecorViewLayoutParams().x,
+ window.getDecorViewLayoutParams().y,
+ window.getDecorViewLayoutParams().x + window.getContentView().getWidth(),
+ window.getDecorViewLayoutParams().y + window.getContentView().getHeight());
+ return Rect.intersects(handleRect, magnifierRect);
}
+ private @Nullable HandleView getOtherSelectionHandle() {
+ final SelectionModifierCursorController controller = getSelectionController();
+ if (controller == null || !controller.isActive()) {
+ return null;
+ }
+ return controller.mStartHandle != this
+ ? controller.mStartHandle
+ : controller.mEndHandle;
+ }
+
+ private final Magnifier.Callback mHandlesVisibilityCallback = new Magnifier.Callback() {
+ @Override
+ public void onOperationComplete() {
+ final Point magnifierTopLeft = mMagnifierAnimator.mMagnifier.getWindowCoords();
+ if (magnifierTopLeft == null) {
+ return;
+ }
+ final Rect magnifierRect = new Rect(magnifierTopLeft.x, magnifierTopLeft.y,
+ magnifierTopLeft.x + mMagnifierAnimator.mMagnifier.getWidth(),
+ magnifierTopLeft.y + mMagnifierAnimator.mMagnifier.getHeight());
+ setVisible(!handleOverlapsMagnifier(HandleView.this, magnifierRect));
+ final HandleView otherHandle = getOtherSelectionHandle();
+ if (otherHandle != null) {
+ otherHandle.setVisible(!handleOverlapsMagnifier(otherHandle, magnifierRect));
+ }
+ }
+ };
+
protected final void updateMagnifier(@NonNull final MotionEvent event) {
if (mMagnifierAnimator == null) {
return;
@@ -4858,12 +4893,8 @@
mRenderCursorRegardlessTiming = true;
mTextView.invalidateCursorPath();
suspendBlink();
- // Hide handle if it overlaps the magnifier.
- if (handleOverlapsMagnifier()) {
- setVisible(false);
- } else {
- setVisible(true);
- }
+ mMagnifierAnimator.mMagnifier
+ .setOnOperationCompleteCallback(mHandlesVisibilityCallback);
mMagnifierAnimator.show(showPosInView.x, showPosInView.y);
} else {
@@ -4877,6 +4908,10 @@
mRenderCursorRegardlessTiming = false;
resumeBlink();
setVisible(true);
+ final HandleView otherHandle = getOtherSelectionHandle();
+ if (otherHandle != null) {
+ otherHandle.setVisible(true);
+ }
}
}
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 5eb6699..cb362e6 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -233,6 +233,17 @@
return mZoom;
}
+ /**
+ * @hide
+ */
+ @Nullable
+ public Point getWindowCoords() {
+ if (mWindow == null) {
+ return null;
+ }
+ return new Point(mWindow.mLastDrawContentPositionX, mWindow.mLastDrawContentPositionY);
+ }
+
@Nullable
private Surface getValidViewSurface() {
// TODO: deduplicate this against the first part of #performPixelCopy
@@ -374,8 +385,11 @@
private final Runnable mMagnifierUpdater;
// The handler where the magnifier updater jobs will be post'd.
private final Handler mHandler;
- // The callback to be run after the next draw. Only used for testing.
+ // The callback to be run after the next draw.
private Callback mCallback;
+ // The position of the magnifier content when the last draw was requested.
+ private int mLastDrawContentPositionX;
+ private int mLastDrawContentPositionY;
// Members below describe the state of the magnifier. Reads/writes to them
// have to be synchronized between the UI thread and the thread that handles
@@ -598,6 +612,8 @@
callback = null;
}
+ mLastDrawContentPositionX = mWindowPositionX + mOffsetX;
+ mLastDrawContentPositionY = mWindowPositionY + mOffsetY;
mFrameDrawScheduled = false;
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index ddba477..9ab057d 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -34,10 +34,6 @@
import android.os.RemoteException;
import android.provider.Settings;
import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
import android.util.Pair;
import android.util.Printer;
import android.util.Slog;
@@ -129,11 +125,6 @@
}
// ----------------------------------------------------------------------
- @Deprecated
- public static boolean isSystemIme(@NonNull InputMethodInfo inputMethod) {
- return inputMethod.isSystem();
- }
-
public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
final Context context, final boolean checkDefaultAttribute,
@Nullable final Locale requiredLocale, final boolean checkCountry,
@@ -783,58 +774,6 @@
}
/**
- * Parses the setting stored input methods and subtypes string value.
- *
- * @param inputMethodsAndSubtypesString The input method subtypes value stored in settings.
- * @return Map from input method ID to set of input method subtypes IDs.
- */
- @VisibleForTesting
- public static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString(
- @Nullable final String inputMethodsAndSubtypesString) {
-
- final ArrayMap<String, ArraySet<String>> imeMap = new ArrayMap<>();
- if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
- return imeMap;
- }
-
- final SimpleStringSplitter typeSplitter =
- new SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
- final SimpleStringSplitter subtypeSplitter =
- new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
- List<Pair<String, ArrayList<String>>> allImeSettings =
- InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString,
- typeSplitter,
- subtypeSplitter);
- for (Pair<String, ArrayList<String>> ime : allImeSettings) {
- ArraySet<String> subtypes = new ArraySet<>();
- if (ime.second != null) {
- subtypes.addAll(ime.second);
- }
- imeMap.put(ime.first, subtypes);
- }
- return imeMap;
- }
-
- @NonNull
- public static String buildInputMethodsAndSubtypesString(
- @NonNull final ArrayMap<String, ArraySet<String>> map) {
- // we want to use the canonical InputMethodSettings implementation,
- // so we convert data structures first.
- List<Pair<String, ArrayList<String>>> imeMap = new ArrayList<>(4);
- for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) {
- final String imeName = entry.getKey();
- final ArraySet<String> subtypeSet = entry.getValue();
- final ArrayList<String> subtypes = new ArrayList<>(2);
- if (subtypeSet != null) {
- subtypes.addAll(subtypeSet);
- }
- imeMap.add(new Pair<>(imeName, subtypes));
- }
- return InputMethodSettings.buildInputMethodsSettingString(imeMap);
- }
-
- /**
* Utility class for putting and getting settings for InputMethod
* TODO: Move all putters and getters of settings to this class.
*/
@@ -871,21 +810,7 @@
}
}
- public static String buildInputMethodsSettingString(
- List<Pair<String, ArrayList<String>>> allImeSettingsMap) {
- final StringBuilder b = new StringBuilder();
- boolean needsSeparator = false;
- for (Pair<String, ArrayList<String>> ime : allImeSettingsMap) {
- if (needsSeparator) {
- b.append(INPUT_METHOD_SEPARATOR);
- }
- buildEnabledInputMethodsSettingString(b, ime);
- needsSeparator = true;
- }
- return b.toString();
- }
-
- public static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
+ private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
String enabledInputMethodsStr,
TextUtils.SimpleStringSplitter inputMethodSplitter,
TextUtils.SimpleStringSplitter subtypeSplitter) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 5d40a73..f537e3e 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -166,6 +166,11 @@
return null;
}
+ if (parsedArgs.hiddenApiAccessLogSampleRate != -1) {
+ handleHiddenApiAccessLogSampleRate(parsedArgs.hiddenApiAccessLogSampleRate);
+ return null;
+ }
+
if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
throw new ZygoteSecurityException("Client may not specify capabilities: " +
"permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
@@ -294,6 +299,15 @@
}
}
+ private void handleHiddenApiAccessLogSampleRate(int percent) {
+ try {
+ ZygoteInit.setHiddenApiAccessLogSampleRate(percent);
+ mSocketOutStream.writeInt(0);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+ }
+
protected void preload() {
ZygoteInit.lazyPreload();
}
@@ -461,6 +475,12 @@
String[] apiBlacklistExemptions;
/**
+ * Sampling rate for logging hidden API accesses to the event log. This is sent to the
+ * pre-forked zygote at boot time, or when it changes, via --hidden-api-log-sampling-rate.
+ */
+ int hiddenApiAccessLogSampleRate = -1;
+
+ /**
* Constructs instance and parses args
* @param args zygote command-line args
* @throws IllegalArgumentException
@@ -483,6 +503,7 @@
boolean seenRuntimeArgs = false;
+ boolean expectRuntimeArgs = true;
for ( /* curArg */ ; curArg < args.length; curArg++) {
String arg = args[curArg];
@@ -612,6 +633,7 @@
preloadPackageCacheKey = args[++curArg];
} else if (arg.equals("--preload-default")) {
preloadDefault = true;
+ expectRuntimeArgs = false;
} else if (arg.equals("--start-child-zygote")) {
startChildZygote = true;
} else if (arg.equals("--set-api-blacklist-exemptions")) {
@@ -619,6 +641,16 @@
// with the regular fork command.
apiBlacklistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
curArg = args.length;
+ expectRuntimeArgs = false;
+ } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
+ String rateStr = arg.substring(arg.indexOf('=') + 1);
+ try {
+ hiddenApiAccessLogSampleRate = Integer.parseInt(rateStr);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Invalid log sampling rate: " + rateStr, nfe);
+ }
+ expectRuntimeArgs = false;
} else {
break;
}
@@ -633,7 +665,7 @@
throw new IllegalArgumentException(
"Unexpected arguments after --preload-package.");
}
- } else if (!preloadDefault && apiBlacklistExemptions == null) {
+ } else if (expectRuntimeArgs) {
if (!seenRuntimeArgs) {
throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index c5d41db..6f58365 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -26,8 +26,8 @@
import android.icu.util.ULocale;
import android.opengl.EGL14;
import android.os.Build;
-import android.os.IInstalld;
import android.os.Environment;
+import android.os.IInstalld;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -44,16 +44,16 @@
import android.system.StructCapUserData;
import android.system.StructCapUserHeader;
import android.text.Hyphenator;
-import android.util.TimingsTraceLog;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
+import android.util.TimingsTraceLog;
import android.webkit.WebViewFactory;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
-
import com.android.internal.util.Preconditions;
+
import dalvik.system.DexFile;
import dalvik.system.VMRuntime;
import dalvik.system.ZygoteHooks;
@@ -67,8 +67,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.security.Security;
import java.security.Provider;
+import java.security.Security;
/**
* Startup class for the zygote process.
@@ -518,6 +518,10 @@
VMRuntime.getRuntime().setHiddenApiExemptions(exemptions);
}
+ public static void setHiddenApiAccessLogSampleRate(int percent) {
+ VMRuntime.getRuntime().setHiddenApiAccessLogSamplingRate(percent);
+ }
+
/**
* Creates a PathClassLoader for the given class path that is associated with a shared
* namespace, i.e., this classloader can access platform-private native libraries. The
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 24f2fbf..2b7221a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -64,6 +64,8 @@
in NotificationVisibility[] noLongerVisibleKeys);
void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
void onNotificationDirectReplied(String key);
+ void onNotificationSmartRepliesAdded(in String key, in int replyCount);
+ void onNotificationSmartReplySent(in String key, in int replyIndex);
void onNotificationSettingsViewed(String key);
void setSystemUiVisibility(int vis, int mask, String cause);
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 35aae15..2ce5a0b 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -1706,6 +1706,7 @@
contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
contentContainer.setTag(FLOATING_TOOLBAR_TAG);
+ contentContainer.setClipToOutline(true);
return contentContainer;
}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index a8d0825..ee371c1 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -220,6 +220,7 @@
optional float adjust_ime_amount = 10;
optional float adjust_divider_amount = 11;
optional .android.graphics.RectProto adjusted_bounds = 12;
+ optional bool animating_bounds = 13;
}
/* represents Task */
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 3196d00..b7395cd 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -138,7 +138,6 @@
android:layout_width="?attr/notificationHeaderIconSize"
android:layout_height="?attr/notificationHeaderIconSize"
android:src="@drawable/ic_camera"
- android:tint="@color/notification_secondary_text_color_light"
android:background="?android:selectableItemBackgroundBorderless"
android:visibility="gone"
/>
@@ -147,7 +146,6 @@
android:layout_width="?attr/notificationHeaderIconSize"
android:layout_height="?attr/notificationHeaderIconSize"
android:src="@drawable/ic_mic"
- android:tint="@color/notification_secondary_text_color_light"
android:background="?android:selectableItemBackgroundBorderless"
android:layout_marginStart="4dp"
android:visibility="gone"
@@ -157,7 +155,6 @@
android:layout_width="?attr/notificationHeaderIconSize"
android:layout_height="?attr/notificationHeaderIconSize"
android:src="@drawable/ic_alert_window_layer"
- android:tint="@color/notification_secondary_text_color_light"
android:background="?android:selectableItemBackgroundBorderless"
android:layout_marginStart="4dp"
android:visibility="gone"
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index f1b61a7..dce8a65 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -18,7 +18,7 @@
-->
<!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. -->
-<zen version="6">
+<zen version="7">
<allow alarms="true" media="true" system="false" calls="false" messages="false" reminders="false"
events="false" />
<!-- all visual effects that exist as of P -->
diff --git a/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java b/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java
new file mode 100644
index 0000000..655efb5
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/drawable/DrawableWrapperTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 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.graphics.drawable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DrawableWrapperTest {
+
+ static class MyWrapper extends DrawableWrapper {
+ MyWrapper(Drawable dr) {
+ super(dr);
+ }
+ }
+
+ /**
+ * Test {@link Drawable#setXfermode(Xfermode)} which is marked
+ * with the hide annotation
+ */
+ @Test
+ public void testSetXfermode() {
+ CacheXfermodeDrawable xferModeDrawable = new CacheXfermodeDrawable();
+ DrawableWrapper wrapper = new MyWrapper(xferModeDrawable);
+ PorterDuffXfermode mode = new PorterDuffXfermode(Mode.MULTIPLY);
+ wrapper.setXfermode(mode);
+ assertSame(xferModeDrawable, wrapper.getDrawable());
+ assertEquals(mode, xferModeDrawable.mXferMode);
+ }
+
+ private static class CacheXfermodeDrawable extends Drawable {
+
+ private Xfermode mXferMode = null;
+
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ @Override
+ public void setXfermode(Xfermode mode) {
+ mXferMode = mode;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index b150a79..96ac589 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -247,6 +247,7 @@
Settings.Global.HDMI_CONTROL_ENABLED,
Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ Settings.Global.HIDDEN_API_ACCESS_LOG_SAMPLING_RATE,
Settings.Global.HIDDEN_API_POLICY_P_APPS,
Settings.Global.HIDDEN_API_POLICY_PRE_P_APPS,
Settings.Global.HIDE_ERROR_DIALOGS,
diff --git a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
index c205f96..6c05601 100644
--- a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
@@ -54,7 +54,7 @@
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// Do not copy NoCopySpan if specified so.
- final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */);
+ final SpannedString copied = new SpannedString(first, true /* ignoreNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
@@ -87,7 +87,7 @@
// Do not copy NoCopySpan if specified so.
final SpannedString copied = new SpannedString(
- new CustomSpannable(first), false /* copyNoCopySpan */);
+ new CustomSpannable(first), true /* ignoreNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
diff --git a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
index 0680924..380e315 100644
--- a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
@@ -54,7 +54,7 @@
first.setSpan(new UnderlineSpan(), 0, first.length(), Spanned.SPAN_PRIORITY);
// Do not copy NoCopySpan if specified so.
- final SpannedString copied = new SpannedString(first, false /* copyNoCopySpan */);
+ final SpannedString copied = new SpannedString(first, true /* ignoreNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
@@ -87,7 +87,7 @@
// Do not copy NoCopySpan if specified so.
final SpannedString copied = new SpannedString(
- new CustomSpanned(first), false /* copyNoCopySpan */);
+ new CustomSpanned(first), true /* ignoreNoCopySpan */);
final Object[] spans = copied.getSpans(0, copied.length(), Object.class);
assertNotNull(spans);
assertEquals(2, spans.length);
diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java
index 2fe7db4..be3a0be 100644
--- a/core/tests/coretests/src/android/text/util/LinkifyTest.java
+++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java
@@ -31,11 +31,6 @@
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.util.Patterns;
-import android.view.textclassifier.SystemTextClassifier;
-import android.view.textclassifier.TextClassificationConstants;
-import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextClassifierImpl;
-import android.view.textclassifier.TextLinks.TextLinkSpan;
import android.widget.TextView;
import org.junit.After;
@@ -43,11 +38,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
import java.util.Locale;
-import java.util.Set;
/**
* LinkifyTest tests {@link Linkify}.
@@ -158,55 +149,4 @@
assertEquals("android.com should be linkified", 1, spans.length);
assertEquals("https://android.com", spans[0].getURL());
}
-
- @Test
- public void testAddLinksAsync_useLegacyIfSmartDisabled_localTextClassifier()
- throws Exception {
- final TextClassificationConstants settings =
- TextClassificationConstants.loadFromString("smart_linkify_enabled=false");
- final TextClassifier classifier = new TextClassifierImpl(mContext, settings);
- testAddLinksAsync_useLegacyIfSmartDisabled(classifier);
- }
-
- @Test
- public void testAddLinksAsync_useLegacyIfSmartDisabled_systemTextClassifier()
- throws Exception {
- final TextClassificationConstants settings =
- TextClassificationConstants.loadFromString("smart_linkify_enabled=false");
- final TextClassifier classifier = new SystemTextClassifier(mContext, settings);
- testAddLinksAsync_useLegacyIfSmartDisabled(classifier);
- }
-
- private void testAddLinksAsync_useLegacyIfSmartDisabled(TextClassifier classifier)
- throws Exception {
- final int linkMask = Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS;
- final String string = "example@android.com\n"
- + "(415) 555-1212\n"
- + "http://android.com\n"
- + "100 Android Rd California";
- final Spannable expected = new SpannableString(string);
- final Spannable actual = new SpannableString(string);
-
- Linkify.addLinks(expected, linkMask); // legacy.
- Linkify.addLinksAsync(actual, classifier, linkMask).get();
-
- final URLSpan[] expectedSpans = expected.getSpans(0, expected.length(), URLSpan.class);
- final TextLinkSpan[] actualSpans = actual.getSpans(0, actual.length(), TextLinkSpan.class);
- assertEquals(expectedSpans.length, actualSpans.length);
- final Set<List> expectedLinkSpec = new HashSet<>();
- final Set<List> actualLinkSpec = new HashSet<>();
- for (int i = 0; i < expectedSpans.length; i++) {
- final URLSpan expectedSpan = expectedSpans[i];
- final TextLinkSpan actualSpan = actualSpans[i];
- expectedLinkSpec.add(Arrays.asList(
- expected.getSpanStart(expectedSpan),
- expected.getSpanEnd(expectedSpan),
- expectedSpan.getURL()));
- actualLinkSpec.add(Arrays.asList(
- actual.getSpanStart(actualSpan),
- actual.getSpanEnd(actualSpan),
- actualSpan.getUrl()));
- }
- assertEquals(expectedLinkSpec, actualLinkSpec);
- }
}
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 6e9401d..6ee74cb 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -208,6 +208,12 @@
}
@Test
+ public void fromSpec_wontCacheIfScreenHeightChanges() {
+ DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 4000, 1f);
+ assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
+ }
+
+ @Test
public void fromSpec_wontCacheIfDensityChanges() {
DisplayCutout cached = fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 2f);
assertThat(fromSpec("L1,0 L1,1 L0,1 z", 200, 400, 1f), not(sameInstance(cached)));
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
index 1fd24e3..a70dae8 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
@@ -40,8 +40,6 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -1192,248 +1190,6 @@
}
@Test
- public void testParseInputMethodsAndSubtypesString() {
- // Trivial cases.
- {
- assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString("").isEmpty());
- assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString(null).isEmpty());
- }
-
- // No subtype cases.
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString("ime0");
- assertEquals(1, r.size());
- assertTrue(r.containsKey("ime0"));
- assertTrue(r.get("ime0").isEmpty());
- }
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString("ime0:ime1");
- assertEquals(2, r.size());
- assertTrue(r.containsKey("ime0"));
- assertTrue(r.get("ime0").isEmpty());
- assertTrue(r.containsKey("ime1"));
- assertTrue(r.get("ime1").isEmpty());
- }
-
- // Input metho IDs and their subtypes.
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0");
- assertEquals(1, r.size());
- assertTrue(r.containsKey("ime0"));
- ArraySet<String> subtypes = r.get("ime0");
- assertEquals(1, subtypes.size());
- assertTrue(subtypes.contains("subtype0"));
- }
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype0");
- assertEquals(1, r.size());
- assertTrue(r.containsKey("ime0"));
- ArraySet<String> subtypes = r.get("ime0");
- assertEquals(1, subtypes.size());
- assertTrue(subtypes.contains("subtype0"));
- }
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype1");
- assertEquals(1, r.size());
- assertTrue(r.containsKey("ime0"));
- ArraySet<String> subtypes = r.get("ime0");
- assertEquals(2, subtypes.size());
- assertTrue(subtypes.contains("subtype0"));
- assertTrue(subtypes.contains("subtype1"));
- }
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString(
- "ime0;subtype0:ime1;subtype1");
- assertEquals(2, r.size());
- assertTrue(r.containsKey("ime0"));
- assertTrue(r.containsKey("ime1"));
- ArraySet<String> subtypes0 = r.get("ime0");
- assertEquals(1, subtypes0.size());
- assertTrue(subtypes0.contains("subtype0"));
-
- ArraySet<String> subtypes1 = r.get("ime1");
- assertEquals(1, subtypes1.size());
- assertTrue(subtypes1.contains("subtype1"));
- }
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString(
- "ime0;subtype0;subtype1:ime1;subtype2");
- assertEquals(2, r.size());
- assertTrue(r.containsKey("ime0"));
- assertTrue(r.containsKey("ime1"));
- ArraySet<String> subtypes0 = r.get("ime0");
- assertEquals(2, subtypes0.size());
- assertTrue(subtypes0.contains("subtype0"));
- assertTrue(subtypes0.contains("subtype1"));
-
- ArraySet<String> subtypes1 = r.get("ime1");
- assertEquals(1, subtypes1.size());
- assertTrue(subtypes1.contains("subtype2"));
- }
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString(
- "ime0;subtype0;subtype1:ime1;subtype1;subtype2");
- assertEquals(2, r.size());
- assertTrue(r.containsKey("ime0"));
- assertTrue(r.containsKey("ime1"));
- ArraySet<String> subtypes0 = r.get("ime0");
- assertEquals(2, subtypes0.size());
- assertTrue(subtypes0.contains("subtype0"));
- assertTrue(subtypes0.contains("subtype1"));
-
- ArraySet<String> subtypes1 = r.get("ime1");
- assertEquals(2, subtypes1.size());
- assertTrue(subtypes0.contains("subtype1"));
- assertTrue(subtypes1.contains("subtype2"));
- }
- {
- ArrayMap<String, ArraySet<String>> r =
- InputMethodUtils.parseInputMethodsAndSubtypesString(
- "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
- assertEquals(3, r.size());
- assertTrue(r.containsKey("ime0"));
- assertTrue(r.containsKey("ime1"));
- assertTrue(r.containsKey("ime2"));
- ArraySet<String> subtypes0 = r.get("ime0");
- assertEquals(2, subtypes0.size());
- assertTrue(subtypes0.contains("subtype0"));
- assertTrue(subtypes0.contains("subtype1"));
-
- ArraySet<String> subtypes1 = r.get("ime1");
- assertEquals(2, subtypes1.size());
- assertTrue(subtypes0.contains("subtype1"));
- assertTrue(subtypes1.contains("subtype2"));
-
- ArraySet<String> subtypes2 = r.get("ime2");
- assertTrue(subtypes2.isEmpty());
- }
- }
-
- @Test
- public void testbuildInputMethodsAndSubtypesString() {
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- assertEquals("", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- map.put("ime0", new ArraySet<>());
- assertEquals("ime0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- ArraySet<String> subtypes1 = new ArraySet<>();
- subtypes1.add("subtype0");
- map.put("ime0", subtypes1);
- assertEquals("ime0;subtype0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- ArraySet<String> subtypes1 = new ArraySet<>();
- subtypes1.add("subtype0");
- subtypes1.add("subtype1");
- map.put("ime0", subtypes1);
-
- // We do not expect what order will be used to concatenate items in
- // InputMethodUtils.buildInputMethodsAndSubtypesString() hence enumerate all possible
- // permutations here.
- ArraySet<String> validSequences = new ArraySet<>();
- validSequences.add("ime0;subtype0;subtype1");
- validSequences.add("ime0;subtype1;subtype0");
- assertTrue(validSequences.contains(
- InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- map.put("ime0", new ArraySet<>());
- map.put("ime1", new ArraySet<>());
-
- ArraySet<String> validSequences = new ArraySet<>();
- validSequences.add("ime0:ime1");
- validSequences.add("ime1:ime0");
- assertTrue(validSequences.contains(
- InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- ArraySet<String> subtypes1 = new ArraySet<>();
- subtypes1.add("subtype0");
- map.put("ime0", subtypes1);
- map.put("ime1", new ArraySet<>());
-
- ArraySet<String> validSequences = new ArraySet<>();
- validSequences.add("ime0;subtype0:ime1");
- validSequences.add("ime1;ime0;subtype0");
- assertTrue(validSequences.contains(
- InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- ArraySet<String> subtypes1 = new ArraySet<>();
- subtypes1.add("subtype0");
- subtypes1.add("subtype1");
- map.put("ime0", subtypes1);
- map.put("ime1", new ArraySet<>());
-
- ArraySet<String> validSequences = new ArraySet<>();
- validSequences.add("ime0;subtype0;subtype1:ime1");
- validSequences.add("ime0;subtype1;subtype0:ime1");
- validSequences.add("ime1:ime0;subtype0;subtype1");
- validSequences.add("ime1:ime0;subtype1;subtype0");
- assertTrue(validSequences.contains(
- InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- ArraySet<String> subtypes1 = new ArraySet<>();
- subtypes1.add("subtype0");
- map.put("ime0", subtypes1);
-
- ArraySet<String> subtypes2 = new ArraySet<>();
- subtypes2.add("subtype1");
- map.put("ime1", subtypes2);
-
- ArraySet<String> validSequences = new ArraySet<>();
- validSequences.add("ime0;subtype0:ime1;subtype1");
- validSequences.add("ime1;subtype1:ime0;subtype0");
- assertTrue(validSequences.contains(
- InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
- }
- {
- ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
- ArraySet<String> subtypes1 = new ArraySet<>();
- subtypes1.add("subtype0");
- subtypes1.add("subtype1");
- map.put("ime0", subtypes1);
-
- ArraySet<String> subtypes2 = new ArraySet<>();
- subtypes2.add("subtype2");
- subtypes2.add("subtype3");
- map.put("ime1", subtypes2);
-
- ArraySet<String> validSequences = new ArraySet<>();
- validSequences.add("ime0;subtype0;subtype1:ime1;subtype2;subtype3");
- validSequences.add("ime0;subtype1;subtype0:ime1;subtype2;subtype3");
- validSequences.add("ime0;subtype0;subtype1:ime1;subtype3;subtype2");
- validSequences.add("ime0;subtype1;subtype0:ime1;subtype3;subtype2");
- validSequences.add("ime1;subtype2;subtype3:ime0;subtype0;subtype1");
- validSequences.add("ime2;subtype3;subtype2:ime0;subtype0;subtype1");
- validSequences.add("ime3;subtype2;subtype3:ime0;subtype1;subtype0");
- validSequences.add("ime4;subtype3;subtype2:ime0;subtype1;subtype0");
- assertTrue(validSequences.contains(
- InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
- }
- }
-
- @Test
public void testConstructLocaleFromString() throws Exception {
assertEquals(new Locale("en"), InputMethodUtils.constructLocaleFromString("en"));
assertEquals(new Locale("en", "US"), InputMethodUtils.constructLocaleFromString("en_US"));
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 22867df..72d9bce 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -111,6 +111,8 @@
<family lang="und-Ethi">
<font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
<font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifEthiopic-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifEthiopic-Bold.otf</font>
</family>
<family lang="und-Hebr">
<font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
@@ -167,6 +169,8 @@
<family lang="und-Guru" variant="elegant">
<font weight="400" style="normal">NotoSansGurmukhi-Regular.ttf</font>
<font weight="700" style="normal">NotoSansGurmukhi-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifGurmukhi-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifGurmukhi-Bold.otf</font>
</family>
<family lang="und-Guru" variant="compact">
<font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font>
@@ -231,9 +235,15 @@
<font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
</family>
- <family lang="und-Sinh">
+ <family lang="und-Sinh" variant="elegant">
<font weight="400" style="normal">NotoSansSinhala-Regular.ttf</font>
<font weight="700" style="normal">NotoSansSinhala-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifSinhala-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifSinhala-Bold.otf</font>
+ </family>
+ <family lang="und-Sinh" variant="compact">
+ <font weight="400" style="normal">NotoSansSinhalaUI-Regular.otf</font>
+ <font weight="700" style="normal">NotoSansSinhalaUI-Bold.otf</font>
</family>
<family lang="und-Khmr" variant="elegant">
<font weight="100" style="normal">NotoSansKhmer-VF.ttf
@@ -272,7 +282,9 @@
<axis tag="wdth" stylevalue="100.0" />
<axis tag="wght" stylevalue="190.0" />
</font>
- </family>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
<family lang="und-Khmr" variant="compact">
<font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font>
<font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
@@ -290,6 +302,8 @@
<family lang="und-Mymr" variant="elegant">
<font weight="400" style="normal">NotoSansMyanmar-Regular.ttf</font>
<font weight="700" style="normal">NotoSansMyanmar-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
</family>
<family lang="und-Mymr" variant="compact">
<font weight="400" style="normal">NotoSansMyanmarUI-Regular.ttf</font>
@@ -303,6 +317,9 @@
<font weight="400" style="normal">NotoSansCham-Regular.ttf</font>
<font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
</family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
<family lang="und-Adlm">
<font weight="400" style="normal">NotoSansAdlam-Regular.ttf</font>
</family>
@@ -354,6 +371,9 @@
<family lang="und-Egyp">
<font weight="400" style="normal">NotoSansEgyptianHieroglyphs-Regular.ttf</font>
</family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
<family lang="und-Glag">
<font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font>
</family>
@@ -538,4 +558,64 @@
<family lang="und-Phag">
<font weight="400" style="normal">NotoSansPhagsPa-Regular.ttf</font>
</family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
</familyset>
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index 01d5825..88a6322 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -35,8 +35,6 @@
* @param mode The porter-duff mode that is applied
*
* @see Color
- * @see #setColor(int)
- * @see #setMode(android.graphics.PorterDuff.Mode)
*/
public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
mColor = color;
@@ -48,7 +46,6 @@
* is applied.
*
* @see Color
- * @see #setColor(int)
*
* @hide
*/
@@ -58,30 +55,10 @@
}
/**
- * Specifies the color to tint the source pixels with when this color
- * filter is applied.
- *
- * @param color An ARGB {@link Color color}
- *
- * @see Color
- * @see #getColor()
- * @see #getMode()
- *
- * @hide
- */
- public void setColor(@ColorInt int color) {
- if (mColor != color) {
- mColor = color;
- discardNativeInstance();
- }
- }
-
- /**
* Returns the Porter-Duff mode used to composite this color filter's
* color with the source pixel when this filter is applied.
*
* @see PorterDuff
- * @see #setMode(android.graphics.PorterDuff.Mode)
*
* @hide
*/
@@ -89,24 +66,6 @@
return mMode;
}
- /**
- * Specifies the Porter-Duff mode to use when compositing this color
- * filter's color with the source pixel at draw time.
- *
- * @see PorterDuff
- * @see #getMode()
- * @see #getColor()
- *
- * @hide
- */
- public void setMode(@NonNull PorterDuff.Mode mode) {
- if (mode == null) {
- throw new IllegalArgumentException("mode must be non-null");
- }
- mMode = mode;
- discardNativeInstance();
- }
-
@Override
long createNativeInstance() {
return native_CreatePorterDuffFilter(mColor, mMode.nativeInt);
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 8af2fd8..b77d74a 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -16,11 +16,6 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.AttrRes;
import android.annotation.ColorInt;
import android.annotation.IntRange;
@@ -57,6 +52,11 @@
import android.util.Xml;
import android.view.View;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -1516,12 +1516,11 @@
}
final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
- if (tintFilter == null) {
+ if (tintFilter == null || tintFilter.getColor() != color
+ || tintFilter.getMode() != tintMode) {
return new PorterDuffColorFilter(color, tintMode);
}
- tintFilter.setColor(color);
- tintFilter.setMode(tintMode);
return tintFilter;
}
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index cf821bb..a9e0f9a 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -16,11 +16,6 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo.Config;
@@ -35,10 +30,16 @@
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
/**
@@ -78,6 +79,16 @@
}
/**
+ * @hide
+ */
+ @Override
+ public void setXfermode(Xfermode mode) {
+ if (mDrawable != null) {
+ mDrawable.setXfermode(mode);
+ }
+ }
+
+ /**
* Sets the wrapped drawable.
*
* @param dr the wrapped drawable
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 0da61c2..266a6ac 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -888,7 +888,10 @@
// The ripple timing depends on the paint's alpha value, so we need
// to push just the alpha channel into the paint and let the filter
// handle the full-alpha color.
- mMaskColorFilter.setColor(color | 0xFF000000);
+ int maskColor = color | 0xFF000000;
+ if (mMaskColorFilter.getColor() != maskColor) {
+ mMaskColorFilter = new PorterDuffColorFilter(maskColor, mMaskColorFilter.getMode());
+ }
p.setColor(color & 0xFF000000);
p.setColorFilter(mMaskColorFilter);
p.setShader(mMaskShader);
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 88e768c..30389a29d 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -16,12 +16,15 @@
package android.security;
+import android.annotation.TestApi;
+
/**
* KeyStore/keymaster exception with positive error codes coming from the KeyStore and negative
* ones from keymaster.
*
* @hide
*/
+@TestApi
public class KeyStoreException extends Exception {
private final int mErrorCode;
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index c0d0fb0..b2e0f67 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -19,6 +19,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.KeyguardManager;
import android.hardware.fingerprint.FingerprintManager;
import android.security.GateKeeper;
@@ -594,6 +595,14 @@
/**
* Returns {@code true} if the key is authorized to be used only if a test of user presence has
* been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls.
+ * It requires that the KeyStore implementation have a direct way to validate the user presence
+ * for example a KeyStore hardware backed strongbox can use a button press that is observable
+ * in hardware. A test for user presence is tangential to authentication. The test can be part
+ * of an authentication step as long as this step can be validated by the hardware protecting
+ * the key and cannot be spoofed. For example, a physical button press can be used as a test of
+ * user presence if the other pins connected to the button are not able to simulate a button
+ * press. There must be no way for the primary processor to fake a button press, or that
+ * button must not be used as a test of user presence.
*/
public boolean isUserPresenceRequired() {
return mUserPresenceRequired;
@@ -673,8 +682,8 @@
}
/**
- * Returns {@code true} if the screen must be unlocked for this key to be used for encryption or
- * signing. Decryption and signature verification will still be available when the screen is
+ * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or
+ * signing. Encryption and signature verification will still be available when the screen is
* locked.
*
* @see Builder#setUnlockedDeviceRequired(boolean)
@@ -1180,6 +1189,14 @@
/**
* Sets whether a test of user presence is required to be performed between the
* {@code Signature.initSign()} and {@code Signature.sign()} method calls.
+ * It requires that the KeyStore implementation have a direct way to validate the user
+ * presence for example a KeyStore hardware backed strongbox can use a button press that
+ * is observable in hardware. A test for user presence is tangential to authentication. The
+ * test can be part of an authentication step as long as this step can be validated by the
+ * hardware protecting the key and cannot be spoofed. For example, a physical button press
+ * can be used as a test of user presence if the other pins connected to the button are not
+ * able to simulate a button press.There must be no way for the primary processor to fake a
+ * button press, or that button must not be used as a test of user presence.
*/
@NonNull
public Builder setUserPresenceRequired(boolean required) {
@@ -1227,6 +1244,7 @@
*
* Sets whether to include a temporary unique ID field in the attestation certificate.
*/
+ @TestApi
@NonNull
public Builder setUniqueIdIncluded(boolean uniqueIdIncluded) {
mUniqueIdIncluded = uniqueIdIncluded;
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 41dc201..fdcad85 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -19,6 +19,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.KeyguardManager;
import android.hardware.fingerprint.FingerprintManager;
import android.security.GateKeeper;
@@ -447,7 +448,12 @@
* been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls.
* It requires that the KeyStore implementation have a direct way to validate the user presence
* for example a KeyStore hardware backed strongbox can use a button press that is observable
- * in hardware.
+ * in hardware. A test for user presence is tangential to authentication. The test can be part
+ * of an authentication step as long as this step can be validated by the hardware protecting
+ * the key and cannot be spoofed. For example, a physical button press can be used as a test of
+ * user presence if the other pins connected to the button are not able to simulate a button
+ * press. There must be no way for the primary processor to fake a button press, or that
+ * button must not be used as a test of user presence.
*/
public boolean isUserPresenceRequired() {
return mUserPresenceRequred;
@@ -496,6 +502,7 @@
* @see KeymasterUtils#addUserAuthArgs
* @hide
*/
+ @TestApi
public long getBoundToSpecificSecureUserId() {
return mBoundToSecureUserId;
}
@@ -511,8 +518,8 @@
}
/**
- * Returns {@code true} if the screen must be unlocked for this key to be used for encryption or
- * signing. Decryption and signature verification will still be available when the screen is
+ * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or
+ * signing. Encryption and signature verification will still be available when the screen is
* locked.
*
* @see Builder#setUnlockedDeviceRequired(boolean)
@@ -843,7 +850,15 @@
/**
* Sets whether a test of user presence is required to be performed between the
- * {@code Signature.initSign()} and {@code Signature.sign()} method calls.
+ * {@code Signature.initSign()} and {@code Signature.sign()} method calls. It requires that
+ * the KeyStore implementation have a direct way to validate the user presence for example
+ * a KeyStore hardware backed strongbox can use a button press that is observable in
+ * hardware. A test for user presence is tangential to authentication. The test can be part
+ * of an authentication step as long as this step can be validated by the hardware
+ * protecting the key and cannot be spoofed. For example, a physical button press can be
+ * used as a test of user presence if the other pins connected to the button are not able
+ * to simulate a button press. There must be no way for the primary processor to fake a
+ * button press, or that button must not be used as a test of user presence.
*/
@NonNull
public Builder setUserPresenceRequired(boolean required) {
@@ -913,6 +928,7 @@
* @see KeyProtection#getBoundToSpecificSecureUserId()
* @hide
*/
+ @TestApi
public Builder setBoundToSpecificSecureUserId(long secureUserId) {
mBoundToSecureUserId = secureUserId;
return this;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index de4eaf5..a27d519 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -340,33 +340,21 @@
srcs: [
"tests/unit/main.cpp",
- "tests/unit/BakedOpDispatcherTests.cpp",
- "tests/unit/BakedOpRendererTests.cpp",
- "tests/unit/BakedOpStateTests.cpp",
"tests/unit/CacheManagerTests.cpp",
"tests/unit/CanvasContextTests.cpp",
"tests/unit/CanvasStateTests.cpp",
"tests/unit/ClipAreaTests.cpp",
"tests/unit/DamageAccumulatorTests.cpp",
"tests/unit/DeferredLayerUpdaterTests.cpp",
- "tests/unit/DeviceInfoTests.cpp",
"tests/unit/FatVectorTests.cpp",
- "tests/unit/FontRendererTests.cpp",
- "tests/unit/FrameBuilderTests.cpp",
- "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",
"tests/unit/MatrixTests.cpp",
- "tests/unit/MeshStateTests.cpp",
- "tests/unit/OffscreenBufferPoolTests.cpp",
"tests/unit/OpDumperTests.cpp",
"tests/unit/PathInterpolatorTests.cpp",
"tests/unit/RenderNodeDrawableTests.cpp",
- "tests/unit/RecordingCanvasTests.cpp",
"tests/unit/RenderNodeTests.cpp",
"tests/unit/RenderPropertiesTests.cpp",
"tests/unit/ShaderCacheTests.cpp",
@@ -378,8 +366,6 @@
"tests/unit/SnapshotTests.cpp",
"tests/unit/StringUtilsTests.cpp",
"tests/unit/TestUtilsTests.cpp",
- "tests/unit/TextDropShadowCacheTests.cpp",
- "tests/unit/TextureCacheTests.cpp",
"tests/unit/ThreadBaseTests.cpp",
"tests/unit/TypefaceTests.cpp",
"tests/unit/VectorDrawableTests.cpp",
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 7b14322..0d1257f 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -193,15 +193,12 @@
}
char prop[PROPERTY_VALUE_MAX];
property_get(PROPERTY_RENDERER, prop, "skiagl");
- if (!strcmp(prop, "skiagl")) {
- ALOGD("Skia GL Pipeline");
- sRenderPipelineType = RenderPipelineType::SkiaGL;
- } else if (!strcmp(prop, "skiavk")) {
+ if (!strcmp(prop, "skiavk")) {
ALOGD("Skia Vulkan Pipeline");
sRenderPipelineType = RenderPipelineType::SkiaVulkan;
- } else { //"opengl"
- ALOGD("HWUI GL Pipeline");
- sRenderPipelineType = RenderPipelineType::OpenGL;
+ } else { //"skiagl"
+ ALOGD("Skia GL Pipeline");
+ sRenderPipelineType = RenderPipelineType::SkiaGL;
}
return sRenderPipelineType;
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 1a0bdfd..03a3e36 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -206,7 +206,7 @@
enum class StencilClipDebug { Hide, ShowHighlight, ShowRegion };
-enum class RenderPipelineType { OpenGL = 0, SkiaGL, SkiaVulkan, NotInitialized = 128 };
+enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
/**
* Renderthread-only singleton which manages several static rendering properties. Most of these
diff --git a/libs/hwui/debug/gles_decls.in b/libs/hwui/debug/gles_decls.in
index 16574a7..3900959 100644
--- a/libs/hwui/debug/gles_decls.in
+++ b/libs/hwui/debug/gles_decls.in
@@ -387,7 +387,6 @@
GL_ENTRY(void, glDrawElementsBaseVertexOES, GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex)
GL_ENTRY(void, glDrawRangeElementsBaseVertexOES, GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex)
GL_ENTRY(void, glDrawElementsInstancedBaseVertexOES, GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex)
-GL_ENTRY(void, glMultiDrawElementsBaseVertexOES, GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex)
GL_ENTRY(void, glFramebufferTextureOES, GLenum target, GLenum attachment, GLuint texture, GLint level)
GL_ENTRY(void, glGetProgramBinaryOES, GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary)
GL_ENTRY(void, glProgramBinaryOES, GLuint program, GLenum binaryFormat, const void *binary, GLint length)
@@ -541,4 +540,4 @@
GL_ENTRY(void, glTextureStorage1DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width)
GL_ENTRY(void, glTextureStorage2DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
GL_ENTRY(void, glTextureStorage3DEXT, GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth)
-GL_ENTRY(void, glTextureViewEXT, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers)
\ No newline at end of file
+GL_ENTRY(void, glTextureViewEXT, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers)
diff --git a/libs/hwui/debug/gles_stubs.in b/libs/hwui/debug/gles_stubs.in
index 4064a39..7cba0c1 100644
--- a/libs/hwui/debug/gles_stubs.in
+++ b/libs/hwui/debug/gles_stubs.in
@@ -1165,9 +1165,6 @@
void API_ENTRY(glDrawElementsInstancedBaseVertexOES)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex) {
CALL_GL_API(glDrawElementsInstancedBaseVertexOES, mode, count, type, indices, instancecount, basevertex);
}
-void API_ENTRY(glMultiDrawElementsBaseVertexOES)(GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex) {
- CALL_GL_API(glMultiDrawElementsBaseVertexOES, mode, count, type, indices, primcount, basevertex);
-}
void API_ENTRY(glFramebufferTextureOES)(GLenum target, GLenum attachment, GLuint texture, GLint level) {
CALL_GL_API(glFramebufferTextureOES, target, attachment, texture, level);
}
@@ -1629,4 +1626,4 @@
}
void API_ENTRY(glTextureViewEXT)(GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) {
CALL_GL_API(glTextureViewEXT, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers);
-}
\ No newline at end of file
+}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index d983200..a0fc83e 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -65,9 +65,6 @@
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
- case RenderPipelineType::OpenGL:
- return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
- std::make_unique<OpenGLPipeline>(thread));
case RenderPipelineType::SkiaGL:
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
@@ -82,28 +79,13 @@
}
void CanvasContext::destroyLayer(RenderNode* node) {
- auto renderType = Properties::getRenderPipelineType();
- switch (renderType) {
- case RenderPipelineType::OpenGL:
- OpenGLPipeline::destroyLayer(node);
- break;
- case RenderPipelineType::SkiaGL:
- case RenderPipelineType::SkiaVulkan:
- skiapipeline::SkiaPipeline::destroyLayer(node);
- break;
- default:
- LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
- break;
- }
+ skiapipeline::SkiaPipeline::destroyLayer(node);
}
void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) {
ATRACE_CALL();
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
- case RenderPipelineType::OpenGL:
- OpenGLPipeline::invokeFunctor(thread, functor);
- break;
case RenderPipelineType::SkiaGL:
skiapipeline::SkiaOpenGLPipeline::invokeFunctor(thread, functor);
break;
@@ -117,19 +99,7 @@
}
void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- auto renderType = Properties::getRenderPipelineType();
- switch (renderType) {
- case RenderPipelineType::OpenGL:
- OpenGLPipeline::prepareToDraw(thread, bitmap);
- break;
- case RenderPipelineType::SkiaGL:
- case RenderPipelineType::SkiaVulkan:
- skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
- break;
- default:
- LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
- break;
- }
+ skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
}
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
@@ -582,37 +552,15 @@
}
void CanvasContext::trimMemory(RenderThread& thread, int level) {
- auto renderType = Properties::getRenderPipelineType();
- switch (renderType) {
- case RenderPipelineType::OpenGL: {
- // No context means nothing to free
- if (!thread.eglManager().hasEglContext()) return;
- ATRACE_CALL();
- if (level >= TRIM_MEMORY_COMPLETE) {
- thread.renderState().flush(Caches::FlushMode::Full);
- thread.eglManager().destroy();
- } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- thread.renderState().flush(Caches::FlushMode::Moderate);
- }
- break;
- }
- case RenderPipelineType::SkiaGL:
- case RenderPipelineType::SkiaVulkan: {
- // No context means nothing to free
- if (!thread.getGrContext()) return;
- ATRACE_CALL();
- if (level >= TRIM_MEMORY_COMPLETE) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
- thread.eglManager().destroy();
- thread.vulkanManager().destroy();
- } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
- }
- break;
- }
- default:
- LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
- break;
+ ATRACE_CALL();
+ if (!thread.getGrContext()) return;
+ ATRACE_CALL();
+ if (level >= TRIM_MEMORY_COMPLETE) {
+ thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ thread.eglManager().destroy();
+ thread.vulkanManager().destroy();
+ } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
+ thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
}
}
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 4b154e6..f4d230e 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -175,16 +175,6 @@
String8 pipeline;
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
- case RenderPipelineType::OpenGL: {
- if (Caches::hasInstance()) {
- cachesOutput.appendFormat("Caches:\n");
- Caches::getInstance().dumpMemoryUsage(cachesOutput);
- } else {
- cachesOutput.appendFormat("No caches instance.");
- }
- pipeline.appendFormat("FrameBuilder");
- break;
- }
case RenderPipelineType::SkiaGL: {
mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
pipeline.appendFormat("Skia (OpenGL)");
@@ -208,9 +198,6 @@
if (!mReadback) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
- case RenderPipelineType::OpenGL:
- mReadback = new OpenGLReadbackImpl(*this);
- break;
case RenderPipelineType::SkiaGL:
mReadback = new skiapipeline::SkiaOpenGLReadback(*this);
break;
@@ -344,8 +331,6 @@
sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
- case RenderPipelineType::OpenGL:
- return OpenGLPipeline::allocateHardwareBitmap(*this, skBitmap);
case RenderPipelineType::SkiaGL:
return skiapipeline::SkiaOpenGLPipeline::allocateHardwareBitmap(*this, skBitmap);
case RenderPipelineType::SkiaVulkan:
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 0306498..f51e7cc 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -53,9 +53,7 @@
sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater(
renderthread::RenderThread& renderThread) {
android::uirenderer::renderthread::IRenderPipeline* pipeline;
- if (Properties::getRenderPipelineType() == RenderPipelineType::OpenGL) {
- pipeline = new renderthread::OpenGLPipeline(renderThread);
- } else if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
+ if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
pipeline = new skiapipeline::SkiaOpenGLPipeline(renderThread);
} else {
pipeline = new skiapipeline::SkiaVulkanPipeline(renderThread);
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 1bfa046..707d61a 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -60,18 +60,6 @@
Properties::overrideRenderPipelineType(oldType); \
};
-/**
- * Like gtests' TEST, but only runs with the OpenGL RenderPipelineType
- */
-#define OPENGL_PIPELINE_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_HwuiTest { \
- public: \
- static void doTheThing(); \
- }; \
- INNER_PIPELINE_TEST(test_case_name, test_name, OpenGL, \
- test_case_name##_##test_name##_HwuiTest::doTheThing()) \
- void test_case_name##_##test_name##_HwuiTest::doTheThing()
-
#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \
TestUtils::runOnRenderThread( \
@@ -86,7 +74,6 @@
public: \
static void doTheThing(renderthread::RenderThread& renderThread); \
}; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, OpenGL); \
INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
/* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
/* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
@@ -94,18 +81,6 @@
renderthread::RenderThread& renderThread)
/**
- * Like RENDERTHREAD_TEST, but only runs with the OpenGL RenderPipelineType
- */
-#define RENDERTHREAD_OPENGL_PIPELINE_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, OpenGL); \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
- renderthread::RenderThread& renderThread)
-
-/**
* Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
*/
#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 072719b..6f3d922 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -66,7 +66,7 @@
--onscreen Render tests on device screen. By default tests
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
- --renderer=TYPE Sets the render pipeline to use. May be opengl, skiagl, or skiavk
+ --renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
--render-ahead=NUM Sets how far to render-ahead. Must be 0 (default), 1, or 2.
)");
}
@@ -147,9 +147,7 @@
}
static bool setRenderer(const char* renderer) {
- if (!strcmp(renderer, "opengl")) {
- Properties::overrideRenderPipelineType(RenderPipelineType::OpenGL);
- } else if (!strcmp(renderer, "skiagl")) {
+ if (!strcmp(renderer, "skiagl")) {
Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
} else if (!strcmp(renderer, "skiavk")) {
Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan);
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
deleted file mode 100644
index 09f0b06..0000000
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * 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 <BakedOpDispatcher.h>
-#include <BakedOpRenderer.h>
-#include <FrameBuilder.h>
-#include <LayerUpdateQueue.h>
-#include <RecordedOp.h>
-#include <hwui/Paint.h>
-#include <tests/common/TestUtils.h>
-#include <utils/Color.h>
-
-#include <SkBlurDrawLooper.h>
-#include <SkDashPathEffect.h>
-#include <SkPath.h>
-
-using namespace android::uirenderer;
-
-static BakedOpRenderer::LightInfo sLightInfo;
-const FrameBuilder::LightGeometry sLightGeometry = {{100, 100, 100}, 50};
-
-class ValidatingBakedOpRenderer : public BakedOpRenderer {
-public:
- ValidatingBakedOpRenderer(RenderState& renderState,
- std::function<void(const Glop& glop)> validator)
- : BakedOpRenderer(Caches::getInstance(), renderState, true, false, sLightInfo)
- , mValidator(validator) {
- mGlopReceiver = ValidatingGlopReceiver;
- }
-
-private:
- static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds,
- const ClipBase* clip, const Glop& glop) {
- auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer);
- vbor->mValidator(glop);
- }
- std::function<void(const Glop& glop)> mValidator;
-};
-
-typedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&);
-
-static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op,
- std::function<void(const Glop& glop)> glopVerifier,
- int expectedGlopCount = 1) {
- // Create op, and wrap with basic state.
- LinearAllocator allocator;
- auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 100));
- auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op);
- ASSERT_NE(nullptr, state);
-
- int glopCount = 0;
- auto glopReceiver = [&glopVerifier, &glopCount, &expectedGlopCount](const Glop& glop) {
- ASSERT_LE(glopCount++, expectedGlopCount) << expectedGlopCount << "glop(s) expected";
- glopVerifier(glop);
- };
- ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
-
-// Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior
-#define X(Type) \
- [](BakedOpRenderer& renderer, const BakedOpState& state) { \
- BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \
- },
- static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
-#undef X
- unmergedReceivers[op->opId](renderer, *state);
- ASSERT_EQ(expectedGlopCount, glopCount) << "Exactly " << expectedGlopCount
- << "Glop(s) expected";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) {
- SkPaint strokePaint;
- strokePaint.setStyle(SkPaint::kStroke_Style);
- strokePaint.setStrokeWidth(4);
-
- float intervals[] = {1.0f, 1.0f};
- strokePaint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0));
-
- auto textureGlopVerifier = [](const Glop& glop) {
- // validate glop produced by renderPathTexture (so texture, unit quad)
- auto texture = glop.fill.texture.texture;
- ASSERT_NE(nullptr, texture);
- float expectedOffset = floor(4 * 1.5f + 0.5f);
- EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset)
- << "Should see conservative offset from PathCache::computeBounds";
- Rect expectedBounds(10, 15, 20, 25);
- expectedBounds.outset(expectedOffset);
-
- Matrix4 expectedModelView;
- expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0);
- expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1);
- EXPECT_EQ(expectedModelView, glop.transform.modelView)
- << "X and Y offsets, and scale both applied to model view";
- };
-
- // Arc and Oval will render functionally the same glop, differing only in texture content
- ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true);
- testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier);
-
- OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint);
- testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, onLayerOp_bufferless) {
- SkPaint layerPaint;
- layerPaint.setAlpha(128);
- OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case
- LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer);
- testUnmergedGlopDispatch(renderThread, &op,
- [](const Glop& glop) { ADD_FAILURE() << "Nothing should happen"; }, 0);
-}
-
-static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) {
- int result = 0;
- testUnmergedGlopDispatch(renderThread, op, [&result](const Glop& glop) {
- result = glop.transform.transformFlags;
- });
- return result;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, offsetFlags) {
- Rect bounds(10, 15, 20, 25);
- SkPaint paint;
- SkPaint aaPaint;
- aaPaint.setAntiAlias(true);
-
- RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270);
- EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp))
- << "Expect no offset for round rect op.";
-
- const float points[4] = {0.5, 0.5, 1.0, 1.0};
- PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
- EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp))
- << "Expect no offset for AA points.";
- PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
- EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp))
- << "Expect an offset for non-AA points.";
-
- LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4);
- EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp))
- << "Expect no offset for AA lines.";
- LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
- EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp))
- << "Expect an offset for non-AA lines.";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, renderTextWithShadow) {
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
-
- android::Paint shadowPaint;
- shadowPaint.setColor(SK_ColorRED);
-
- SkScalar sigma = Blur::convertRadiusToSigma(5);
- shadowPaint.setLooper(SkBlurDrawLooper::Make(SK_ColorWHITE, sigma, 3, 3));
-
- TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25);
- TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50);
- });
-
- int glopCount = 0;
- auto glopReceiver = [&glopCount](const Glop& glop) {
- if (glopCount < 2) {
- // two white shadows
- EXPECT_EQ(FloatColor({1, 1, 1, 1}), glop.fill.color);
- } else {
- // two text draws merged into one, drawn after both shadows
- EXPECT_EQ(FloatColor({1, 0, 0, 1}), glop.fill.color);
- }
- glopCount++;
- };
-
- ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
- ASSERT_EQ(3, glopCount) << "Exactly three glops expected";
-}
-
-static void validateLayerDraw(renderthread::RenderThread& renderThread,
- std::function<void(const Glop& glop)> validator) {
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.mutateLayerProperties().setType(LayerType::RenderLayer);
-
- // provide different blend mode, so decoration draws contrast
- props.mutateLayerProperties().setXferMode(SkBlendMode::kSrc);
- canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
- });
- OffscreenBuffer** layerHandle = node->getLayerHandle();
-
- auto syncedNode = TestUtils::getSyncedNode(node);
-
- // create RenderNode's layer here in same way prepareTree would
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
- *layerHandle = &layer;
- {
- LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
- layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(0, 0, 100, 100));
-
- ValidatingBakedOpRenderer renderer(renderThread.renderState(), validator);
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferLayers(layerUpdateQueue);
- frameBuilder.deferRenderNode(*syncedNode);
- frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
- }
-
- // clean up layer pointer, so we can safely destruct RenderNode
- *layerHandle = nullptr;
-}
-
-static FloatColor makeFloatColor(uint32_t color) {
- FloatColor c;
- c.set(color);
- return c;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, layerUpdateProperties) {
- for (bool debugOverdraw : {false, true}) {
- for (bool debugLayersUpdates : {false, true}) {
- ScopedProperty<bool> ovdProp(Properties::debugOverdraw, debugOverdraw);
- ScopedProperty<bool> lupProp(Properties::debugLayersUpdates, debugLayersUpdates);
-
- int glopCount = 0;
- validateLayerDraw(renderThread, [&glopCount, &debugLayersUpdates](const Glop& glop) {
- if (glopCount == 0) {
- // 0 - Black layer fill
- EXPECT_TRUE(glop.fill.colorEnabled);
- EXPECT_EQ(makeFloatColor(Color::Black), glop.fill.color);
- } else if (glopCount == 1) {
- // 1 - Uncolored (textured) layer draw
- EXPECT_FALSE(glop.fill.colorEnabled);
- } else if (glopCount == 2) {
- // 2 - layer overlay, if present
- EXPECT_TRUE(glop.fill.colorEnabled);
- // blend srcover, different from that of layer
- EXPECT_EQ(GLenum(GL_ONE), glop.blend.src);
- EXPECT_EQ(GLenum(GL_ONE_MINUS_SRC_ALPHA), glop.blend.dst);
- EXPECT_EQ(makeFloatColor(debugLayersUpdates ? 0x7f00ff00 : 0), glop.fill.color)
- << "Should be transparent green if debugLayersUpdates";
- } else if (glopCount < 7) {
- // 3 - 6 - overdraw indicator overlays, if present
- EXPECT_TRUE(glop.fill.colorEnabled);
- uint32_t expectedColor = Caches::getInstance().getOverdrawColor(glopCount - 2);
- ASSERT_EQ(makeFloatColor(expectedColor), glop.fill.color);
- } else {
- ADD_FAILURE() << "Too many glops observed";
- }
- glopCount++;
- });
- int expectedCount = 2;
- if (debugLayersUpdates || debugOverdraw) expectedCount++;
- if (debugOverdraw) expectedCount += 4;
- EXPECT_EQ(expectedCount, glopCount);
- }
- }
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTextureSnapping) {
- Rect bounds(10, 15, 20, 25);
- SkPaint paint;
- SkPath path;
- path.addRect(SkRect::MakeXYWH(1.5, 3.8, 100, 90));
- PathOp op(bounds, Matrix4::identity(), nullptr, &paint, &path);
- testUnmergedGlopDispatch(renderThread, &op, [](const Glop& glop) {
- auto texture = glop.fill.texture.texture;
- ASSERT_NE(nullptr, texture);
- EXPECT_EQ(1, reinterpret_cast<PathTexture*>(texture)->left);
- EXPECT_EQ(3, reinterpret_cast<PathTexture*>(texture)->top);
- });
-}
diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp
deleted file mode 100644
index 1a3ec39..0000000
--- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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 <BakedOpRenderer.h>
-#include <GlopBuilder.h>
-#include <tests/common/TestUtils.h>
-
-using namespace android::uirenderer;
-
-const BakedOpRenderer::LightInfo sLightInfo = {128, 128};
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, startRepaintLayer_clear) {
- BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, false,
- sLightInfo);
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u);
-
- layer.dirty(Rect(200, 200));
- {
- renderer.startRepaintLayer(&layer, Rect(200, 200));
- EXPECT_TRUE(layer.region.isEmpty()) << "Repaint full layer should clear region";
- renderer.endLayer();
- }
-
- layer.dirty(Rect(200, 200));
- {
- renderer.startRepaintLayer(&layer, Rect(100, 200)); // repainting left side
- EXPECT_TRUE(layer.region.isRect());
- // ALOGD("bounds %d %d %d %d", RECT_ARGS(layer.region.getBounds()));
- EXPECT_EQ(android::Rect(100, 0, 200, 200), layer.region.getBounds())
- << "Left side being repainted, so right side should be clear";
- renderer.endLayer();
- }
-
- // right side is now only dirty portion
- {
- renderer.startRepaintLayer(&layer, Rect(100, 0, 200, 200)); // repainting right side
- EXPECT_TRUE(layer.region.isEmpty())
- << "Now right side being repainted, so region should be entirely clear";
- renderer.endLayer();
- }
-}
-
-static void drawFirstOp(RenderState& renderState, int color, SkBlendMode mode) {
- BakedOpRenderer renderer(Caches::getInstance(), renderState, true, false, sLightInfo);
-
- renderer.startFrame(100, 100, Rect(100, 100));
- SkPaint paint;
- paint.setColor(color);
- paint.setBlendMode(mode);
-
- Rect dest(0, 0, 100, 100);
- Glop glop;
- GlopBuilder(renderState, Caches::getInstance(), &glop)
- .setRoundRectClipState(nullptr)
- .setMeshUnitQuad()
- .setFillPaint(paint, 1.0f)
- .setTransform(Matrix4::identity(), TransformFlags::None)
- .setModelViewMapUnitToRectSnap(dest)
- .build();
- renderer.renderGlop(nullptr, nullptr, glop);
- renderer.endFrame(Rect(100, 100));
-}
-
-static void verifyBlend(RenderState& renderState, GLenum expectedSrc, GLenum expectedDst) {
- EXPECT_TRUE(renderState.blend().getEnabled());
- GLenum src;
- GLenum dst;
- renderState.blend().getFactors(&src, &dst);
- EXPECT_EQ(expectedSrc, src);
- EXPECT_EQ(expectedDst, dst);
-}
-
-static void verifyBlendDisabled(RenderState& renderState) {
- EXPECT_FALSE(renderState.blend().getEnabled());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_clear) {
- // initialize blend state to nonsense value
- renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE);
-
- drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kClear);
- verifyBlend(renderThread.renderState(), GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_srcover) {
- // initialize blend state to nonsense value
- renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE);
-
- drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kSrcOver);
- verifyBlendDisabled(renderThread.renderState());
-}
diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp
deleted file mode 100644
index 6f8e249..0000000
--- a/libs/hwui/tests/unit/BakedOpStateTests.cpp
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2015 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 <BakedOpState.h>
-#include <ClipArea.h>
-#include <RecordedOp.h>
-#include <tests/common/TestUtils.h>
-
-namespace android {
-namespace uirenderer {
-
-TEST(ResolvedRenderState, construct) {
- LinearAllocator allocator;
- Matrix4 translate10x20;
- translate10x20.loadTranslate(10, 20, 0);
-
- SkPaint paint;
- ClipRect clip(Rect(100, 200));
- RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, &clip, &paint);
- {
- // recorded with transform, no parent transform
- auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
- ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
- EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
- EXPECT_EQ(Rect(100, 200), state.clipRect());
- EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped
- EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
- }
- {
- // recorded with transform and parent transform
- auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
- ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
-
- Matrix4 expectedTranslate;
- expectedTranslate.loadTranslate(20, 40, 0);
- EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform);
-
- // intersection of parent & transformed child clip
- EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect());
-
- // translated and also clipped
- EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds);
- EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
- }
-}
-
-TEST(ResolvedRenderState, computeLocalSpaceClip) {
- LinearAllocator allocator;
- Matrix4 translate10x20;
- translate10x20.loadTranslate(10, 20, 0);
-
- SkPaint paint;
- ClipRect clip(Rect(100, 200));
- RectOp recordedOp(Rect(1000, 1000), translate10x20, &clip, &paint);
- {
- // recorded with transform, no parent transform
- auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
- ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
- EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
- << "Local clip rect should be 100x200, offset by -10,-20";
- }
- {
- // recorded with transform + parent transform
- auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
- ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false);
- EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
- << "Local clip rect should be 90x190, offset by -10,-20";
- }
-}
-
-const float HAIRLINE = 0.0f;
-
-// Note: bounds will be conservative, but not precise for non-hairline
-// - use approx bounds checks for these
-const float SEMI_HAIRLINE = 0.3f;
-
-struct StrokeTestCase {
- float scale;
- float strokeWidth;
- const std::function<void(const ResolvedRenderState&)> validator;
-};
-
-const static StrokeTestCase sStrokeTestCases[] = {
- {1, HAIRLINE,
- [](const ResolvedRenderState& state) {
- EXPECT_EQ(Rect(49.5f, 49.5f, 150.5f, 150.5f), state.clippedBounds);
- }},
- {1, SEMI_HAIRLINE,
- [](const ResolvedRenderState& state) {
- EXPECT_TRUE(state.clippedBounds.contains(49.5f, 49.5f, 150.5f, 150.5f));
- EXPECT_TRUE(Rect(49, 49, 151, 151).contains(state.clippedBounds));
- }},
- {1, 20,
- [](const ResolvedRenderState& state) {
- EXPECT_EQ(Rect(40, 40, 160, 160), state.clippedBounds);
- }},
-
- // 3x3 scale:
- {3, HAIRLINE,
- [](const ResolvedRenderState& state) {
- EXPECT_EQ(Rect(149.5f, 149.5f, 200, 200), state.clippedBounds);
- EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
- }},
- {3, SEMI_HAIRLINE,
- [](const ResolvedRenderState& state) {
- EXPECT_TRUE(state.clippedBounds.contains(149.5f, 149.5f, 200, 200));
- EXPECT_TRUE(Rect(149, 149, 200, 200).contains(state.clippedBounds));
- }},
- {3, 20,
- [](const ResolvedRenderState& state) {
- EXPECT_TRUE(state.clippedBounds.contains(120, 120, 200, 200));
- EXPECT_TRUE(Rect(119, 119, 200, 200).contains(state.clippedBounds));
- }},
-
- // 0.5f x 0.5f scale
- {0.5f, HAIRLINE,
- [](const ResolvedRenderState& state) {
- EXPECT_EQ(Rect(24.5f, 24.5f, 75.5f, 75.5f), state.clippedBounds);
- }},
- {0.5f, SEMI_HAIRLINE,
- [](const ResolvedRenderState& state) {
- EXPECT_TRUE(state.clippedBounds.contains(24.5f, 24.5f, 75.5f, 75.5f));
- EXPECT_TRUE(Rect(24, 24, 76, 76).contains(state.clippedBounds));
- }},
- {0.5f, 20, [](const ResolvedRenderState& state) {
- EXPECT_TRUE(state.clippedBounds.contains(19.5f, 19.5f, 80.5f, 80.5f));
- EXPECT_TRUE(Rect(19, 19, 81, 81).contains(state.clippedBounds));
- }}};
-
-TEST(ResolvedRenderState, construct_expandForStroke) {
- LinearAllocator allocator;
- // Loop over table of test cases and verify different combinations of stroke width and transform
- for (auto&& testCase : sStrokeTestCases) {
- SkPaint strokedPaint;
- strokedPaint.setAntiAlias(true);
- strokedPaint.setStyle(SkPaint::kStroke_Style);
- strokedPaint.setStrokeWidth(testCase.strokeWidth);
-
- ClipRect clip(Rect(200, 200));
- RectOp recordedOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &strokedPaint);
-
- Matrix4 snapshotMatrix;
- snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1);
- auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200));
-
- ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true, false);
- testCase.validator(state);
- }
-}
-
-TEST(BakedOpState, tryConstruct) {
- Matrix4 translate100x0;
- translate100x0.loadTranslate(100, 0, 0);
-
- SkPaint paint;
- ClipRect clip(Rect(100, 200));
-
- LinearAllocator allocator;
- RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
- auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
- EXPECT_NE(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, successOp))
- << "successOp NOT rejected by clip, so should be constructed";
- size_t successAllocSize = allocator.usedSize();
- EXPECT_LE(64u, successAllocSize) << "relatively large alloc for non-rejected op";
-
- RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
- EXPECT_EQ(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, rejectOp))
- << "rejectOp rejected by clip, so should not be constructed";
-
- // NOTE: this relies on the clip having already been serialized by the op above
- EXPECT_EQ(successAllocSize, allocator.usedSize()) << "no extra allocation used for rejected op";
-}
-
-TEST(BakedOpState, tryShadowOpConstruct) {
- Matrix4 translate10x20;
- translate10x20.loadTranslate(10, 20, 0);
-
- LinearAllocator allocator;
- {
- auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect()); // Note: empty clip
- BakedOpState* bakedState =
- BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
-
- EXPECT_EQ(nullptr, bakedState) << "op should be rejected by clip, so not constructed";
- EXPECT_EQ(0u, allocator.usedSize()) << "no serialization, even for clip,"
- "since op is quick rejected based on snapshot clip";
- }
- {
- auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
- BakedOpState* bakedState =
- BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
-
- ASSERT_NE(nullptr, bakedState) << "NOT rejected by clip, so op should be constructed";
- EXPECT_LE(64u, allocator.usedSize()) << "relatively large alloc for non-rejected op";
-
- EXPECT_MATRIX_APPROX_EQ(translate10x20, bakedState->computedState.transform);
- EXPECT_EQ(Rect(100, 200), bakedState->computedState.clippedBounds);
- }
-}
-
-TEST(BakedOpState, tryStrokeableOpConstruct) {
- LinearAllocator allocator;
- {
- // check regular rejection
- SkPaint paint;
- paint.setStyle(SkPaint::kStrokeAndFill_Style);
- paint.setStrokeWidth(0.0f);
- ClipRect clip(Rect(100, 200));
- RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint);
- auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip
- auto bakedState = BakedOpState::tryStrokeableOpConstruct(
- allocator, *snapshot, rejectOp, BakedOpState::StrokeBehavior::StyleDefined, false);
-
- EXPECT_EQ(nullptr, bakedState);
- EXPECT_GT(8u,
- allocator.usedSize()); // no significant allocation space used for rejected op
- }
- {
- // check simple unscaled expansion
- SkPaint paint;
- paint.setStyle(SkPaint::kStrokeAndFill_Style);
- paint.setStrokeWidth(10.0f);
- ClipRect clip(Rect(200, 200));
- RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
- auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
- auto bakedState = BakedOpState::tryStrokeableOpConstruct(
- allocator, *snapshot, rejectOp, BakedOpState::StrokeBehavior::StyleDefined, false);
-
- ASSERT_NE(nullptr, bakedState);
- EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds);
- EXPECT_EQ(0, bakedState->computedState.clipSideFlags);
- }
- {
- // check simple unscaled expansion, and fill style with stroke forced
- SkPaint paint;
- paint.setStyle(SkPaint::kFill_Style);
- paint.setStrokeWidth(10.0f);
- ClipRect clip(Rect(200, 200));
- RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
- auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
- auto bakedState = BakedOpState::tryStrokeableOpConstruct(
- allocator, *snapshot, rejectOp, BakedOpState::StrokeBehavior::Forced, false);
-
- ASSERT_NE(nullptr, bakedState);
- EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds);
- EXPECT_EQ(0, bakedState->computedState.clipSideFlags);
- }
-}
-
-} // namespace uirenderer
-} // namespace android
diff --git a/libs/hwui/tests/unit/DeviceInfoTests.cpp b/libs/hwui/tests/unit/DeviceInfoTests.cpp
deleted file mode 100644
index af37938..0000000
--- a/libs/hwui/tests/unit/DeviceInfoTests.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 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 <DeviceInfo.h>
-
-#include <gtest/gtest.h>
-#include "tests/common/TestUtils.h"
-
-using namespace android;
-using namespace android::uirenderer;
-
-OPENGL_PIPELINE_TEST(DeviceInfo, basic) {
- // can't assert state before init - another test may have initialized the singleton
- DeviceInfo::initialize();
- const DeviceInfo* di = DeviceInfo::get();
- ASSERT_NE(nullptr, di) << "DeviceInfo initialization failed";
- EXPECT_EQ(2048, di->maxTextureSize()) << "Max texture size didn't match";
-}
diff --git a/libs/hwui/tests/unit/FontRendererTests.cpp b/libs/hwui/tests/unit/FontRendererTests.cpp
deleted file mode 100644
index c78f131..0000000
--- a/libs/hwui/tests/unit/FontRendererTests.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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 "GammaFontRenderer.h"
-#include "tests/common/TestUtils.h"
-
-using namespace android::uirenderer;
-
-static bool isZero(uint8_t* data, int size) {
- for (int i = 0; i < size; i++) {
- if (data[i]) return false;
- }
- return true;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FontRenderer, renderDropShadow) {
- SkPaint paint;
- paint.setTextSize(10);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
- GammaFontRenderer gammaFontRenderer;
- FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
- fontRenderer.setFont(&paint, SkMatrix::I());
-
- std::vector<glyph_t> glyphs;
- std::vector<float> positions;
- float totalAdvance;
- Rect bounds;
- TestUtils::layoutTextUnscaled(paint, "This is a test", &glyphs, &positions, &totalAdvance,
- &bounds);
-
- for (int radius : {28, 20, 2}) {
- auto result = fontRenderer.renderDropShadow(&paint, glyphs.data(), glyphs.size(), radius,
- positions.data());
- ASSERT_NE(nullptr, result.image);
- EXPECT_FALSE(isZero(result.image, result.width * result.height));
- EXPECT_LE(bounds.getWidth() + radius * 2, (int)result.width);
- EXPECT_LE(bounds.getHeight() + radius * 2, (int)result.height);
- delete result.image;
- }
-}
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
deleted file mode 100644
index 4eb7751..0000000
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ /dev/null
@@ -1,2705 +0,0 @@
-/*
- * 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 <BakedOpState.h>
-#include <DeferredLayerUpdater.h>
-#include <FrameBuilder.h>
-#include <GlLayer.h>
-#include <LayerUpdateQueue.h>
-#include <RecordedOp.h>
-#include <RecordingCanvas.h>
-#include <tests/common/TestUtils.h>
-
-#include <unordered_map>
-
-namespace android {
-namespace uirenderer {
-
-const FrameBuilder::LightGeometry sLightGeometry = {{100, 100, 100}, 50};
-
-/**
- * Virtual class implemented by each test to redirect static operation / state transitions to
- * virtual methods.
- *
- * Virtual dispatch allows for default behaviors to be specified (very common case in below tests),
- * and allows Renderer vs Dispatching behavior to be merged.
- *
- * onXXXOp methods fail by default - tests should override ops they expect
- * startRepaintLayer fails by default - tests should override if expected
- * startFrame/endFrame do nothing by default - tests should override to intercept
- */
-class TestRendererBase {
-public:
- virtual ~TestRendererBase() {}
- virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) {
- ADD_FAILURE() << "Temporary layers not expected in this test";
- return nullptr;
- }
- virtual void recycleTemporaryLayer(OffscreenBuffer*) {
- ADD_FAILURE() << "Temporary layers not expected in this test";
- }
- virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
- ADD_FAILURE() << "Layer repaint not expected in this test";
- }
- virtual void endLayer() { ADD_FAILURE() << "Layer updates not expected in this test"; }
- virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
- virtual void endFrame(const Rect& repaintRect) {}
-
-// define virtual defaults for single draw methods
-#define X(Type) \
- virtual void on##Type(const Type&, const BakedOpState&) { \
- ADD_FAILURE() << #Type " not expected in this test"; \
- }
- MAP_RENDERABLE_OPS(X)
-#undef X
-
-// define virtual defaults for merged draw methods
-#define X(Type) \
- virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \
- ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \
- }
- MAP_MERGEABLE_OPS(X)
-#undef X
-
- int getIndex() { return mIndex; }
-
-protected:
- int mIndex = 0;
-};
-
-/**
- * Dispatches all static methods to similar formed methods on renderer, which fail by default but
- * are overridden by subclasses per test.
- */
-class TestDispatcher {
-public:
-// define single op methods, which redirect to TestRendererBase
-#define X(Type) \
- static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \
- renderer.on##Type(op, state); \
- }
- MAP_RENDERABLE_OPS(X);
-#undef X
-
-// define merged op methods, which redirect to TestRendererBase
-#define X(Type) \
- static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \
- renderer.onMerged##Type##s(opList); \
- }
- MAP_MERGEABLE_OPS(X);
-#undef X
-};
-
-class FailRenderer : public TestRendererBase {};
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simple) {
- class SimpleTestRenderer : public TestRendererBase {
- public:
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(100u, width);
- EXPECT_EQ(200u, height);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- }
- void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_EQ(2, mIndex++);
- }
- void endFrame(const Rect& repaintRect) override { EXPECT_EQ(3, mIndex++); }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
- canvas.drawRect(0, 0, 100, 200, SkPaint());
- canvas.drawBitmap(*bitmap, 10, 10, nullptr);
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SimpleTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleStroke) {
- class SimpleStrokeTestRenderer : public TestRendererBase {
- public:
- void onPointsOp(const PointsOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- // even though initial bounds are empty...
- EXPECT_TRUE(op.unmappedBounds.isEmpty())
- << "initial bounds should be empty, since they're unstroked";
- EXPECT_EQ(Rect(45, 45, 55, 55), state.computedState.clippedBounds)
- << "final bounds should account for stroke";
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- SkPaint strokedPaint;
- strokedPaint.setStrokeWidth(10);
- canvas.drawPoint(50, 50, strokedPaint);
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SimpleStrokeTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, arcStrokeClip) {
- class ArcStrokeClipTestRenderer : public TestRendererBase {
- public:
- void onArcOp(const ArcOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(25, 25, 175, 175), op.unmappedBounds);
- EXPECT_EQ(Rect(25, 25, 175, 175), state.computedState.clippedBounds);
- EXPECT_EQ(OpClipSideFlags::Full, state.computedState.clipSideFlags)
- << "Arc op clipped conservatively, since path texture may be expanded";
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.clipRect(25, 25, 175, 175, SkClipOp::kIntersect);
- SkPaint aaPaint;
- aaPaint.setAntiAlias(true);
- canvas.drawArc(25, 25, 175, 175, 40, 180, true, aaPaint);
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- ArcStrokeClipTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleRejection) {
- auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RenderProperties& props,
- RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipRect(200, 200, 400, 400, SkClipOp::kIntersect); // intersection should be empty
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- canvas.restore();
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- FailRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleBatching) {
- const int LOOPS = 5;
- class SimpleBatchingTestRenderer : public TestRendererBase {
- public:
- void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects";
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps";
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
-
- sk_sp<Bitmap> bitmap(TestUtils::createBitmap(
- 10, 10,
- kAlpha_8_SkColorType)); // Disable merging by using alpha 8 bitmap
-
- // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
- // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
- canvas.save(SaveFlags::MatrixClip);
- for (int i = 0; i < LOOPS; i++) {
- canvas.translate(0, 10);
- canvas.drawRect(0, 0, 10, 10, SkPaint());
- canvas.drawBitmap(*bitmap, 5, 0, nullptr);
- }
- canvas.restore();
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SimpleBatchingTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2 * LOOPS, renderer.getIndex()) << "Expect number of ops = 2 * loop count";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferRenderNode_translateClip) {
- class DeferRenderNodeTranslateClipTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(5, 10, 55, 60), state.computedState.clippedBounds);
- EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom,
- state.computedState.clipSideFlags);
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(5, 10, Rect(50, 50), // translate + clip node
- *TestUtils::getSyncedNode(node));
-
- DeferRenderNodeTranslateClipTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferRenderNodeScene) {
- class DeferRenderNodeSceneTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- const Rect& clippedBounds = state.computedState.clippedBounds;
- Matrix4 expected;
- switch (mIndex++) {
- case 0:
- // background - left side
- EXPECT_EQ(Rect(600, 100, 700, 500), clippedBounds);
- expected.loadTranslate(100, 100, 0);
- break;
- case 1:
- // background - top side
- EXPECT_EQ(Rect(100, 400, 600, 500), clippedBounds);
- expected.loadTranslate(100, 100, 0);
- break;
- case 2:
- // content
- EXPECT_EQ(Rect(100, 100, 700, 500), clippedBounds);
- expected.loadTranslate(-50, -50, 0);
- break;
- case 3:
- // overlay
- EXPECT_EQ(Rect(0, 0, 800, 200), clippedBounds);
- break;
- default:
- ADD_FAILURE() << "Too many rects observed";
- }
- EXPECT_EQ(expected, state.computedState.transform);
- }
- };
-
- std::vector<sp<RenderNode>> nodes;
- SkPaint transparentPaint;
- transparentPaint.setAlpha(128);
-
- // backdrop
- nodes.push_back(TestUtils::createNode<RecordingCanvas>(
- 100, 100, 700, 500, // 600x400
- [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 600, 400, transparentPaint);
- }));
-
- // content
- Rect contentDrawBounds(150, 150, 650, 450); // 500x300
- nodes.push_back(TestUtils::createNode<RecordingCanvas>(
- 0, 0, 800, 600, [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 800, 600, transparentPaint);
- }));
-
- // overlay
- nodes.push_back(TestUtils::createNode<RecordingCanvas>(
- 0, 0, 800, 600, [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 800, 200, transparentPaint);
- }));
-
- for (auto& node : nodes) {
- TestUtils::syncHierarchyPropertiesAndDisplayList(node);
- }
-
- {
- FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
-
- DeferRenderNodeSceneTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
- }
-
- for (auto& node : nodes) {
- EXPECT_TRUE(node->isValid());
- EXPECT_FALSE(node->nothingToDraw());
- node->setStagingDisplayList(nullptr);
- EXPECT_FALSE(node->isValid());
- EXPECT_FALSE(node->nothingToDraw());
- node->destroyHardwareResources();
- EXPECT_TRUE(node->nothingToDraw());
- EXPECT_FALSE(node->isValid());
- }
-
- {
- // Validate no crashes if any nodes are missing DisplayLists
- FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds);
-
- FailRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- }
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_noFbo0) {
- class EmptyNoFbo0TestRenderer : public TestRendererBase {
- public:
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- ADD_FAILURE() << "Primary frame draw not expected in this test";
- }
- void endFrame(const Rect& repaintRect) override {
- ADD_FAILURE() << "Primary frame draw not expected in this test";
- }
- };
-
- // Use layer update constructor, so no work is enqueued for Fbo0
- LayerUpdateQueue emptyLayerUpdateQueue;
- FrameBuilder frameBuilder(emptyLayerUpdateQueue, sLightGeometry, Caches::getInstance());
- EmptyNoFbo0TestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_withFbo0) {
- class EmptyWithFbo0TestRenderer : public TestRendererBase {
- public:
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- EXPECT_EQ(0, mIndex++);
- }
- void endFrame(const Rect& repaintRect) override { EXPECT_EQ(1, mIndex++); }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(
- 10, 10, 110, 110, [](RenderProperties& props, RecordingCanvas& canvas) {
- // no drawn content
- });
-
- // Draw, but pass node without draw content, so no work is done for primary frame
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- EmptyWithFbo0TestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced,"
- " but fbo0 update lifecycle should still be observed";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, avoidOverdraw_rects) {
- class AvoidOverdrawRectsTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(mIndex++, 0) << "Should be one rect";
- EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds)
- << "Last rect should occlude others.";
- }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- canvas.drawRect(10, 10, 190, 190, SkPaint());
- });
-
- // Damage (and therefore clip) is same as last draw, subset of renderable area.
- // This means last op occludes other contents, and they'll be rejected to avoid overdraw.
- FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 190, 190), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- EXPECT_EQ(3u, node->getDisplayList()->getOps().size())
- << "Recording must not have rejected ops, in order for this test to be valid";
-
- AvoidOverdrawRectsTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
- static sk_sp<Bitmap> opaqueBitmap(
- TestUtils::createBitmap(50, 50, SkColorType::kRGB_565_SkColorType));
- static sk_sp<Bitmap> transpBitmap(
- TestUtils::createBitmap(50, 50, SkColorType::kAlpha_8_SkColorType));
- class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase {
- public:
- void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- switch (mIndex++) {
- case 0:
- EXPECT_EQ(opaqueBitmap.get(), op.bitmap);
- break;
- case 1:
- EXPECT_EQ(transpBitmap.get(), op.bitmap);
- break;
- default:
- ADD_FAILURE() << "Only two ops expected.";
- }
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 50, 50, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 50, 50, SkPaint());
- canvas.drawRect(0, 0, 50, 50, SkPaint());
- canvas.drawBitmap(*transpBitmap, 0, 0, nullptr);
-
- // only the below draws should remain, since they're
- canvas.drawBitmap(*opaqueBitmap, 0, 0, nullptr);
- canvas.drawBitmap(*transpBitmap, 0, 0, nullptr);
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(50, 50), 50, 50, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- EXPECT_EQ(5u, node->getDisplayList()->getOps().size())
- << "Recording must not have rejected ops, in order for this test to be valid";
-
- AvoidOverdrawBitmapsTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly two ops";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clippedMerging) {
- class ClippedMergingTestRenderer : public TestRendererBase {
- public:
- void onMergedBitmapOps(const MergedBakedOpList& opList) override {
- EXPECT_EQ(0, mIndex);
- mIndex += opList.count;
- EXPECT_EQ(4u, opList.count);
- EXPECT_EQ(Rect(10, 10, 90, 90), opList.clip);
- EXPECT_EQ(OpClipSideFlags::Left | OpClipSideFlags::Top | OpClipSideFlags::Right,
- opList.clipSideFlags);
- }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- sk_sp<Bitmap> bitmap(TestUtils::createBitmap(20, 20));
-
- // left side clipped (to inset left half)
- canvas.clipRect(10, 0, 50, 100, SkClipOp::kReplace_deprecated);
- canvas.drawBitmap(*bitmap, 0, 40, nullptr);
-
- // top side clipped (to inset top half)
- canvas.clipRect(0, 10, 100, 50, SkClipOp::kReplace_deprecated);
- canvas.drawBitmap(*bitmap, 40, 0, nullptr);
-
- // right side clipped (to inset right half)
- canvas.clipRect(50, 0, 90, 100, SkClipOp::kReplace_deprecated);
- canvas.drawBitmap(*bitmap, 80, 40, nullptr);
-
- // bottom not clipped, just abutting (inset bottom half)
- canvas.clipRect(0, 50, 100, 90, SkClipOp::kReplace_deprecated);
- canvas.drawBitmap(*bitmap, 40, 70, nullptr);
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- ClippedMergingTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, regionClipStopsMerge) {
- class RegionClipStopsMergeTestRenderer : public TestRendererBase {
- public:
- void onTextOp(const TextOp& op, const BakedOpState& state) override { mIndex++; }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) {
- SkPath path;
- path.addCircle(200, 200, 200, SkPath::kCW_Direction);
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipPath(&path, SkClipOp::kIntersect);
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(50);
- TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
- TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 200);
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- RegionClipStopsMergeTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textMerging) {
- class TextMergingTestRenderer : public TestRendererBase {
- public:
- void onMergedTextOps(const MergedBakedOpList& opList) override {
- EXPECT_EQ(0, mIndex);
- mIndex += opList.count;
- EXPECT_EQ(2u, opList.count);
- EXPECT_EQ(OpClipSideFlags::Top, opList.clipSideFlags);
- EXPECT_EQ(OpClipSideFlags::Top, opList.states[0]->computedState.clipSideFlags);
- EXPECT_EQ(OpClipSideFlags::None, opList.states[1]->computedState.clipSideFlags);
- }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 400, 400, [](RenderProperties& props,
- RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(50);
- TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
- TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- TextMergingTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStrikethrough) {
- const int LOOPS = 5;
- class TextStrikethroughTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text";
- }
- void onMergedTextOps(const MergedBakedOpList& opList) override {
- EXPECT_EQ(0, mIndex);
- mIndex += opList.count;
- EXPECT_EQ(5u, opList.count);
- }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 2000, [](RenderProperties& props, RecordingCanvas& canvas) {
- SkPaint textPaint;
- textPaint.setAntiAlias(true);
- textPaint.setTextSize(20);
- textPaint.setFlags(textPaint.getFlags() | SkPaint::kStrikeThruText_ReserveFlag);
- for (int i = 0; i < LOOPS; i++) {
- TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
- }
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 2000), 200, 2000, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- TextStrikethroughTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2 * LOOPS, renderer.getIndex()) << "Expect number of ops = 2 * loop count";
-}
-
-static auto styles = {SkPaint::kFill_Style, SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style};
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStyle) {
- class TextStyleTestRenderer : public TestRendererBase {
- public:
- void onMergedTextOps(const MergedBakedOpList& opList) override {
- ASSERT_EQ(0, mIndex);
- ASSERT_EQ(3u, opList.count);
- mIndex += opList.count;
-
- int index = 0;
- for (auto style : styles) {
- auto state = opList.states[index++];
- ASSERT_EQ(style, state->op->paint->getStyle())
- << "Remainder of validation relies upon stable merged order";
- ASSERT_EQ(0, state->computedState.clipSideFlags)
- << "Clipped bounds validation requires unclipped ops";
- }
-
- Rect fill = opList.states[0]->computedState.clippedBounds;
- Rect stroke = opList.states[1]->computedState.clippedBounds;
- EXPECT_EQ(stroke, opList.states[2]->computedState.clippedBounds)
- << "Stroke+Fill should be same as stroke";
-
- EXPECT_TRUE(stroke.contains(fill));
- EXPECT_FALSE(fill.contains(stroke));
-
- // outset by half the stroke width
- Rect outsetFill(fill);
- outsetFill.outset(5);
- EXPECT_EQ(stroke, outsetFill);
- }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(50);
- paint.setStrokeWidth(10);
-
- // draw 3 copies of the same text overlapping, each with a different style.
- // They'll get merged, but with
- for (auto style : styles) {
- paint.setStyle(style);
- TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100);
- }
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
- TextStyleTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_clipLocalMatrix) {
- class TextureLayerClipLocalMatrixTestRenderer : public TestRendererBase {
- public:
- void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect());
- EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds);
-
- Matrix4 expected;
- expected.loadTranslate(5, 5, 0);
- EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
- }
- };
-
- auto layerUpdater =
- TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5));
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipRect(50, 50, 150, 150, SkClipOp::kIntersect);
- canvas.drawLayer(layerUpdater.get());
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- TextureLayerClipLocalMatrixTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_combineMatrices) {
- class TextureLayerCombineMatricesTestRenderer : public TestRendererBase {
- public:
- void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
-
- Matrix4 expected;
- expected.loadTranslate(35, 45, 0);
- EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
- }
- };
-
- auto layerUpdater =
- TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5));
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(30, 40);
- canvas.drawLayer(layerUpdater.get());
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- TextureLayerCombineMatricesTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_reject) {
- auto layerUpdater =
- TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5));
- EXPECT_EQ(Layer::Api::OpenGL, layerUpdater->backingLayer()->getApi());
-
- GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer());
- glLayer->setRenderTarget(GL_NONE); // Should be rejected
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.drawLayer(layerUpdater.get());
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- FailRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, functor_reject) {
- class FunctorTestRenderer : public TestRendererBase {
- public:
- void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- }
- };
- Functor noopFunctor;
-
- // 1 million pixel tall view, scrolled down 80%
- auto scrolledFunctorView = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 1000000, [&noopFunctor](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.translate(0, -800000);
- canvas.callDrawGLFunction(&noopFunctor, nullptr);
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(scrolledFunctorView));
-
- FunctorTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferColorOp_unbounded) {
- class ColorTestRenderer : public TestRendererBase {
- public:
- void onColorOp(const ColorOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds)
- << "Color op should be expanded to bounds of surrounding";
- }
- };
-
- auto unclippedColorView = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 10, 10, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setClipToBounds(false);
- canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(unclippedColorView));
-
- ColorTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex()) << "ColorOp should not be rejected";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderNode) {
- class RenderNodeTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- switch (mIndex++) {
- case 0:
- EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds);
- EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
- break;
- case 1:
- EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds);
- EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
- break;
- default:
- ADD_FAILURE();
- }
- }
- };
-
- auto child = TestUtils::createNode<RecordingCanvas>(
- 10, 10, 110, 110, [](RenderProperties& props, RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
-
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [&child](RenderProperties& props, RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setColor(SK_ColorDKGRAY);
- canvas.drawRect(0, 0, 200, 200, paint);
-
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(40, 40);
- canvas.drawRenderNode(child.get());
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- RenderNodeTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clipped) {
- class ClippedTestRenderer : public TestRendererBase {
- public:
- void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect());
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- sk_sp<Bitmap> bitmap(TestUtils::createBitmap(200, 200));
- canvas.drawBitmap(*bitmap, 0, 0, nullptr);
- });
-
- // clip to small area, should see in receiver
- FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 20, 30, 40), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- ClippedTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_simple) {
- class SaveLayerSimpleTestRenderer : public TestRendererBase {
- public:
- OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(180u, width);
- EXPECT_EQ(180u, height);
- return nullptr;
- }
- void endLayer() override { EXPECT_EQ(2, mIndex++); }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
- EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(180, 180), state.computedState.clipRect());
-
- Matrix4 expectedTransform;
- expectedTransform.loadTranslate(-10, -10, 0);
- EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(3, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
- void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
- EXPECT_EQ(4, mIndex++);
- EXPECT_EQ(nullptr, offscreenBuffer);
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(10, 10, 190, 190, SkPaint());
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SaveLayerSimpleTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(5, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_nested) {
- /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
- * - startTemporaryLayer2, rect2 endLayer2
- * - startTemporaryLayer1, rect1, drawLayer2, endLayer1
- * - startFrame, layerOp1, endFrame
- */
- class SaveLayerNestedTestRenderer : public TestRendererBase {
- public:
- OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
- const int index = mIndex++;
- if (index == 0) {
- EXPECT_EQ(400u, width);
- EXPECT_EQ(400u, height);
- return (OffscreenBuffer*)0x400;
- } else if (index == 3) {
- EXPECT_EQ(800u, width);
- EXPECT_EQ(800u, height);
- return (OffscreenBuffer*)0x800;
- } else {
- ADD_FAILURE();
- }
- return (OffscreenBuffer*)nullptr;
- }
- void endLayer() override {
- int index = mIndex++;
- EXPECT_TRUE(index == 2 || index == 6);
- }
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- EXPECT_EQ(7, mIndex++);
- }
- void endFrame(const Rect& repaintRect) override { EXPECT_EQ(9, mIndex++); }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- const int index = mIndex++;
- if (index == 1) {
- EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner rect
- } else if (index == 4) {
- EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer rect
- } else {
- ADD_FAILURE();
- }
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- const int index = mIndex++;
- if (index == 5) {
- EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle);
- EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner layer
- } else if (index == 8) {
- EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle);
- EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer layer
- } else {
- ADD_FAILURE();
- }
- }
- void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
- const int index = mIndex++;
- // order isn't important, but we need to see both
- if (index == 10) {
- EXPECT_EQ((OffscreenBuffer*)0x400, offscreenBuffer);
- } else if (index == 11) {
- EXPECT_EQ((OffscreenBuffer*)0x800, offscreenBuffer);
- } else {
- ADD_FAILURE();
- }
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 800, 800, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 800, 800, 128, SaveFlags::ClipToLayer);
- {
- canvas.drawRect(0, 0, 800, 800, SkPaint());
- canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
- { canvas.drawRect(0, 0, 400, 400, SkPaint()); }
- canvas.restore();
- }
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(800, 800), 800, 800, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SaveLayerNestedTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(12, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_contentRejection) {
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipRect(200, 200, 400, 400, SkClipOp::kIntersect);
- canvas.saveLayerAlpha(200, 200, 400, 400, 128, SaveFlags::ClipToLayer);
-
- // draw within save layer may still be recorded, but shouldn't be drawn
- canvas.drawRect(200, 200, 400, 400, SkPaint());
-
- canvas.restore();
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- FailRenderer renderer;
- // should see no ops, even within the layer, since the layer should be rejected
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_simple) {
- class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase {
- public:
- void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
- EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState);
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
- void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- ASSERT_NE(nullptr, op.paint);
- ASSERT_EQ(SkBlendMode::kClear, PaintUtils::getBlendModeDirect(op.paint));
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(2, mIndex++);
- EXPECT_EQ(Rect(200, 200), op.unmappedBounds);
- EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds);
- EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
- void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(3, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
- EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState);
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SaveLayerUnclippedSimpleTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_round) {
- class SaveLayerUnclippedRoundTestRenderer : public TestRendererBase {
- public:
- void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds)
- << "Bounds rect should round out";
- }
- void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {}
- void onRectOp(const RectOp& op, const BakedOpState& state) override {}
- void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds)
- << "Bounds rect should round out";
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RenderProperties& props,
- RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10.95f, 10.5f, 189.75f, 189.25f, // values should all round out
- 128, (SaveFlags::Flags)(0));
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SaveLayerUnclippedRoundTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_mergedClears) {
- class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase {
- public:
- void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- EXPECT_GT(4, index);
- EXPECT_EQ(5, op.unmappedBounds.getWidth());
- EXPECT_EQ(5, op.unmappedBounds.getHeight());
- if (index == 0) {
- EXPECT_EQ(Rect(10, 10), state.computedState.clippedBounds);
- } else if (index == 1) {
- EXPECT_EQ(Rect(190, 0, 200, 10), state.computedState.clippedBounds);
- } else if (index == 2) {
- EXPECT_EQ(Rect(0, 190, 10, 200), state.computedState.clippedBounds);
- } else if (index == 3) {
- EXPECT_EQ(Rect(190, 190, 200, 200), state.computedState.clippedBounds);
- }
- }
- void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
- EXPECT_EQ(4, mIndex++);
- ASSERT_EQ(op.vertexCount, 16u);
- for (size_t i = 0; i < op.vertexCount; i++) {
- auto v = op.vertices[i];
- EXPECT_TRUE(v.x == 0 || v.x == 10 || v.x == 190 || v.x == 200);
- EXPECT_TRUE(v.y == 0 || v.y == 10 || v.y == 190 || v.y == 200);
- }
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(5, mIndex++);
- }
- void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
- EXPECT_LT(5, mIndex++);
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
-
- int restoreTo = canvas.save(SaveFlags::MatrixClip);
- canvas.scale(2, 2);
- canvas.saveLayerAlpha(0, 0, 5, 5, 128, SaveFlags::MatrixClip);
- canvas.saveLayerAlpha(95, 0, 100, 5, 128, SaveFlags::MatrixClip);
- canvas.saveLayerAlpha(0, 95, 5, 100, 128, SaveFlags::MatrixClip);
- canvas.saveLayerAlpha(95, 95, 100, 100, 128, SaveFlags::MatrixClip);
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- canvas.restoreToCount(restoreTo);
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SaveLayerUnclippedMergedClearsTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(10, renderer.getIndex())
- << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect.";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_clearClip) {
- class SaveLayerUnclippedClearClipTestRenderer : public TestRendererBase {
- public:
- void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- }
- void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- ASSERT_NE(nullptr, op.paint);
- EXPECT_EQ(SkBlendMode::kClear, PaintUtils::getBlendModeDirect(op.paint));
- EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds)
- << "Expect dirty rect as clip";
- ASSERT_NE(nullptr, state.computedState.clipState);
- EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipState->rect);
- EXPECT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(2, mIndex++);
- }
- void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(3, mIndex++);
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- // save smaller than clip, so we get unclipped behavior
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- canvas.restore();
- });
-
- // draw with partial screen dirty, and assert we see that rect later
- FrameBuilder frameBuilder(SkRect::MakeLTRB(50, 50, 150, 150), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SaveLayerUnclippedClearClipTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_reject) {
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- // unclipped savelayer + rect both in area that won't intersect with dirty
- canvas.saveLayerAlpha(100, 100, 200, 200, 128, (SaveFlags::Flags)(0));
- canvas.drawRect(100, 100, 200, 200, SkPaint());
- canvas.restore();
- });
-
- // draw with partial screen dirty that doesn't intersect with savelayer
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- FailRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-}
-
-/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as:
- * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer
- * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe
- */
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_complex) {
- class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase {
- public:
- OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
- EXPECT_EQ(0, mIndex++); // savelayer first
- return (OffscreenBuffer*)0xabcd;
- }
- void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- EXPECT_TRUE(index == 1 || index == 7);
- }
- void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- EXPECT_TRUE(index == 2 || index == 8);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(3, mIndex++);
- Matrix4 expected;
- expected.loadTranslate(-100, -100, 0);
- EXPECT_EQ(Rect(100, 100, 200, 200), state.computedState.clippedBounds);
- EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
- }
- void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- EXPECT_TRUE(index == 4 || index == 10);
- }
- void endLayer() override { EXPECT_EQ(5, mIndex++); }
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- EXPECT_EQ(6, mIndex++);
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(9, mIndex++);
- EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
- }
- void endFrame(const Rect& repaintRect) override { EXPECT_EQ(11, mIndex++); }
- void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
- EXPECT_EQ(12, mIndex++);
- EXPECT_EQ((OffscreenBuffer*)0xabcd, offscreenBuffer);
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 600, 600, // 500x500 triggers clipping
- [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SaveFlags::Flags)0); // unclipped
- canvas.saveLayerAlpha(100, 100, 400, 400, 128, SaveFlags::ClipToLayer); // clipped
- canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SaveFlags::Flags)0); // unclipped
- canvas.drawRect(200, 200, 300, 300, SkPaint());
- canvas.restore();
- canvas.restore();
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(600, 600), 600, 600, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- SaveLayerUnclippedComplexTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(13, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, hwLayer_simple) {
- class HwLayerSimpleTestRenderer : public TestRendererBase {
- public:
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
- EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
- EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
-
- EXPECT_TRUE(state.computedState.transform.isIdentity())
- << "Transform should be reset within layer";
-
- EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
- << "Damage rect should be used to clip layer content";
- }
- void endLayer() override { EXPECT_EQ(2, mIndex++); }
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- EXPECT_EQ(3, mIndex++);
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(4, mIndex++);
- }
- void endFrame(const Rect& repaintRect) override { EXPECT_EQ(5, mIndex++); }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 10, 10, 110, 110, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.mutateLayerProperties().setType(LayerType::RenderLayer);
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
- OffscreenBuffer** layerHandle = node->getLayerHandle();
-
- // create RenderNode's layer here in same way prepareTree would
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
- *layerHandle = &layer;
-
- auto syncedNode = TestUtils::getSyncedNode(node);
-
- // only enqueue partial damage
- LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
- layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferLayers(layerUpdateQueue);
- frameBuilder.deferRenderNode(*syncedNode);
-
- HwLayerSimpleTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(6, renderer.getIndex());
-
- // clean up layer pointer, so we can safely destruct RenderNode
- *layerHandle = nullptr;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, hwLayer_complex) {
- /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
- * - startRepaintLayer(child), rect(grey), endLayer
- * - startTemporaryLayer, drawLayer(child), endLayer
- * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer
- * - startFrame, drawLayer(parent), endLayerb
- */
- class HwLayerComplexTestRenderer : public TestRendererBase {
- public:
- OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
- EXPECT_EQ(3, mIndex++); // savelayer first
- return (OffscreenBuffer*)0xabcd;
- }
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
- int index = mIndex++;
- if (index == 0) {
- // starting inner layer
- EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
- EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
- } else if (index == 6) {
- // starting outer layer
- EXPECT_EQ(200u, offscreenBuffer->viewportWidth);
- EXPECT_EQ(200u, offscreenBuffer->viewportHeight);
- } else {
- ADD_FAILURE();
- }
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- if (index == 1) {
- // inner layer's rect (white)
- EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
- } else if (index == 7) {
- // outer layer's rect (grey)
- EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
- } else {
- ADD_FAILURE();
- }
- }
- void endLayer() override {
- int index = mIndex++;
- EXPECT_TRUE(index == 2 || index == 5 || index == 9);
- }
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- EXPECT_EQ(10, mIndex++);
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- OffscreenBuffer* layer = *op.layerHandle;
- int index = mIndex++;
- if (index == 4) {
- EXPECT_EQ(100u, layer->viewportWidth);
- EXPECT_EQ(100u, layer->viewportHeight);
- } else if (index == 8) {
- EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
- } else if (index == 11) {
- EXPECT_EQ(200u, layer->viewportWidth);
- EXPECT_EQ(200u, layer->viewportHeight);
- } else {
- ADD_FAILURE();
- }
- }
- void endFrame(const Rect& repaintRect) override { EXPECT_EQ(12, mIndex++); }
- void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
- EXPECT_EQ(13, mIndex++);
- }
- };
-
- auto child = TestUtils::createNode<RecordingCanvas>(
- 50, 50, 150, 150, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.mutateLayerProperties().setType(LayerType::RenderLayer);
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
- OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
- *(child->getLayerHandle()) = &childLayer;
-
- RenderNode* childPtr = child.get();
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [childPtr](RenderProperties& props, RecordingCanvas& canvas) {
- props.mutateLayerProperties().setType(LayerType::RenderLayer);
- SkPaint paint;
- paint.setColor(SK_ColorDKGRAY);
- canvas.drawRect(0, 0, 200, 200, paint);
-
- canvas.saveLayerAlpha(50, 50, 150, 150, 128, SaveFlags::ClipToLayer);
- canvas.drawRenderNode(childPtr);
- canvas.restore();
- });
- OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
- *(parent->getLayerHandle()) = &parentLayer;
-
- auto syncedNode = TestUtils::getSyncedNode(parent);
-
- LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
- layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
- layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferLayers(layerUpdateQueue);
- frameBuilder.deferRenderNode(*syncedNode);
-
- HwLayerComplexTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(14, renderer.getIndex());
-
- // clean up layer pointers, so we can safely destruct RenderNodes
- *(child->getLayerHandle()) = nullptr;
- *(parent->getLayerHandle()) = nullptr;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, buildLayer) {
- class BuildLayerTestRenderer : public TestRendererBase {
- public:
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
- EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
- EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
- }
- void onColorOp(const ColorOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
-
- EXPECT_TRUE(state.computedState.transform.isIdentity())
- << "Transform should be reset within layer";
-
- EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
- << "Damage rect should be used to clip layer content";
- }
- void endLayer() override { EXPECT_EQ(2, mIndex++); }
- void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
- ADD_FAILURE() << "Primary frame draw not expected in this test";
- }
- void endFrame(const Rect& repaintRect) override {
- ADD_FAILURE() << "Primary frame draw not expected in this test";
- }
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 10, 10, 110, 110, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.mutateLayerProperties().setType(LayerType::RenderLayer);
- canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
- });
- OffscreenBuffer** layerHandle = node->getLayerHandle();
-
- // create RenderNode's layer here in same way prepareTree would
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
- *layerHandle = &layer;
-
- TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-
- // only enqueue partial damage
- LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
- layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
-
- // Draw, but pass empty node list, so no work is done for primary frame
- FrameBuilder frameBuilder(layerUpdateQueue, sLightGeometry, Caches::getInstance());
- BuildLayerTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex());
-
- // clean up layer pointer, so we can safely destruct RenderNode
- *layerHandle = nullptr;
-}
-
-namespace {
-
-static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
- SkPaint paint;
- // order put in blue channel, transparent so overlapped content doesn't get rejected
- paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder));
- canvas->drawRect(0, 0, 100, 100, paint);
-}
-static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z) {
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [expectedDrawOrder](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedRect(&canvas, expectedDrawOrder);
- });
- node->mutateStagingProperties().setTranslationZ(z);
- node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
- canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
-}
-
-static void drawOrderedNode(
- Canvas* canvas, uint8_t expectedDrawOrder,
- std::function<void(RenderProperties& props, RecordingCanvas& canvas)> setup) {
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100,
- [expectedDrawOrder, setup](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedRect(&canvas, expectedDrawOrder);
- if (setup) {
- setup(props, canvas);
- }
- });
- canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
-}
-
-class ZReorderTestRenderer : public TestRendererBase {
-public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
- EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
- }
-};
-
-} // end anonymous namespace
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, zReorder) {
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.insertReorderBarrier(true);
- canvas.insertReorderBarrier(false);
- drawOrderedNode(&canvas, 0,
- 10.0f); // in reorder=false at this point, so played inorder
- drawOrderedRect(&canvas, 1);
- canvas.insertReorderBarrier(true);
- drawOrderedNode(&canvas, 6, 2.0f);
- drawOrderedRect(&canvas, 3);
- drawOrderedNode(&canvas, 4, 0.0f);
- drawOrderedRect(&canvas, 5);
- drawOrderedNode(&canvas, 2, -2.0f);
- drawOrderedNode(&canvas, 7, 2.0f);
- canvas.insertReorderBarrier(false);
- drawOrderedRect(&canvas, 8);
- drawOrderedNode(&canvas, 9,
- -10.0f); // in reorder=false at this point, so played inorder
- canvas.insertReorderBarrier(true); // reorder a node ahead of drawrect op
- drawOrderedRect(&canvas, 11);
- drawOrderedNode(&canvas, 10, -1.0f);
- canvas.insertReorderBarrier(false);
- canvas.insertReorderBarrier(true); // test with two empty reorder sections
- canvas.insertReorderBarrier(true);
- canvas.insertReorderBarrier(false);
- drawOrderedRect(&canvas, 12);
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(13, renderer.getIndex());
-};
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorder) {
- static const int scrollX = 5;
- static const int scrollY = 10;
- class ProjectionReorderTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- const int index = mIndex++;
-
- Matrix4 expectedMatrix;
- switch (index) {
- case 0:
- EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
- EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
- expectedMatrix.loadIdentity();
- EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
- break;
- case 1:
- EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
- EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
- expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0);
- ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
- EXPECT_EQ(Rect(-35, -30, 45, 50),
- Rect(state.computedState.localProjectionPathMask->getBounds()));
- break;
- case 2:
- EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
- EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
- expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
- EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
- break;
- default:
- ADD_FAILURE();
- }
- EXPECT_EQ(expectedMatrix, state.computedState.transform);
- }
- };
-
- /**
- * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
- * with a projecting child (P) of its own. P would normally draw between B and C's "background"
- * draw, but because it is projected backwards, it's drawn in between B and C.
- *
- * The parent is scrolled by scrollX/scrollY, but this does not affect the background
- * (which isn't affected by scroll).
- */
- auto receiverBackground = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.setProjectionReceiver(true);
- // scroll doesn't apply to background, so undone via translationX/Y
- // NOTE: translationX/Y only! no other transform properties may be set for a proj
- // receiver!
- properties.setTranslationX(scrollX);
- properties.setTranslationY(scrollY);
-
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
- auto projectingRipple = TestUtils::createNode<RecordingCanvas>(
- 50, 0, 100, 50, [](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.setProjectBackwards(true);
- properties.setClipToBounds(false);
- SkPaint paint;
- paint.setColor(SK_ColorDKGRAY);
- canvas.drawRect(-10, -10, 60, 60, paint);
- });
- auto child = TestUtils::createNode<RecordingCanvas>(
- 0, 50, 100, 100,
- [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setColor(SK_ColorBLUE);
- canvas.drawRect(0, 0, 100, 50, paint);
- canvas.drawRenderNode(projectingRipple.get());
- });
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100,
- [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
- // Set a rect outline for the projecting ripple to be masked against.
- properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
-
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(-scrollX,
- -scrollY); // Apply scroll (note: bg undoes this internally)
- canvas.drawRenderNode(receiverBackground.get());
- canvas.drawRenderNode(child.get());
- canvas.restore();
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- ProjectionReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionHwLayer) {
- static const int scrollX = 5;
- static const int scrollY = 10;
- class ProjectionHwLayerTestRenderer : public TestRendererBase {
- public:
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
- EXPECT_EQ(0, mIndex++);
- }
- void onArcOp(const ArcOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
- }
- void endLayer() override { EXPECT_EQ(2, mIndex++); }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(3, mIndex++);
- ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
- }
- void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
- EXPECT_EQ(4, mIndex++);
- ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
- Matrix4 expected;
- expected.loadTranslate(100 - scrollX, 100 - scrollY, 0);
- EXPECT_EQ(expected, state.computedState.transform);
- EXPECT_EQ(Rect(-85, -80, 295, 300),
- Rect(state.computedState.localProjectionPathMask->getBounds()));
- }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(5, mIndex++);
- ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
- }
- };
- auto receiverBackground = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 400, [](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.setProjectionReceiver(true);
- // scroll doesn't apply to background, so undone via translationX/Y
- // NOTE: translationX/Y only! no other transform properties may be set for a proj
- // receiver!
- properties.setTranslationX(scrollX);
- properties.setTranslationY(scrollY);
-
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- });
- auto projectingRipple = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.setProjectBackwards(true);
- properties.setClipToBounds(false);
- canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
- });
- auto child = TestUtils::createNode<RecordingCanvas>(
- 100, 100, 300, 300,
- [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.mutateLayerProperties().setType(LayerType::RenderLayer);
- canvas.drawRenderNode(projectingRipple.get());
- canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint());
- });
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 400,
- [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
- // Set a rect outline for the projecting ripple to be masked against.
- properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
- canvas.translate(-scrollX,
- -scrollY); // Apply scroll (note: bg undoes this internally)
- canvas.drawRenderNode(receiverBackground.get());
- canvas.drawRenderNode(child.get());
- });
-
- OffscreenBuffer** layerHandle = child->getLayerHandle();
-
- // create RenderNode's layer here in same way prepareTree would, setting windowTransform
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200);
- Matrix4 windowTransform;
- windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin
- layer.setWindowTransform(windowTransform);
- *layerHandle = &layer;
-
- auto syncedNode = TestUtils::getSyncedNode(parent);
-
- LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
- layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200));
-
- FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferLayers(layerUpdateQueue);
- frameBuilder.deferRenderNode(*syncedNode);
-
- ProjectionHwLayerTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(6, renderer.getIndex());
-
- // clean up layer pointer, so we can safely destruct RenderNode
- *layerHandle = nullptr;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionChildScroll) {
- static const int scrollX = 500000;
- static const int scrollY = 0;
- class ProjectionChildScrollTestRenderer : public TestRendererBase {
- public:
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
- void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- ASSERT_NE(nullptr, state.computedState.clipState);
- ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode);
- ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect);
- EXPECT_TRUE(state.computedState.transform.isIdentity());
- }
- };
- auto receiverBackground = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 400, [](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.setProjectionReceiver(true);
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- });
- auto projectingRipple = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& properties, RecordingCanvas& canvas) {
- // scroll doesn't apply to background, so undone via translationX/Y
- // NOTE: translationX/Y only! no other transform properties may be set for a proj
- // receiver!
- properties.setTranslationX(scrollX);
- properties.setTranslationY(scrollY);
- properties.setProjectBackwards(true);
- properties.setClipToBounds(false);
- canvas.drawOval(0, 0, 200, 200, SkPaint());
- });
- auto child = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 400,
- [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
- // Record time clip will be ignored by projectee
- canvas.clipRect(100, 100, 300, 300, SkClipOp::kIntersect);
-
- canvas.translate(-scrollX,
- -scrollY); // Apply scroll (note: bg undoes this internally)
- canvas.drawRenderNode(projectingRipple.get());
- });
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 400, 400,
- [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
- canvas.drawRenderNode(receiverBackground.get());
- canvas.drawRenderNode(child.get());
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- ProjectionChildScrollTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-// creates a 100x100 shadow casting node with provided translationZ
-static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
- return TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [translationZ](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.setTranslationZ(translationZ);
- properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadow) {
- class ShadowTestRenderer : public TestRendererBase {
- public:
- void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_FLOAT_EQ(1.0f, op.casterAlpha);
- EXPECT_TRUE(op.shadowTask->casterPerimeter.isRect(nullptr));
- EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowTask->transformXY);
-
- Matrix4 expectedZ;
- expectedZ.loadTranslate(0, 0, 5);
- EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowTask->transformZ);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- }
- };
-
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.insertReorderBarrier(true);
- canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- ShadowTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowSaveLayer) {
- class ShadowSaveLayerTestRenderer : public TestRendererBase {
- public:
- OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
- EXPECT_EQ(0, mIndex++);
- return nullptr;
- }
- void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x);
- EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(2, mIndex++);
- }
- void endLayer() override { EXPECT_EQ(3, mIndex++); }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(4, mIndex++);
- }
- void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
- EXPECT_EQ(5, mIndex++);
- }
- };
-
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- // save/restore outside of reorderBarrier, so they don't get moved out of place
- canvas.translate(20, 10);
- int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SaveFlags::ClipToLayer);
- canvas.insertReorderBarrier(true);
- canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
- canvas.insertReorderBarrier(false);
- canvas.restoreToCount(count);
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
- (FrameBuilder::LightGeometry){{100, 100, 100}, 50},
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- ShadowSaveLayerTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(6, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowHwLayer) {
- class ShadowHwLayerTestRenderer : public TestRendererBase {
- public:
- void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
- EXPECT_EQ(0, mIndex++);
- }
- void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x);
- EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y);
- EXPECT_FLOAT_EQ(30, op.shadowTask->lightRadius);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(2, mIndex++);
- }
- void endLayer() override { EXPECT_EQ(3, mIndex++); }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(4, mIndex++);
- }
- };
-
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 50, 60, 150, 160, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.mutateLayerProperties().setType(LayerType::RenderLayer);
- canvas.insertReorderBarrier(true);
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(20, 10);
- canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
- canvas.restore();
- });
- OffscreenBuffer** layerHandle = parent->getLayerHandle();
-
- // create RenderNode's layer here in same way prepareTree would, setting windowTransform
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
- Matrix4 windowTransform;
- windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin
- layer.setWindowTransform(windowTransform);
- *layerHandle = &layer;
-
- auto syncedNode = TestUtils::getSyncedNode(parent);
- LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
- layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
- (FrameBuilder::LightGeometry){{100, 100, 100}, 30},
- Caches::getInstance());
- frameBuilder.deferLayers(layerUpdateQueue);
- frameBuilder.deferRenderNode(*syncedNode);
-
- ShadowHwLayerTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(5, renderer.getIndex());
-
- // clean up layer pointer, so we can safely destruct RenderNode
- *layerHandle = nullptr;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowLayering) {
- class ShadowLayeringTestRenderer : public TestRendererBase {
- public:
- void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- EXPECT_TRUE(index == 0 || index == 1);
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- int index = mIndex++;
- EXPECT_TRUE(index == 2 || index == 3);
- }
- };
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.insertReorderBarrier(true);
- canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
- canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
- });
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200,
- (FrameBuilder::LightGeometry){{100, 100, 100}, 50},
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- ShadowLayeringTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowClipping) {
- class ShadowClippingTestRenderer : public TestRendererBase {
- public:
- void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipState->rect)
- << "Shadow must respect pre-barrier canvas clip value.";
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
- }
- };
- auto parent = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- // Apply a clip before the reorder barrier/shadow casting child is drawn.
- // This clip must be applied to the shadow cast by the child.
- canvas.clipRect(25, 25, 75, 75, SkClipOp::kIntersect);
- canvas.insertReorderBarrier(true);
- canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
- (FrameBuilder::LightGeometry){{100, 100, 100}, 50},
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent));
-
- ShadowClippingTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-static void testProperty(
- std::function<void(RenderProperties&)> propSetupCallback,
- std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
- class PropertyTestRenderer : public TestRendererBase {
- public:
- explicit PropertyTestRenderer(
- std::function<void(const RectOp&, const BakedOpState&)> callback)
- : mCallback(callback) {}
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(mIndex++, 0);
- mCallback(op, state);
- }
- std::function<void(const RectOp&, const BakedOpState&)> mCallback;
- };
-
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) {
- propSetupCallback(props);
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- PropertyTestRenderer renderer(opValidateCallback);
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) {
- testProperty(
- [](RenderProperties& properties) {
- properties.setAlpha(0.5f);
- properties.setHasOverlappingRendering(false);
- },
- [](const RectOp& op, const BakedOpState& state) {
- EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op";
- });
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropClipping) {
- testProperty(
- [](RenderProperties& properties) {
- properties.setClipToBounds(true);
- properties.setClipBounds(Rect(10, 20, 300, 400));
- },
- [](const RectOp& op, const BakedOpState& state) {
- EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds)
- << "Clip rect should be intersection of node bounds and clip bounds";
- });
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropRevealClip) {
- testProperty(
- [](RenderProperties& properties) {
- properties.mutableRevealClip().set(true, 50, 50, 25);
- },
- [](const RectOp& op, const BakedOpState& state) {
- ASSERT_NE(nullptr, state.roundRectClipState);
- EXPECT_TRUE(state.roundRectClipState->highPriority);
- EXPECT_EQ(25, state.roundRectClipState->radius);
- EXPECT_EQ(Rect(50, 50, 50, 50), state.roundRectClipState->innerRect);
- });
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropOutlineClip) {
- testProperty(
- [](RenderProperties& properties) {
- properties.mutableOutline().setShouldClip(true);
- properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
- },
- [](const RectOp& op, const BakedOpState& state) {
- ASSERT_NE(nullptr, state.roundRectClipState);
- EXPECT_FALSE(state.roundRectClipState->highPriority);
- EXPECT_EQ(5, state.roundRectClipState->radius);
- EXPECT_EQ(Rect(15, 25, 25, 35), state.roundRectClipState->innerRect);
- });
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropTransform) {
- testProperty(
- [](RenderProperties& properties) {
- properties.setLeftTopRightBottom(10, 10, 110, 110);
-
- SkMatrix staticMatrix = SkMatrix::MakeScale(1.2f, 1.2f);
- properties.setStaticMatrix(&staticMatrix);
-
- // ignored, since static overrides animation
- SkMatrix animationMatrix = SkMatrix::MakeTrans(15, 15);
- properties.setAnimationMatrix(&animationMatrix);
-
- properties.setTranslationX(10);
- properties.setTranslationY(20);
- properties.setScaleX(0.5f);
- properties.setScaleY(0.7f);
- },
- [](const RectOp& op, const BakedOpState& state) {
- Matrix4 matrix;
- matrix.loadTranslate(10, 10, 0); // left, top
- matrix.scale(1.2f, 1.2f, 1); // static matrix
- // ignore animation matrix, since static overrides it
-
- // translation xy
- matrix.translate(10, 20);
-
- // scale xy (from default pivot - center)
- matrix.translate(50, 50);
- matrix.scale(0.5f, 0.7f, 1);
- matrix.translate(-50, -50);
- EXPECT_MATRIX_APPROX_EQ(matrix, state.computedState.transform)
- << "Op draw matrix must match expected combination of transformation "
- "properties";
- });
-}
-
-struct SaveLayerAlphaData {
- uint32_t layerWidth = 0;
- uint32_t layerHeight = 0;
- Rect rectClippedBounds;
- Matrix4 rectMatrix;
- Matrix4 drawLayerMatrix;
-};
-/**
- * Constructs a view to hit the temporary layer alpha property implementation:
- * a) 0 < alpha < 1
- * b) too big for layer (larger than maxTextureSize)
- * c) overlapping rendering content
- * returning observed data about layer size and content clip/transform.
- *
- * Used to validate clipping behavior of temporary layer, where requested layer size is reduced
- * (for efficiency, and to fit in layer size constraints) based on parent clip.
- */
-void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData,
- std::function<void(RenderProperties&)> propSetupCallback) {
- class SaveLayerAlphaClipTestRenderer : public TestRendererBase {
- public:
- explicit SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData) : mOutData(outData) {}
-
- OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
- EXPECT_EQ(0, mIndex++);
- mOutData->layerWidth = width;
- mOutData->layerHeight = height;
- return nullptr;
- }
- void onRectOp(const RectOp& op, const BakedOpState& state) override {
- EXPECT_EQ(1, mIndex++);
-
- mOutData->rectClippedBounds = state.computedState.clippedBounds;
- mOutData->rectMatrix = state.computedState.transform;
- }
- void endLayer() override { EXPECT_EQ(2, mIndex++); }
- void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
- EXPECT_EQ(3, mIndex++);
- mOutData->drawLayerMatrix = state.computedState.transform;
- }
- void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override {
- EXPECT_EQ(4, mIndex++);
- }
-
- private:
- SaveLayerAlphaData* mOutData;
- };
-
- ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize())
- << "Node must be bigger than max texture size to exercise saveLayer codepath";
- auto node = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 10000, 10000,
- [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) {
- properties.setHasOverlappingRendering(true);
- properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer
- // apply other properties
- propSetupCallback(properties);
-
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 10000, 10000, paint);
- });
- auto syncedNode = TestUtils::getSyncedNode(node); // sync before querying height
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*syncedNode);
-
- SaveLayerAlphaClipTestRenderer renderer(outObservedData);
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-
- // assert, since output won't be valid if we haven't seen a save layer triggered
- ASSERT_EQ(5, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) {
- SaveLayerAlphaData observedData;
- testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
- properties.setTranslationX(10); // offset rendering content
- properties.setTranslationY(-2000); // offset rendering content
- });
- EXPECT_EQ(190u, observedData.layerWidth);
- EXPECT_EQ(200u, observedData.layerHeight);
- EXPECT_EQ(Rect(190, 200), observedData.rectClippedBounds)
- << "expect content to be clipped to screen area";
- Matrix4 expected;
- expected.loadTranslate(0, -2000, 0);
- EXPECT_MATRIX_APPROX_EQ(expected, observedData.rectMatrix)
- << "expect content to be translated as part of being clipped";
- expected.loadTranslate(10, 0, 0);
- EXPECT_MATRIX_APPROX_EQ(expected, observedData.drawLayerMatrix)
- << "expect drawLayer to be translated as part of being clipped";
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) {
- SaveLayerAlphaData observedData;
- testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
- // Translate and rotate the view so that the only visible part is the top left corner of
- // the view. It will form an isosceles right triangle with a long side length of 200 at the
- // bottom of the viewport.
- properties.setTranslationX(100);
- properties.setTranslationY(100);
- properties.setPivotX(0);
- properties.setPivotY(0);
- properties.setRotation(45);
- });
- // ceil(sqrt(2) / 2 * 200) = 142
- EXPECT_EQ(142u, observedData.layerWidth);
- EXPECT_EQ(142u, observedData.layerHeight);
- EXPECT_EQ(Rect(142, 142), observedData.rectClippedBounds);
- EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) {
- SaveLayerAlphaData observedData;
- testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
- properties.setPivotX(0);
- properties.setPivotY(0);
- properties.setScaleX(2);
- properties.setScaleY(0.5f);
- });
- EXPECT_EQ(100u, observedData.layerWidth);
- EXPECT_EQ(400u, observedData.layerHeight);
- EXPECT_EQ(Rect(100, 400), observedData.rectClippedBounds);
- EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clip_replace) {
- class ClipReplaceTestRenderer : public TestRendererBase {
- public:
- void onColorOp(const ColorOp& op, const BakedOpState& state) override {
- EXPECT_EQ(0, mIndex++);
- EXPECT_TRUE(op.localClip->intersectWithRoot);
- EXPECT_EQ(Rect(20, 10, 30, 40), state.computedState.clipState->rect)
- << "Expect resolved clip to be intersection of viewport clip and clip op";
- }
- };
- auto node = TestUtils::createNode<RecordingCanvas>(
- 20, 20, 30, 30, [](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace_deprecated);
- canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
- });
-
- FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 40, 40), 50, 50, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
-
- ClipReplaceTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(1, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedInMiddle) {
- /* R is backward projected on B
- A
- / \
- B C
- |
- R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setProjectionReceiver(true);
- }); // nodeB
- drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 1,
- [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeC
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectLast) {
- /* R is backward projected on E
- A
- / | \
- / | \
- B C E
- |
- R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, nullptr); // nodeB
- drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 3, [](RenderProperties& props,
- RecordingCanvas& canvas) { // drawn as 2
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeC
- drawOrderedNode(&canvas, 2, [](RenderProperties& props,
- RecordingCanvas& canvas) { // drawn as 3
- props.setProjectionReceiver(true);
- }); // nodeE
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderNoReceivable) {
- /* R is backward projected without receiver
- A
- / \
- B C
- |
- R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, nullptr); // nodeB
- drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 255,
- [](RenderProperties& props, RecordingCanvas& canvas) {
- // not having a projection receiver is an undefined behavior
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeC
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderParentReceivable) {
- /* R is backward projected on C
- A
- / \
- B C
- |
- R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, nullptr); // nodeB
- drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setProjectionReceiver(true);
- drawOrderedNode(&canvas, 2,
- [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeC
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderSameNodeReceivable) {
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, nullptr); // nodeB
- drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 255,
- [](RenderProperties& props, RecordingCanvas& canvas) {
- // having a node that is projected on itself is an
- // undefined/unexpected behavior
- props.setProjectionReceiver(true);
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeC
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(2, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedSibling) {
- // TODO: this test together with the next "projectionReorderProjectedSibling2" likely expose a
- // bug in HWUI. First test draws R, while the second test does not draw R for a nearly identical
- // tree setup. The correct behaviour is to not draw R, because the receiver cannot be a sibling
- /* R is backward projected on B. R is not expected to be drawn (see Sibling2 outcome below),
- but for some reason it is drawn.
- A
- /|\
- / | \
- B C R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setProjectionReceiver(true);
- }); // nodeB
- drawOrderedNode(&canvas, 2,
- [](RenderProperties& props, RecordingCanvas& canvas) {}); // nodeC
- drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedSibling2) {
- /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
- A
- |
- G
- /|\
- / | \
- B C R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, [](RenderProperties& props,
- RecordingCanvas& canvas) { // G
- drawOrderedNode(&canvas, 1,
- [](RenderProperties& props, RecordingCanvas& canvas) { // B
- props.setProjectionReceiver(true);
- }); // nodeB
- drawOrderedNode(&canvas, 2,
- [](RenderProperties& props, RecordingCanvas& canvas) { // C
- }); // nodeC
- drawOrderedNode(&canvas, 255,
- [](RenderProperties& props, RecordingCanvas& canvas) { // R
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeG
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderGrandparentReceivable) {
- /* R is backward projected on B
- A
- |
- B
- |
- C
- |
- R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
- props.setProjectionReceiver(true);
- drawOrderedNode(&canvas, 1,
- [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 2, [](RenderProperties& props,
- RecordingCanvas& canvas) {
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeC
- }); // nodeB
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(3, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivables) {
- /* B and G are receivables, R is backward projected
- A
- / \
- B C
- / \
- G R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0,
- [](RenderProperties& props, RecordingCanvas& canvas) { // B
- props.setProjectionReceiver(true);
- }); // nodeB
- drawOrderedNode(&canvas, 2, [](RenderProperties& props,
- RecordingCanvas& canvas) { // C
- drawOrderedNode(&canvas, 3,
- [](RenderProperties& props, RecordingCanvas& canvas) { // G
- props.setProjectionReceiver(true);
- }); // nodeG
- drawOrderedNode(&canvas, 1,
- [](RenderProperties& props, RecordingCanvas& canvas) { // R
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeC
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) {
- /* B and G are receivables, G is backward projected
- A
- / \
- B C
- / \
- G R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0,
- [](RenderProperties& props, RecordingCanvas& canvas) { // B
- props.setProjectionReceiver(true);
- }); // nodeB
- drawOrderedNode(&canvas, 2, [](RenderProperties& props,
- RecordingCanvas& canvas) { // C
- drawOrderedNode(&canvas, 1,
- [](RenderProperties& props, RecordingCanvas& canvas) { // G
- props.setProjectionReceiver(true);
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeG
- drawOrderedNode(&canvas, 3,
- [](RenderProperties& props, RecordingCanvas& canvas) { // R
- }); // nodeR
- }); // nodeC
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(4, renderer.getIndex());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) {
- /* B and G are receivables, R is backward projected
- A
- / \
- B C
- / \
- G D
- |
- R
- */
- auto nodeA = TestUtils::createNode<RecordingCanvas>(
- 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) {
- drawOrderedNode(&canvas, 0,
- [](RenderProperties& props, RecordingCanvas& canvas) { // B
- props.setProjectionReceiver(true);
- }); // nodeB
- drawOrderedNode(&canvas, 1, [](RenderProperties& props,
- RecordingCanvas& canvas) { // C
- drawOrderedNode(&canvas, 2,
- [](RenderProperties& props, RecordingCanvas& canvas) { // G
- props.setProjectionReceiver(true);
- }); // nodeG
- drawOrderedNode(
- &canvas, 4, [](RenderProperties& props, RecordingCanvas& canvas) { // D
- drawOrderedNode(&canvas, 3, [](RenderProperties& props,
- RecordingCanvas& canvas) { // R
- props.setProjectBackwards(true);
- props.setClipToBounds(false);
- }); // nodeR
- }); // nodeD
- }); // nodeC
- }); // nodeA
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
-
- ZReorderTestRenderer renderer;
- frameBuilder.replayBakedOps<TestDispatcher>(renderer);
- EXPECT_EQ(5, renderer.getIndex());
-}
-
-} // namespace uirenderer
-} // namespace android
diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp
deleted file mode 100644
index c8bfc99..0000000
--- a/libs/hwui/tests/unit/GlopBuilderTests.cpp
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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 "Glop.h"
-#include "GlopBuilder.h"
-#include "Rect.h"
-#include "tests/common/TestUtils.h"
-#include "utils/Color.h"
-
-#include <SkPaint.h>
-
-using namespace android::uirenderer;
-
-static void expectFillEq(Glop::Fill& expectedFill, Glop::Fill& builtFill) {
- EXPECT_EQ(expectedFill.colorEnabled, builtFill.colorEnabled);
- if (expectedFill.colorEnabled) EXPECT_EQ(expectedFill.color, builtFill.color);
-
- EXPECT_EQ(expectedFill.filterMode, builtFill.filterMode);
- if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Blend) {
- EXPECT_EQ(expectedFill.filter.color, builtFill.filter.color);
- } else if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Matrix) {
- Glop::Fill::Filter::Matrix& expectedMatrix = expectedFill.filter.matrix;
- Glop::Fill::Filter::Matrix& builtMatrix = expectedFill.filter.matrix;
- EXPECT_TRUE(std::memcmp(expectedMatrix.matrix, builtMatrix.matrix,
- sizeof(Glop::Fill::Filter::Matrix::matrix)));
- EXPECT_TRUE(std::memcmp(expectedMatrix.vector, builtMatrix.vector,
- sizeof(Glop::Fill::Filter::Matrix::vector)));
- }
- EXPECT_EQ(expectedFill.skiaShaderData.skiaShaderType, builtFill.skiaShaderData.skiaShaderType);
- EXPECT_EQ(expectedFill.texture.clamp, builtFill.texture.clamp);
- EXPECT_EQ(expectedFill.texture.filter, builtFill.texture.filter);
- EXPECT_TRUE((expectedFill.texture.texture && builtFill.texture.texture) ||
- (!expectedFill.texture.texture && !builtFill.texture.texture));
- if (expectedFill.texture.texture) {
- EXPECT_EQ(expectedFill.texture.texture->target(), builtFill.texture.texture->target());
- }
- EXPECT_EQ(expectedFill.texture.textureTransform, builtFill.texture.textureTransform);
-}
-
-static void expectBlendEq(Glop::Blend& expectedBlend, Glop::Blend& builtBlend) {
- EXPECT_EQ(expectedBlend.src, builtBlend.src);
- EXPECT_EQ(expectedBlend.dst, builtBlend.dst);
-}
-
-static void expectMeshEq(Glop::Mesh& expectedMesh, Glop::Mesh& builtMesh) {
- EXPECT_EQ(expectedMesh.elementCount, builtMesh.elementCount);
- EXPECT_EQ(expectedMesh.primitiveMode, builtMesh.primitiveMode);
- EXPECT_EQ(expectedMesh.indices.indices, builtMesh.indices.indices);
- EXPECT_EQ(expectedMesh.indices.bufferObject, builtMesh.indices.bufferObject);
- EXPECT_EQ(expectedMesh.vertices.attribFlags, builtMesh.vertices.attribFlags);
- EXPECT_EQ(expectedMesh.vertices.bufferObject, builtMesh.vertices.bufferObject);
- EXPECT_EQ(expectedMesh.vertices.color, builtMesh.vertices.color);
- EXPECT_EQ(expectedMesh.vertices.position, builtMesh.vertices.position);
- EXPECT_EQ(expectedMesh.vertices.stride, builtMesh.vertices.stride);
- EXPECT_EQ(expectedMesh.vertices.texCoord, builtMesh.vertices.texCoord);
-
- if (builtMesh.vertices.position) {
- for (int i = 0; i < 4; i++) {
- TextureVertex& expectedVertex = expectedMesh.mappedVertices[i];
- TextureVertex& builtVertex = builtMesh.mappedVertices[i];
- EXPECT_EQ(expectedVertex.u, builtVertex.u);
- EXPECT_EQ(expectedVertex.v, builtVertex.v);
- EXPECT_EQ(expectedVertex.x, builtVertex.x);
- EXPECT_EQ(expectedVertex.y, builtVertex.y);
- }
- }
-}
-
-static void expectTransformEq(Glop::Transform& expectedTransform, Glop::Transform& builtTransform) {
- EXPECT_EQ(expectedTransform.canvas, builtTransform.canvas);
- EXPECT_EQ(expectedTransform.modelView, builtTransform.modelView);
- EXPECT_EQ(expectedTransform.transformFlags, expectedTransform.transformFlags);
-}
-
-static void expectGlopEq(Glop& expectedGlop, Glop& builtGlop) {
- expectBlendEq(expectedGlop.blend, builtGlop.blend);
- expectFillEq(expectedGlop.fill, builtGlop.fill);
- expectMeshEq(expectedGlop.mesh, builtGlop.mesh);
- expectTransformEq(expectedGlop.transform, builtGlop.transform);
-}
-
-static std::unique_ptr<Glop> blackUnitQuadGlop(RenderState& renderState) {
- std::unique_ptr<Glop> glop(new Glop());
- glop->blend = {GL_ZERO, GL_ZERO};
- glop->mesh.elementCount = 4;
- glop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
- glop->mesh.indices.indices = nullptr;
- glop->mesh.indices.bufferObject = GL_ZERO;
- glop->mesh.vertices = {renderState.meshState().getUnitQuadVBO(),
- VertexAttribFlags::None,
- nullptr,
- nullptr,
- nullptr,
- kTextureVertexStride};
- glop->transform.modelView.loadIdentity();
- glop->fill.colorEnabled = true;
- glop->fill.color.set(Color::Black);
- glop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType;
- glop->fill.filterMode = ProgramDescription::ColorFilterMode::None;
- glop->fill.texture = {nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr};
- return glop;
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(GlopBuilder, rectSnapTest) {
- RenderState& renderState = renderThread.renderState();
- Caches& caches = Caches::getInstance();
- SkPaint paint;
- Rect dest(1, 1, 100, 100);
- Matrix4 simpleTranslate;
- simpleTranslate.loadTranslate(0.7, 0.7, 0);
- Glop glop;
- GlopBuilder(renderState, caches, &glop)
- .setRoundRectClipState(nullptr)
- .setMeshUnitQuad()
- .setFillPaint(paint, 1.0f)
- .setTransform(simpleTranslate, TransformFlags::None)
- .setModelViewMapUnitToRectSnap(dest)
- .build();
-
- std::unique_ptr<Glop> goldenGlop(blackUnitQuadGlop(renderState));
- // Rect(1,1,100,100) is the set destination,
- // so unit quad should be translated by (1,1) and scaled by (99, 99)
- // Tricky part: because translate (0.7, 0.7) and snapping were set in glopBuilder,
- // unit quad also should be translate by additional (0.3, 0.3) to snap to exact pixels.
- goldenGlop->transform.modelView.loadTranslate(1.3, 1.3, 0);
- goldenGlop->transform.modelView.scale(99, 99, 1);
- goldenGlop->transform.canvas = simpleTranslate;
- goldenGlop->fill.texture.filter = GL_NEAREST;
- expectGlopEq(*goldenGlop, glop);
-}
diff --git a/libs/hwui/tests/unit/GradientCacheTests.cpp b/libs/hwui/tests/unit/GradientCacheTests.cpp
deleted file mode 100644
index 6710c71..0000000
--- a/libs/hwui/tests/unit/GradientCacheTests.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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 "Extensions.h"
-#include "GradientCache.h"
-#include "tests/common/TestUtils.h"
-
-using namespace android;
-using namespace android::uirenderer;
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(GradientCache, addRemove) {
- Extensions extensions;
- GradientCache cache(extensions);
- ASSERT_LT(1000u, cache.getMaxSize()) << "Expect non-trivial size";
-
- SkColor colors[] = {0xFF00FF00, 0xFFFF0000, 0xFF0000FF};
- float positions[] = {1, 2, 3};
- Texture* texture = cache.get(colors, positions, 3);
- ASSERT_TRUE(texture);
- ASSERT_FALSE(texture->cleanup);
- ASSERT_EQ((uint32_t)texture->objectSize(), cache.getSize());
- ASSERT_TRUE(cache.getSize());
- cache.clear();
- ASSERT_EQ(cache.getSize(), 0u);
-}
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
deleted file mode 100644
index 20ec084..0000000
--- a/libs/hwui/tests/unit/LeakCheckTests.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 "BakedOpDispatcher.h"
-#include "BakedOpRenderer.h"
-#include "FrameBuilder.h"
-#include "LayerUpdateQueue.h"
-#include "RecordingCanvas.h"
-#include "tests/common/TestUtils.h"
-
-#include <gtest/gtest.h>
-
-using namespace android;
-using namespace android::uirenderer;
-
-const FrameBuilder::LightGeometry sLightGeometery = {{100, 100, 100}, 50};
-const BakedOpRenderer::LightInfo sLightInfo = {128, 128};
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayer_overdrawRejection) {
- auto node = TestUtils::createNode(0, 0, 100, 100, [](RenderProperties& props, Canvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- canvas.restore();
-
- // opaque draw, rejects saveLayer beneath
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- });
- RenderState& renderState = renderThread.renderState();
- Caches& caches = Caches::getInstance();
-
- FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometery,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
- BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
- frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayerUnclipped_simple) {
- auto node = TestUtils::createNode(0, 0, 200, 200, [](RenderProperties& props, Canvas& canvas) {
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0));
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- canvas.restore();
- });
- RenderState& renderState = renderThread.renderState();
- Caches& caches = Caches::getInstance();
-
- FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometery,
- Caches::getInstance());
- frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
- BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo);
- frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
-}
diff --git a/libs/hwui/tests/unit/MeshStateTests.cpp b/libs/hwui/tests/unit/MeshStateTests.cpp
deleted file mode 100644
index 1573fd3..0000000
--- a/libs/hwui/tests/unit/MeshStateTests.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 <debug/MockGlesDriver.h>
-#include <debug/ScopedReplaceDriver.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <renderstate/MeshState.h>
-#include <tests/common/TestUtils.h>
-
-using namespace android::uirenderer;
-using namespace testing;
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(MeshState, genOrUpdate) {
- debug::ScopedReplaceDriver<debug::MockGlesDriver> driverRef;
- auto& mockGlDriver = driverRef.get();
- EXPECT_CALL(mockGlDriver, glGenBuffers_(_, _)).WillOnce(SetArgPointee<1>(35));
- EXPECT_CALL(mockGlDriver, glBindBuffer_(_, 35));
- EXPECT_CALL(mockGlDriver, glBufferData_(_, _, _, _));
-
- GLuint buffer = 0;
- renderThread.renderState().meshState().genOrUpdateMeshBuffer(&buffer, 10, nullptr,
- GL_DYNAMIC_DRAW);
-}
diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
deleted file mode 100644
index 0d47367..0000000
--- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2015 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 <Rect.h>
-#include <gtest/gtest.h>
-#include <renderstate/OffscreenBufferPool.h>
-
-#include <tests/common/TestUtils.h>
-
-using namespace android::uirenderer;
-
-TEST(OffscreenBuffer, computeIdealDimension) {
- EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1));
- EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31));
- EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33));
- EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64));
- EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000));
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, construct) {
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u);
- EXPECT_EQ(49u, layer.viewportWidth);
- EXPECT_EQ(149u, layer.viewportHeight);
-
- EXPECT_EQ(64u, layer.texture.width());
- EXPECT_EQ(192u, layer.texture.height());
-
- EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, constructWideColorGamut) {
- OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u, true);
- EXPECT_EQ(49u, layer.viewportWidth);
- EXPECT_EQ(149u, layer.viewportHeight);
-
- EXPECT_EQ(64u, layer.texture.width());
- EXPECT_EQ(192u, layer.texture.height());
-
- EXPECT_TRUE(layer.wideColorGamut);
-
- EXPECT_EQ(64u * 192u * 8u, layer.getSizeInBytes());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, getTextureCoordinates) {
- OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
- EXPECT_EQ(Rect(0, 1, 1, 0), layerAligned.getTextureCoordinates());
-
- OffscreenBuffer layerUnaligned(renderThread.renderState(), Caches::getInstance(), 200u, 225u);
- EXPECT_EQ(Rect(0, 225.0f / 256.0f, 200.0f / 256.0f, 0), layerUnaligned.getTextureCoordinates());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, dirty) {
- OffscreenBuffer buffer(renderThread.renderState(), Caches::getInstance(), 256u, 256u);
- buffer.dirty(Rect(-100, -100, 100, 100));
- EXPECT_EQ(android::Rect(100, 100), buffer.region.getBounds());
-}
-
-RENDERTHREAD_TEST(OffscreenBufferPool, construct) {
- OffscreenBufferPool pool;
- EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty";
- EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty";
- // TODO: Does this really make sense as a test?
- EXPECT_EQ(DeviceInfo::multiplyByResolution(4 * 4), pool.getMaxSize());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClear) {
- OffscreenBufferPool pool;
-
- auto layer = pool.get(renderThread.renderState(), 100u, 200u);
- EXPECT_EQ(100u, layer->viewportWidth);
- EXPECT_EQ(200u, layer->viewportHeight);
-
- ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
-
- pool.putOrDelete(layer);
- ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
-
- auto layer2 = pool.get(renderThread.renderState(), 102u, 202u);
- EXPECT_EQ(layer, layer2) << "layer should be recycled";
- ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
-
- pool.putOrDelete(layer);
- EXPECT_EQ(1u, pool.getCount());
- pool.clear();
- EXPECT_EQ(0u, pool.getSize());
- EXPECT_EQ(0u, pool.getCount());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClearWideColorGamut) {
- OffscreenBufferPool pool;
-
- auto layer = pool.get(renderThread.renderState(), 100u, 200u, true);
- EXPECT_EQ(100u, layer->viewportWidth);
- EXPECT_EQ(200u, layer->viewportHeight);
- EXPECT_TRUE(layer->wideColorGamut);
-
- ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize());
-
- pool.putOrDelete(layer);
- ASSERT_EQ(layer->getSizeInBytes(), pool.getSize());
-
- auto layer2 = pool.get(renderThread.renderState(), 102u, 202u, true);
- EXPECT_EQ(layer, layer2) << "layer should be recycled";
- ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer";
-
- pool.putOrDelete(layer2);
- EXPECT_EQ(1u, pool.getCount());
- pool.clear();
- EXPECT_EQ(0u, pool.getSize());
- EXPECT_EQ(0u, pool.getCount());
-
- // add non wide gamut layer
- auto layer3 = pool.get(renderThread.renderState(), 100u, 200u);
- EXPECT_FALSE(layer3->wideColorGamut);
- pool.putOrDelete(layer3);
- EXPECT_EQ(1u, pool.getCount());
-
- auto layer4 = pool.get(renderThread.renderState(), 100u, 200u, true);
- EXPECT_TRUE(layer4->wideColorGamut);
- EXPECT_EQ(1u, pool.getCount());
- ASSERT_NE(layer3, layer4);
-
- pool.putOrDelete(layer4);
-
- pool.clear();
- EXPECT_EQ(0u, pool.getSize());
- EXPECT_EQ(0u, pool.getCount());
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resize) {
- OffscreenBufferPool pool;
-
- auto layer = pool.get(renderThread.renderState(), 64u, 64u);
- layer->dirty(Rect(64, 64));
-
- // resize in place
- ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
- EXPECT_TRUE(layer->region.isEmpty()) << "In place resize should clear usage region";
- EXPECT_EQ(60u, layer->viewportWidth);
- EXPECT_EQ(55u, layer->viewportHeight);
- EXPECT_EQ(64u, layer->texture.width());
- EXPECT_EQ(64u, layer->texture.height());
-
- // resized to use different object in pool
- auto layer2 = pool.get(renderThread.renderState(), 128u, 128u);
- layer2->dirty(Rect(128, 128));
- EXPECT_FALSE(layer2->region.isEmpty());
- pool.putOrDelete(layer2);
- ASSERT_EQ(1u, pool.getCount());
-
- ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
- EXPECT_TRUE(layer2->region.isEmpty()) << "Swap resize should clear usage region";
- EXPECT_EQ(120u, layer2->viewportWidth);
- EXPECT_EQ(125u, layer2->viewportHeight);
- EXPECT_EQ(128u, layer2->texture.width());
- EXPECT_EQ(128u, layer2->texture.height());
-
- // original allocation now only thing in pool
- EXPECT_EQ(1u, pool.getCount());
- EXPECT_EQ(layer->getSizeInBytes(), pool.getSize());
-
- pool.putOrDelete(layer2);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resizeWideColorGamut) {
- OffscreenBufferPool pool;
-
- auto layer = pool.get(renderThread.renderState(), 64u, 64u, true);
-
- // resize in place
- ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
- EXPECT_EQ(60u, layer->viewportWidth);
- EXPECT_EQ(55u, layer->viewportHeight);
- EXPECT_EQ(64u, layer->texture.width());
- EXPECT_EQ(64u, layer->texture.height());
-
- EXPECT_TRUE(layer->wideColorGamut);
- EXPECT_EQ(64u * 64u * 8u, layer->getSizeInBytes());
-
- // resized to use different object in pool
- auto layer2 = pool.get(renderThread.renderState(), 128u, 128u, true);
- pool.putOrDelete(layer2);
- ASSERT_EQ(1u, pool.getCount());
-
- // add a non-wide gamut layer
- auto layer3 = pool.get(renderThread.renderState(), 128u, 128u);
- pool.putOrDelete(layer3);
- ASSERT_EQ(2u, pool.getCount());
-
- ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
- EXPECT_EQ(120u, layer2->viewportWidth);
- EXPECT_EQ(125u, layer2->viewportHeight);
- EXPECT_EQ(128u, layer2->texture.width());
- EXPECT_EQ(128u, layer2->texture.height());
-
- EXPECT_TRUE(layer2->wideColorGamut);
- EXPECT_EQ(128u * 128u * 8u, layer2->getSizeInBytes());
-
- pool.putOrDelete(layer2);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, putAndDestroy) {
- OffscreenBufferPool pool;
- // layer too big to return to the pool
- // Note: this relies on the fact that the pool won't reject based on max texture size
- auto hugeLayer = pool.get(renderThread.renderState(), pool.getMaxSize() / 64, 64);
- EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize());
- pool.putOrDelete(hugeLayer);
- EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead)
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, clear) {
- EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
- OffscreenBufferPool pool;
-
- // Create many buffers, with several at each size
- std::vector<OffscreenBuffer*> buffers;
- for (int size = 32; size <= 128; size += 32) {
- for (int i = 0; i < 10; i++) {
- buffers.push_back(pool.get(renderThread.renderState(), size, size));
- }
- }
- EXPECT_EQ(0u, pool.getCount()) << "Expect nothing inside";
- for (auto& buffer : buffers) pool.putOrDelete(buffer);
- EXPECT_EQ(40u, pool.getCount()) << "Expect all items added";
- EXPECT_EQ(40, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
- pool.clear();
- EXPECT_EQ(0u, pool.getCount()) << "Expect all items cleared";
-
- EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer));
-}
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
deleted file mode 100644
index 8a9e34f..0000000
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ /dev/null
@@ -1,847 +0,0 @@
-/*
- * Copyright (C) 2015 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 <DeferredLayerUpdater.h>
-#include <RecordedOp.h>
-#include <RecordingCanvas.h>
-#include <hwui/Paint.h>
-#include <minikin/Layout.h>
-#include <tests/common/TestUtils.h>
-#include <utils/Color.h>
-
-#include <SkGradientShader.h>
-#include <SkImagePriv.h>
-#include <SkShader.h>
-
-namespace android {
-namespace uirenderer {
-
-static void playbackOps(const DisplayList& displayList,
- std::function<void(const RecordedOp&)> opReceiver) {
- for (auto& chunk : displayList.getChunks()) {
- for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
- RecordedOp* op = displayList.getOps()[opIndex];
- opReceiver(*op);
- }
- }
-}
-
-static void validateSingleOp(std::unique_ptr<DisplayList>& dl,
- std::function<void(const RecordedOp& op)> opValidator) {
- ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
- opValidator(*(dl->getOps()[0]));
-}
-
-// The RecordingCanvas is only ever used by the OpenGL RenderPipeline and never when Skia is in use.
-// Thus, even though many of these test are not directly dependent on the current RenderPipeline, we
-// set them all to be OPENGL_PIPELINE_TESTs in case the underlying code in RecordingCanvas ever
-// changes to require the use of the OPENGL_PIPELINE. Currently the textureLayer test is the only
-// test that requires being an OPENGL_PIPELINE_TEST.
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPlayback) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.restore();
- });
- playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, clipRect) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect);
- canvas.drawRect(0, 0, 50, 50, SkPaint());
- canvas.drawRect(50, 50, 100, 100, SkPaint());
- canvas.restore();
- });
-
- ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
- EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
- EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
- EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
- << "Clip should be serialized once";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, emptyClipRect) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect);
- canvas.clipRect(100, 100, 200, 200, SkClipOp::kIntersect);
- canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time
- canvas.restore();
- });
- ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected.";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPaintRejection) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- SkPaint emptyPaint;
- emptyPaint.setColor(Color::Transparent);
-
- float points[] = {0, 0, 200, 200};
- canvas.drawPoints(points, 4, emptyPaint);
- canvas.drawLines(points, 4, emptyPaint);
- canvas.drawRect(0, 0, 200, 200, emptyPaint);
- canvas.drawRegion(SkRegion(SkIRect::MakeWH(200, 200)), emptyPaint);
- canvas.drawRoundRect(0, 0, 200, 200, 10, 10, emptyPaint);
- canvas.drawCircle(100, 100, 100, emptyPaint);
- canvas.drawOval(0, 0, 200, 200, emptyPaint);
- canvas.drawArc(0, 0, 200, 200, 0, 360, true, emptyPaint);
- SkPath path;
- path.addRect(0, 0, 200, 200);
- canvas.drawPath(path, emptyPaint);
- });
- EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawArc) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint());
- canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint());
- });
-
- auto&& ops = dl->getOps();
- ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops";
- EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId);
- EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds);
-
- EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId) << "Circular arcs should be converted to ovals";
- EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawLines) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setStrokeWidth(
- 20); // doesn't affect recorded bounds - would be resolved at bake time
- float points[] = {0, 0, 20, 10, 30, 40, 90}; // NB: only 1 valid line
- canvas.drawLines(&points[0], 7, paint);
- });
-
- ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
- auto op = dl->getOps()[0];
- ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
- EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
- << "float count must be rounded down to closest multiple of 4";
- EXPECT_EQ(Rect(20, 10), op->unmappedBounds)
- << "unmapped bounds must be size of line, and not outset for stroke width";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawRect) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 100, 200, [](RecordingCanvas& canvas) { canvas.drawRect(10, 20, 90, 180, SkPaint()); });
-
- ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
- auto op = *(dl->getOps()[0]);
- ASSERT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_EQ(nullptr, op.localClip);
- EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawRoundRect) {
- // Round case - stays rounded
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
- canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint());
- });
- ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
- ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId);
-
- // Non-rounded case - turned into drawRect
- dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
- canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint());
- });
- ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
- ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId)
- << "Non-rounded rects should be converted";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(20);
- TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
- });
-
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- count++;
- ASSERT_EQ(RecordedOpId::TextOp, op.opId);
- EXPECT_EQ(nullptr, op.localClip);
- EXPECT_TRUE(op.localMatrix.isIdentity());
- EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
- << "Op expected to be 25+ pixels wide, 10+ pixels tall";
- });
- ASSERT_EQ(1, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(20);
- for (int i = 0; i < 2; i++) {
- for (int j = 0; j < 2; j++) {
- uint32_t flags = paint.getFlags();
- if (i != 0) {
- flags |= SkPaint::kUnderlineText_ReserveFlag;
- } else {
- flags &= ~SkPaint::kUnderlineText_ReserveFlag;
- }
- if (j != 0) {
- flags |= SkPaint::kStrikeThruText_ReserveFlag;
- } else {
- flags &= ~SkPaint::kStrikeThruText_ReserveFlag;
- }
- paint.setFlags(flags);
- TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
- }
- }
- });
-
- auto ops = dl->getOps();
- ASSERT_EQ(8u, ops.size());
-
- int index = 0;
- EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
-
- EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
- EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
-
- EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
- EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
-
- EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
- EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
- EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(20);
- paint.setTextAlign(SkPaint::kLeft_Align);
- TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
- paint.setTextAlign(SkPaint::kCenter_Align);
- TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
- paint.setTextAlign(SkPaint::kRight_Align);
- TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25);
- });
-
- int count = 0;
- float lastX = FLT_MAX;
- playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
- count++;
- ASSERT_EQ(RecordedOpId::TextOp, op.opId);
- EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
- << "recorded drawText commands must force kLeft_Align on their paint";
-
- // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
- EXPECT_GT(lastX, ((const TextOp&)op).x)
- << "x coordinate should reduce across each of the draw commands, from alignment";
- lastX = ((const TextOp&)op).x;
- });
- ASSERT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawColor) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.drawColor(Color::Black, SkBlendMode::kSrcOver);
- });
-
- ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
- auto op = *(dl->getOps()[0]);
- EXPECT_EQ(RecordedOpId::ColorOp, op.opId);
- EXPECT_EQ(nullptr, op.localClip);
- EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, backgroundAndImage) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
- sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
- SkPaint paint;
- paint.setColor(SK_ColorBLUE);
-
- canvas.save(SaveFlags::MatrixClip);
- {
- // a background!
- canvas.save(SaveFlags::MatrixClip);
- canvas.drawRect(0, 0, 100, 200, paint);
- canvas.restore();
- }
- {
- // an image!
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(25, 25);
- canvas.scale(2, 2);
- canvas.drawBitmap(*bitmap, 0, 0, nullptr);
- canvas.restore();
- }
- canvas.restore();
- });
-
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- if (count == 0) {
- ASSERT_EQ(RecordedOpId::RectOp, op.opId);
- ASSERT_NE(nullptr, op.paint);
- EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
- EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
- EXPECT_EQ(nullptr, op.localClip);
-
- Matrix4 expectedMatrix;
- expectedMatrix.loadIdentity();
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
- } else {
- ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
- EXPECT_EQ(nullptr, op.paint);
- EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
- EXPECT_EQ(nullptr, op.localClip);
-
- Matrix4 expectedMatrix;
- expectedMatrix.loadTranslate(25, 25, 0);
- expectedMatrix.scale(2, 2, 1);
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
- }
- count++;
- });
- ASSERT_EQ(2, count);
-}
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(RecordingCanvas, textureLayer) {
- auto layerUpdater =
- TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5));
-
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 200, 200,
- [&layerUpdater](RecordingCanvas& canvas) { canvas.drawLayer(layerUpdater.get()); });
-
- validateSingleOp(dl, [](const RecordedOp& op) {
- ASSERT_EQ(RecordedOpId::TextureLayerOp, op.opId);
- ASSERT_TRUE(op.localMatrix.isIdentity()) << "Op must not apply matrix at record time.";
- });
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simple) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(10, 20, 190, 180, SkPaint());
- canvas.restore();
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- Matrix4 expectedMatrix;
- switch (count++) {
- case 0:
- EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
- EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
- EXPECT_EQ(nullptr, op.localClip);
- EXPECT_TRUE(op.localMatrix.isIdentity());
- break;
- case 1:
- EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
- EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
- expectedMatrix.loadTranslate(-10, -20, 0);
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
- break;
- case 2:
- EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
- // Don't bother asserting recording state data - it's not used
- break;
- default:
- ADD_FAILURE();
- }
- });
- EXPECT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rounding) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(20, 20, 80, 80, SkPaint());
- canvas.restore();
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- Matrix4 expectedMatrix;
- switch (count++) {
- case 0:
- EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
- EXPECT_EQ(Rect(10, 10, 90, 90), op.unmappedBounds) << "Expect bounds rounded out";
- break;
- case 1:
- EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- expectedMatrix.loadTranslate(-10, -10, 0);
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix) << "Expect rounded offset";
- break;
- case 2:
- EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
- // Don't bother asserting recording state data - it's not used
- break;
- default:
- ADD_FAILURE();
- }
- });
- EXPECT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_missingRestore) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- // Note: restore omitted, shouldn't result in unmatched save
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- if (count++ == 2) {
- EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
- }
- });
- EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
- canvas.drawRect(10, 20, 190, 180, SkPaint());
- canvas.restore();
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- switch (count++) {
- case 0:
- EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId);
- EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
- EXPECT_EQ(nullptr, op.localClip);
- EXPECT_TRUE(op.localMatrix.isIdentity());
- break;
- case 1:
- EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_EQ(nullptr, op.localClip);
- EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
- EXPECT_TRUE(op.localMatrix.isIdentity());
- break;
- case 2:
- EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
- // Don't bother asserting recording state data - it's not used
- break;
- default:
- ADD_FAILURE();
- }
- });
- EXPECT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_addClipFlag) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipRect(10, 20, 190, 180, SkClipOp::kIntersect);
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped
- canvas.drawRect(10, 20, 190, 180, SkPaint());
- canvas.restore();
- canvas.restore();
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- if (count++ == 0) {
- EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
- << "Clip + unclipped saveLayer should result in a clipped layer";
- }
- });
- EXPECT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_viewportCrop) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- // shouldn't matter, since saveLayer will clip to its bounds
- canvas.clipRect(-1000, -1000, 1000, 1000, SkClipOp::kReplace_deprecated);
-
- canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- canvas.restore();
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- if (count++ == 1) {
- Matrix4 expectedMatrix;
- EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
- // intersection of viewport and saveLayer bounds, in layer space;
- EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
- expectedMatrix.loadTranslate(-100, -100, 0);
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
- }
- });
- EXPECT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(100, 100);
- canvas.rotate(45);
- canvas.translate(-50, -50);
-
- canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- canvas.restore();
-
- canvas.restore();
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- if (count++ == 1) {
- EXPECT_EQ(RecordedOpId::RectOp, op.opId);
- EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
- EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
- EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
- << "Recorded op shouldn't see any canvas transform before the saveLayer";
- }
- });
- EXPECT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateClipped) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(100, 100);
- canvas.rotate(45);
- canvas.translate(-200, -200);
-
- // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
- canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- canvas.restore();
-
- canvas.restore();
- });
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- if (count++ == 1) {
- Matrix4 expectedMatrix;
- EXPECT_EQ(RecordedOpId::RectOp, op.opId);
-
- // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
- // the parent 200x200 viewport, but prior to rotation
- ASSERT_NE(nullptr, op.localClip);
- ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
- // NOTE: this check relies on saveLayer altering the clip post-viewport init. This
- // causes the clip to be recorded by contained draw commands, though it's not necessary
- // since the same clip will be computed at draw time. If such a change is made, this
- // check could be done at record time by querying the clip, or the clip could be altered
- // slightly so that it is serialized.
- EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect);
- EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
- expectedMatrix.loadIdentity();
- EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
- }
- });
- EXPECT_EQ(3, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rejectBegin) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.translate(0, -20); // avoid identity case
- // empty clip rect should force layer + contents to be rejected
- canvas.clipRect(0, -20, 200, -20, SkClipOp::kIntersect);
- canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer);
- canvas.drawRect(0, 0, 200, 200, SkPaint());
- canvas.restore();
- canvas.restore();
- });
-
- ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected.";
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_rejection) {
- auto child =
- TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, Canvas& canvas) {
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
-
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 200, 200, [&child](RecordingCanvas& canvas) {
- canvas.clipRect(0, 0, 0, 0, SkClipOp::kIntersect); // empty clip, reject node
- canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node...
- });
- ASSERT_TRUE(dl->isEmpty());
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_projection) {
- sp<RenderNode> background =
- TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, Canvas& canvas) {
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
- });
- {
- background->mutateStagingProperties().setProjectionReceiver(false);
-
- // NO RECEIVER PRESENT
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 200, 200, [&background](RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- canvas.drawRenderNode(background.get());
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- });
- EXPECT_EQ(-1, dl->projectionReceiveIndex)
- << "no projection receiver should have been observed";
- }
- {
- background->mutateStagingProperties().setProjectionReceiver(true);
-
- // RECEIVER PRESENT
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 200, 200, [&background](RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- canvas.drawRenderNode(background.get());
- canvas.drawRect(0, 0, 100, 100, SkPaint());
- });
-
- ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
- auto op = dl->getOps()[1];
- EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
- EXPECT_EQ(1, dl->projectionReceiveIndex) << "correct projection receiver not identified";
-
- // verify the behavior works even though projection receiver hasn't been sync'd yet
- EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
- EXPECT_FALSE(background->properties().isProjectionReceiver());
- }
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, firstClipWillReplace) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- // since no explicit clip set on canvas, this should be the one observed on op:
- canvas.clipRect(-100, -100, 300, 300, SkClipOp::kIntersect);
-
- SkPaint paint;
- paint.setColor(SK_ColorWHITE);
- canvas.drawRect(0, 0, 100, 100, paint);
-
- canvas.restore();
- });
- ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
- // first clip must be preserved, even if it extends beyond canvas bounds
- EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, replaceClipIntersectWithRoot) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
- canvas.save(SaveFlags::MatrixClip);
- canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace_deprecated);
- canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
- canvas.restore();
- });
- ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op";
- // first clip must be preserved, even if it extends beyond canvas bounds
- EXPECT_CLIP_RECT(Rect(-10, -10, 110, 110), dl->getOps()[0]->localClip);
- EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- canvas.insertReorderBarrier(true);
- canvas.insertReorderBarrier(false);
- canvas.insertReorderBarrier(false);
- canvas.insertReorderBarrier(true);
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- canvas.insertReorderBarrier(false);
- });
-
- auto chunks = dl->getChunks();
- EXPECT_EQ(0u, chunks[0].beginOpIndex);
- EXPECT_EQ(1u, chunks[0].endOpIndex);
- EXPECT_FALSE(chunks[0].reorderChildren);
-
- EXPECT_EQ(1u, chunks[1].beginOpIndex);
- EXPECT_EQ(2u, chunks[1].endOpIndex);
- EXPECT_TRUE(chunks[1].reorderChildren);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier_clip) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- // first chunk: no recorded clip
- canvas.insertReorderBarrier(true);
- canvas.drawRect(0, 0, 400, 400, SkPaint());
-
- // second chunk: no recorded clip, since inorder region
- canvas.clipRect(0, 0, 200, 200, SkClipOp::kIntersect);
- canvas.insertReorderBarrier(false);
- canvas.drawRect(0, 0, 400, 400, SkPaint());
-
- // third chunk: recorded clip
- canvas.insertReorderBarrier(true);
- canvas.drawRect(0, 0, 400, 400, SkPaint());
- });
-
- auto chunks = dl->getChunks();
- ASSERT_EQ(3u, chunks.size());
-
- EXPECT_TRUE(chunks[0].reorderChildren);
- EXPECT_EQ(nullptr, chunks[0].reorderClip);
-
- EXPECT_FALSE(chunks[1].reorderChildren);
- EXPECT_EQ(nullptr, chunks[1].reorderClip);
-
- EXPECT_TRUE(chunks[2].reorderChildren);
- ASSERT_NE(nullptr, chunks[2].reorderClip);
- EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, refPaint) {
- SkPaint paint;
-
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 200, 200, [&paint](RecordingCanvas& canvas) {
- paint.setColor(SK_ColorBLUE);
- // first two should use same paint
- canvas.drawRect(0, 0, 200, 10, paint);
- SkPaint paintCopy(paint);
- canvas.drawRect(0, 10, 200, 20, paintCopy);
-
- // only here do we use different paint ptr
- paint.setColor(SK_ColorRED);
- canvas.drawRect(0, 20, 200, 30, paint);
- });
- auto ops = dl->getOps();
- ASSERT_EQ(3u, ops.size());
-
- // first two are the same
- EXPECT_NE(nullptr, ops[0]->paint);
- EXPECT_NE(&paint, ops[0]->paint);
- EXPECT_EQ(ops[0]->paint, ops[1]->paint);
-
- // last is different, but still copied / non-null
- EXPECT_NE(nullptr, ops[2]->paint);
- EXPECT_NE(ops[0]->paint, ops[2]->paint);
- EXPECT_NE(&paint, ops[2]->paint);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmap) {
- sk_sp<Bitmap> bitmap(TestUtils::createBitmap(100, 100));
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 100, 100,
- [&bitmap](RecordingCanvas& canvas) { canvas.drawBitmap(*bitmap, 0, 0, nullptr); });
- auto& bitmaps = dl->getBitmapResources();
- EXPECT_EQ(1u, bitmaps.size());
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_bitmapShader) {
- sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 100, 100, [&bitmap](RecordingCanvas& canvas) {
- SkPaint paint;
- SkBitmap skBitmap;
- bitmap->getSkBitmap(&skBitmap);
- sk_sp<SkImage> image =
- SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
- sk_sp<SkShader> shader =
- image->makeShader(SkShader::TileMode::kClamp_TileMode,
- SkShader::TileMode::kClamp_TileMode, nullptr);
- paint.setShader(std::move(shader));
- canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
- });
- auto& bitmaps = dl->getBitmapResources();
- EXPECT_EQ(1u, bitmaps.size());
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_composeShader) {
- sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100);
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(
- 100, 100, [&bitmap](RecordingCanvas& canvas) {
- SkPaint paint;
- SkBitmap skBitmap;
- bitmap->getSkBitmap(&skBitmap);
- sk_sp<SkImage> image =
- SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode);
- sk_sp<SkShader> shader1 =
- image->makeShader(SkShader::TileMode::kClamp_TileMode,
- SkShader::TileMode::kClamp_TileMode, nullptr);
-
- SkPoint center;
- center.set(50, 50);
- SkColor colors[2];
- colors[0] = Color::Black;
- colors[1] = Color::White;
- sk_sp<SkShader> shader2 = SkGradientShader::MakeRadial(
- center, 50, colors, nullptr, 2, SkShader::TileMode::kRepeat_TileMode);
-
- sk_sp<SkShader> composeShader = SkShader::MakeComposeShader(
- std::move(shader1), std::move(shader2), SkBlendMode::kMultiply);
- paint.setShader(std::move(composeShader));
- canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint);
- });
- auto& bitmaps = dl->getBitmapResources();
- EXPECT_EQ(1u, bitmaps.size());
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawText) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- Paint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(20);
- TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
- });
-
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- count++;
- ASSERT_EQ(RecordedOpId::TextOp, op.opId);
- EXPECT_EQ(nullptr, op.localClip);
- EXPECT_TRUE(op.localMatrix.isIdentity());
- EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10);
- EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25);
- });
- ASSERT_EQ(1, count);
-}
-
-OPENGL_PIPELINE_TEST(RecordingCanvas, drawTextInHighContrast) {
- Properties::enableHighContrastText = true;
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- Paint paint;
- paint.setColor(SK_ColorWHITE);
- paint.setAntiAlias(true);
- paint.setTextSize(20);
- TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25);
- });
- Properties::enableHighContrastText = false;
-
- int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
- ASSERT_EQ(RecordedOpId::TextOp, op.opId);
- if (count++ == 0) {
- EXPECT_EQ(SK_ColorBLACK, op.paint->getColor());
- EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle());
- } else {
- EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
- EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle());
- }
-
- });
- ASSERT_EQ(2, count);
-}
-
-} // namespace uirenderer
-} // namespace android
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 1d7dc3d..2e4de0b 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -26,41 +26,6 @@
using namespace android;
using namespace android::uirenderer;
-/**
- * Verify that we get the same culling bounds for text for (1) drawing glyphs
- * directly to a Canvas or (2) going through a SkPicture as an intermediate step.
- */
-OPENGL_PIPELINE_TEST(SkiaCanvasProxy, drawGlyphsViaPicture) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- // setup test variables
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setTextSize(20);
- static const char* text = "testing text bounds";
-
- // draw text directly into Recording canvas
- TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 25, 25);
-
- // record the same text draw into a SkPicture and replay it into a Recording canvas
- SkPictureRecorder recorder;
- SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0);
- std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas));
- TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25);
- sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
-
- canvas.asSkCanvas()->drawPicture(picture);
- });
-
- // verify that the text bounds and matrices match
- ASSERT_EQ(2U, dl->getOps().size());
- auto directOp = dl->getOps()[0];
- auto pictureOp = dl->getOps()[1];
- ASSERT_EQ(RecordedOpId::TextOp, directOp->opId);
- EXPECT_EQ(directOp->opId, pictureOp->opId);
- EXPECT_EQ(directOp->unmappedBounds, pictureOp->unmappedBounds);
- EXPECT_EQ(directOp->localMatrix, pictureOp->localMatrix);
-}
-
TEST(SkiaCanvas, drawShadowLayer) {
auto surface = SkSurface::MakeRasterN32Premul(10, 10);
SkiaCanvas canvas(surface->getCanvas());
diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
deleted file mode 100644
index 92d05e4..0000000
--- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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 "GammaFontRenderer.h"
-#include "TextDropShadowCache.h"
-#include "tests/common/TestUtils.h"
-#include "utils/Blur.h"
-
-#include <SkPaint.h>
-
-using namespace android;
-using namespace android::uirenderer;
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) {
- SkPaint paint;
- paint.setTextSize(20);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-
- GammaFontRenderer gammaFontRenderer;
- FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
- fontRenderer.setFont(&paint, SkMatrix::I());
- TextDropShadowCache cache(MB(5));
- cache.setFontRenderer(fontRenderer);
-
- std::vector<glyph_t> glyphs;
- std::vector<float> positions;
- float totalAdvance;
- uirenderer::Rect bounds;
- TestUtils::layoutTextUnscaled(paint, "This is a test", &glyphs, &positions, &totalAdvance,
- &bounds);
- EXPECT_TRUE(bounds.contains(5, -10, 100, 0)) << "Expect input to be nontrivially sized";
-
- ShadowTexture* texture = cache.get(&paint, glyphs.data(), glyphs.size(), 10, positions.data());
-
- ASSERT_TRUE(texture);
- ASSERT_FALSE(texture->cleanup);
- ASSERT_EQ((uint32_t)texture->objectSize(), cache.getSize());
- ASSERT_TRUE(cache.getSize());
- cache.clear();
- ASSERT_EQ(cache.getSize(), 0u);
-}
diff --git a/libs/hwui/tests/unit/TextureCacheTests.cpp b/libs/hwui/tests/unit/TextureCacheTests.cpp
deleted file mode 100644
index ab740dd..0000000
--- a/libs/hwui/tests/unit/TextureCacheTests.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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 <gtest/gtest.h>
-
-#include "Extensions.h"
-#include "TextureCache.h"
-#include "tests/common/TestUtils.h"
-
-using namespace android;
-using namespace android::uirenderer;
-
-RENDERTHREAD_OPENGL_PIPELINE_TEST(TextureCache, clear) {
- TextureCache cache;
- ASSERT_EQ(cache.getSize(), 0u);
- // it is not 0, because FontRenderer allocates one texture
- int initialCount = GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture);
- SkBitmap skBitmap;
- SkImageInfo info = SkImageInfo::Make(100, 100, kN32_SkColorType, kPremul_SkAlphaType);
- skBitmap.setInfo(info);
- sk_sp<Bitmap> hwBitmap(renderThread.allocateHardwareBitmap(skBitmap));
- cache.get(hwBitmap.get());
- ASSERT_EQ(GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture), initialCount + 1);
- cache.clear();
- ASSERT_EQ(GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture), initialCount);
-}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 6a3a6e5..f0d9ca3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -63,6 +63,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -4787,6 +4788,21 @@
}
/**
+ * Add {@link MicrophoneInfo} by device information while filtering certain types.
+ */
+ private void addMicrophonesFromAudioDeviceInfo(ArrayList<MicrophoneInfo> microphones,
+ HashSet<Integer> filterTypes) {
+ AudioDeviceInfo[] devices = getDevicesStatic(GET_DEVICES_INPUTS);
+ for (AudioDeviceInfo device : devices) {
+ if (filterTypes.contains(device.getType())) {
+ continue;
+ }
+ MicrophoneInfo microphone = microphoneInfoFromAudioDeviceInfo(device);
+ microphones.add(microphone);
+ }
+ }
+
+ /**
* Returns a list of {@link MicrophoneInfo} that corresponds to the characteristics
* of all available microphones. The list is empty when no microphones are available
* on the device. An error during the query will result in an IOException being thrown.
@@ -4797,21 +4813,17 @@
public List<MicrophoneInfo> getMicrophones() throws IOException {
ArrayList<MicrophoneInfo> microphones = new ArrayList<MicrophoneInfo>();
int status = AudioSystem.getMicrophones(microphones);
+ HashSet<Integer> filterTypes = new HashSet<>();
+ filterTypes.add(AudioDeviceInfo.TYPE_TELEPHONY);
if (status != AudioManager.SUCCESS) {
- // fail and bail!
+ // fail and populate microphones with unknown characteristics by device information.
Log.e(TAG, "getMicrophones failed:" + status);
- return new ArrayList<MicrophoneInfo>(); // Always return a list.
+ addMicrophonesFromAudioDeviceInfo(microphones, filterTypes);
+ return microphones;
}
setPortIdForMicrophones(microphones);
- AudioDeviceInfo[] devices = getDevicesStatic(GET_DEVICES_INPUTS);
- for (AudioDeviceInfo device : devices) {
- if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_MIC ||
- device.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
- continue;
- }
- MicrophoneInfo microphone = microphoneInfoFromAudioDeviceInfo(device);
- microphones.add(microphone);
- }
+ filterTypes.add(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ addMicrophonesFromAudioDeviceInfo(microphones, filterTypes);
return microphones;
}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 4f0dccb..6b35dd4 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1628,7 +1628,6 @@
int status = native_get_active_microphones(activeMicrophones);
if (status != AudioManager.SUCCESS) {
Log.e(TAG, "getActiveMicrophones failed:" + status);
- return new ArrayList<MicrophoneInfo>();
}
AudioManager.setPortIdForMicrophones(activeMicrophones);
diff --git a/media/java/android/media/BufferingParams.java b/media/java/android/media/BufferingParams.java
index 521e897..aaae5e7b 100644
--- a/media/java/android/media/BufferingParams.java
+++ b/media/java/android/media/BufferingParams.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -63,6 +64,7 @@
* <p>Users should use {@link Builder} to change {@link BufferingParams}.
* @hide
*/
+@TestApi
public final class BufferingParams implements Parcelable {
private static final int BUFFERING_NO_MARK = -1;
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index f9a1f8b..392a1eb 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.ActivityThread;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -1680,6 +1681,7 @@
* @hide
*/
@NonNull
+ @TestApi
public native BufferingParams getBufferingParams();
/**
@@ -1696,6 +1698,7 @@
* @throws IllegalArgumentException if params is invalid or not supported.
* @hide
*/
+ @TestApi
public native void setBufferingParams(@NonNull BufferingParams params);
/**
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 90b6bff..82d64f3 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -1434,7 +1434,6 @@
int status = native_getActiveMicrophones(activeMicrophones);
if (status != AudioManager.SUCCESS) {
Log.e(TAG, "getActiveMicrophones failed:" + status);
- return new ArrayList<MicrophoneInfo>();
}
AudioManager.setPortIdForMicrophones(activeMicrophones);
diff --git a/native/webview/plat_support/draw_gl_functor.cpp b/native/webview/plat_support/draw_gl_functor.cpp
index d54f558..7cb49da 100644
--- a/native/webview/plat_support/draw_gl_functor.cpp
+++ b/native/webview/plat_support/draw_gl_functor.cpp
@@ -21,7 +21,6 @@
#include "draw_gl.h"
-#include <Properties.h>
#include <errno.h>
#include <jni.h>
#include <private/hwui/DrawGlInfo.h>
@@ -54,13 +53,7 @@
}
AwDrawGLInfo aw_info;
- // TODO(boliu): Remove property check once OpenGL fallback is removed.
- auto render_pipeline_type =
- android::uirenderer::Properties::getRenderPipelineType();
- aw_info.version = (render_pipeline_type ==
- android::uirenderer::RenderPipelineType::OpenGL)
- ? 2
- : kAwDrawGLInfoVersion;
+ aw_info.version = kAwDrawGLInfoVersion;
switch (what) {
case DrawGlInfo::kModeDraw: {
aw_info.mode = AwDrawGLInfo::kModeDraw;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index 4a6df50..bab59f1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -29,5 +29,5 @@
void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state);
void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile);
- void onProfileAudioStateChanged(int bluetoothProfile, int state);
+ void onAudioModeChanged();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index b74b2cd..06fe4de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.settingslib.R;
@@ -119,6 +120,12 @@
addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
new ActiveDeviceChangedHandler());
+ // Headset state changed broadcasts
+ addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,
+ new AudioModeChangedHandler());
+ addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
+ new AudioModeChangedHandler());
+
mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
}
@@ -456,4 +463,25 @@
}
}
}
+
+ private class AudioModeChangedHandler implements Handler {
+
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ final String action = intent.getAction();
+ if (action == null) {
+ Log.w(TAG, "AudioModeChangedHandler() action is null");
+ return;
+ }
+ dispatchAudioModeChanged();
+ }
+ }
+
+ private void dispatchAudioModeChanged() {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onAudioModeChanged();
+ }
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 54d1aba..148b7a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -16,6 +16,7 @@
package com.android.settingslib.drawable;
+import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
@@ -251,11 +252,8 @@
mPaint.setColorFilter(null);
} else {
int color = mTintColor.getColorForState(getState(), mTintColor.getDefaultColor());
- if (mPaint.getColorFilter() == null) {
+ if (shouldUpdateColorFilter(color, mTintMode)) {
mPaint.setColorFilter(new PorterDuffColorFilter(color, mTintMode));
- } else {
- ((PorterDuffColorFilter) mPaint.getColorFilter()).setMode(mTintMode);
- ((PorterDuffColorFilter) mPaint.getColorFilter()).setColor(color);
}
}
@@ -263,6 +261,18 @@
}
}
+ private boolean shouldUpdateColorFilter(@ColorInt int color, PorterDuff.Mode mode) {
+ ColorFilter colorFilter = mPaint.getColorFilter();
+ if (colorFilter instanceof PorterDuffColorFilter) {
+ PorterDuffColorFilter porterDuffColorFilter = (PorterDuffColorFilter) colorFilter;
+ int currentColor = porterDuffColorFilter.getColor();
+ PorterDuff.Mode currentMode = porterDuffColorFilter.getMode();
+ return currentColor != color || currentMode != mode;
+ } else {
+ return false;
+ }
+ }
+
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
index e63bdd6..a6c1634 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
@@ -60,7 +60,7 @@
// InputMethods and subtypes are saved in the settings as follows:
// ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
- private static String buildInputMethodsAndSubtypesString(
+ public static String buildInputMethodsAndSubtypesString(
final HashMap<String, HashSet<String>> imeToSubtypesMap) {
final StringBuilder builder = new StringBuilder();
for (final String imi : imeToSubtypesMap.keySet()) {
@@ -115,7 +115,7 @@
return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
}
- private static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
+ public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
final String inputMethodsAndSubtypesString) {
final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/UserManagerHelper.java b/packages/SettingsLib/src/com/android/settingslib/users/UserManagerHelper.java
index c4ca339..113256f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/UserManagerHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/UserManagerHelper.java
@@ -41,6 +41,7 @@
private static final String TAG = "UserManagerHelper";
private final Context mContext;
private final UserManager mUserManager;
+ private final ActivityManager mActivityManager;
private OnUsersUpdateListener mUpdateListener;
private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
@Override
@@ -52,6 +53,7 @@
public UserManagerHelper(Context context) {
mContext = context;
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
/**
@@ -72,30 +74,64 @@
}
/**
- * Gets {@link UserInfo} for the current user.
+ * Gets UserInfo for the foreground user.
*
- * @return {@link UserInfo} for the current user.
+ * Concept of foreground user is relevant for the multi-user deployment. Foreground user
+ * corresponds to the currently "logged in" user.
+ *
+ * @return {@link UserInfo} for the foreground user.
*/
- public UserInfo getCurrentUserInfo() {
- return mUserManager.getUserInfo(UserHandle.myUserId());
+ public UserInfo getForegroundUserInfo() {
+ return mUserManager.getUserInfo(getForegroundUserId());
}
/**
- * Gets all the other users on the system that are not the current user.
- *
- * @return List of {@code UserInfo} for each user that is not the current user.
+ * @return Id of the foreground user.
*/
- public List<UserInfo> getAllUsersExcludesCurrentUser() {
- List<UserInfo> others = getAllUsers();
+ public int getForegroundUserId() {
+ return mActivityManager.getCurrentUser();
+ }
- for (Iterator<UserInfo> iterator = others.iterator(); iterator.hasNext(); ) {
- UserInfo userInfo = iterator.next();
- if (userInfo.id == UserHandle.myUserId()) {
- // Remove current user from the list.
- iterator.remove();
- }
- }
- return others;
+ /**
+ * Gets UserInfo for the user running the caller process.
+ *
+ * Differentiation between foreground user and current process user is relevant for multi-user
+ * deployments.
+ *
+ * Some multi-user aware components (like SystemUI) might run as a singleton - one component
+ * for all users. Current process user is then always the same for that component, even when
+ * the foreground user changes.
+ *
+ * @return {@link UserInfo} for the user running the current process.
+ */
+ public UserInfo getCurrentProcessUserInfo() {
+ return mUserManager.getUserInfo(getCurrentProcessUserId());
+ }
+
+ /**
+ * @return Id for the user running the current process.
+ */
+ public int getCurrentProcessUserId() {
+ return UserHandle.myUserId();
+ }
+
+ /**
+ * Gets all the other users on the system that are not the user running the current process.
+ *
+ * @return List of {@code UserInfo} for each user that is not the user running the process.
+ */
+ public List<UserInfo> getAllUsersExcludesCurrentProcessUser() {
+ return getAllUsersExceptUser(getCurrentProcessUserId());
+ }
+
+ /**
+ * Gets all the existing users on the system that are not the currently running as the
+ * foreground user.
+ *
+ * @return List of {@code UserInfo} for each user that is not the foreground user.
+ */
+ public List<UserInfo> getAllUsersExcludesForegroundUser() {
+ return getAllUsersExceptUser(getForegroundUserId());
}
/**
@@ -104,12 +140,22 @@
* @return List of {@code UserInfo} for each user that is not the system user.
*/
public List<UserInfo> getAllUsersExcludesSystemUser() {
+ return getAllUsersExceptUser(UserHandle.USER_SYSTEM);
+ }
+
+ /**
+ * Get all the users except the one with userId passed in.
+ *
+ * @param userId of the user not to be returned.
+ * @return All users other than user with userId.
+ */
+ public List<UserInfo> getAllUsersExceptUser(int userId) {
List<UserInfo> others = getAllUsers();
for (Iterator<UserInfo> iterator = others.iterator(); iterator.hasNext(); ) {
UserInfo userInfo = iterator.next();
- if (userIsSystemUser(userInfo)) {
- // Remove system user from the list.
+ if (userInfo.id == userId) {
+ // Remove user with userId from the list.
iterator.remove();
}
}
@@ -146,78 +192,115 @@
}
/**
- * Checks whether passed in user is the user that's currently logged in.
+ * Checks whether passed in user is the foreground user.
*
* @param userInfo User to check.
- * @return {@code true} if current user, {@code false} otherwise.
+ * @return {@code true} if foreground user, {@code false} otherwise.
*/
- public boolean userIsCurrentUser(UserInfo userInfo) {
- return getCurrentUserInfo().id == userInfo.id;
+ public boolean userIsForegroundUser(UserInfo userInfo) {
+ return getForegroundUserId() == userInfo.id;
}
- // Current user information accessors
+ /**
+ * Checks whether passed in user is the user that's running the current process.
+ *
+ * @param userInfo User to check.
+ * @return {@code true} if user running the process, {@code false} otherwise.
+ */
+ public boolean userIsRunningCurrentProcess(UserInfo userInfo) {
+ return getCurrentProcessUserId() == userInfo.id;
+ }
+
+ // Foreground user information accessors.
/**
- * Checks if the current user is a demo user.
+ * Checks if the foreground user is a guest user.
*/
- public boolean isDemoUser() {
+ public boolean foregroundUserIsGuestUser() {
+ return getForegroundUserInfo().isGuest();
+ }
+
+ /**
+ * Return whether the foreground user has a restriction.
+ *
+ * @param restriction Restriction to check. Should be a UserManager.* restriction.
+ * @return Whether that restriction exists for the foreground user.
+ */
+ public boolean foregroundUserHasUserRestriction(String restriction) {
+ return mUserManager.hasUserRestriction(restriction, getForegroundUserInfo().getUserHandle());
+ }
+
+ /**
+ * Checks if the foreground user can add new users.
+ */
+ public boolean foregroundUserCanAddUsers() {
+ return !foregroundUserHasUserRestriction(UserManager.DISALLOW_ADD_USER);
+ }
+
+ // Current process user information accessors
+
+ /**
+ * Checks if the calling app is running in a demo user.
+ */
+ public boolean currentProcessRunningAsDemoUser() {
return mUserManager.isDemoUser();
}
/**
- * Checks if the current user is a guest user.
+ * Checks if the calling app is running as a guest user.
*/
- public boolean isGuestUser() {
+ public boolean currentProcessRunningAsGuestUser() {
return mUserManager.isGuestUser();
}
/**
- * Checks if the current user is the system user (User 0).
+ * Checks whether this process is running under the system user.
*/
- public boolean isSystemUser() {
+ public boolean currentProcessRunningAsSystemUser() {
return mUserManager.isSystemUser();
}
- // Current user restriction accessors
+ // Current process user restriction accessors
/**
- * Return whether the current user has a restriction.
+ * Return whether the user running the current process has a restriction.
*
* @param restriction Restriction to check. Should be a UserManager.* restriction.
- * @return Whether that restriction exists for the current user.
+ * @return Whether that restriction exists for the user running the process.
*/
- public boolean hasUserRestriction(String restriction) {
+ public boolean currentProcessHasUserRestriction(String restriction) {
return mUserManager.hasUserRestriction(restriction);
}
/**
- * Checks if the current user can add new users.
+ * Checks if the user running the current process can add new users.
*/
- public boolean canAddUsers() {
- return !hasUserRestriction(UserManager.DISALLOW_ADD_USER);
+ public boolean currentProcessCanAddUsers() {
+ return !currentProcessHasUserRestriction(UserManager.DISALLOW_ADD_USER);
}
/**
- * Checks if the current user can remove users.
+ * Checks if the user running the current process can remove users.
*/
- public boolean canRemoveUsers() {
- return !hasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
+ public boolean currentProcessCanRemoveUsers() {
+ return !currentProcessHasUserRestriction(UserManager.DISALLOW_REMOVE_USER);
}
/**
- * Checks if the current user is allowed to switch to another user.
+ * Checks if the user running the current process is allowed to switch to another user.
*/
- public boolean canSwitchUsers() {
- return !hasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
+ public boolean currentProcessCanSwitchUsers() {
+ return !currentProcessHasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
}
/**
- * Checks if the current user can modify accounts. Demo and Guest users cannot modify accounts
- * even if the DISALLOW_MODIFY_ACCOUNTS restriction is not applied.
+ * Checks if the current process user can modify accounts. Demo and Guest users cannot modify
+ * accounts even if the DISALLOW_MODIFY_ACCOUNTS restriction is not applied.
*/
- public boolean canModifyAccounts() {
- return !hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS) && !isDemoUser()
- && !isGuestUser();
+ public boolean currentProcessCanModifyAccounts() {
+ return !currentProcessHasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)
+ && !currentProcessRunningAsDemoUser()
+ && !currentProcessRunningAsGuestUser();
}
// User actions
@@ -242,8 +325,8 @@
/**
* Tries to remove the user that's passed in. System user cannot be removed.
- * If the user to be removed is current user, it switches to the system user first, and then
- * removes the user.
+ * If the user to be removed is user currently running the process,
+ * it switches to the system user first, and then removes the user.
*
* @param userInfo User to be removed
* @return {@code true} if user is successfully removed, {@code false} otherwise.
@@ -254,7 +337,7 @@
return false;
}
- if (userInfo.id == getCurrentUserInfo().id) {
+ if (userInfo.id == getCurrentProcessUserId()) {
switchToUserId(UserHandle.USER_SYSTEM);
}
@@ -267,7 +350,7 @@
* @param userInfo User to switch to.
*/
public void switchToUser(UserInfo userInfo) {
- if (userInfo.id == getCurrentUserInfo().id) {
+ if (userInfo.id == getForegroundUserId()) {
return;
}
@@ -276,15 +359,6 @@
return;
}
- if (UserManager.isGuestUserEphemeral()) {
- // If switching from guest, we want to bring up the guest exit dialog instead of
- // switching
- UserInfo currUserInfo = getCurrentUserInfo();
- if (currUserInfo != null && currUserInfo.isGuest()) {
- return;
- }
- }
-
switchToUserId(userInfo.id);
}
@@ -348,6 +422,9 @@
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_STOPPED);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null, null);
}
@@ -366,9 +443,7 @@
private void switchToUserId(int id) {
try {
- final ActivityManager am = (ActivityManager)
- mContext.getSystemService(Context.ACTIVITY_SERVICE);
- am.switchUser(id);
+ mActivityManager.switchUser(id);
} catch (Exception e) {
Log.e(TAG, "Couldn't switch user.", e);
}
@@ -389,4 +464,3 @@
void onUsersUpdate();
}
}
-
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/UserManagerHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/UserManagerHelperTest.java
index 3f1fcbb..15f7770 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/UserManagerHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/UserManagerHelperTest.java
@@ -58,7 +58,7 @@
private UserManagerHelper.OnUsersUpdateListener mTestListener;
private UserManagerHelper mHelper;
- private UserInfo mCurrentUser;
+ private UserInfo mCurrentProcessUser;
private UserInfo mSystemUser;
@Before
@@ -70,13 +70,13 @@
.thenReturn(InstrumentationRegistry.getTargetContext().getResources());
mHelper = new UserManagerHelper(mContext);
- mCurrentUser = createUserInfoForId(UserHandle.myUserId());
+ mCurrentProcessUser = createUserInfoForId(UserHandle.myUserId());
mSystemUser = createUserInfoForId(UserHandle.USER_SYSTEM);
- when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(mCurrentUser);
+ when(mUserManager.getUserInfo(UserHandle.myUserId())).thenReturn(mCurrentProcessUser);
}
@Test
- public void testUserIsSystemUser() {
+ public void userIsSystemUser() {
UserInfo testInfo = new UserInfo();
testInfo.id = UserHandle.USER_SYSTEM;
@@ -87,33 +87,7 @@
}
@Test
- public void testGetAllUsersExcludesCurrentUser() {
- int currentUser = UserHandle.myUserId();
-
- UserInfo otherUser1 = createUserInfoForId(currentUser + 1);
- UserInfo otherUser2 = createUserInfoForId(currentUser - 1);
- UserInfo otherUser3 = createUserInfoForId(currentUser + 2);
-
- List<UserInfo> testUsers = new ArrayList<>();
- testUsers.add(otherUser1);
- testUsers.add(otherUser2);
- testUsers.add(mCurrentUser);
- testUsers.add(otherUser3);
-
- when(mUserManager.getUsers(true)).thenReturn(testUsers);
-
- // Should return 3 users that don't have currentUser id.
- assertThat(mHelper.getAllUsersExcludesCurrentUser().size()).isEqualTo(3);
- // Should not contain current user.
- assertThat(mHelper.getAllUsersExcludesCurrentUser()).doesNotContain(mCurrentUser);
- // Should contain non-current users.
- assertThat(mHelper.getAllUsersExcludesCurrentUser()).contains(otherUser1);
- assertThat(mHelper.getAllUsersExcludesCurrentUser()).contains(otherUser2);
- assertThat(mHelper.getAllUsersExcludesCurrentUser()).contains(otherUser3);
- }
-
- @Test
- public void testGetAllUsersExcludesSystemUser() {
+ public void getAllUsersExcludesSystemUser() {
UserInfo otherUser1 = createUserInfoForId(10);
UserInfo otherUser2 = createUserInfoForId(11);
UserInfo otherUser3 = createUserInfoForId(12);
@@ -127,17 +101,41 @@
when(mUserManager.getUsers(true)).thenReturn(testUsers);
// Should return 3 users that don't have SYSTEM USER id.
- assertThat(mHelper.getAllUsersExcludesSystemUser().size()).isEqualTo(3);
- // Should not contain system user.
- assertThat(mHelper.getAllUsersExcludesSystemUser()).doesNotContain(mSystemUser);
- // Should contain non-system users.
- assertThat(mHelper.getAllUsersExcludesSystemUser()).contains(otherUser1);
- assertThat(mHelper.getAllUsersExcludesSystemUser()).contains(otherUser2);
- assertThat(mHelper.getAllUsersExcludesSystemUser()).contains(otherUser3);
+ assertThat(mHelper.getAllUsersExcludesSystemUser()).hasSize(3);
+ assertThat(mHelper.getAllUsersExcludesSystemUser())
+ .containsExactly(otherUser1, otherUser2, otherUser3);
}
@Test
- public void testGetAllUsers() {
+ public void getAllUsersExceptUser() {
+ UserInfo user1 = createUserInfoForId(10);
+ UserInfo user2 = createUserInfoForId(10);
+ UserInfo user3 = createUserInfoForId(12);
+
+ List<UserInfo> testUsers = new ArrayList<>();
+ testUsers.add(user1);
+ testUsers.add(user2);
+ testUsers.add(user3);
+
+ when(mUserManager.getUsers(true)).thenReturn(new ArrayList<>(testUsers));
+
+ // Should return all 3 users.
+ assertThat(mHelper.getAllUsersExceptUser(9).size()).isEqualTo(3);
+
+ // Should return only user 12.
+ assertThat(mHelper.getAllUsersExceptUser(10).size()).isEqualTo(1);
+ assertThat(mHelper.getAllUsersExceptUser(10)).contains(user3);
+
+ when(mUserManager.getUsers(true)).thenReturn(new ArrayList<>(testUsers));
+
+ // Should drop user 12.
+ assertThat(mHelper.getAllUsersExceptUser(12).size()).isEqualTo(2);
+ assertThat(mHelper.getAllUsersExceptUser(12)).contains(user1);
+ assertThat(mHelper.getAllUsersExceptUser(12)).contains(user2);
+ }
+
+ @Test
+ public void getAllUsers() {
int currentUser = UserHandle.myUserId();
UserInfo otherUser1 = createUserInfoForId(currentUser + 1);
@@ -147,21 +145,18 @@
List<UserInfo> testUsers = new ArrayList<>();
testUsers.add(otherUser1);
testUsers.add(otherUser2);
- testUsers.add(mCurrentUser);
+ testUsers.add(mCurrentProcessUser);
testUsers.add(otherUser3);
when(mUserManager.getUsers(true)).thenReturn(testUsers);
- // Should return 3 users that don't have currentUser id.
assertThat(mHelper.getAllUsers().size()).isEqualTo(4);
- assertThat(mHelper.getAllUsers()).contains(mCurrentUser);
- assertThat(mHelper.getAllUsers()).contains(otherUser1);
- assertThat(mHelper.getAllUsers()).contains(otherUser2);
- assertThat(mHelper.getAllUsers()).contains(otherUser3);
+ assertThat(mHelper.getAllUsers())
+ .containsExactly(mCurrentProcessUser, otherUser1, otherUser2, otherUser3);
}
@Test
- public void testUserCanBeRemoved() {
+ public void userCanBeRemoved() {
UserInfo testInfo = new UserInfo();
// System user cannot be removed.
@@ -173,71 +168,59 @@
}
@Test
- public void testUserIsCurrentUser() {
- UserInfo testInfo = new UserInfo();
-
- // System user cannot be removed.
- testInfo.id = UserHandle.myUserId();
- assertThat(mHelper.userIsCurrentUser(testInfo)).isTrue();
-
- testInfo.id = UserHandle.myUserId() + 2;
- assertThat(mHelper.userIsCurrentUser(testInfo)).isFalse();
- }
-
- @Test
- public void testCanAddUsers() {
+ public void currentProcessCanAddUsers() {
when(mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)).thenReturn(false);
- assertThat(mHelper.canAddUsers()).isTrue();
+ assertThat(mHelper.currentProcessCanAddUsers()).isTrue();
when(mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER)).thenReturn(true);
- assertThat(mHelper.canAddUsers()).isFalse();
+ assertThat(mHelper.currentProcessCanAddUsers()).isFalse();
}
@Test
- public void testCanRemoveUsers() {
+ public void currentProcessCanRemoveUsers() {
when(mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)).thenReturn(false);
- assertThat(mHelper.canRemoveUsers()).isTrue();
+ assertThat(mHelper.currentProcessCanRemoveUsers()).isTrue();
when(mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_USER)).thenReturn(true);
- assertThat(mHelper.canRemoveUsers()).isFalse();
+ assertThat(mHelper.currentProcessCanRemoveUsers()).isFalse();
}
@Test
- public void testCanSwitchUsers() {
+ public void currentProcessCanSwitchUsers() {
when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(false);
- assertThat(mHelper.canSwitchUsers()).isTrue();
+ assertThat(mHelper.currentProcessCanSwitchUsers()).isTrue();
when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(true);
- assertThat(mHelper.canSwitchUsers()).isFalse();
+ assertThat(mHelper.currentProcessCanSwitchUsers()).isFalse();
}
@Test
- public void testGuestCannotModifyAccounts() {
- assertThat(mHelper.canModifyAccounts()).isTrue();
+ public void currentProcessRunningAsGuestCannotModifyAccounts() {
+ assertThat(mHelper.currentProcessCanModifyAccounts()).isTrue();
when(mUserManager.isGuestUser()).thenReturn(true);
- assertThat(mHelper.canModifyAccounts()).isFalse();
+ assertThat(mHelper.currentProcessCanModifyAccounts()).isFalse();
}
@Test
- public void testDemoUserCannotModifyAccounts() {
- assertThat(mHelper.canModifyAccounts()).isTrue();
+ public void currentProcessRunningAsDemoUserCannotModifyAccounts() {
+ assertThat(mHelper.currentProcessCanModifyAccounts()).isTrue();
when(mUserManager.isDemoUser()).thenReturn(true);
- assertThat(mHelper.canModifyAccounts()).isFalse();
+ assertThat(mHelper.currentProcessCanModifyAccounts()).isFalse();
}
@Test
- public void testUserWithDisallowModifyAccountsRestrictionCannotModifyAccounts() {
- assertThat(mHelper.canModifyAccounts()).isTrue();
+ public void currentProcessWithDisallowModifyAccountsRestrictionCannotModifyAccounts() {
+ assertThat(mHelper.currentProcessCanModifyAccounts()).isTrue();
when(mUserManager.hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS))
.thenReturn(true);
- assertThat(mHelper.canModifyAccounts()).isFalse();
+ assertThat(mHelper.currentProcessCanModifyAccounts()).isFalse();
}
@Test
- public void testCreateNewUser() {
+ public void createNewUser() {
// Verify createUser on UserManager gets called.
mHelper.createNewUser("Test User");
verify(mUserManager).createUser("Test User", 0);
@@ -252,52 +235,36 @@
}
@Test
- public void testRemoveUser() {
+ public void removeUser() {
// Cannot remove system user.
assertThat(mHelper.removeUser(mSystemUser)).isFalse();
// Removing non-current, non-system user, simply calls removeUser.
- UserInfo userToRemove = createUserInfoForId(mCurrentUser.id + 2);
+ UserInfo userToRemove = createUserInfoForId(mCurrentProcessUser.id + 2);
+
mHelper.removeUser(userToRemove);
- verify(mUserManager).removeUser(mCurrentUser.id + 2);
+ verify(mUserManager).removeUser(mCurrentProcessUser.id + 2);
}
@Test
- public void testSwitchToUser() {
- // Switching to current user doesn't do anything.
- mHelper.switchToUser(mCurrentUser);
- verify(mActivityManager, never()).switchUser(mCurrentUser.id);
-
- // Switching to Guest calls createGuest.
- UserInfo guestInfo = new UserInfo(mCurrentUser.id + 1, "Test Guest", UserInfo.FLAG_GUEST);
- mHelper.switchToUser(guestInfo);
- verify(mUserManager).createGuest(mContext, "Test Guest");
-
- // Switching to non-current, non-guest user, simply calls switchUser.
- UserInfo userToSwitchTo = new UserInfo(mCurrentUser.id + 5, "Test User", 0);
- mHelper.switchToUser(userToSwitchTo);
- verify(mActivityManager).switchUser(mCurrentUser.id + 5);
- }
-
- @Test
- public void testSwitchToGuest() {
+ public void switchToGuest() {
mHelper.switchToGuest("Test Guest");
verify(mUserManager).createGuest(mContext, "Test Guest");
- UserInfo guestInfo = new UserInfo(mCurrentUser.id + 2, "Test Guest", UserInfo.FLAG_GUEST);
+ UserInfo guestInfo = new UserInfo(21, "Test Guest", UserInfo.FLAG_GUEST);
when(mUserManager.createGuest(mContext, "Test Guest")).thenReturn(guestInfo);
mHelper.switchToGuest("Test Guest");
- verify(mActivityManager).switchUser(mCurrentUser.id + 2);
+ verify(mActivityManager).switchUser(21);
}
@Test
- public void testGetUserIcon() {
- mHelper.getUserIcon(mCurrentUser);
- verify(mUserManager).getUserIcon(mCurrentUser.id);
+ public void getUserIcon() {
+ mHelper.getUserIcon(mCurrentProcessUser);
+ verify(mUserManager).getUserIcon(mCurrentProcessUser.id);
}
@Test
- public void testScaleUserIcon() {
+ public void scaleUserIcon() {
Bitmap fakeIcon = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Drawable scaledIcon = mHelper.scaleUserIcon(fakeIcon, 300);
assertThat(scaledIcon.getIntrinsicWidth()).isEqualTo(300);
@@ -305,14 +272,14 @@
}
@Test
- public void testSetUserName() {
- UserInfo testInfo = createUserInfoForId(mCurrentUser.id + 3);
+ public void setUserName() {
+ UserInfo testInfo = createUserInfoForId(mCurrentProcessUser.id + 3);
mHelper.setUserName(testInfo, "New Test Name");
- verify(mUserManager).setUserName(mCurrentUser.id + 3, "New Test Name");
+ verify(mUserManager).setUserName(mCurrentProcessUser.id + 3, "New Test Name");
}
@Test
- public void testRegisterUserChangeReceiver() {
+ public void registerUserChangeReceiver() {
mHelper.registerOnUsersUpdateListener(mTestListener);
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
@@ -335,10 +302,14 @@
// Verify the presence of each intent in the filter.
// Verify the exact number of filters. Every time a new intent is added, this test should
// get updated.
- assertThat(filterCaptor.getValue().countActions()).isEqualTo(3);
+ assertThat(filterCaptor.getValue().countActions()).isEqualTo(6);
assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_REMOVED)).isTrue();
assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_ADDED)).isTrue();
assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue();
+ assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_SWITCHED)).isTrue();
+ assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_STOPPED)).isTrue();
+ assertThat(filterCaptor.getValue().hasAction(Intent.ACTION_USER_UNLOCKED)).isTrue();
+
// Verify that calling the receiver calls the listener.
receiverCaptor.getValue().onReceive(mContext, new Intent());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
new file mode 100644
index 0000000..d1e37f6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 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.bluetooth;
+
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothHeadset;
+import android.content.Context;
+import android.content.Intent;
+
+import android.telephony.TelephonyManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BluetoothEventManagerTest {
+
+ @Mock
+ private LocalBluetoothAdapter mLocalAdapter;
+ @Mock
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ @Mock
+ private BluetoothCallback mBluetoothCallback;
+
+ private Context mContext;
+ private Intent mIntent;
+ private BluetoothEventManager mBluetoothEventManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter,
+ mCachedDeviceManager, mContext);
+ }
+
+ /**
+ * Intent ACTION_AUDIO_STATE_CHANGED should dispatch to callback.
+ */
+ @Test
+ public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() {
+ mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+
+ mContext.sendBroadcast(mIntent);
+
+ verify(mBluetoothCallback).onAudioModeChanged();
+ }
+
+ /**
+ * Intent ACTION_PHONE_STATE_CHANGED should dispatch to callback.
+ */
+ @Test
+ public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() {
+ mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+
+ mContext.sendBroadcast(mIntent);
+
+ verify(mBluetoothCallback).onAudioModeChanged();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
new file mode 100644
index 0000000..7c7957c
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2018 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+@RunWith(RobolectricTestRunner.class)
+public class InputMethodAndSubtypeUtilTest {
+
+ private static final HashSet<String> EMPTY_STRING_SET = new HashSet<>();
+
+ private static HashSet<String> asHashSet(String... strings) {
+ HashSet<String> hashSet = new HashSet<>();
+ for (String s : strings) {
+ hashSet.add(s);
+ }
+ return hashSet;
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_EmptyString() {
+ assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("")).isEmpty();
+ assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(null)).isEmpty();
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeNoSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0");
+ assertThat(r).containsExactly("ime0", EMPTY_STRING_SET);
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_MultipleImesNoSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0:ime1");
+ assertThat(r).containsExactly("ime0", EMPTY_STRING_SET, "ime1", EMPTY_STRING_SET);
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0;subtype0");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeDuplicateSameSubtypes() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype0");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"));
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_MultiplePairsOfImeSubtype() {
+ assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0:ime1;subtype1"))
+ .containsExactly("ime0", asHashSet("subtype0"), "ime1", asHashSet("subtype1"));
+ assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1:ime1;subtype2"))
+ .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+ "ime1", asHashSet("subtype2"));
+ assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1:ime1;subtype1;subtype2"))
+ .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+ "ime1", asHashSet("subtype1", "subtype2"));
+
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_MixedImeSubtypePairsAndImeNoSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+ "ime1", asHashSet("subtype1", "subtype2"),
+ "ime2", EMPTY_STRING_SET);
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_EmptyInput() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ assertThat(map).isEmpty();
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_SingleIme() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", new HashSet<>());
+ String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+ assertThat(result).isEqualTo("ime0");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0"));
+ String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+ assertThat(result).isEqualTo("ime0;subtype0");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0", "subtype1"));
+ String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("(ime0;subtype0;subtype1)|(ime0;subtype1;subtype0)");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_MultipleImesNoSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", EMPTY_STRING_SET);
+ map.put("ime1", EMPTY_STRING_SET);
+ String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("(ime0:ime1)|(ime1:ime0)");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_MultipleImesWithAndWithoutSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0", "subtype1"));
+ map.put("ime1", EMPTY_STRING_SET);
+ String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("(ime0;subtype0;subtype1:ime1)|(ime0;subtype1;subtype0:ime1)"
+ + "|(ime1:ime0;subtype0;subtype1)|(ime1:ime0;subtype1;subtype0)");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_MultipleImesWithSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0", "subtype1"));
+ map.put("ime1", asHashSet("subtype2", "subtype3"));
+ String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("|(ime0;subtype0;subtype1:ime1;subtype2;subtype3)|"
+ + "|(ime0;subtype1;subtype0:ime1;subtype2;subtype3)|"
+ + "|(ime0;subtype0;subtype1:ime1;subtype3;subtype2)|"
+ + "|(ime0;subtype1;subtype0:ime1;subtype3;subtype2)|"
+ + "|(ime1;subtype2;subtype3:ime0;subtype0;subtype1)|"
+ + "|(ime2;subtype3;subtype2:ime0;subtype0;subtype1)|"
+ + "|(ime3;subtype2;subtype3:ime0;subtype1;subtype0)|"
+ + "|(ime4;subtype3;subtype2:ime0;subtype1;subtype0)");
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
new file mode 100644
index 0000000..4a8ef1e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowActivityManager.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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.testutils.shadow;
+
+import android.app.ActivityManager;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+import org.robolectric.shadow.api.Shadow;
+
+@Implements(ActivityManager.class)
+public class ShadowActivityManager {
+ private static int sCurrentUserId = 0;
+ private int mUserSwitchedTo = -1;
+
+ @Resetter
+ public void reset() {
+ sCurrentUserId = 0;
+ mUserSwitchedTo = 0;
+ }
+
+ @Implementation
+ public static int getCurrentUser() {
+ return sCurrentUserId;
+ }
+
+ @Implementation
+ public boolean switchUser(int userId) {
+ mUserSwitchedTo = userId;
+ return true;
+ }
+
+ public boolean getSwitchUserCalled() {
+ return mUserSwitchedTo != -1;
+ }
+
+ public int getUserSwitchedTo() {
+ return mUserSwitchedTo;
+ }
+
+ public static void setCurrentUser(int userId) {
+ sCurrentUserId = userId;
+ }
+
+ public static ShadowActivityManager getShadow() {
+ return (ShadowActivityManager) Shadow.extract(
+ RuntimeEnvironment.application.getSystemService(ActivityManager.class));
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
new file mode 100644
index 0000000..f2ea3a4
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2018 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.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.settingslib.testutils.shadow.ShadowActivityManager;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(shadows = { ShadowActivityManager.class, UserManagerHelperRoboTest.ShadowUserHandle.class})
+public class UserManagerHelperRoboTest {
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+
+ private UserManagerHelper mHelper;
+
+ @Before
+ public void setUpMocksAndUserManagerHelper() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(
+ RuntimeEnvironment.application.getSystemService(ActivityManager.class));
+ mHelper = new UserManagerHelper(mContext);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowActivityManager.getShadow().reset();
+ }
+
+ @Test
+ public void getForegroundUserId() {
+ ShadowActivityManager.setCurrentUser(15);
+ assertThat(mHelper.getForegroundUserId()).isEqualTo(15);
+ }
+
+ @Test
+ public void getForegroundUserInfo() {
+ ShadowActivityManager.setCurrentUser(17);
+ when(mUserManager.getUserInfo(ShadowActivityManager.getCurrentUser()))
+ .thenReturn(createUserInfoForId(ShadowActivityManager.getCurrentUser()));
+ assertThat(mHelper.getForegroundUserInfo().id).isEqualTo(17);
+ }
+
+ @Test
+ public void getCurrentProcessUserId() {
+ ShadowUserHandle.setUid(11);
+ assertThat(mHelper.getCurrentProcessUserId()).isEqualTo(11);
+ }
+
+ @Test
+ public void getCurrentProcessUserInfo() {
+ ShadowUserHandle.setUid(12);
+ when(mUserManager.getUserInfo(UserHandle.myUserId()))
+ .thenReturn(createUserInfoForId(UserHandle.myUserId()));
+ assertThat(mHelper.getCurrentProcessUserInfo().id).isEqualTo(12);
+ }
+
+ @Test
+ public void getAllUsersExcludesCurrentProcessUser() {
+ ShadowUserHandle.setUid(12);
+ UserInfo currentProcessUser = createUserInfoForId(12);
+
+ UserInfo otherUser1 = createUserInfoForId(13);
+ UserInfo otherUser2 = createUserInfoForId(11);
+ UserInfo otherUser3 = createUserInfoForId(14);
+
+ List<UserInfo> testUsers = new ArrayList<>();
+ testUsers.add(otherUser1);
+ testUsers.add(otherUser2);
+ testUsers.add(currentProcessUser);
+ testUsers.add(otherUser3);
+
+ when(mUserManager.getUsers(true)).thenReturn(testUsers);
+
+ // Should return 3 users that don't have currentProcessUser id.
+ assertThat(mHelper.getAllUsersExcludesCurrentProcessUser()).hasSize(3);
+ assertThat(mHelper.getAllUsersExcludesCurrentProcessUser())
+ .containsExactly(otherUser1, otherUser2, otherUser3);
+ }
+
+ @Test
+ public void getAllUsersExcludesForegroundUser() {
+ ShadowActivityManager.setCurrentUser(17);
+ UserInfo foregroundUser = createUserInfoForId(17);
+
+ UserInfo otherUser1 = createUserInfoForId(11);
+ UserInfo otherUser2 = createUserInfoForId(18);
+ UserInfo otherUser3 = createUserInfoForId(16);
+
+ List<UserInfo> testUsers = new ArrayList<>();
+ testUsers.add(otherUser1);
+ testUsers.add(otherUser2);
+ testUsers.add(foregroundUser);
+ testUsers.add(otherUser3);
+
+ when(mUserManager.getUsers(true)).thenReturn(testUsers);
+
+ // Should return 3 users that don't have foregroundUser id.
+ assertThat(mHelper.getAllUsersExcludesForegroundUser()).hasSize(3);
+ assertThat(mHelper.getAllUsersExcludesForegroundUser())
+ .containsExactly(otherUser1, otherUser2, otherUser3);
+ }
+
+ @Test
+ public void userIsForegroundUser() {
+ ShadowActivityManager.setCurrentUser(10);
+ assertThat(mHelper.userIsForegroundUser(createUserInfoForId(10))).isTrue();
+ assertThat(mHelper.userIsForegroundUser(createUserInfoForId(11))).isFalse();
+
+ ShadowActivityManager.setCurrentUser(11);
+ assertThat(mHelper.userIsForegroundUser(createUserInfoForId(11))).isTrue();
+ }
+
+ @Test
+ public void userIsRunningCurrentProcess() {
+ ShadowUserHandle.setUid(10);
+ assertThat(mHelper.userIsRunningCurrentProcess(createUserInfoForId(10))).isTrue();
+ assertThat(mHelper.userIsRunningCurrentProcess(createUserInfoForId(11))).isFalse();
+
+ ShadowUserHandle.setUid(11);
+ assertThat(mHelper.userIsRunningCurrentProcess(createUserInfoForId(11))).isTrue();
+ }
+
+ @Test
+ public void removingCurrentProcessUserSwitchesToSystemUser() {
+ // Set currentProcess user to be user 10.
+ ShadowUserHandle.setUid(10);
+
+ // Removing a currentProcess user, calls "switch" to system user
+ mHelper.removeUser(createUserInfoForId(10));
+ assertThat(ShadowActivityManager.getShadow().getSwitchUserCalled()).isTrue();
+ assertThat(ShadowActivityManager.getShadow().getUserSwitchedTo()).isEqualTo(0);
+
+ verify(mUserManager).removeUser(10);
+ }
+
+ @Test
+ public void switchToUser() {
+ ShadowActivityManager.setCurrentUser(20);
+
+ // Switching to foreground user doesn't do anything.
+ mHelper.switchToUser(createUserInfoForId(20));
+ assertThat(ShadowActivityManager.getShadow().getSwitchUserCalled()).isFalse();
+
+ // Switching to Guest calls createGuest.
+ UserInfo guestInfo = new UserInfo(21, "Test Guest", UserInfo.FLAG_GUEST);
+ mHelper.switchToUser(guestInfo);
+ verify(mUserManager).createGuest(mContext, "Test Guest");
+
+ // Switching to non-current, non-guest user, simply calls switchUser.
+ UserInfo userToSwitchTo = new UserInfo(22, "Test User", 0);
+ mHelper.switchToUser(userToSwitchTo);
+ assertThat(ShadowActivityManager.getShadow().getSwitchUserCalled()).isTrue();
+ assertThat(ShadowActivityManager.getShadow().getUserSwitchedTo()).isEqualTo(22);
+ }
+
+ private UserInfo createUserInfoForId(int id) {
+ UserInfo userInfo = new UserInfo();
+ userInfo.id = id;
+ return userInfo;
+ }
+
+ @Implements(UserHandle.class)
+ public static class ShadowUserHandle {
+ private static int sUid = 0; // SYSTEM by default
+
+ public static void setUid(int uid) {
+ sUid = uid;
+ }
+
+ @Implementation
+ public static int myUserId() {
+ return sUid;
+ }
+
+ @Resetter
+ public static void reset() {
+ sUid = 0;
+ }
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 313f73f..f728684 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -253,6 +253,7 @@
&& !RESTORE_FROM_HIGHER_SDK_INT_SUPPORTED_KEYS.contains(key)) {
Log.w(TAG, "Not restoring unrecognized key '"
+ key + "' from future version " + appVersionCode);
+ data.skipEntityData();
continue;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a6d6250..022e306 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2935,7 +2935,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 162;
+ private static final int SETTINGS_VERSION = 163;
private final int mUserId;
@@ -3709,6 +3709,21 @@
currentVersion = 162;
}
+ if (currentVersion == 162) {
+ // Version 162: Add a gesture for silencing phones
+ final SettingsState settings = getGlobalSettingsLocked();
+ final Setting currentSetting = settings.getSettingLocked(
+ Global.SHOW_ZEN_UPGRADE_NOTIFICATION);
+ if (!currentSetting.isNull()
+ && TextUtils.equals("0", currentSetting.getValue())) {
+ settings.insertSettingLocked(
+ Global.SHOW_ZEN_UPGRADE_NOTIFICATION, "1",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 163;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d6fab4c..285b89f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -206,7 +206,7 @@
<uses-permission android:name="android.permission.WATCH_APPOPS" />
<!-- to read and change hvac values in a car -->
- <uses-permission android:name="android.car.permission.ADJUST_CAR_CLIMATE" />
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
<application
android:name=".SystemUIApplication"
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index addbf84..37de433 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -47,6 +47,8 @@
<dimen name="widget_title_font_size">24sp</dimen>
<!-- Slice subtitle -->
<dimen name="widget_label_font_size">16sp</dimen>
+ <!-- Slice offset when pulsing -->
+ <dimen name="widget_pulsing_bottom_padding">24dp</dimen>
<!-- Clock without header -->
<dimen name="widget_big_font_size">64dp</dimen>
<!-- Clock with header -->
diff --git a/packages/SystemUI/res/drawable/recents_onboarding_toast_rounded_background.xml b/packages/SystemUI/res/drawable/recents_onboarding_toast_rounded_background.xml
new file mode 100644
index 0000000..05db3a8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_onboarding_toast_rounded_background.xml
@@ -0,0 +1,19 @@
+<!--
+ Copyright (C) 2018 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <solid android:color="?android:attr/colorAccent" />
+ <corners android:radius="8dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/rounded_ripple.xml b/packages/SystemUI/res/drawable/rounded_ripple.xml
index 5588eb2..d9ed823 100644
--- a/packages/SystemUI/res/drawable/rounded_ripple.xml
+++ b/packages/SystemUI/res/drawable/rounded_ripple.xml
@@ -23,7 +23,7 @@
</item>
<item android:id="@android:id/background">
<shape android:shape="rectangle">
- <solid android:color="#FFFFFFFF"/>
+ <solid android:color="?android:attr/colorBackgroundFloating"/>
<corners android:radius="8dp"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index ef18725..f424171 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -38,9 +38,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="1dp"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
+ android:layout_marginStart="16dp"
android:layout_gravity="center_vertical"
android:gravity="end" >
@@ -48,18 +46,17 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
- android:layout_margin="15dp"
+ android:layout_marginEnd="8dp"
android:visibility="gone"
layout="@layout/mobile_signal_group" />
<com.android.keyguard.CarrierText
android:id="@+id/qs_carrier_text"
android:layout_width="0dp"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_marginStart="8dp"
+ android:layout_gravity="center_vertical|start"
android:layout_marginEnd="32dp"
- android:gravity="center_vertical|start"
android:ellipsize="marquee"
android:textAppearance="@style/TextAppearance.QS.TileLabel"
android:textColor="?android:attr/textColorPrimary"
diff --git a/packages/SystemUI/res/layout/recents_onboarding.xml b/packages/SystemUI/res/layout/recents_onboarding.xml
index 12f278a..6764eee 100644
--- a/packages/SystemUI/res/layout/recents_onboarding.xml
+++ b/packages/SystemUI/res/layout/recents_onboarding.xml
@@ -14,32 +14,48 @@
limitations under the License.
-->
-<FrameLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="48dp"
- android:layout_width="match_parent"
- android:background="@android:color/black"
- android:layout_gravity="center">
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider"
- android:gravity="top"/>
- <TextView
- android:id="@+id/onboarding_text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingBottom="13dp"
+ android:orientation="vertical">
+
+ <LinearLayout
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:textColor="@android:color/white"
- android:textSize="16sp"
- android:drawableBottom="@drawable/ic_chevron_up"/>
- <ImageView
- android:id="@+id/dismiss"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:padding="12dp"
- android:layout_marginEnd="6dp"
- android:src="@drawable/ic_close_white"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:layout_gravity="center_vertical|end"/>
-</FrameLayout>
\ No newline at end of file
+ android:layout_height="40dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="4dp"
+ android:background="@drawable/recents_onboarding_toast_rounded_background"
+ android:layout_gravity="center_horizontal"
+ android:elevation="2dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/onboarding_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textColor="@android:color/white"
+ android:textSize="16sp"/>
+ <ImageView
+ android:id="@+id/dismiss"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_gravity="center_vertical"
+ android:padding="10dp"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:alpha="0.7"
+ android:src="@drawable/ic_close_white"
+ android:background="?android:attr/selectableItemBackgroundBorderless"/>
+ </LinearLayout>
+
+ <View
+ android:id="@+id/arrow"
+ android:elevation="2dp"
+ android:layout_width="10dp"
+ android:layout_height="8dp"
+ android:layout_marginTop="-2dp"
+ android:layout_gravity="center_horizontal"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 84ca657..870aed4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -136,6 +136,10 @@
views where the distance can't be measured -->
<dimen name="notification_icon_appear_padding">15dp</dimen>
+ <!-- Vertical translation of the shelf during animation that happens after the
+ notification panel collapses -->
+ <dimen name="shelf_appear_translation">9dp</dimen>
+
<!-- The amount the content shifts upwards when transforming into the icon -->
<dimen name="notification_icon_transform_content_shift">32dp</dimen>
@@ -371,7 +375,7 @@
<dimen name="qs_header_alarm_icon_size">18dp</dimen>
<dimen name="qs_header_alarm_text_margin_start">6dp</dimen>
<dimen name="qs_footer_padding_start">16dp</dimen>
- <dimen name="qs_footer_padding_end">24dp</dimen>
+ <dimen name="qs_footer_padding_end">16dp</dimen>
<dimen name="qs_footer_icon_size">16dp</dimen>
<dimen name="qs_notif_collapsed_space">64dp</dimen>
@@ -841,6 +845,9 @@
<!-- The size of the drag hint text. -->
<dimen name="recents_drag_hint_text_size">14sp</dimen>
+ <!-- The size of corner radius of the arrow in the onboarding toast. -->
+ <dimen name="recents_onboarding_toast_arrow_corner_radius">2dp</dimen>
+
<!-- The min alpha to apply to a task affiliation group color. -->
<item name="recents_task_affiliation_color_min_alpha_percentage" format="float" type="dimen">0.6</item>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c1e1873..29f1335 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -405,7 +405,10 @@
<string name="accessibility_cell_data_on">Mobile Data On</string>
<!-- Content description of the cell data being disabled. [CHAR LIMIT=NONE] -->
- <string name="cell_data_off">Mobile data off</string>
+ <string name="cell_data_off_content_description">Mobile data off</string>
+
+ <!-- Content description of the cell data being disabled but shortened. [CHAR LIMIT=20] -->
+ <string name="cell_data_off">Off</string>
<!-- Content description of the bluetooth tethering icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_tether">Bluetooth tethering.</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowCallbacksCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowCallbacksCompat.java
new file mode 100644
index 0000000..b2b140e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowCallbacksCompat.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 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.systemui.shared.system;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.DisplayListCanvas;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowCallbacks;
+
+public class WindowCallbacksCompat {
+
+ private final WindowCallbacks mWindowCallbacks = new WindowCallbacks() {
+ @Override
+ public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets,
+ Rect stableInsets) {
+ WindowCallbacksCompat.this.onWindowSizeIsChanging(newBounds, fullscreen, systemInsets,
+ stableInsets);
+ }
+
+ @Override
+ public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen,
+ Rect systemInsets, Rect stableInsets, int resizeMode) {
+ WindowCallbacksCompat.this.onWindowDragResizeStart(initialBounds, fullscreen,
+ systemInsets, stableInsets, resizeMode);
+ }
+
+ @Override
+ public void onWindowDragResizeEnd() {
+ WindowCallbacksCompat.this.onWindowDragResizeEnd();
+ }
+
+ @Override
+ public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
+ return WindowCallbacksCompat.this.onContentDrawn(offsetX, offsetY, sizeX, sizeY);
+ }
+
+ @Override
+ public void onRequestDraw(boolean reportNextDraw) {
+ WindowCallbacksCompat.this.onRequestDraw(reportNextDraw);
+ }
+
+ @Override
+ public void onPostDraw(DisplayListCanvas canvas) {
+ WindowCallbacksCompat.this.onPostDraw(canvas);
+ }
+ };
+
+ private final View mView;
+
+ public WindowCallbacksCompat(View view) {
+ mView = view;
+ }
+
+ public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets,
+ Rect stableInsets) { }
+
+ public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
+ Rect stableInsets, int resizeMode) { }
+
+ public void onWindowDragResizeEnd() { }
+
+ public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
+ return false;
+ }
+
+ public void onRequestDraw(boolean reportNextDraw) {
+ if (reportNextDraw) {
+ reportDrawFinish();
+ }
+ }
+
+ public void onPostDraw(Canvas canvas) { }
+
+ public void reportDrawFinish() {
+ mView.getViewRootImpl().reportDrawFinish();
+ }
+
+ public boolean attach() {
+ ViewRootImpl root = mView.getViewRootImpl();
+ if (root != null) {
+ root.addWindowCallbacks(mWindowCallbacks);
+ root.requestInvalidateRootRenderNode();
+ return true;
+ }
+ return false;
+ }
+
+ public void detach() {
+ ViewRootImpl root = mView.getViewRootImpl();
+ if (root != null) {
+ root.removeWindowCallbacks(mWindowCallbacks);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index d63ad08..00cd5a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -265,11 +265,11 @@
mPendingLockCheck.cancel(false);
mPendingLockCheck = null;
}
+ reset();
}
@Override
public void onResume(int reason) {
- reset();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index c71c433..42c7a56 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -78,6 +78,11 @@
}
break;
}
+ case READY: {
+ mRemainingAttempts = -1;
+ resetState();
+ break;
+ }
default:
resetState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index a0fa69e..e54b083 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -14,6 +14,10 @@
package com.android.systemui;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -21,12 +25,14 @@
import static com.android.systemui.tuner.TunablePadding.FLAG_START;
import static com.android.systemui.tuner.TunablePadding.FLAG_END;
+import android.annotation.Dimension;
import android.app.Fragment;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
@@ -41,6 +47,7 @@
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.Surface;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
@@ -359,6 +366,7 @@
if (!mBoundingPath.isEmpty()) {
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setAntiAlias(true);
canvas.drawPath(mBoundingPath, mPaint);
}
}
@@ -388,7 +396,7 @@
if (hasCutout()) {
mBounds.set(mInfo.displayCutout.getBounds());
localBounds(mBoundingRect);
- mInfo.displayCutout.getBounds().getBoundaryPath(mBoundingPath);
+ updateBoundingPath();
invalidate();
newVisible = VISIBLE;
} else {
@@ -400,6 +408,44 @@
}
}
+ private void updateBoundingPath() {
+ int lw = mInfo.logicalWidth;
+ int lh = mInfo.logicalHeight;
+
+ boolean flipped = mInfo.rotation == ROTATION_90 || mInfo.rotation == ROTATION_270;
+
+ int dw = flipped ? lh : lw;
+ int dh = flipped ? lw : lh;
+
+ mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), lw, lh));
+ Matrix m = new Matrix();
+ transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
+ mBoundingPath.transform(m);
+ }
+
+ private static void transformPhysicalToLogicalCoordinates(@Surface.Rotation int rotation,
+ @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
+ switch (rotation) {
+ case ROTATION_0:
+ out.reset();
+ break;
+ case ROTATION_90:
+ out.setRotate(270);
+ out.postTranslate(0, physicalWidth);
+ break;
+ case ROTATION_180:
+ out.setRotate(180);
+ out.postTranslate(physicalWidth, physicalHeight);
+ break;
+ case ROTATION_270:
+ out.setRotate(90);
+ out.postTranslate(physicalHeight, 0);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown rotation: " + rotation);
+ }
+ }
+
private boolean hasCutout() {
final DisplayCutout displayCutout = mInfo.displayCutout;
if (displayCutout == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index c01cafa..52d458c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.SmartReplyLogger;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
@@ -146,5 +147,6 @@
() -> new NotificationViewHierarchyManager(context));
providers.put(NotificationEntryManager.class, () -> new NotificationEntryManager(context));
providers.put(KeyguardDismissUtil.class, KeyguardDismissUtil::new);
+ providers.put(SmartReplyLogger.class, () -> new SmartReplyLogger(context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 9464105..ebd15f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -613,7 +613,7 @@
int bluetoothProfile) { }
@Override
- public void onProfileAudioStateChanged(int bluetoothProfile, int state) { }
+ public void onAudioModeChanged() { }
}
private final class BluetoothErrorListener implements Utils.ErrorListener {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d6e59c7..553b2ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -357,7 +357,12 @@
// ActivityManagerService) will not reconstruct the keyguard if it is already showing.
synchronized (KeyguardViewMediator.this) {
resetKeyguardDonePendingLocked();
- resetStateLocked();
+ if (mLockPatternUtils.isLockScreenDisabled(userId)) {
+ // If we switching to a user that has keyguard disabled, dismiss keyguard.
+ dismiss(null /* callback */, null /* message */);
+ } else {
+ resetStateLocked();
+ }
adjustStatusBarLocked();
}
}
@@ -1184,6 +1189,10 @@
Trace.endSection();
}
+ public boolean isHiding() {
+ return mHiding;
+ }
+
/**
* Handles SET_OCCLUDED message sent by setOccluded()
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index cf549fa..28dd26f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -383,7 +383,7 @@
if (TextUtils.equals(mInfo.typeContentDescription,
mContext.getString(R.string.data_connection_no_internet))
|| TextUtils.equals(mInfo.typeContentDescription,
- mContext.getString(R.string.cell_data_off))) {
+ mContext.getString(R.string.cell_data_off_content_description))) {
contentDescription.append(mInfo.typeContentDescription);
}
mMobileSignal.setContentDescription(contentDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 2abe9d9..4554c79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -194,7 +194,18 @@
state.state = Tile.STATE_INACTIVE;
state.secondaryLabel = r.getString(R.string.cell_data_off);
}
- state.contentDescription = state.label + ", " + state.secondaryLabel;
+
+
+ // TODO(b/77881974): Instead of switching out the description via a string check for
+ // we need to have two strings provided by the MobileIconGroup.
+ final CharSequence contentDescriptionSuffix;
+ if (state.state == Tile.STATE_INACTIVE) {
+ contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description);
+ } else {
+ contentDescriptionSuffix = state.secondaryLabel;
+ }
+
+ state.contentDescription = state.label + ", " + contentDescriptionSuffix;
}
private CharSequence getMobileDataDescription(CallbackInfo cb) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f7cbee6..4616d1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -143,26 +143,41 @@
public void showDetail(boolean show) {
int zenDuration = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_DURATION, 0);
- switch (zenDuration) {
- case Settings.Global.ZEN_DURATION_PROMPT:
- mUiHandler.post(() -> {
- Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
- mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- SystemUIDialog.setShowForAllUsers(mDialog, true);
- SystemUIDialog.registerDismissListener(mDialog);
- SystemUIDialog.setWindowOnTop(mDialog);
- mUiHandler.post(() -> mDialog.show());
- mHost.collapsePanels();
- });
- break;
- case Settings.Global.ZEN_DURATION_FOREVER:
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
- break;
- default:
- Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
- ActivityManager.getCurrentUser(), true).id;
- mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- conditionId, TAG);
+ boolean showOnboarding = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0;
+ if (showOnboarding) {
+ // don't show on-boarding again or notification ever
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Global.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
+ // turn on DND
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+ // show on-boarding screen
+ Intent intent = new Intent(Settings.ZEN_MODE_ONBOARDING);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+ } else {
+ switch (zenDuration) {
+ case Settings.Global.ZEN_DURATION_PROMPT:
+ mUiHandler.post(() -> {
+ Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
+ mDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mDialog, true);
+ SystemUIDialog.registerDismissListener(mDialog);
+ SystemUIDialog.setWindowOnTop(mDialog);
+ mUiHandler.post(() -> mDialog.show());
+ mHost.collapsePanels();
+ });
+ break;
+ case Settings.Global.ZEN_DURATION_FOREVER:
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+ break;
+ default:
+ Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
+ ActivityManager.getCurrentUser(), true).id;
+ mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ conditionId, TAG);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 30e9afd8..0d8aed4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -24,15 +24,16 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.CornerPathEffect;
+import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.RippleDrawable;
+import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.os.SystemProperties;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -76,17 +77,13 @@
private final View mLayout;
private final TextView mTextView;
private final ImageView mDismissView;
- private final ColorDrawable mBackgroundDrawable;
- private final int mDarkBackgroundColor;
- private final int mLightBackgroundColor;
- private final int mDarkContentColor;
- private final int mLightContentColor;
- private final RippleDrawable mDarkRipple;
- private final RippleDrawable mLightRipple;
+ private final View mArrowView;
+ private final int mOnboardingToastColor;
+ private final int mOnboardingToastArrowRadius;
+ private int mNavBarHeight;
private boolean mTaskListenerRegistered;
private boolean mLayoutAttachedToWindow;
- private boolean mBackgroundIsLight;
private int mLastTaskId;
private boolean mHasDismissed;
private int mNumAppsLaunchedSinceDismiss;
@@ -159,24 +156,30 @@
mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_onboarding, null);
mTextView = mLayout.findViewById(R.id.onboarding_text);
mDismissView = mLayout.findViewById(R.id.dismiss);
- mDarkBackgroundColor = res.getColor(android.R.color.background_dark);
- mLightBackgroundColor = res.getColor(android.R.color.background_light);
- mDarkContentColor = res.getColor(R.color.primary_text_default_material_light);
- mLightContentColor = res.getColor(R.color.primary_text_default_material_dark);
- mDarkRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_light),
- null, null);
- mLightRipple = new RippleDrawable(res.getColorStateList(R.color.ripple_material_dark),
- null, null);
- mBackgroundDrawable = new ColorDrawable(mDarkBackgroundColor);
+ mArrowView = mLayout.findViewById(R.id.arrow);
+
+ TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ mOnboardingToastColor = res.getColor(typedValue.resourceId);
+ mOnboardingToastArrowRadius = res.getDimensionPixelSize(
+ R.dimen.recents_onboarding_toast_arrow_corner_radius);
mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- mLayout.setBackground(mBackgroundDrawable);
mDismissView.setOnClickListener(v -> {
hide(true);
mHasDismissed = true;
mNumAppsLaunchedSinceDismiss = 0;
});
+ ViewGroup.LayoutParams arrowLp = mArrowView.getLayoutParams();
+ ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+ arrowLp.width, arrowLp.height, false));
+ Paint arrowPaint = arrowDrawable.getPaint();
+ arrowPaint.setColor(mOnboardingToastColor);
+ // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+ arrowPaint.setPathEffect(new CornerPathEffect(mOnboardingToastArrowRadius));
+ mArrowView.setBackground(arrowDrawable);
+
if (RESET_PREFS_FOR_DEBUG) {
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
@@ -234,6 +237,7 @@
int orientation = mContext.getResources().getConfiguration().orientation;
if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
mLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
mWindowManager.addView(mLayout, getWindowLayoutParams());
int layoutHeight = mLayout.getHeight();
if (layoutHeight == 0) {
@@ -281,29 +285,18 @@
}
}
- public void setContentDarkIntensity(float contentDarkIntensity) {
- boolean backgroundIsLight = contentDarkIntensity > 0.5f;
- if (backgroundIsLight != mBackgroundIsLight) {
- mBackgroundIsLight = backgroundIsLight;
- mBackgroundDrawable.setColor(mBackgroundIsLight
- ? mLightBackgroundColor : mDarkBackgroundColor);
- int contentColor = mBackgroundIsLight ? mDarkContentColor : mLightContentColor;
- mTextView.setTextColor(contentColor);
- mTextView.getCompoundDrawables()[3].setColorFilter(contentColor,
- PorterDuff.Mode.SRC_IN);
- mDismissView.setColorFilter(contentColor);
- mDismissView.setBackground(mBackgroundIsLight ? mDarkRipple : mLightRipple);
- }
+ public void setNavBarHeight(int navBarHeight) {
+ mNavBarHeight = navBarHeight;
}
private WindowManager.LayoutParams getWindowLayoutParams() {
- int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ int flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
+ 0, -mNavBarHeight / 2,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
flags,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java
new file mode 100644
index 0000000..de85c0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/TriangleShape.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 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.systemui.recents;
+
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.drawable.shapes.PathShape;
+import android.support.annotation.NonNull;
+
+/**
+ * Wrapper around {@link android.graphics.drawable.shapes.PathShape}
+ * that creates a shape with a triangular path (pointing up or down).
+ */
+public class TriangleShape extends PathShape {
+ private Path mTriangularPath;
+
+ public TriangleShape(Path path, float stdWidth, float stdHeight) {
+ super(path, stdWidth, stdHeight);
+ mTriangularPath = path;
+ }
+
+ public static TriangleShape create(float width, float height, boolean isPointingUp) {
+ Path triangularPath = new Path();
+ if (isPointingUp) {
+ triangularPath.moveTo(0, height);
+ triangularPath.lineTo(width, height);
+ triangularPath.lineTo(width / 2, 0);
+ triangularPath.close();
+ } else {
+ triangularPath.moveTo(0, 0);
+ triangularPath.lineTo(width / 2, height);
+ triangularPath.lineTo(width, 0);
+ triangularPath.close();
+ }
+ return new TriangleShape(triangularPath, width, height);
+ }
+
+ @Override
+ public void getOutline(@NonNull Outline outline) {
+ outline.setConvexPath(mTriangularPath);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 29c2edc..4256cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -80,7 +80,7 @@
private RemoteInputView mHeadsUpRemoteInput;
private SmartReplyConstants mSmartReplyConstants;
- private SmartReplyView mExpandedSmartReplyView;
+ private SmartReplyLogger mSmartReplyLogger;
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
@@ -153,6 +153,7 @@
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
+ mSmartReplyLogger = Dependency.get(SmartReplyLogger.class);
initView();
}
@@ -1243,7 +1244,7 @@
}
applyRemoteInput(entry, hasRemoteInput);
- applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices);
+ applySmartReplyView(remoteInputWithChoices, pendingIntentWithChoices, entry);
}
private void applyRemoteInput(NotificationData.Entry entry, boolean hasRemoteInput) {
@@ -1344,13 +1345,21 @@
return null;
}
- private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent) {
- mExpandedSmartReplyView = mExpandedChild == null ?
- null : applySmartReplyView(mExpandedChild, remoteInput, pendingIntent);
+ private void applySmartReplyView(RemoteInput remoteInput, PendingIntent pendingIntent,
+ NotificationData.Entry entry) {
+ if (mExpandedChild != null) {
+ SmartReplyView view =
+ applySmartReplyView(mExpandedChild, remoteInput, pendingIntent, entry);
+ if (view != null && remoteInput != null && remoteInput.getChoices() != null
+ && remoteInput.getChoices().length > 0) {
+ mSmartReplyLogger.smartRepliesAdded(entry, remoteInput.getChoices().length);
+ }
+ }
}
private SmartReplyView applySmartReplyView(
- View view, RemoteInput remoteInput, PendingIntent pendingIntent) {
+ View view, RemoteInput remoteInput, PendingIntent pendingIntent,
+ NotificationData.Entry entry) {
View smartReplyContainerCandidate = view.findViewById(
com.android.internal.R.id.smart_reply_container);
if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
@@ -1372,7 +1381,8 @@
}
}
if (smartReplyView != null) {
- smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent);
+ smartReplyView.setRepliesFromRemoteInput(remoteInput, pendingIntent,
+ mSmartReplyLogger, entry);
smartReplyContainer.setVisibility(View.VISIBLE);
}
return smartReplyView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 0112661..6364f5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE;
-import static com.android.systemui.statusbar.phone.NotificationIconContainer.OVERFLOW_EARLY_AMOUNT;
import android.content.Context;
import android.content.res.Configuration;
@@ -26,6 +25,7 @@
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -58,6 +58,8 @@
= SystemProperties.getBoolean("debug.icon_scroll_animations", true);
private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
private static final String TAG = "NotificationShelf";
+ private static final long SHELF_IN_TRANSLATION_DURATION = 220;
+
private ViewInvertHelper mViewInvertHelper;
private boolean mDark;
private NotificationIconContainer mShelfIcons;
@@ -65,6 +67,7 @@
private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mIconAppearTopPadding;
+ private int mShelfAppearTranslation;
private int mStatusBarHeight;
private int mStatusBarPaddingStart;
private AmbientState mAmbientState;
@@ -120,6 +123,7 @@
mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
+ mShelfAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
@@ -151,6 +155,18 @@
updateInteractiveness();
}
+ public void fadeInTranslating() {
+ float translation = mShelfIcons.getTranslationY();
+ mShelfIcons.setTranslationY(translation + mShelfAppearTranslation);
+ mShelfIcons.setAlpha(0);
+ mShelfIcons.animate()
+ .alpha(1)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .translationY(translation)
+ .setDuration(SHELF_IN_TRANSLATION_DURATION)
+ .start();
+ }
+
@Override
protected View getContentView() {
return mShelfIcons;
@@ -175,12 +191,14 @@
float viewEnd = lastViewState.yTranslation + lastViewState.height;
mShelfState.copyFrom(lastViewState);
mShelfState.height = getIntrinsicHeight();
- mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height,
+
+ float awakenTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height,
getFullyClosedTranslation());
+ float darkTranslation = mAmbientState.getDarkTopPadding();
+ float yRatio = mAmbientState.hasPulsingNotifications() ?
+ 0 : mAmbientState.getDarkAmount();
+ mShelfState.yTranslation = MathUtils.lerp(awakenTranslation, darkTranslation, yRatio);
mShelfState.zTranslation = ambientState.getBaseZHeight();
- if (mAmbientState.isDark() && !mAmbientState.hasPulsingNotifications()) {
- mShelfState.yTranslation = mAmbientState.getDarkTopPadding();
- }
float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation())
/ (getIntrinsicHeight() * 2);
openedAmount = Math.min(1.0f, openedAmount);
@@ -555,7 +573,9 @@
iconState.translateContent = false;
}
float transitionAmount;
- if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
+ if (mAmbientState.getDarkAmount() > 0 && !row.isInShelf()) {
+ transitionAmount = mAmbientState.isFullyDark() ? 1 : 0;
+ } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
|| iconState.useLinearTransitionAmount) {
transitionAmount = iconTransitionAmount;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index 1807465..c99bdef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -26,6 +26,7 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
@@ -272,12 +273,18 @@
tintAmount);
drawable.setColors(mainTinted, secondaryTinted, animated);
} else {
- if (mColorFilter == null) {
- mColorFilter = new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_OVER);
+ boolean hasAlpha = Color.alpha(mTintColor) != 0;
+ if (hasAlpha) {
+ PorterDuff.Mode targetMode = mColorFilter == null ? Mode.SRC_OVER :
+ mColorFilter.getMode();
+ if (mColorFilter == null || mColorFilter.getColor() != mTintColor) {
+ mColorFilter = new PorterDuffColorFilter(mTintColor, targetMode);
+ }
} else {
- mColorFilter.setColor(mTintColor);
+ mColorFilter = null;
}
- mDrawable.setColorFilter(Color.alpha(mTintColor) == 0 ? null : mColorFilter);
+
+ mDrawable.setColorFilter(mColorFilter);
mDrawable.invalidateSelf();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyLogger.java
new file mode 100644
index 0000000..75dd77d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyLogger.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.systemui.statusbar;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.statusbar.IStatusBarService;
+
+/**
+ * Handles reporting when smart replies are added to a notification
+ * and clicked upon.
+ */
+public class SmartReplyLogger {
+ protected IStatusBarService mBarService;
+
+ public SmartReplyLogger(Context context) {
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+
+ public void smartReplySent(NotificationData.Entry entry, int replyIndex) {
+ try {
+ mBarService.onNotificationSmartReplySent(entry.notification.getKey(),
+ replyIndex);
+ } catch (RemoteException e) {
+ // Nothing to do, system going down
+ }
+ }
+
+ public void smartRepliesAdded(final NotificationData.Entry entry, int replyCount) {
+ try {
+ mBarService.onNotificationSmartRepliesAdded(entry.notification.getKey(),
+ replyCount);
+ } catch (RemoteException e) {
+ // Nothing to do, system going down
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index e09a360..9c9d3ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -109,12 +109,12 @@
}
// Add guest user record if the current user is not a guest
- if (!mUserManagerHelper.isGuestUser()) {
+ if (!mUserManagerHelper.foregroundUserIsGuestUser()) {
userRecords.add(addGuestUserRecord());
}
// Add add user record if the current user can add users
- if (mUserManagerHelper.canAddUsers()) {
+ if (mUserManagerHelper.foregroundUserCanAddUsers()) {
userRecords.add(addUserRecord());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
index 9f79ef2..43e45b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.notification;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
@@ -29,8 +30,9 @@
private final int mImageDarkAlpha;
private final int mImageDarkColor = 0xffffffff;
- private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter(
- 0, PorterDuff.Mode.SRC_ATOP);
+
+ @Nullable
+ private PorterDuffColorFilter mImageColorFilter = null;
private int mColor = Color.BLACK;
@@ -80,7 +82,9 @@
private void updateImageColorFilter(ImageView target, float intensity) {
int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity);
- mImageColorFilter.setColor(color);
+ if (mImageColorFilter == null || mImageColorFilter.getColor() != color) {
+ mImageColorFilter = new PorterDuffColorFilter(color, Mode.SRC_ATOP);
+ }
Drawable imageDrawable = target.getDrawable();
// Also, the notification might have been modified during the animation, so background
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 4bca797..5ec822d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -183,20 +183,19 @@
@Override
public void setTint(int color) {
- if (mTintFilter == null) {
- mTintFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
- } else {
- mTintFilter.setColor(color);
+ PorterDuff.Mode targetMode = mTintFilter == null ? Mode.SRC_IN :
+ mTintFilter.getMode();
+ if (mTintFilter == null || mTintFilter.getColor() != color) {
+ mTintFilter = new PorterDuffColorFilter(color, targetMode);
}
invalidateSelf();
}
@Override
public void setTintMode(Mode tintMode) {
- if (mTintFilter == null) {
- mTintFilter = new PorterDuffColorFilter(0, tintMode);
- } else {
- mTintFilter.setMode(tintMode);
+ int targetColor = mTintFilter == null ? 0 : mTintFilter.getColor();
+ if (mTintFilter == null || mTintFilter.getMode() != tintMode) {
+ mTintFilter = new PorterDuffColorFilter(targetColor, tintMode);
}
invalidateSelf();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 3d7067d..1fb1ddd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -99,6 +99,11 @@
private int mBurnInPreventionOffsetY;
/**
+ * Clock vertical padding when pulsing.
+ */
+ private int mPulsingPadding;
+
+ /**
* Doze/AOD transition amount.
*/
private float mDarkAmount;
@@ -109,9 +114,9 @@
private boolean mCurrentlySecure;
/**
- * If notification panel view currently has a touch.
+ * Dozing and receiving a notification (AOD notification.)
*/
- private boolean mTracking;
+ private boolean mPulsing;
/**
* Distance in pixels between the top of the screen and the first view of the bouncer.
@@ -130,11 +135,13 @@
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_y);
+ mPulsingPadding = res.getDimensionPixelSize(
+ R.dimen.widget_pulsing_bottom_padding);
}
public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight,
float expandedHeight, float maxPanelHeight, int parentHeight, int keyguardStatusHeight,
- float dark, boolean secure, boolean tracking, int bouncerTop) {
+ float dark, boolean secure, boolean pulsing, int bouncerTop) {
mMinTopMargin = minTopMargin + mContainerTopPadding;
mMaxShadeBottom = maxShadeBottom;
mNotificationStackHeight = notificationStackHeight;
@@ -144,7 +151,7 @@
mKeyguardStatusHeight = keyguardStatusHeight;
mDarkAmount = dark;
mCurrentlySecure = secure;
- mTracking = tracking;
+ mPulsing = pulsing;
mBouncerTop = bouncerTop;
}
@@ -152,7 +159,7 @@
final int y = getClockY();
result.clockY = y;
result.clockAlpha = getClockAlpha(y);
- result.stackScrollerPadding = y + mKeyguardStatusHeight;
+ result.stackScrollerPadding = y + (mPulsing ? 0 : mKeyguardStatusHeight);
result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
}
@@ -194,9 +201,13 @@
private int getClockY() {
// Dark: Align the bottom edge of the clock at about half of the screen:
- final float clockYDark = getMaxClockY() + burnInPreventionOffsetY();
- final float clockYRegular = getExpandedClockPosition();
- final boolean hasEnoughSpace = mMinTopMargin + mKeyguardStatusHeight < mBouncerTop;
+ float clockYDark = getMaxClockY() + burnInPreventionOffsetY();
+ if (mPulsing) {
+ clockYDark -= mPulsingPadding;
+ }
+
+ float clockYRegular = getExpandedClockPosition();
+ boolean hasEnoughSpace = mMinTopMargin + mKeyguardStatusHeight < mBouncerTop;
float clockYTarget = mCurrentlySecure && hasEnoughSpace ?
mMinTopMargin : -mKeyguardStatusHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 58f8baa..ca6d596 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -103,6 +103,7 @@
import java.io.PrintWriter;
import java.util.List;
import java.util.Locale;
+import java.util.Optional;
/**
* Fragment containing the NavigationBarFragment. Contains logic for what happens
@@ -1109,8 +1110,11 @@
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
// Only hide the icon if the top task changes its requestedOrientation
// Launcher can alter its requestedOrientation while it's not on top, don't hide on this
- final boolean top = ActivityManagerWrapper.getInstance().getRunningTask().id == taskId;
- if (top) setRotateSuggestionButtonState(false);
+ Optional.ofNullable(ActivityManagerWrapper.getInstance())
+ .map(ActivityManagerWrapper::getRunningTask)
+ .ifPresent(a -> {
+ if (a.id == taskId) setRotateSuggestionButtonState(false);
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index db2139d..14a36d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -812,9 +812,6 @@
if (mGestureHelper != null) {
mGestureHelper.onDarkIntensityChange(intensity);
}
- if (mRecentsOnboarding != null) {
- mRecentsOnboarding.setContentDarkIntensity(intensity);
- }
}
@Override
@@ -831,6 +828,7 @@
updateButtonLocationOnScreen(getHomeButton(), mHomeButtonBounds);
updateButtonLocationOnScreen(getRecentsButton(), mRecentsButtonBounds);
mGestureHelper.onLayout(changed, left, top, right, bottom);
+ mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
}
private void updateButtonLocationOnScreen(ButtonDispatcher button, Rect buttonBounds) {
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 b7af84a..351633b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -238,6 +238,7 @@
private boolean mIsFullWidth;
private float mDarkAmount;
private float mDarkAmountTarget;
+ private boolean mPulsing;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mNoVisibleNotifications = true;
private ValueAnimator mDarkAnimator;
@@ -477,7 +478,7 @@
mKeyguardStatusView.getHeight(),
mDarkAmount,
mStatusBar.isKeyguardCurrentlySecure(),
- mTracking,
+ mPulsing,
mBouncerTop);
mClockPositionAlgorithm.run(mClockPositionResult);
if (animate || mClockAnimator != null) {
@@ -2689,14 +2690,8 @@
positionClockAndNotifications();
}
- public void setNoVisibleNotifications(boolean noNotifications) {
- mNoVisibleNotifications = noNotifications;
- if (mQs != null) {
- mQs.setHasNotifications(!noNotifications);
- }
- }
-
public void setPulsing(boolean pulsing) {
+ mPulsing = pulsing;
mKeyguardStatusView.setPulsing(pulsing);
positionClockAndNotifications();
mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ab13678..4b2bc45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3942,7 +3942,8 @@
}
private void showBouncerIfKeyguard() {
- if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+ if ((mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)
+ && !mKeyguardViewMediator.isHiding()) {
showBouncer(true /* animated */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 94db95a..cd17cfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -280,7 +280,7 @@
public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {}
@Override
- public void onProfileAudioStateChanged(int bluetoothProfile, int state) {}
+ public void onAudioModeChanged() {}
private ActuallyCachedState getCachedState(CachedBluetoothDevice device) {
ActuallyCachedState state = mCachedState.get(device);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 74b3926..4c79ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -23,6 +23,8 @@
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.SmartReplyLogger;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import java.text.BreakIterator;
@@ -109,14 +111,16 @@
Math.max(getChildCount(), 1), DECREASING_MEASURED_WIDTH_WITHOUT_PADDING_COMPARATOR);
}
- public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent) {
+ public void setRepliesFromRemoteInput(RemoteInput remoteInput, PendingIntent pendingIntent,
+ SmartReplyLogger smartReplyLogger, NotificationData.Entry entry) {
removeAllViews();
if (remoteInput != null && pendingIntent != null) {
CharSequence[] choices = remoteInput.getChoices();
if (choices != null) {
- for (CharSequence choice : choices) {
+ for (int i = 0; i < choices.length; ++i) {
Button replyButton = inflateReplyButton(
- getContext(), this, choice, remoteInput, pendingIntent);
+ getContext(), this, i, choices[i], remoteInput, pendingIntent,
+ smartReplyLogger, entry);
addView(replyButton);
}
}
@@ -130,8 +134,9 @@
}
@VisibleForTesting
- Button inflateReplyButton(Context context, ViewGroup root, CharSequence choice,
- RemoteInput remoteInput, PendingIntent pendingIntent) {
+ Button inflateReplyButton(Context context, ViewGroup root, int replyIndex,
+ CharSequence choice, RemoteInput remoteInput, PendingIntent pendingIntent,
+ SmartReplyLogger smartReplyLogger, NotificationData.Entry entry) {
Button b = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_reply_button, root, false);
b.setText(choice);
@@ -147,6 +152,7 @@
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Unable to send smart reply", e);
}
+ smartReplyLogger.smartReplySent(entry, replyIndex);
return false; // do not defer
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 7e6fe02..bd76820 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -208,7 +208,7 @@
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- R.string.cell_data_off,
+ R.string.cell_data_off_content_description,
0,
false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 7c1c566..91a4b07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -70,8 +70,8 @@
private int mIntrinsicPadding;
private int mExpandAnimationTopChange;
private ExpandableNotificationRow mExpandingNotification;
- private boolean mFullyDark;
private int mDarkTopPadding;
+ private float mDarkAmount;
public AmbientState(Context context) {
reload(context);
@@ -149,6 +149,16 @@
mDark = dark;
}
+ /** Dark ratio of the status bar **/
+ public void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
+ }
+
+ /** Returns the dark ratio of the status bar */
+ public float getDarkAmount() {
+ return mDarkAmount;
+ }
+
public void setHideSensitive(boolean hideSensitive) {
mHideSensitive = hideSensitive;
}
@@ -413,17 +423,10 @@
}
/**
- * {@see isFullyDark}
- */
- public void setFullyDark(boolean fullyDark) {
- mFullyDark = fullyDark;
- }
-
- /**
* @return {@code true } when shade is completely dark: in AOD or ambient display.
*/
public boolean isFullyDark() {
- return mFullyDark;
+ return mDarkAmount == 1;
}
public void setDarkTopPadding(int darkTopPadding) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index d282f25..bc5a848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -96,6 +96,7 @@
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -269,8 +270,6 @@
*/
private boolean mOnlyScrollingInThisMotion;
private boolean mDisallowDismissInThisMotion;
- private boolean mInterceptDelegateEnabled;
- private boolean mDelegateToScrollView;
private boolean mDisallowScrollingInThisMotion;
private long mGoToFullShadeDelay;
private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
@@ -562,17 +561,17 @@
return;
}
- final int color;
- if (mAmbientState.isDark()) {
- color = Color.WHITE;
- } else {
- float alpha =
- BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
- alpha *= 1f - mDarkAmount;
- // We need to manually blend in the background color
- int scrimColor = mScrimController.getBackgroundColor();
- color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);
- }
+ float alpha =
+ BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
+ alpha *= 1f - mDarkAmount;
+ // We need to manually blend in the background color.
+ int scrimColor = mScrimController.getBackgroundColor();
+ int awakeColor = ColorUtils.blendARGB(scrimColor, mBgColor, alpha);
+
+ // Interpolate between semi-transparent notification panel background color
+ // and white AOD separator.
+ float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(mDarkAmount);
+ int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation);
if (mCachedBackgroundColor != color) {
mCachedBackgroundColor = color;
@@ -3961,13 +3960,18 @@
private void setDarkAmount(float darkAmount) {
mDarkAmount = darkAmount;
- final boolean fullyDark = darkAmount == 1;
- if (mAmbientState.isFullyDark() != fullyDark) {
- mAmbientState.setFullyDark(fullyDark);
+ boolean wasFullyDark = mAmbientState.isFullyDark();
+ mAmbientState.setDarkAmount(darkAmount);
+ if (mAmbientState.isFullyDark() != wasFullyDark) {
updateContentHeight();
+ DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
+ if (mAmbientState.isFullyDark() && dozeParameters.shouldControlScreenOff()) {
+ mShelf.fadeInTranslating();
+ }
}
updateBackgroundDimming();
updateAntiBurnInTranslation();
+ requestChildrenUpdate();
}
public float getDarkAmount() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index a8d2d98..f4d7f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -276,8 +276,6 @@
if (i >= firstHiddenIndex) {
// we need normal padding now, to be in sync with what the stack calculates
lastView = null;
- ExpandableViewState viewState = resultState.getViewStateForView(v);
- viewState.hidden = true;
}
notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
float increasedPadding = v.getIncreasedPaddingAmount();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 5a4478f..639e49b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -58,7 +58,7 @@
private static final String TUNER_VERSION = "sysui_tuner_version";
- private static final int CURRENT_TUNER_VERSION = 2;
+ private static final int CURRENT_TUNER_VERSION = 3;
private final Observer mObserver = new Observer();
// Map of Uris we listen on to their settings keys.
@@ -119,6 +119,10 @@
if (oldVersion < 2) {
setTunerEnabled(mContext, false);
}
+ if (oldVersion < 3) {
+ // Delay this so that we can wait for everything to be registered first.
+ new Handler(Dependency.get(Dependency.BG_LOOPER)).postDelayed(() -> clearAll(), 5000);
+ }
setValue(TUNER_VERSION, newVersion);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 2c85bb6..ca55e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -52,26 +52,28 @@
public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool)
public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|bool)
public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string)
+ public static final int EVENT_RINGER_TOGGLE = 18; // (ringer_mode)
private static final String[] EVENT_TAGS = {
- "show_dialog",
- "dismiss_dialog",
- "active_stream_changed",
- "expand",
- "key",
- "collection_started",
- "collection_stopped",
- "icon_click",
- "settings_click",
- "touch_level_changed",
- "level_changed",
- "internal_ringer_mode_changed",
- "external_ringer_mode_changed",
- "zen_mode_changed",
- "suppressor_changed",
- "mute_changed",
- "touch_level_done",
- "zen_mode_config_changed",
+ "show_dialog",
+ "dismiss_dialog",
+ "active_stream_changed",
+ "expand",
+ "key",
+ "collection_started",
+ "collection_stopped",
+ "icon_click",
+ "settings_click",
+ "touch_level_changed",
+ "level_changed",
+ "internal_ringer_mode_changed",
+ "external_ringer_mode_changed",
+ "zen_mode_changed",
+ "suppressor_changed",
+ "mute_changed",
+ "touch_level_done",
+ "zen_mode_config_changed",
+ "ringer_toggle"
};
public static final int DISMISS_REASON_UNKNOWN = 0;
@@ -112,6 +114,7 @@
public static Callback sCallback;
public static void writeEvent(Context context, int tag, Object... list) {
+ MetricsLogger logger = new MetricsLogger();
final long time = System.currentTimeMillis();
final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
if (list != null && list.length > 0) {
@@ -139,7 +142,7 @@
break;
case EVENT_ICON_CLICK:
MetricsLogger.action(context, MetricsEvent.ACTION_VOLUME_ICON,
- (Integer) list[1]);
+ (Integer) list[0]);
sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
.append(iconStateToString((Integer) list[1]));
break;
@@ -155,10 +158,16 @@
break;
case EVENT_KEY:
MetricsLogger.action(context, MetricsEvent.ACTION_VOLUME_KEY,
- (Integer) list[1]);
+ (Integer) list[0]);
sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
.append(list[1]);
break;
+ case EVENT_RINGER_TOGGLE:
+ logger.action(MetricsEvent.ACTION_VOLUME_RINGER_TOGGLE, (Integer) list[0]);
+ break;
+ case EVENT_SETTINGS_CLICK:
+ logger.action(MetricsEvent.ACTION_VOLUME_SETTINGS);
+ break;
case EVENT_EXTERNAL_RINGER_MODE_CHANGED:
MetricsLogger.action(context, MetricsEvent.ACTION_RINGER_MODE,
(Integer) list[0]);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index a2d2615..00874e3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -301,15 +301,8 @@
if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
VolumeRow row = new VolumeRow();
initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
- if (dynamic && mRows.size() > 2) {
- // Dynamic Streams should be the first in the list, so they're shown to start of
- // everything except a11y
- mDialogRowsView.addView(row.view, 1);
- mRows.add(1, row);
- } else {
- mDialogRowsView.addView(row.view);
- mRows.add(row);
- }
+ mDialogRowsView.addView(row.view);
+ mRows.add(row);
}
private void addExistingRows() {
@@ -422,6 +415,7 @@
mSettingsView.setVisibility(
mDeviceProvisionedController.isDeviceProvisioned() ? VISIBLE : GONE);
mSettingsIcon.setOnClickListener(v -> {
+ Events.writeEvent(mContext, Events.EVENT_SETTINGS_CLICK);
Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
@@ -431,8 +425,6 @@
public void initRingerH() {
mRingerIcon.setOnClickListener(v -> {
- Events.writeEvent(mContext, Events.EVENT_ICON_CLICK, AudioManager.STREAM_RING,
- mRingerIcon.getTag());
Prefs.putBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, true);
final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
if (ss == null) {
@@ -456,6 +448,7 @@
mController.setStreamVolume(AudioManager.STREAM_RING, 1);
}
}
+ Events.writeEvent(mContext, Events.EVENT_RINGER_TOGGLE, newRingerMode);
updateRingerH();
provideTouchFeedbackH(newRingerMode);
mController.setRingerMode(newRingerMode, false);
@@ -603,7 +596,8 @@
return activeRow.stream == STREAM_RING
|| activeRow.stream == STREAM_ALARM
|| activeRow.stream == STREAM_VOICE_CALL
- || activeRow.stream == STREAM_ACCESSIBILITY;
+ || activeRow.stream == STREAM_ACCESSIBILITY
+ || mDynamic.get(activeRow.stream);
}
return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 56882c6..2bb8106 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -22,11 +22,16 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
+import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -38,6 +43,8 @@
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.SmartReplyLogger;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import java.util.concurrent.atomic.AtomicReference;
@@ -46,6 +53,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -66,8 +75,12 @@
private int mDoubleLinePaddingHorizontal;
private int mSpacing;
+ @Mock private SmartReplyLogger mLogger;
+ private NotificationData.Entry mEntry;
+
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mReceiver = new BlockingQueueIntentReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
mDependency.get(KeyguardDismissUtil.class).setDismissHandler(
@@ -82,6 +95,10 @@
mDoubleLinePaddingHorizontal = res.getDimensionPixelSize(
R.dimen.smart_reply_button_padding_horizontal_double_line);
mSpacing = res.getDimensionPixelSize(R.dimen.smart_reply_button_spacing);
+
+ StatusBarNotification notification = mock(StatusBarNotification.class);
+ when(notification.getKey()).thenReturn("akey");
+ mEntry = new NotificationData.Entry(notification);
}
@After
@@ -138,6 +155,13 @@
}
@Test
+ public void testSendSmartReply_LoggerCall() {
+ setRepliesFromRemoteInput(TEST_CHOICES);
+ mView.getChildAt(2).performClick();
+ verify(mLogger).smartReplySent(mEntry, 2);
+ }
+
+ @Test
public void testMeasure_empty() {
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
assertEquals(500, mView.getMeasuredWidthAndState());
@@ -316,7 +340,7 @@
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
new Intent(TEST_ACTION), 0);
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
- mView.setRepliesFromRemoteInput(input, pendingIntent);
+ mView.setRepliesFromRemoteInput(input, pendingIntent, mLogger, mEntry);
}
/** Builds a {@link ViewGroup} whose measures and layout mirror a {@link SmartReplyView}. */
@@ -343,8 +367,9 @@
}
Button previous = null;
- for (CharSequence choice : choices) {
- Button current = mView.inflateReplyButton(mContext, mView, choice, null, null);
+ for (int i = 0; i < choices.length; ++i) {
+ Button current = mView.inflateReplyButton(mContext, mView, i, choices[i],
+ null, null, null, null);
current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
current.getPaddingBottom());
if (previous != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
index cd3031b..dd2b581 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
@@ -97,6 +97,7 @@
doNothing().when(mGroupManager).collapseAllGroups();
doNothing().when(mExpandHelper).cancelImmediately();
doNothing().when(notificationShelf).setAnimationsEnabled(anyBoolean());
+ doNothing().when(notificationShelf).fadeInTranslating();
}
@Test
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 2e4c3b8..1631316 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5613,6 +5613,38 @@
// OS: P
SETTINGS_ZEN_ONBOARDING = 1380;
+ // OPEN: Settings > Display > Auto brightness
+ // CATEGORY: SETTINGS
+ // OS: P
+ SETTINGS_AUTO_BRIGHTNESS = 1381;
+
+ // OPEN: Smart replies in a notification seen at least once
+ // CATEGORY: NOTIFICATION
+ // PACKAGE: App that posted the notification
+ // SUBTYPE: Number of smart replies.
+ // OS: P
+ SMART_REPLY_VISIBLE = 1382;
+
+ // ACTION: Smart reply in a notification clicked.
+ // CATEGORY: NOTIFICATION
+ // PACKAGE: App that posted the notification
+ // SUBTYPE: Index of smart reply clicked.
+ // OS: P
+ SMART_REPLY_ACTION = 1383;
+
+ // Tagged data for SMART_REPLY_VISIBLE. Count of number of smart replies.
+ // OS: P
+ NOTIFICATION_SMART_REPLY_COUNT = 1384;
+
+ // Volume dialog > ringer toggle
+ // OS: P
+ ACTION_VOLUME_RINGER_TOGGLE = 1385;
+
+ // Volume dialog > settings button
+ // OS: P
+ ACTION_VOLUME_SETTINGS = 1386;
+
+
// ---- End P Constants, all P constants go above this line ----
// First Q constant in master goes here:
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
index a2de8e7..fbec5cb 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -64,8 +64,7 @@
try {
File restoreData = prepareRestoreData(mInfo, mInFD);
- // TODO: version ?
- invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0);
+ invokeAgentForAdbRestore(mAgent, mInfo, restoreData);
} catch (IOException e) {
e.printStackTrace();
}
@@ -83,8 +82,8 @@
return sortedDataName;
}
- private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData,
- int versionCode) throws IOException {
+ private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData)
+ throws IOException {
String pkg = info.packageName;
File newStateName = new File(mDataDir, pkg + ".new");
try {
@@ -95,9 +94,9 @@
if (DEBUG) {
Slog.i(TAG, "Starting restore of package " + pkg + " for version code "
- + versionCode);
+ + info.version);
}
- agent.doRestore(backupData, versionCode, newState, mToken,
+ agent.doRestore(backupData, info.version, newState, mToken,
mBackupManagerService.getBackupManagerBinder());
} catch (IOException e) {
Slog.e(TAG, "Exception opening file. " + e);
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index dc28cd1..e4ce62d 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -27,6 +27,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
@@ -240,12 +241,13 @@
PackageManager.GET_SIGNING_CERTIFICATES);
homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
homeVersion = homeInfo.getLongVersionCode();
- Signature[][] signingHistory = homeInfo.signingCertificateHistory;
- if (signingHistory == null || signingHistory.length == 0) {
- Slog.e(TAG, "Home app has no signing history");
+ SigningInfo signingInfo = homeInfo.signingInfo;
+ if (signingInfo == null) {
+ Slog.e(TAG, "Home app has no signing information");
} else {
// retrieve the newest sigs to back up
- Signature[] homeInfoSignatures = signingHistory[signingHistory.length - 1];
+ // TODO (b/73988180) use entire signing history in case of rollbacks
+ Signature[] homeInfoSignatures = signingInfo.getApkContentsSigners();
homeSigHashes = BackupUtils.hashSignatureArray(homeInfoSignatures);
}
} catch (NameNotFoundException e) {
@@ -334,8 +336,8 @@
}
}
- Signature[][] signingHistory = info.signingCertificateHistory;
- if (signingHistory == null || signingHistory.length == 0) {
+ SigningInfo signingInfo = info.signingInfo;
+ if (signingInfo == null) {
Slog.w(TAG, "Not backing up package " + packName
+ " since it appears to have no signatures.");
continue;
@@ -358,7 +360,7 @@
outputBufferStream.writeInt(info.versionCode);
}
// retrieve the newest sigs to back up
- Signature[] infoSignatures = signingHistory[signingHistory.length - 1];
+ Signature[] infoSignatures = signingInfo.getApkContentsSigners();
writeSignatureHashArray(outputBufferStream,
BackupUtils.hashSignatureArray(infoSignatures));
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 77163d3..0c99b44 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -99,6 +99,7 @@
private FullBackupObbConnection mObbConnection = null;
private ParcelFileDescriptor[] mPipes = null;
private byte[] mWidgetData = null;
+ private long mAppVersion;
private long mBytes;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
@@ -476,6 +477,9 @@
if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
info);
+ // readAppManifestAndReturnSignatures() will have extracted the version from
+ // the manifest, so we save it to use in key-value restore later.
+ mAppVersion = info.version;
PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
@@ -667,6 +671,8 @@
Slog.d(TAG, "Restoring key-value file for " + pkg
+ " : " + info.path);
}
+ // Set the version saved from manifest entry.
+ info.version = mAppVersion;
KeyValueAdbRestoreEngine restoreEngine =
new KeyValueAdbRestoreEngine(
mBackupManagerService,
diff --git a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
index 5518374..c39cceb 100644
--- a/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
@@ -27,6 +27,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.os.Process;
import android.util.Slog;
@@ -203,15 +204,16 @@
return false;
}
- Signature[][] deviceHistorySigs = target.signingCertificateHistory;
- if (ArrayUtils.isEmpty(deviceHistorySigs)) {
- Slog.w(TAG, "signingCertificateHistory is empty, app was either unsigned or the flag" +
+ SigningInfo signingInfo = target.signingInfo;
+ if (signingInfo == null) {
+ Slog.w(TAG, "signingInfo is empty, app was either unsigned or the flag" +
" PackageManager#GET_SIGNING_CERTIFICATES was not specified");
return false;
}
if (DEBUG) {
- Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" + deviceHistorySigs);
+ Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device="
+ + signingInfo.getApkContentsSigners());
}
final int nStored = storedSigs.length;
@@ -225,8 +227,8 @@
} else {
// the app couldn't have rotated keys, since it was signed with multiple sigs - do
// a check to see if we find a match for all stored sigs
- // since app hasn't rotated key, we only need to check with deviceHistorySigs[0]
- Signature[] deviceSigs = deviceHistorySigs[0];
+ // since app hasn't rotated key, we only need to check with its current signers
+ Signature[] deviceSigs = signingInfo.getApkContentsSigners();
int nDevice = deviceSigs.length;
// ensure that each stored sig matches an on-device sig
diff --git a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
index 994d5a9..a3d5601 100644
--- a/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/FullBackupUtils.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
@@ -106,12 +107,13 @@
printer.println(withApk ? "1" : "0");
// write the signature block
- Signature[][] signingHistory = pkg.signingCertificateHistory;
- if (signingHistory == null) {
+ SigningInfo signingInfo = pkg.signingInfo;
+ if (signingInfo == null) {
printer.println("0");
} else {
// retrieve the newest sigs to write
- Signature[] signatures = signingHistory[signingHistory.length - 1];
+ // TODO (b/73988180) use entire signing history in case of rollbacks
+ Signature[] signatures = signingInfo.getApkContentsSigners();
printer.println(Integer.toString(signatures.length));
for (Signature sig : signatures) {
printer.println(sig.toCharsString());
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c3f4809..022f865 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -767,7 +767,7 @@
/**
* The controller for all operations related to locktask.
*/
- final LockTaskController mLockTaskController;
+ private final LockTaskController mLockTaskController;
final UserController mUserController;
@@ -2902,6 +2902,7 @@
private boolean mBlacklistDisabled;
private String mExemptionsStr;
private List<String> mExemptions = Collections.emptyList();
+ private int mLogSampleRate = -1;
@HiddenApiEnforcementPolicy private int mPolicyPreP = HIDDEN_API_ENFORCEMENT_DEFAULT;
@HiddenApiEnforcementPolicy private int mPolicyP = HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -2916,6 +2917,10 @@
false,
this);
mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.HIDDEN_API_ACCESS_LOG_SAMPLING_RATE),
+ false,
+ this);
+ mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.HIDDEN_API_POLICY_PRE_P_APPS),
false,
this);
@@ -2942,6 +2947,15 @@
}
zygoteProcess.setApiBlacklistExemptions(mExemptions);
}
+ int logSampleRate = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.HIDDEN_API_ACCESS_LOG_SAMPLING_RATE, -1);
+ if (logSampleRate < 0 || logSampleRate > 0x10000) {
+ logSampleRate = -1;
+ }
+ if (logSampleRate != -1 && logSampleRate != mLogSampleRate) {
+ mLogSampleRate = logSampleRate;
+ zygoteProcess.setHiddenApiAccessLogSampleRate(mLogSampleRate);
+ }
mPolicyPreP = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY_PRE_P_APPS);
mPolicyP = getValidEnforcementPolicy(Settings.Global.HIDDEN_API_POLICY_P_APPS);
}
@@ -5292,7 +5306,7 @@
synchronized (this) {
mWindowManager.cancelRecentsAnimation(restoreHomeStackPosition
? REORDER_MOVE_TO_ORIGINAL_POSITION
- : REORDER_KEEP_IN_PLACE);
+ : REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation");
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -12315,6 +12329,10 @@
return mActivityStartController;
}
+ LockTaskController getLockTaskController() {
+ return mLockTaskController;
+ }
+
ClientLifecycleManager getLifecycleManager() {
return mLifecycleManager;
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e539bf8..f32717a 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -779,11 +779,13 @@
* @param task The new parent {@link TaskRecord}.
*/
void setTask(TaskRecord task) {
- setTask(task, false /*reparenting*/);
+ setTask(task /* task */, false /* reparenting */);
}
/**
* This method should only be called by {@link TaskRecord#removeActivity(ActivityRecord)}.
+ * @param task The new parent task.
+ * @param reparenting Whether we're in the middle of reparenting.
*/
void setTask(TaskRecord task, boolean reparenting) {
// Do nothing if the {@link TaskRecord} is the same as the current {@link getTask}.
@@ -791,12 +793,19 @@
return;
}
- final ActivityStack stack = getStack();
+ final ActivityStack oldStack = getStack();
+ final ActivityStack newStack = task != null ? task.getStack() : null;
- // If the new {@link TaskRecord} is from a different {@link ActivityStack}, remove this
- // {@link ActivityRecord} from its current {@link ActivityStack}.
- if (!reparenting && stack != null && (task == null || stack != task.getStack())) {
- stack.onActivityRemovedFromStack(this);
+ // Inform old stack (if present) of activity removal and new stack (if set) of activity
+ // addition.
+ if (oldStack != newStack) {
+ if (!reparenting && oldStack != null) {
+ oldStack.onActivityRemovedFromStack(this);
+ }
+
+ if (newStack != null) {
+ newStack.onActivityAddedToStack(this);
+ }
}
this.task = task;
@@ -1073,8 +1082,15 @@
// Must reparent first in window manager
mWindowContainerController.reparent(newTask.getWindowContainerController(), position);
+ // Reparenting prevents informing the parent stack of activity removal in the case that
+ // the new stack has the same parent. we must manually signal here if this is not the case.
+ final ActivityStack prevStack = prevTask.getStack();
+
+ if (prevStack != newTask.getStack()) {
+ prevStack.onActivityRemovedFromStack(this);
+ }
// Remove the activity from the old task and add it to the new task.
- prevTask.removeActivity(this, true /*reparenting*/);
+ prevTask.removeActivity(this, true /* reparenting */);
newTask.addActivityAtIndex(position, this);
}
@@ -1198,10 +1214,7 @@
}
boolean isFocusable() {
- if (inSplitScreenPrimaryWindowingMode() && mStackSupervisor.mIsDockMinimized) {
- return false;
- }
- return getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable();
+ return mStackSupervisor.isFocusable(this, isAlwaysFocusable());
}
boolean isResizeable() {
@@ -1589,14 +1602,20 @@
void pauseKeyDispatchingLocked() {
if (!keysPaused) {
keysPaused = true;
- mWindowContainerController.pauseKeyDispatching();
+
+ if (mWindowContainerController != null) {
+ mWindowContainerController.pauseKeyDispatching();
+ }
}
}
void resumeKeyDispatchingLocked() {
if (keysPaused) {
keysPaused = false;
- mWindowContainerController.resumeKeyDispatching();
+
+ if (mWindowContainerController != null) {
+ mWindowContainerController.resumeKeyDispatching();
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 649d577..eb482c1 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -489,13 +489,13 @@
*/
void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
if (record == mResumedActivity && state != RESUMED) {
- clearResumedActivity(reason + " - onActivityStateChanged");
+ setResumedActivity(null, reason + " - onActivityStateChanged");
}
if (state == RESUMED) {
if (DEBUG_STACK) Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:"
+ reason);
- mResumedActivity = record;
+ setResumedActivity(record, reason + " - onActivityStateChanged");
mService.setResumedActivityUncheckLocked(record, reason);
mStackSupervisor.mRecentTasks.add(record.getTask());
}
@@ -1077,13 +1077,8 @@
}
boolean isFocusable() {
- if (getWindowConfiguration().canReceiveKeys()) {
- return true;
- }
- // The stack isn't focusable. See if its top activity is focusable to force focus on the
- // stack.
final ActivityRecord r = topRunningActivityLocked();
- return r != null && r.isFocusable();
+ return mStackSupervisor.isFocusable(this, r != null && r.isFocusable());
}
final boolean isAttached() {
@@ -2314,14 +2309,14 @@
return mResumedActivity;
}
- /**
- * Clears reference to currently resumed activity.
- */
- private void clearResumedActivity(String reason) {
- if (DEBUG_STACK) Slog.d(TAG_STACK, "clearResumedActivity: " + mResumedActivity + " reason:"
- + reason);
+ private void setResumedActivity(ActivityRecord r, String reason) {
+ if (mResumedActivity == r) {
+ return;
+ }
- mResumedActivity = null;
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "setResumedActivity stack:" + this + " + from: "
+ + mResumedActivity + " to:" + r + " reason:" + reason);
+ mResumedActivity = r;
}
@GuardedBy("mService")
@@ -3743,7 +3738,7 @@
}
if (endTask) {
- mService.mLockTaskController.clearLockedTask(task);
+ mService.getLockTaskController().clearLockedTask(task);
}
} else if (!r.isState(PAUSING)) {
// If the activity is PAUSING, we will complete the finish once
@@ -4027,14 +4022,20 @@
* an activity moves away from the stack.
*/
void onActivityRemovedFromStack(ActivityRecord r) {
- if (mResumedActivity == r) {
- clearResumedActivity("onActivityRemovedFromStack");
+ removeTimeoutsForActivityLocked(r);
+
+ if (mResumedActivity != null && mResumedActivity == r) {
+ setResumedActivity(null, "onActivityRemovedFromStack");
}
- if (mPausingActivity == r) {
+ if (mPausingActivity != null && mPausingActivity == r) {
mPausingActivity = null;
}
+ }
- removeTimeoutsForActivityLocked(r);
+ void onActivityAddedToStack(ActivityRecord r) {
+ if(r.getState() == RESUMED) {
+ setResumedActivity(r, "onActivityAddedToStack");
+ }
}
/**
@@ -4639,7 +4640,7 @@
// In LockTask mode, moving a locked task to the back of the stack may expose unlocked
// ones. Therefore we need to check if this operation is allowed.
- if (!mService.mLockTaskController.canMoveTaskToBack(tr)) {
+ if (!mService.getLockTaskController().canMoveTaskToBack(tr)) {
return false;
}
@@ -4752,30 +4753,32 @@
mTmpBounds.clear();
mTmpInsetBounds.clear();
- for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
- final TaskRecord task = mTaskHistory.get(i);
- if (task.isResizeable()) {
- if (inFreeformWindowingMode()) {
- // TODO: Can be removed now since each freeform task is in its own stack.
- // For freeform stack we don't adjust the size of the tasks to match that
- // of the stack, but we do try to make sure the tasks are still contained
- // with the bounds of the stack.
- mTmpRect2.set(task.getOverrideBounds());
- fitWithinBounds(mTmpRect2, bounds);
- task.updateOverrideConfiguration(mTmpRect2);
- } else {
- task.updateOverrideConfiguration(taskBounds, insetBounds);
+ synchronized (mWindowManager.getWindowManagerLock()) {
+ for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+ final TaskRecord task = mTaskHistory.get(i);
+ if (task.isResizeable()) {
+ if (inFreeformWindowingMode()) {
+ // TODO: Can be removed now since each freeform task is in its own stack.
+ // For freeform stack we don't adjust the size of the tasks to match that
+ // of the stack, but we do try to make sure the tasks are still contained
+ // with the bounds of the stack.
+ mTmpRect2.set(task.getOverrideBounds());
+ fitWithinBounds(mTmpRect2, bounds);
+ task.updateOverrideConfiguration(mTmpRect2);
+ } else {
+ task.updateOverrideConfiguration(taskBounds, insetBounds);
+ }
+ }
+
+ mTmpBounds.put(task.taskId, task.getOverrideBounds());
+ if (tempTaskInsetBounds != null) {
+ mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
}
}
- mTmpBounds.put(task.taskId, task.getOverrideBounds());
- if (tempTaskInsetBounds != null) {
- mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
- }
+ mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds);
+ setBounds(bounds);
}
-
- mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds);
- setBounds(bounds);
}
@@ -5084,7 +5087,12 @@
onActivityRemovedFromStack(record);
}
- mTaskHistory.remove(task);
+ final boolean removed = mTaskHistory.remove(task);
+
+ if (removed) {
+ EventLog.writeEvent(EventLogTags.AM_REMOVE_TASK, task.taskId, getStackId());
+ }
+
removeActivitiesFromLRUListLocked(task);
updateTaskMovement(task, true);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e9a1358..cbf30bd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -667,6 +667,14 @@
return mFocusedStack;
}
+ boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) {
+ if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) {
+ return false;
+ }
+
+ return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable;
+ }
+
ActivityStack getLastStack() {
return mLastFocusedStack;
}
@@ -1372,12 +1380,13 @@
mService.updateLruProcessLocked(app, true, null);
mService.updateOomAdjLocked();
+ final LockTaskController lockTaskController = mService.getLockTaskController();
if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
|| task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
|| (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
- && mService.mLockTaskController.getLockTaskModeState()
- == LOCK_TASK_MODE_LOCKED)) {
- mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
+ && lockTaskController.getLockTaskModeState()
+ == LOCK_TASK_MODE_LOCKED)) {
+ lockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
}
try {
@@ -2900,7 +2909,7 @@
if (tr != null) {
tr.removeTaskActivitiesLocked(pauseImmediately, reason);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
- mService.mLockTaskController.clearLockedTask(tr);
+ mService.getLockTaskController().clearLockedTask(tr);
if (tr.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
@@ -3814,7 +3823,7 @@
pw.print(mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
getKeyguardController().dump(pw, prefix);
- mService.mLockTaskController.dump(pw, prefix);
+ mService.getLockTaskController().dump(pw, prefix);
}
public void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 5337566..fb89e67 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1156,9 +1156,10 @@
// If we are not able to proceed, disassociate the activity from the task. Leaving an
// activity in an incomplete state can lead to issues, such as performing operations
// without a window container.
- if (!ActivityManager.isStartResultSuccessful(result)
- && mStartActivity.getTask() != null) {
- mStartActivity.getTask().removeActivity(mStartActivity);
+ final ActivityStack stack = mStartActivity.getStack();
+ if (!ActivityManager.isStartResultSuccessful(result) && stack != null) {
+ stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
+ null /* intentResultData */, "startActivity", true /* oomAdj */);
}
mService.mWindowManager.continueSurfaceLayout();
}
@@ -1208,7 +1209,7 @@
// When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
// still needs to be a lock task mode violation since the task gets cleared out and
// the device would otherwise leave the locked task.
- if (mService.mLockTaskController.isLockTaskModeViolation(reusedActivity.getTask(),
+ if (mService.getLockTaskController().isLockTaskModeViolation(reusedActivity.getTask(),
(mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
@@ -2020,7 +2021,7 @@
mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
}
- if (mService.mLockTaskController.isLockTaskModeViolation(mStartActivity.getTask())) {
+ if (mService.getLockTaskController().isLockTaskModeViolation(mStartActivity.getTask())) {
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
@@ -2043,7 +2044,7 @@
}
private int setTaskFromSourceRecord() {
- if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) {
+ if (mService.getLockTaskController().isLockTaskModeViolation(mSourceRecord.getTask())) {
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
@@ -2137,7 +2138,7 @@
private int setTaskFromInTask() {
// The caller is asking that the new activity be started in an explicit
// task it has provided to us.
- if (mService.mLockTaskController.isLockTaskModeViolation(mInTask)) {
+ if (mService.getLockTaskController().isLockTaskModeViolation(mInTask)) {
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 9caef4a..40b9e4f 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -133,4 +133,7 @@
# The activity's onStart has been called.
30059 am_on_start_called (User|1|5),(Component Name|3),(Reason|3)
# The activity's onDestroy has been called.
-30060 am_on_destroy_called (User|1|5),(Component Name|3),(Reason|3)
\ No newline at end of file
+30060 am_on_destroy_called (User|1|5),(Component Name|3),(Reason|3)
+
+# The task is being removed from its parent stack
+30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 2b988d3..a20452b 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -523,7 +523,7 @@
}
for (int i = mTasks.size() - 1; i >= 0; --i) {
final TaskRecord tr = mTasks.get(i);
- if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) {
+ if (tr.userId == userId && !mService.getLockTaskController().isTaskWhitelisted(tr)) {
remove(tr);
}
}
@@ -1156,7 +1156,7 @@
}
// If we're in lock task mode, ignore the root task
- if (task == mService.mLockTaskController.getRootTask()) {
+ if (task == mService.getLockTaskController().getRootTask()) {
return false;
}
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index 17eeb5b..06b5e20 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -46,6 +46,8 @@
*/
class RecentsAnimation implements RecentsAnimationCallbacks {
private static final String TAG = RecentsAnimation.class.getSimpleName();
+ // TODO (b/73188263): Reset debugging flags
+ private static final boolean DEBUG = true;
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
@@ -74,10 +76,13 @@
void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner,
ComponentName recentsComponent, int recentsUid) {
+ if (DEBUG) Slog.d(TAG, "startRecentsActivity(): intent=" + intent);
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
if (!mWindowManager.canStartRecentsAnimation()) {
notifyAnimationCancelBeforeStart(recentsAnimationRunner);
+ if (DEBUG) Slog.d(TAG, "Can't start recents animation, nextAppTransition="
+ + mWindowManager.getPendingAppTransition());
return;
}
@@ -97,6 +102,7 @@
mRestoreTargetBehindStack = display.getStackAbove(targetStack);
if (mRestoreTargetBehindStack == null) {
notifyAnimationCancelBeforeStart(recentsAnimationRunner);
+ if (DEBUG) Slog.d(TAG, "No stack above target stack=" + targetStack);
return;
}
}
@@ -119,6 +125,8 @@
// Move the recents activity into place for the animation if it is not top most
display = targetActivity.getDisplay();
display.moveStackBehindBottomMostVisibleStack(targetStack);
+ if (DEBUG) Slog.d(TAG, "Moved stack=" + targetStack + " behind stack="
+ + display.getStackAbove(targetStack));
} else {
// No recents activity
ActivityOptions options = ActivityOptions.makeBasic();
@@ -140,6 +148,8 @@
display = targetActivity.getDisplay();
// TODO: Maybe wait for app to draw in this particular case?
+
+ if (DEBUG) Slog.d(TAG, "Started intent=" + intent);
}
// Mark the target activity as launch-behind to bump its visibility for the
@@ -148,7 +158,8 @@
// Fetch all the surface controls and pass them to the client to get the animation
// started
- mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION,
+ "startRecentsActivity");
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
this, display.mDisplayId, mStackSupervisor.mRecentTasks.getRecentTaskIds());
@@ -158,6 +169,9 @@
mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunched(START_TASK_TO_FRONT,
targetActivity);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to start recents activity", e);
+ throw e;
} finally {
mWindowManager.continueSurfaceLayout();
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -167,6 +181,9 @@
@Override
public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode) {
synchronized (mService) {
+ if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller="
+ + mWindowManager.getRecentsAnimationController()
+ + " reorderMode=" + reorderMode);
if (mWindowManager.getRecentsAnimationController() == null) return;
// Just to be sure end the launch hint in case the target activity was never launched.
@@ -187,6 +204,9 @@
final ActivityStack targetStack = mDefaultDisplay.getStack(
WINDOWING_MODE_UNDEFINED, mTargetActivityType);
final ActivityRecord targetActivity = targetStack.getTopActivity();
+ if (DEBUG) Slog.d(TAG, "onAnimationFinished(): targetStack=" + targetStack
+ + " targetActivity=" + targetActivity
+ + " mRestoreTargetBehindStack=" + mRestoreTargetBehindStack);
if (targetActivity == null) {
return;
}
@@ -198,10 +218,27 @@
// Bring the target stack to the front
mStackSupervisor.mNoAnimActivities.add(targetActivity);
targetStack.moveToFront("RecentsAnimation.onAnimationFinished()");
+ if (DEBUG) {
+ final ActivityStack topStack = getTopNonAlwaysOnTopStack();
+ if (topStack != targetStack) {
+ Slog.w(TAG, "Expected target stack=" + targetStack
+ + " to be top most but found stack=" + topStack);
+ }
+ }
} else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
// Restore the target stack to its previous position
final ActivityDisplay display = targetActivity.getDisplay();
display.moveStackBehindStack(targetStack, mRestoreTargetBehindStack);
+ if (DEBUG) {
+ final ActivityStack aboveTargetStack =
+ mDefaultDisplay.getStackAbove(targetStack);
+ if (mRestoreTargetBehindStack != null
+ && aboveTargetStack != mRestoreTargetBehindStack) {
+ Slog.w(TAG, "Expected target stack=" + targetStack
+ + " to restored behind stack=" + mRestoreTargetBehindStack
+ + " but it is behind stack=" + aboveTargetStack);
+ }
+ }
} else {
// Keep target stack in place, nothing changes, so ignore the transition
// logic below
@@ -221,6 +258,9 @@
// split-screen), or we will have returned to the app, and the minimized state
// should be reset
mWindowManager.checkSplitScreenMinimizedChanged(true /* animate */);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to clean up recents activity", e);
+ throw e;
} finally {
mWindowManager.continueSurfaceLayout();
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -239,4 +279,18 @@
Slog.e(TAG, "Failed to cancel recents animation before start", e);
}
}
+
+ /**
+ * @return The top stack that is not always-on-top.
+ */
+ private ActivityStack getTopNonAlwaysOnTopStack() {
+ for (int i = mDefaultDisplay.getChildCount() - 1; i >= 0; i--) {
+ final ActivityStack s = mDefaultDisplay.getChildAt(i);
+ if (s.getWindowConfiguration().isAlwaysOnTop()) {
+ continue;
+ }
+ return s;
+ }
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/am/SafeActivityOptions.java b/services/core/java/com/android/server/am/SafeActivityOptions.java
index ac6f01f..2de75273 100644
--- a/services/core/java/com/android/server/am/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/am/SafeActivityOptions.java
@@ -210,7 +210,7 @@
// Check if someone tries to launch an unwhitelisted activity into LockTask mode.
final boolean lockTaskMode = options.getLockTaskMode();
if (aInfo != null && lockTaskMode
- && !supervisor.mService.mLockTaskController.isPackageWhitelisted(
+ && !supervisor.mService.getLockTaskController().isPackageWhitelisted(
UserHandle.getUserId(callingUid), aInfo.packageName)) {
final String msg = "Permission Denial: starting " + getIntentString(intent)
+ " from " + callerApp + " (pid=" + callingPid
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 034cb2e..0e418ad 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -451,7 +451,7 @@
}
void removeWindowContainer() {
- mService.mLockTaskController.clearLockedTask(this);
+ mService.getLockTaskController().clearLockedTask(this);
mWindowContainerController.removeContainer();
if (!getWindowConfiguration().persistTaskBounds()) {
// Reset current bounds for task whose bounds shouldn't be persisted so it uses
@@ -927,7 +927,26 @@
if (stack != null && !stack.isInStackLocked(this)) {
throw new IllegalStateException("Task must be added as a Stack child first.");
}
+ final ActivityStack oldStack = mStack;
mStack = stack;
+
+ // If the new {@link TaskRecord} is from a different {@link ActivityStack}, remove this
+ // {@link ActivityRecord} from its current {@link ActivityStack}.
+
+ if (oldStack != mStack) {
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord activity = getChildAt(i);
+
+ if (oldStack != null) {
+ oldStack.onActivityRemovedFromStack(activity);
+ }
+
+ if (mStack != null) {
+ stack.onActivityAddedToStack(activity);
+ }
+ }
+ }
+
onParentChanged();
}
@@ -1232,6 +1251,7 @@
index = Math.min(size, index);
mActivities.add(index, r);
+
updateEffectiveIntent();
if (r.isPersistable()) {
mService.notifyTaskPersisterLocked(this, false);
@@ -1257,7 +1277,7 @@
* @return true if this was the last activity in the task.
*/
boolean removeActivity(ActivityRecord r) {
- return removeActivity(r, false /*reparenting*/);
+ return removeActivity(r, false /* reparenting */);
}
boolean removeActivity(ActivityRecord r, boolean reparenting) {
@@ -1266,7 +1286,7 @@
"Activity=" + r + " does not belong to task=" + this);
}
- r.setTask(null /*task*/, reparenting);
+ r.setTask(null /* task */, reparenting /* reparenting */);
if (mActivities.remove(r) && r.fullscreen) {
// Was previously in list.
@@ -1446,9 +1466,10 @@
}
final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
+ final LockTaskController lockTaskController = mService.getLockTaskController();
switch (r.lockTaskLaunchMode) {
case LOCK_TASK_LAUNCH_MODE_DEFAULT:
- mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+ mLockTaskAuth = lockTaskController.isPackageWhitelisted(userId, pkg)
? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
break;
@@ -1461,7 +1482,7 @@
break;
case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
- mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+ mLockTaskAuth = lockTaskController.isPackageWhitelisted(userId, pkg)
? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
break;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b6e414a2..fecb934 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2215,7 +2215,7 @@
protected void clearAllLockedTasks(String reason) {
synchronized (mService) {
- mService.mLockTaskController.clearLockedTasks(reason);
+ mService.getLockTaskController().clearLockedTasks(reason);
}
}
diff --git a/services/core/java/com/android/server/backup/BackupUtils.java b/services/core/java/com/android/server/backup/BackupUtils.java
index d817534..96c5621 100644
--- a/services/core/java/com/android/server/backup/BackupUtils.java
+++ b/services/core/java/com/android/server/backup/BackupUtils.java
@@ -20,6 +20,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
@@ -55,16 +56,16 @@
return false;
}
- Signature[][] deviceHistorySigs = target.signingCertificateHistory;
- if (ArrayUtils.isEmpty(deviceHistorySigs)) {
- Slog.w(TAG, "signingCertificateHistory is empty, app was either unsigned or the flag" +
+ SigningInfo signingInfo = target.signingInfo;
+ if (signingInfo == null) {
+ Slog.w(TAG, "signingInfo is empty, app was either unsigned or the flag" +
" PackageManager#GET_SIGNING_CERTIFICATES was not specified");
return false;
}
if (DEBUG) {
Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
- + " device=" + deviceHistorySigs);
+ + " device=" + signingInfo.getApkContentsSigners());
}
final int nStored = storedSigHashes.size();
@@ -78,8 +79,9 @@
} else {
// the app couldn't have rotated keys, since it was signed with multiple sigs - do
// a check to see if we find a match for all stored sigs
- // since app hasn't rotated key, we only need to check with deviceHistorySigs[0]
- ArrayList<byte[]> deviceHashes = hashSignatureArray(deviceHistorySigs[0]);
+ // since app hasn't rotated key, we only need to check with current signers
+ ArrayList<byte[]> deviceHashes =
+ hashSignatureArray(signingInfo.getApkContentsSigners());
int nDevice = deviceHashes.size();
// ensure that each stored sig matches an on-device sig
for (int i = 0; i < nStored; i++) {
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 906a6a3..6fa999c 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -24,6 +24,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
@@ -55,12 +56,14 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.util.DataUnit;
import android.util.DebugUtils;
import android.util.Pair;
import android.util.Range;
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -71,6 +74,7 @@
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
+import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -96,9 +100,11 @@
private final Clock mClock;
private final Dependencies mDeps;
private final ContentResolver mResolver;
- private final SettingsObserver mSettingsObserver;
private final ConfigChangeReceiver mConfigChangeReceiver;
+ @VisibleForTesting
+ final ContentObserver mSettingsObserver;
+
private ConnectivityManager mCM;
private NetworkPolicyManager mNPM;
private NetworkStatsManager mStatsManager;
@@ -288,12 +294,16 @@
final NetworkPolicy[] policies = mNPM.getNetworkPolicies();
for (NetworkPolicy policy : policies) {
- if (hasActiveCycle(policy) && policy.template.matches(identity)) {
+ if (policy.hasCycle() && policy.template.matches(identity)) {
+ final long cycleStart = policy.cycleIterator().next().getLower()
+ .toInstant().toEpochMilli();
// Prefer user-defined warning, otherwise use hard limit
- final long policyBytes = (policy.warningBytes == LIMIT_DISABLED)
- ? policy.limitBytes : policy.warningBytes;
+ final long activeWarning = getActiveWarning(policy, cycleStart);
+ final long policyBytes = (activeWarning == WARNING_DISABLED)
+ ? getActiveLimit(policy, cycleStart)
+ : activeWarning;
- if (policyBytes != LIMIT_DISABLED) {
+ if (policyBytes != LIMIT_DISABLED && policyBytes != WARNING_DISABLED) {
final long policyBudget = getRemainingDailyBudget(policyBytes,
policy.cycleIterator().next());
minQuota = Math.min(minQuota, policyBudget);
@@ -324,11 +334,11 @@
if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes");
}
+ // TODO: re-register if day changed: budget may have run out but should be refreshed.
if (haveMultipathBudget() && quota == mQuota) {
- // If we already have a usage callback pending , there's no need to re-register it
+ // If there is already a usage callback pending , there's no need to re-register it
// if the quota hasn't changed. The callback will simply fire as expected when the
- // budget is spent. Also: if we re-register the callback when we're below the
- // UsageCallback's minimum value of 2MB, we'll overshoot the budget.
+ // budget is spent.
if (DBG) Slog.d(TAG, "Quota still " + quota + ", not updating.");
return;
}
@@ -338,7 +348,17 @@
// ourselves any budget to work with.
final long usage = getDailyNonDefaultDataUsage();
final long budget = (usage == -1) ? 0 : Math.max(0, quota - usage);
- if (budget > 0) {
+
+ // Only consider budgets greater than MIN_THRESHOLD_BYTES, otherwise the callback will
+ // fire late, after data usage went over budget. Also budget should be 0 if remaining
+ // data is close to 0.
+ // This is necessary because the usage callback does not accept smaller thresholds.
+ // Because it snaps everything to MIN_THRESHOLD_BYTES, the lesser of the two evils is
+ // to snap to 0 here.
+ // This will only be called if the total quota for the day changed, not if usage changed
+ // since last time, so even if this is called very often the budget will not snap to 0
+ // as soon as there are less than 2MB left for today.
+ if (budget > NetworkStatsManager.MIN_THRESHOLD_BYTES) {
if (DBG) Slog.d(TAG, "Setting callback for " + budget +
" bytes on network " + network);
registerUsageCallback(budget);
@@ -388,9 +408,16 @@
}
}
- private static boolean hasActiveCycle(NetworkPolicy policy) {
- return policy.hasCycle() && policy.lastLimitSnooze <
- policy.cycleIterator().next().getLower().toInstant().toEpochMilli();
+ private static long getActiveWarning(NetworkPolicy policy, long cycleStart) {
+ return policy.lastWarningSnooze < cycleStart
+ ? policy.warningBytes
+ : WARNING_DISABLED;
+ }
+
+ private static long getActiveLimit(NetworkPolicy policy, long cycleStart) {
+ return policy.lastLimitSnooze < cycleStart
+ ? policy.limitBytes
+ : LIMIT_DISABLED;
}
// Only ever updated on the handler thread. Accessed from other binder threads to retrieve
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index d37dd18..df6a6f8 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -249,6 +249,7 @@
"CarrierConfigChangeListener", mContext, smHandler, filter,
(Intent ignored) -> {
mLog.log("OBSERVED carrier config change");
+ updateConfiguration();
reevaluateSimCardProvisioning();
});
// TODO: Remove SimChangeListener altogether. For now, we retain it
@@ -261,28 +262,35 @@
});
mStateReceiver = new StateReceiver();
- filter = new IntentFilter();
+
+ // Load tethering configuration.
+ updateConfiguration();
+
+ startStateMachineUpdaters();
+ }
+
+ private void startStateMachineUpdaters() {
+ mCarrierConfigChange.startListening();
+
+ final Handler handler = mTetherMasterSM.getHandler();
+ IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- mContext.registerReceiver(mStateReceiver, filter, null, smHandler);
+ mContext.registerReceiver(mStateReceiver, filter, null, handler);
filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
filter.addDataScheme("file");
- mContext.registerReceiver(mStateReceiver, filter, null, smHandler);
+ mContext.registerReceiver(mStateReceiver, filter, null, handler);
- UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
-
- // this check is useful only for some unit tests; example: ConnectivityServiceTest
- if (userManager != null) {
- userManager.addUserRestrictionsListener(new TetheringUserRestrictionListener(this));
+ final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ // This check is useful only for some unit tests; example: ConnectivityServiceTest.
+ if (umi != null) {
+ umi.addUserRestrictionsListener(new TetheringUserRestrictionListener(this));
}
-
- // load device config info
- updateConfiguration();
}
private WifiManager getWifiManager() {
@@ -384,17 +392,15 @@
*/
@VisibleForTesting
protected boolean isTetherProvisioningRequired() {
- String[] provisionApp = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_mobile_hotspot_provision_app);
+ final TetheringConfiguration cfg = mConfig;
if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)
- || provisionApp == null) {
+ || cfg.provisioningApp.length == 0) {
return false;
}
-
if (carrierConfigAffirmsEntitlementCheckNotRequired()) {
return false;
}
- return (provisionApp.length == 2);
+ return (cfg.provisioningApp.length == 2);
}
// The logic here is aimed solely at confirming that a CarrierConfig exists
@@ -417,20 +423,6 @@
return !isEntitlementCheckRequired;
}
- // Used by the SIM card change observation code.
- // TODO: De-duplicate above code.
- private boolean hasMobileHotspotProvisionApp() {
- try {
- if (!mContext.getResources().getString(com.android.internal.R.string.
- config_mobile_hotspot_provision_app_no_ui).isEmpty()) {
- Log.d(TAG, "re-evaluate provisioning");
- return true;
- }
- } catch (Resources.NotFoundException e) {}
- Log.d(TAG, "no prov-check needed for new SIM");
- return false;
- }
-
/**
* Enables or disables tethering for the given type. This should only be called once
* provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks
@@ -1187,7 +1179,7 @@
}
private void reevaluateSimCardProvisioning() {
- if (!hasMobileHotspotProvisionApp()) return;
+ if (!mConfig.hasMobileHotspotProvisionApp()) return;
if (carrierConfigAffirmsEntitlementCheckNotRequired()) return;
ArrayList<Integer> tethered = new ArrayList<>();
@@ -1546,7 +1538,6 @@
return;
}
- mCarrierConfigChange.startListening();
mSimChange.startListening();
mUpstreamNetworkMonitor.start();
@@ -1564,7 +1555,6 @@
mOffload.stop();
mUpstreamNetworkMonitor.stop();
mSimChange.stopListening();
- mCarrierConfigChange.stopListening();
notifyDownstreamsOfNewUpstreamIface(null);
handleNewUpstreamNetworkState(null);
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 09bce7f..454c579 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -21,14 +21,23 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
+import static com.android.internal.R.array.config_tether_bluetooth_regexs;
+import static com.android.internal.R.array.config_tether_dhcp_range;
+import static com.android.internal.R.array.config_tether_usb_regexs;
+import static com.android.internal.R.array.config_tether_upstream_types;
+import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.telephony.TelephonyManager;
import android.net.util.SharedLog;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -51,6 +60,8 @@
public class TetheringConfiguration {
private static final String TAG = TetheringConfiguration.class.getSimpleName();
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
@VisibleForTesting
public static final int DUN_NOT_REQUIRED = 0;
public static final int DUN_REQUIRED = 1;
@@ -79,18 +90,18 @@
public final String[] dhcpRanges;
public final String[] defaultIPv4DNS;
+ public final String[] provisioningApp;
+ public final String provisioningAppNoUi;
+
public TetheringConfiguration(Context ctx, SharedLog log) {
final SharedLog configLog = log.forSubComponent("config");
- tetherableUsbRegexs = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_usb_regexs);
+ tetherableUsbRegexs = getResourceStringArray(ctx, config_tether_usb_regexs);
// TODO: Evaluate deleting this altogether now that Wi-Fi always passes
// us an interface name. Careful consideration needs to be given to
// implications for Settings and for provisioning checks.
- tetherableWifiRegexs = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_wifi_regexs);
- tetherableBluetoothRegexs = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_bluetooth_regexs);
+ tetherableWifiRegexs = getResourceStringArray(ctx, config_tether_wifi_regexs);
+ tetherableBluetoothRegexs = getResourceStringArray(ctx, config_tether_bluetooth_regexs);
dunCheck = checkDunRequired(ctx);
configLog.log("DUN check returned: " + dunCheckString(dunCheck));
@@ -101,6 +112,9 @@
dhcpRanges = getDhcpRanges(ctx);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+ provisioningApp = getResourceStringArray(ctx, config_mobile_hotspot_provision_app);
+ provisioningAppNoUi = getProvisioningAppNoUi(ctx);
+
configLog.log(toString());
}
@@ -116,6 +130,10 @@
return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
}
+ public boolean hasMobileHotspotProvisionApp() {
+ return !TextUtils.isEmpty(provisioningAppNoUi);
+ }
+
public void dump(PrintWriter pw) {
dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
@@ -129,6 +147,10 @@
dumpStringArray(pw, "dhcpRanges", dhcpRanges);
dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
+
+ dumpStringArray(pw, "provisioningApp", provisioningApp);
+ pw.print("provisioningAppNoUi: ");
+ pw.println(provisioningAppNoUi);
}
public String toString() {
@@ -140,6 +162,8 @@
sj.add(String.format("isDunRequired:%s", isDunRequired));
sj.add(String.format("preferredUpstreamIfaceTypes:%s",
makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
+ sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
+ sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
return String.format("TetheringConfiguration{%s}", sj.toString());
}
@@ -159,6 +183,7 @@
}
private static String makeString(String[] strings) {
+ if (strings == null) return "null";
final StringJoiner sj = new StringJoiner(",", "[", "]");
for (String s : strings) sj.add(s);
return sj.toString();
@@ -195,8 +220,7 @@
}
private static Collection<Integer> getUpstreamIfaceTypes(Context ctx, int dunCheck) {
- final int ifaceTypes[] = ctx.getResources().getIntArray(
- com.android.internal.R.array.config_tether_upstream_types);
+ final int ifaceTypes[] = ctx.getResources().getIntArray(config_tether_upstream_types);
final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
for (int i : ifaceTypes) {
switch (i) {
@@ -247,14 +271,30 @@
}
private static String[] getDhcpRanges(Context ctx) {
- final String[] fromResource = ctx.getResources().getStringArray(
- com.android.internal.R.array.config_tether_dhcp_range);
+ final String[] fromResource = getResourceStringArray(ctx, config_tether_dhcp_range);
if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
return fromResource;
}
return copy(DHCP_DEFAULT_RANGE);
}
+ private static String getProvisioningAppNoUi(Context ctx) {
+ try {
+ return ctx.getResources().getString(config_mobile_hotspot_provision_app_no_ui);
+ } catch (Resources.NotFoundException e) {
+ return "";
+ }
+ }
+
+ private static String[] getResourceStringArray(Context ctx, int resId) {
+ try {
+ final String[] strArray = ctx.getResources().getStringArray(resId);
+ return (strArray != null) ? strArray : EMPTY_STRING_ARRAY;
+ } catch (Resources.NotFoundException e404) {
+ return EMPTY_STRING_ARRAY;
+ }
+ }
+
private static String[] copy(String[] strarray) {
return Arrays.copyOf(strarray, strarray.length);
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index f74daf2..f7439b9 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -42,7 +42,7 @@
*/
public abstract class BrightnessMappingStrategy {
private static final String TAG = "BrightnessMappingStrategy";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final float LUX_GRAD_SMOOTHING = 0.25f;
private static final float MAX_GRAD = 1.0f;
@@ -352,9 +352,9 @@
// current^gamma = desired => gamma = log[current](desired)
gamma = MathUtils.log(desiredBrightness) / MathUtils.log(currentBrightness);
// max^-adjustment = gamma => adjustment = -log[max](gamma)
- adjustment = -MathUtils.constrain(
- MathUtils.log(gamma) / MathUtils.log(maxGamma), -1, 1);
+ adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma);
}
+ adjustment = MathUtils.constrain(adjustment, -1, +1);
if (DEBUG) {
Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" +
MathUtils.pow(maxGamma, -adjustment) + " == " + gamma);
diff --git a/services/core/java/com/android/server/net/NetworkStatsObservers.java b/services/core/java/com/android/server/net/NetworkStatsObservers.java
index 741c206..d840873 100644
--- a/services/core/java/com/android/server/net/NetworkStatsObservers.java
+++ b/services/core/java/com/android/server/net/NetworkStatsObservers.java
@@ -16,7 +16,7 @@
package com.android.server.net;
-import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -52,8 +52,6 @@
private static final String TAG = "NetworkStatsObservers";
private static final boolean LOGV = false;
- private static final long MIN_THRESHOLD_BYTES = 2 * MB_IN_BYTES;
-
private static final int MSG_REGISTER = 1;
private static final int MSG_UNREGISTER = 2;
private static final int MSG_UPDATE_STATS = 3;
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 36bc096..b61a27a 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -40,4 +40,6 @@
void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
void onNotificationDirectReplied(String key);
void onNotificationSettingsViewed(String key);
+ void onNotificationSmartRepliesAdded(String key, int replyCount);
+ void onNotificationSmartReplySent(String key, int replyIndex);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7e04d33..7254acf 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -121,6 +121,7 @@
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.IRingtonePlayer;
+import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -395,6 +396,8 @@
private GroupHelper mGroupHelper;
private boolean mIsTelevision;
+ private MetricsLogger mMetricsLogger;
+
private static class Archive {
final int mBufferSize;
final ArrayDeque<StatusBarNotification> mBuffer;
@@ -801,6 +804,18 @@
// Report to usage stats that notification was made visible
if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
reportSeen(r);
+
+ // If the newly visible notification has smart replies
+ // then log that the user has seen them.
+ if (r.getNumSmartRepliesAdded() > 0
+ && !r.hasSeenSmartReplies()) {
+ r.setSeenSmartReplies(true);
+ LogMaker logMaker = r.getLogMaker()
+ .setCategory(MetricsEvent.SMART_REPLY_VISIBLE)
+ .addTaggedData(MetricsEvent.NOTIFICATION_SMART_REPLY_COUNT,
+ r.getNumSmartRepliesAdded());
+ mMetricsLogger.write(logMaker);
+ }
}
r.setVisibility(true, nv.rank);
nv.recycle();
@@ -855,6 +870,31 @@
}
@Override
+ public void onNotificationSmartRepliesAdded(String key, int replyCount) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ r.setNumSmartRepliesAdded(replyCount);
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationSmartReplySent(String key, int replyIndex) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ LogMaker logMaker = r.getLogMaker()
+ .setCategory(MetricsEvent.SMART_REPLY_ACTION)
+ .setSubtype(replyIndex);
+ mMetricsLogger.write(logMaker);
+ // Treat clicking on a smart reply as a user interaction.
+ reportUserInteraction(r);
+ }
+ }
+ }
+
+ @Override
public void onNotificationSettingsViewed(String key) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
@@ -1349,6 +1389,7 @@
extractorNames = new String[0];
}
mUsageStats = usageStats;
+ mMetricsLogger = new MetricsLogger();
mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
mConditionProviders = conditionProviders;
mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
@@ -3956,9 +3997,14 @@
+ ", notificationUid=" + notificationUid
+ ", notification=" + notification;
Log.e(TAG, noChannelStr);
- doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
- "Failed to post notification on channel \"" + channelId + "\"\n" +
- "See log for more details");
+ boolean appNotificationsOff = mRankingHelper.getImportance(pkg, notificationUid)
+ == NotificationManager.IMPORTANCE_NONE;
+
+ if (!appNotificationsOff) {
+ doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
+ "Failed to post notification on channel \"" + channelId + "\"\n" +
+ "See log for more details");
+ }
return;
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index c887085..9bd3e52 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -149,6 +149,8 @@
private final NotificationStats mStats;
private int mUserSentiment;
private boolean mIsInterruptive;
+ private int mNumberOfSmartRepliesAdded;
+ private boolean mHasSeenSmartReplies;
@VisibleForTesting
public NotificationRecord(Context context, StatusBarNotification sbn,
@@ -962,6 +964,22 @@
mStats.setViewedSettings();
}
+ public void setNumSmartRepliesAdded(int noReplies) {
+ mNumberOfSmartRepliesAdded = noReplies;
+ }
+
+ public int getNumSmartRepliesAdded() {
+ return mNumberOfSmartRepliesAdded;
+ }
+
+ public boolean hasSeenSmartReplies() {
+ return mHasSeenSmartReplies;
+ }
+
+ public void setSeenSmartReplies(boolean hasSeenSmartReplies) {
+ mHasSeenSmartReplies = hasSeenSmartReplies;
+ }
+
public Set<Uri> getNotificationUris() {
Notification notification = getNotification();
Set<Uri> uris = new ArraySet<>();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a1ca702..bfce33d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1523,7 +1523,6 @@
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
params.traceMethod, params.traceCookie);
}
- return;
}
mPendingInstalls.clear();
} else {
@@ -5309,7 +5308,7 @@
synchronized (mPackages) {
final String[] packageNames = getPackagesForUid(uid);
final PackageParser.Package pkg = (packageNames != null && packageNames.length > 0)
- ? mSettings.getPackageLPr(packageNames[0]).getPackage()
+ ? mPackages.get(packageNames[0])
: null;
return mPermissionManager.checkUidPermission(permName, pkg, uid, getCallingUid());
}
@@ -8075,6 +8074,7 @@
callingUid = mIsolatedOwners.get(callingUid);
}
final PackageSetting ps = mSettings.mPackages.get(packageName);
+ PackageParser.Package pkg = mPackages.get(packageName);
final boolean returnAllowed =
ps != null
&& (isCallerSameApp(packageName, callingUid)
@@ -8145,7 +8145,7 @@
}
private boolean isCallerSameApp(String packageName, int uid) {
- PackageParser.Package pkg = mSettings.getPackageLPr(packageName).getPackage();
+ PackageParser.Package pkg = mPackages.get(packageName);
return pkg != null
&& UserHandle.getAppId(uid) == pkg.applicationInfo.uid;
}
@@ -10197,20 +10197,10 @@
// The signature has changed, but this package is in the system
// image... let's recover!
pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails;
- // However... if this package is part of a shared user, but it
- // doesn't match the signature of the shared user, let's fail.
- // What this means is that you can't change the signatures
- // associated with an overall shared user, which doesn't seem all
- // that unreasonable.
+ // If the system app is part of a shared user we allow that shared user to change
+ // signatures as well in part as part of an OTA.
if (signatureCheckPs.sharedUser != null) {
- if (compareSignatures(
- signatureCheckPs.sharedUser.signatures.mSigningDetails.signatures,
- pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) {
- throw new PackageManagerException(
- INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
- "Signature mismatch for shared user: "
- + pkgSetting.sharedUser);
- }
+ signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
}
// File a report about this.
String msg = "System package " + pkg.packageName
@@ -14036,7 +14026,7 @@
"setPackagesSuspended for user " + userId);
if (callingUid != Process.ROOT_UID &&
!UserHandle.isSameApp(getPackageUid(callingPackage, 0, userId), callingUid)) {
- throw new IllegalArgumentException("callingPackage " + callingPackage + " does not"
+ throw new IllegalArgumentException("CallingPackage " + callingPackage + " does not"
+ " belong to calling app id " + UserHandle.getAppId(callingUid));
}
@@ -14060,20 +14050,18 @@
final PackageSetting pkgSetting = mSettings.mPackages.get(packageName);
if (pkgSetting == null
|| filterAppAccessLPr(pkgSetting, callingUid, userId)) {
- Slog.w(TAG, "Could not find package setting for package \"" + packageName
- + "\". Skipping suspending/un-suspending.");
+ Slog.w(TAG, "Could not find package setting for package: " + packageName
+ + ". Skipping suspending/un-suspending.");
unactionedPackages.add(packageName);
continue;
}
- if (pkgSetting.getSuspended(userId) != suspended) {
- if (!canSuspendPackageForUserLocked(packageName, userId)) {
- unactionedPackages.add(packageName);
- continue;
- }
- pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras,
- launcherExtras, userId);
- changedPackagesList.add(packageName);
+ if (!canSuspendPackageForUserLocked(packageName, userId)) {
+ unactionedPackages.add(packageName);
+ continue;
}
+ pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras,
+ launcherExtras, userId);
+ changedPackagesList.add(packageName);
}
}
} finally {
@@ -14088,7 +14076,6 @@
scheduleWritePackageRestrictionsLocked(userId);
}
}
-
return unactionedPackages.toArray(new String[unactionedPackages.size()]);
}
@@ -14096,7 +14083,8 @@
public PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
if (getPackageUid(packageName, 0, userId) != callingUid) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null);
+ throw new SecurityException("Calling package " + packageName
+ + " does not belong to calling uid " + callingUid);
}
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
@@ -14111,25 +14099,6 @@
}
}
- @Override
- public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras,
- int userId) {
- final int callingUid = Binder.getCallingUid();
- mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null);
- synchronized (mPackages) {
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps == null || filterAppAccessLPr(ps, callingUid, userId)) {
- throw new IllegalArgumentException("Unknown target package: " + packageName);
- }
- final PackageUserState packageUserState = ps.readUserState(userId);
- if (packageUserState.suspended) {
- packageUserState.suspendedAppExtras = appExtras;
- sendMyPackageSuspendedOrUnsuspended(new String[] {packageName}, true, appExtras,
- userId);
- }
- }
- }
-
private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
PersistableBundle appExtras, int userId) {
final String action;
@@ -14172,9 +14141,6 @@
mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */,
"isPackageSuspendedForUser for user " + userId);
- if (getPackageUid(packageName, 0, userId) != callingUid) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS, null);
- }
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps == null || filterAppAccessLPr(ps, callingUid, userId)) {
@@ -14184,18 +14150,26 @@
}
}
- void onSuspendingPackageRemoved(String packageName, int userId) {
- final int[] userIds = (userId == UserHandle.USER_ALL) ? sUserManager.getUserIds()
- : new int[] {userId};
- synchronized (mPackages) {
- for (PackageSetting ps : mSettings.mPackages.values()) {
- for (int user : userIds) {
- final PackageUserState pus = ps.readUserState(user);
+ void onSuspendingPackageRemoved(String packageName, int removedForUser) {
+ final int[] userIds = (removedForUser == UserHandle.USER_ALL) ? sUserManager.getUserIds()
+ : new int[] {removedForUser};
+ for (int userId : userIds) {
+ List<String> affectedPackages = new ArrayList<>();
+ synchronized (mPackages) {
+ for (PackageSetting ps : mSettings.mPackages.values()) {
+ final PackageUserState pus = ps.readUserState(userId);
if (pus.suspended && packageName.equals(pus.suspendingPackage)) {
- ps.setSuspended(false, null, null, null, null, user);
+ ps.setSuspended(false, null, null, null, null, userId);
+ affectedPackages.add(ps.name);
}
}
}
+ if (!affectedPackages.isEmpty()) {
+ final String[] packageArray = affectedPackages.toArray(
+ new String[affectedPackages.size()]);
+ sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId);
+ sendPackagesSuspendedForUser(packageArray, userId, false, null);
+ }
}
}
@@ -23639,13 +23613,6 @@
}
@Override
- public Object getPackageSetting(String packageName) {
- synchronized (mPackages) {
- return mSettings.getPackageLPr(packageName);
- }
- }
-
- @Override
public PackageList getPackageList(PackageListObserver observer) {
synchronized (mPackages) {
final int N = mPackages.size();
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index fd4c5e9..138594c 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,8 +20,6 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
import android.content.pm.ApplicationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 407ceb7..b6b94f5 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -52,6 +52,7 @@
uidFlags = _pkgFlags;
uidPrivateFlags = _pkgPrivateFlags;
name = _name;
+ seInfoTargetSdkVersion = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
}
@Override
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index eeaa333..8c7871f 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -21,6 +21,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -164,12 +165,13 @@
ShortcutService s, String packageName, @UserIdInt int packageUserId) {
final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
// retrieve the newest sigs
- Signature[][] signingHistory = pi.signingCertificateHistory;
- if (signingHistory == null || signingHistory.length == 0) {
+ SigningInfo signingInfo = pi.signingInfo;
+ if (signingInfo == null) {
Slog.e(TAG, "Can't get signatures: package=" + packageName);
return null;
}
- Signature[] signatures = signingHistory[signingHistory.length - 1];
+ // TODO (b/73988180) use entire signing history in case of rollbacks
+ Signature[] signatures = signingInfo.getApkContentsSigners();
final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.getLongVersionCode(),
pi.lastUpdateTime, BackupUtils.hashSignatureArray(signatures), /* shadow=*/ false);
@@ -192,13 +194,14 @@
return;
}
// retrieve the newest sigs
- Signature[][] signingHistory = pi.signingCertificateHistory;
- if (signingHistory == null || signingHistory.length == 0) {
+ SigningInfo signingInfo = pi.signingInfo;
+ if (signingInfo == null) {
Slog.w(TAG, "Not refreshing signature for " + pkg.getPackageName()
- + " since it appears to have no signature history.");
+ + " since it appears to have no signing info.");
return;
}
- Signature[] signatures = signingHistory[signingHistory.length - 1];
+ // TODO (b/73988180) use entire signing history in case of rollbacks
+ Signature[] signatures = signingInfo.getApkContentsSigners();
mSigHashes = BackupUtils.hashSignatureArray(signatures);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 065133b..f5b52fc 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -212,11 +212,12 @@
return PackageManager.PERMISSION_DENIED;
}
- final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(pkgName);
- if (ps != null && ps.getPackage() != null) {
- if (mPackageManagerInt.filterAppAccess(ps.getPackage(), callingUid, userId)) {
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+ if (pkg != null && pkg.mExtras != null) {
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
return PackageManager.PERMISSION_DENIED;
}
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
final boolean instantApp = ps.getInstantApp(userId);
final PermissionsState permissionsState = ps.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
diff --git a/services/core/java/com/android/server/slice/DirtyTracker.java b/services/core/java/com/android/server/slice/DirtyTracker.java
new file mode 100644
index 0000000..4288edc
--- /dev/null
+++ b/services/core/java/com/android/server/slice/DirtyTracker.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 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.slice;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * A parent object that cares when a Persistable changes and will schedule a serialization
+ * in response to the onPersistableDirty callback.
+ */
+public interface DirtyTracker {
+ void onPersistableDirty(Persistable obj);
+
+ /**
+ * An object that can be written to XML.
+ */
+ interface Persistable {
+ String getFileName();
+ void writeTo(XmlSerializer out) throws IOException;
+ }
+}
diff --git a/services/core/java/com/android/server/slice/SliceClientPermissions.java b/services/core/java/com/android/server/slice/SliceClientPermissions.java
new file mode 100644
index 0000000..e461e0d
--- /dev/null
+++ b/services/core/java/com/android/server/slice/SliceClientPermissions.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2018 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.slice;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.server.slice.DirtyTracker.Persistable;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class SliceClientPermissions implements DirtyTracker, Persistable {
+
+ private static final String TAG = "SliceClientPermissions";
+
+ static final String TAG_CLIENT = "client";
+ private static final String TAG_AUTHORITY = "authority";
+ private static final String TAG_PATH = "path";
+ private static final String NAMESPACE = null;
+
+ private static final String ATTR_PKG = "pkg";
+ private static final String ATTR_AUTHORITY = "authority";
+ private static final String ATTR_FULL_ACCESS = "fullAccess";
+
+ private final PkgUser mPkg;
+ // Keyed off (authority, userId) rather than the standard (pkg, userId)
+ private final ArrayMap<PkgUser, SliceAuthority> mAuths = new ArrayMap<>();
+ private final DirtyTracker mTracker;
+ private boolean mHasFullAccess;
+
+ public SliceClientPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
+ mPkg = pkg;
+ mTracker = tracker;
+ }
+
+ public PkgUser getPkg() {
+ return mPkg;
+ }
+
+ public synchronized Collection<SliceAuthority> getAuthorities() {
+ return new ArrayList<>(mAuths.values());
+ }
+
+ public synchronized SliceAuthority getOrCreateAuthority(PkgUser authority, PkgUser provider) {
+ SliceAuthority ret = mAuths.get(authority);
+ if (ret == null) {
+ ret = new SliceAuthority(authority.getPkg(), provider, this);
+ mAuths.put(authority, ret);
+ onPersistableDirty(ret);
+ }
+ return ret;
+ }
+
+ public synchronized SliceAuthority getAuthority(PkgUser authority) {
+ return mAuths.get(authority);
+ }
+
+ public boolean hasFullAccess() {
+ return mHasFullAccess;
+ }
+
+ public void setHasFullAccess(boolean hasFullAccess) {
+ if (mHasFullAccess == hasFullAccess) return;
+ mHasFullAccess = hasFullAccess;
+ mTracker.onPersistableDirty(this);
+ }
+
+ public void removeAuthority(String authority, int userId) {
+ if (mAuths.remove(new PkgUser(authority, userId)) != null) {
+ mTracker.onPersistableDirty(this);
+ }
+ }
+
+ public synchronized boolean hasPermission(Uri uri, int userId) {
+ if (!Objects.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())) return false;
+ SliceAuthority authority = getAuthority(new PkgUser(uri.getAuthority(), userId));
+ return authority != null && authority.hasPermission(uri.getPathSegments());
+ }
+
+ public void grantUri(Uri uri, PkgUser providerPkg) {
+ SliceAuthority authority = getOrCreateAuthority(
+ new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
+ providerPkg);
+ authority.addPath(uri.getPathSegments());
+ }
+
+ public void revokeUri(Uri uri, PkgUser providerPkg) {
+ SliceAuthority authority = getOrCreateAuthority(
+ new PkgUser(uri.getAuthority(), providerPkg.getUserId()),
+ providerPkg);
+ authority.removePath(uri.getPathSegments());
+ }
+
+ public void clear() {
+ if (!mHasFullAccess && mAuths.isEmpty()) return;
+ mHasFullAccess = false;
+ mAuths.clear();
+ onPersistableDirty(this);
+ }
+
+ @Override
+ public void onPersistableDirty(Persistable obj) {
+ mTracker.onPersistableDirty(this);
+ }
+
+ @Override
+ public String getFileName() {
+ return getFileName(mPkg);
+ }
+
+ public synchronized void writeTo(XmlSerializer out) throws IOException {
+ out.startTag(NAMESPACE, TAG_CLIENT);
+ out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
+ out.attribute(NAMESPACE, ATTR_FULL_ACCESS, mHasFullAccess ? "1" : "0");
+
+ final int N = mAuths.size();
+ for (int i = 0; i < N; i++) {
+ out.startTag(NAMESPACE, TAG_AUTHORITY);
+ out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
+ out.attribute(NAMESPACE, ATTR_PKG, mAuths.valueAt(i).mPkg.toString());
+
+ mAuths.valueAt(i).writeTo(out);
+
+ out.endTag(NAMESPACE, TAG_AUTHORITY);
+ }
+
+ out.endTag(NAMESPACE, TAG_CLIENT);
+ }
+
+ public static SliceClientPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
+ throws XmlPullParserException, IOException {
+ // Get to the beginning of the provider.
+ while (parser.getEventType() != XmlPullParser.START_TAG
+ || !TAG_CLIENT.equals(parser.getName())) {
+ parser.next();
+ }
+ int depth = parser.getDepth();
+ PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
+ SliceClientPermissions provider = new SliceClientPermissions(pkgUser, tracker);
+ String fullAccess = parser.getAttributeValue(NAMESPACE, ATTR_FULL_ACCESS);
+ if (fullAccess == null) {
+ fullAccess = "0";
+ }
+ provider.mHasFullAccess = Integer.parseInt(fullAccess) != 0;
+ parser.next();
+
+ while (parser.getDepth() > depth) {
+ if (parser.getEventType() == XmlPullParser.START_TAG
+ && TAG_AUTHORITY.equals(parser.getName())) {
+ try {
+ PkgUser pkg = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
+ SliceAuthority authority = new SliceAuthority(
+ parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), pkg, provider);
+ authority.readFrom(parser);
+ provider.mAuths.put(new PkgUser(authority.getAuthority(), pkg.getUserId()),
+ authority);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Couldn't read PkgUser", e);
+ }
+ }
+
+ parser.next();
+ }
+ return provider;
+ }
+
+ public static String getFileName(PkgUser pkg) {
+ return String.format("client_%s", pkg.toString());
+ }
+
+ public static class SliceAuthority implements Persistable {
+ public static final String DELIMITER = "/";
+ private final String mAuthority;
+ private final DirtyTracker mTracker;
+ private final PkgUser mPkg;
+ private final ArraySet<String[]> mPaths = new ArraySet<>();
+
+ public SliceAuthority(String authority, PkgUser pkg, DirtyTracker tracker) {
+ mAuthority = authority;
+ mPkg = pkg;
+ mTracker = tracker;
+ }
+
+ public String getAuthority() {
+ return mAuthority;
+ }
+
+ public PkgUser getPkg() {
+ return mPkg;
+ }
+
+ void addPath(List<String> path) {
+ String[] pathSegs = path.toArray(new String[path.size()]);
+ for (int i = mPaths.size() - 1; i >= 0; i--) {
+ String[] existing = mPaths.valueAt(i);
+ if (isPathPrefixMatch(existing, pathSegs)) {
+ // Nothing to add here.
+ return;
+ }
+ if (isPathPrefixMatch(pathSegs, existing)) {
+ mPaths.removeAt(i);
+ }
+ }
+ mPaths.add(pathSegs);
+ mTracker.onPersistableDirty(this);
+ }
+
+ void removePath(List<String> path) {
+ boolean changed = false;
+ String[] pathSegs = path.toArray(new String[path.size()]);
+ for (int i = mPaths.size() - 1; i >= 0; i--) {
+ String[] existing = mPaths.valueAt(i);
+ if (isPathPrefixMatch(pathSegs, existing)) {
+ changed = true;
+ mPaths.removeAt(i);
+ }
+ }
+ if (changed) {
+ mTracker.onPersistableDirty(this);
+ }
+ }
+
+ public synchronized Collection<String[]> getPaths() {
+ return new ArraySet<>(mPaths);
+ }
+
+ public boolean hasPermission(List<String> path) {
+ for (String[] p : mPaths) {
+ if (isPathPrefixMatch(p, path.toArray(new String[path.size()]))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isPathPrefixMatch(String[] prefix, String[] path) {
+ final int prefixSize = prefix.length;
+ if (path.length < prefixSize) return false;
+
+ for (int i = 0; i < prefixSize; i++) {
+ if (!Objects.equals(path[i], prefix[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public String getFileName() {
+ return null;
+ }
+
+ public synchronized void writeTo(XmlSerializer out) throws IOException {
+ final int N = mPaths.size();
+ for (int i = 0; i < N; i++) {
+ out.startTag(NAMESPACE, TAG_PATH);
+ out.text(encodeSegments(mPaths.valueAt(i)));
+ out.endTag(NAMESPACE, TAG_PATH);
+ }
+ }
+
+ public synchronized void readFrom(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ int depth = parser.getDepth();
+ while (parser.getDepth() >= depth) {
+ if (parser.getEventType() == XmlPullParser.START_TAG
+ && TAG_PATH.equals(parser.getName())) {
+ mPaths.add(decodeSegments(parser.nextText()));
+ }
+ parser.next();
+ }
+ }
+
+ private String encodeSegments(String[] s) {
+ String[] out = new String[s.length];
+ for (int i = 0; i < s.length; i++) {
+ out[i] = Uri.encode(s[i]);
+ }
+ return TextUtils.join(DELIMITER, out);
+ }
+
+ private String[] decodeSegments(String s) {
+ String[] sets = s.split(DELIMITER, -1);
+ for (int i = 0; i < sets.length; i++) {
+ sets[i] = Uri.decode(sets[i]);
+ }
+ return sets;
+ }
+
+ /**
+ * Only for testing, no deep equality of these are done normally.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
+ SliceAuthority other = (SliceAuthority) obj;
+ if (mPaths.size() != other.mPaths.size()) return false;
+ ArrayList<String[]> p1 = new ArrayList<>(mPaths);
+ ArrayList<String[]> p2 = new ArrayList<>(other.mPaths);
+ p1.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
+ p2.sort(Comparator.comparing(o -> TextUtils.join(",", o)));
+ for (int i = 0; i < p1.size(); i++) {
+ String[] a1 = p1.get(i);
+ String[] a2 = p2.get(i);
+ if (a1.length != a2.length) return false;
+ for (int j = 0; j < a1.length; j++) {
+ if (!Objects.equals(a1[j], a2[j])) return false;
+ }
+ }
+ return Objects.equals(mAuthority, other.mAuthority)
+ && Objects.equals(mPkg, other.mPkg);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%s, %s: %s)", mAuthority, mPkg.toString(), pathToString(mPaths));
+ }
+
+ private String pathToString(ArraySet<String[]> paths) {
+ return TextUtils.join(", ", paths.stream().map(s -> TextUtils.join("/", s))
+ .collect(Collectors.toList()));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index fd0b6f1..b7b9612 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -31,14 +31,15 @@
import android.app.ContentProviderHolder;
import android.app.IActivityManager;
import android.app.slice.ISliceManager;
-import android.app.slice.SliceManager;
import android.app.slice.SliceSpec;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.net.Uri;
@@ -51,7 +52,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml.Encoding;
@@ -72,7 +72,6 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -91,13 +90,9 @@
@GuardedBy("mLock")
private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
- @GuardedBy("mLock")
- private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>();
private final Handler mHandler;
- @GuardedBy("mSliceAccessFile")
- private final AtomicFile mSliceAccessFile;
- @GuardedBy("mAccessList")
- private final SliceFullAccessList mAccessList;
+
+ private final SlicePermissionManager mPermissions;
private final UsageStatsManagerInternal mAppUsageStats;
public SliceManagerService(Context context) {
@@ -113,24 +108,9 @@
mAssistUtils = new AssistUtils(context);
mHandler = new Handler(looper);
- final File systemDir = new File(Environment.getDataDirectory(), "system");
- mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml"));
- mAccessList = new SliceFullAccessList(mContext);
mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- synchronized (mSliceAccessFile) {
- if (!mSliceAccessFile.exists()) return;
- try {
- InputStream input = mSliceAccessFile.openRead();
- XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
- parser.setInput(input, Encoding.UTF_8.name());
- synchronized (mAccessList) {
- mAccessList.readXml(parser);
- }
- } catch (IOException | XmlPullParserException e) {
- Slog.d(TAG, "Can't read slice access file", e);
- }
- }
+ mPermissions = new SlicePermissionManager(mContext, looper);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
@@ -211,26 +191,58 @@
}
@Override
+ public void grantSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException {
+ verifyCaller(pkg);
+ int user = Binder.getCallingUserHandle().getIdentifier();
+ enforceOwner(pkg, uri, user);
+ mPermissions.grantSliceAccess(toPkg, user, pkg, user, uri);
+ }
+
+ @Override
+ public void revokeSlicePermission(String pkg, String toPkg, Uri uri) throws RemoteException {
+ verifyCaller(pkg);
+ int user = Binder.getCallingUserHandle().getIdentifier();
+ enforceOwner(pkg, uri, user);
+ mPermissions.revokeSliceAccess(toPkg, user, pkg, user, uri);
+ }
+
+ @Override
public int checkSlicePermission(Uri uri, String pkg, int pid, int uid,
- String[] autoGrantPermissions) throws RemoteException {
+ String[] autoGrantPermissions) {
+ int userId = UserHandle.getUserId(uid);
+ if (pkg == null) {
+ for (String p : mContext.getPackageManager().getPackagesForUid(uid)) {
+ if (checkSlicePermission(uri, p, pid, uid, autoGrantPermissions)
+ == PERMISSION_GRANTED) {
+ return PERMISSION_GRANTED;
+ }
+ }
+ return PERMISSION_DENIED;
+ }
+ if (hasFullSliceAccess(pkg, userId)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ if (mPermissions.hasPermission(pkg, userId, uri)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ if (autoGrantPermissions != null) {
+ // Need to own the Uri to call in with permissions to grant.
+ enforceOwner(pkg, uri, userId);
+ for (String perm : autoGrantPermissions) {
+ if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) {
+ int providerUser = ContentProvider.getUserIdFromUri(uri, userId);
+ String providerPkg = getProviderPkg(uri, providerUser);
+ mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, uri);
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ }
+ // Fallback to allowing uri permissions through.
if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
== PERMISSION_GRANTED) {
- return SliceManager.PERMISSION_GRANTED;
+ return PackageManager.PERMISSION_GRANTED;
}
- if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) {
- return SliceManager.PERMISSION_GRANTED;
- }
- for (String perm : autoGrantPermissions) {
- if (mContext.checkPermission(perm, pid, uid) == PERMISSION_GRANTED) {
- return SliceManager.PERMISSION_USER_GRANTED;
- }
- }
- synchronized (mLock) {
- if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) {
- return SliceManager.PERMISSION_USER_GRANTED;
- }
- }
- return SliceManager.PERMISSION_DENIED;
+ return PackageManager.PERMISSION_DENIED;
}
@Override
@@ -238,16 +250,17 @@
verifyCaller(callingPkg);
getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS,
"Slice granting requires MANAGE_SLICE_PERMISSIONS");
+ int userId = Binder.getCallingUserHandle().getIdentifier();
if (allSlices) {
- synchronized (mAccessList) {
- mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier());
- }
- mHandler.post(mSaveAccessList);
+ mPermissions.grantFullAccess(pkg, userId);
} else {
- synchronized (mLock) {
- mUserGrants.add(new SliceGrant(uri, pkg,
- Binder.getCallingUserHandle().getIdentifier()));
- }
+ // When granting, grant to all slices in the provider.
+ Uri grantUri = uri.buildUpon()
+ .path("")
+ .build();
+ int providerUser = ContentProvider.getUserIdFromUri(grantUri, userId);
+ String providerPkg = getProviderPkg(grantUri, providerUser);
+ mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, grantUri);
}
long ident = Binder.clearCallingIdentity();
try {
@@ -268,19 +281,17 @@
Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
return null;
}
- synchronized(mSliceAccessFile) {
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
- out.setOutput(baos, Encoding.UTF_8.name());
- synchronized (mAccessList) {
- mAccessList.writeXml(out, user);
- }
- out.flush();
- return baos.toByteArray();
- } catch (IOException | XmlPullParserException e) {
- Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
- }
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
+ out.setOutput(baos, Encoding.UTF_8.name());
+
+ mPermissions.writeBackup(out);
+
+ out.flush();
+ return baos.toByteArray();
+ } catch (IOException | XmlPullParserException e) {
+ Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
}
return null;
}
@@ -299,27 +310,21 @@
Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
return;
}
- synchronized(mSliceAccessFile) {
- final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
- try {
- XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
- parser.setInput(bais, Encoding.UTF_8.name());
- synchronized (mAccessList) {
- mAccessList.readXml(parser);
- }
- mHandler.post(mSaveAccessList);
- } catch (NumberFormatException | XmlPullParserException | IOException e) {
- Slog.w(TAG, "applyRestore: error reading payload", e);
- }
+ final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+ try {
+ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+ parser.setInput(bais, Encoding.UTF_8.name());
+ mPermissions.readRestore(parser);
+ } catch (NumberFormatException | XmlPullParserException | IOException e) {
+ Slog.w(TAG, "applyRestore: error reading payload", e);
}
}
/// ----- internal code -----
- private void removeFullAccess(String pkg, int userId) {
- synchronized (mAccessList) {
- mAccessList.removeGrant(pkg, userId);
+ private void enforceOwner(String pkg, Uri uri, int user) {
+ if (!Objects.equals(getProviderPkg(uri, user), pkg) || pkg == null) {
+ throw new SecurityException("Caller must own " + uri);
}
- mHandler.post(mSaveAccessList);
}
protected void removePinnedSlice(Uri uri) {
@@ -368,19 +373,7 @@
}
protected int checkAccess(String pkg, Uri uri, int uid, int pid) {
- int user = UserHandle.getUserId(uid);
- // Check for default launcher/assistant.
- if (!hasFullSliceAccess(pkg, user)) {
- // Also allow things with uri access.
- if (getContext().checkUriPermission(uri, pid, uid,
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) {
- // Last fallback (if the calling app owns the authority, then it can have access).
- if (!Objects.equals(getProviderPkg(uri, user), pkg)) {
- return PERMISSION_DENIED;
- }
- }
- }
- return PERMISSION_GRANTED;
+ return checkSlicePermission(uri, pkg, uid, pid, null);
}
private String getProviderPkg(Uri uri, int user) {
@@ -425,15 +418,11 @@
private void enforceAccess(String pkg, Uri uri) throws RemoteException {
if (checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingPid())
!= PERMISSION_GRANTED) {
- throw new SecurityException("Access to slice " + uri + " is required");
- }
- enforceCrossUser(pkg, uri);
- }
-
- private void enforceFullAccess(String pkg, String name, Uri uri) {
- int user = Binder.getCallingUserHandle().getIdentifier();
- if (!hasFullSliceAccess(pkg, user)) {
- throw new SecurityException(String.format("Call %s requires full slice access", name));
+ int userId = ContentProvider.getUserIdFromUri(uri,
+ Binder.getCallingUserHandle().getIdentifier());
+ if (!Objects.equals(pkg, getProviderPkg(uri, userId))) {
+ throw new SecurityException("Access to slice " + uri + " is required");
+ }
}
enforceCrossUser(pkg, uri);
}
@@ -513,9 +502,7 @@
}
private boolean isGrantedFullAccess(String pkg, int userId) {
- synchronized (mAccessList) {
- return mAccessList.hasFullAccess(pkg, userId);
- }
+ return mPermissions.hasFullAccess(pkg, userId);
}
private static ServiceThread createHandler() {
@@ -525,34 +512,6 @@
return handlerThread;
}
- private final Runnable mSaveAccessList = new Runnable() {
- @Override
- public void run() {
- synchronized (mSliceAccessFile) {
- final FileOutputStream stream;
- try {
- stream = mSliceAccessFile.startWrite();
- } catch (IOException e) {
- Slog.w(TAG, "Failed to save access file", e);
- return;
- }
-
- try {
- XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
- out.setOutput(stream, Encoding.UTF_8.name());
- synchronized (mAccessList) {
- mAccessList.writeXml(out, UserHandle.USER_ALL);
- }
- out.flush();
- mSliceAccessFile.finishWrite(stream);
- } catch (IOException | XmlPullParserException e) {
- Slog.w(TAG, "Failed to save access file, restoring backup", e);
- mSliceAccessFile.failWrite(stream);
- }
- }
- }
- };
-
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -572,11 +531,11 @@
final boolean replacing =
intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (!replacing) {
- removeFullAccess(pkg, userId);
+ mPermissions.removePkg(pkg, userId);
}
break;
case Intent.ACTION_PACKAGE_DATA_CLEARED:
- removeFullAccess(pkg, userId);
+ mPermissions.removePkg(pkg, userId);
break;
}
}
diff --git a/services/core/java/com/android/server/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java
new file mode 100644
index 0000000..d25ec89
--- /dev/null
+++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2018 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.slice;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml.Encoding;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+
+public class SlicePermissionManager implements DirtyTracker {
+
+ private static final String TAG = "SlicePermissionManager";
+
+ /**
+ * The amount of time we'll cache a SliceProviderPermissions or SliceClientPermissions
+ * in case they are used again.
+ */
+ private static final long PERMISSION_CACHE_PERIOD = 5 * DateUtils.MINUTE_IN_MILLIS;
+
+ /**
+ * The amount of time we delay flushing out permission changes to disk because they usually
+ * come in short bursts.
+ */
+ private static final long WRITE_GRACE_PERIOD = 500;
+
+ private static final String SLICE_DIR = "slice";
+
+ // If/when this bumps again we'll need to write it out in the disk somewhere.
+ // Currently we don't have a central file for this in version 2 and there is no
+ // reason to add one until we actually have incompatible version bumps.
+ // This does however block us from reading backups from P-DP1 which may contain
+ // a very different XML format for perms.
+ static final int DB_VERSION = 2;
+
+ private static final String TAG_LIST = "slice-access-list";
+ private final String ATT_VERSION = "version";
+
+ private final File mSliceDir;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>();
+ private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>();
+ private final ArraySet<Persistable> mDirty = new ArraySet<>();
+
+ @VisibleForTesting
+ SlicePermissionManager(Context context, Looper looper, File sliceDir) {
+ mContext = context;
+ mHandler = new H(looper);
+ mSliceDir = sliceDir;
+ }
+
+ public SlicePermissionManager(Context context, Looper looper) {
+ this(context, looper, new File(Environment.getDataDirectory(), "system/" + SLICE_DIR));
+ }
+
+ public void grantFullAccess(String pkg, int userId) {
+ PkgUser pkgUser = new PkgUser(pkg, userId);
+ SliceClientPermissions client = getClient(pkgUser);
+ client.setHasFullAccess(true);
+ }
+
+ public void grantSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
+ Uri uri) {
+ PkgUser pkgUser = new PkgUser(pkg, userId);
+ PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
+
+ SliceClientPermissions client = getClient(pkgUser);
+ client.grantUri(uri, providerPkgUser);
+
+ SliceProviderPermissions provider = getProvider(providerPkgUser);
+ provider.getOrCreateAuthority(ContentProvider.getUriWithoutUserId(uri).getAuthority())
+ .addPkg(pkgUser);
+ }
+
+ public void revokeSliceAccess(String pkg, int userId, String providerPkg, int providerUser,
+ Uri uri) {
+ PkgUser pkgUser = new PkgUser(pkg, userId);
+ PkgUser providerPkgUser = new PkgUser(providerPkg, providerUser);
+
+ SliceClientPermissions client = getClient(pkgUser);
+ client.revokeUri(uri, providerPkgUser);
+ }
+
+ public void removePkg(String pkg, int userId) {
+ PkgUser pkgUser = new PkgUser(pkg, userId);
+ SliceProviderPermissions provider = getProvider(pkgUser);
+
+ for (SliceAuthority authority : provider.getAuthorities()) {
+ for (PkgUser p : authority.getPkgs()) {
+ getClient(p).removeAuthority(authority.getAuthority(), userId);
+ }
+ }
+ SliceClientPermissions client = getClient(pkgUser);
+ client.clear();
+ mHandler.obtainMessage(H.MSG_REMOVE, pkgUser);
+ }
+
+ public boolean hasFullAccess(String pkg, int userId) {
+ PkgUser pkgUser = new PkgUser(pkg, userId);
+ return getClient(pkgUser).hasFullAccess();
+ }
+
+ public boolean hasPermission(String pkg, int userId, Uri uri) {
+ PkgUser pkgUser = new PkgUser(pkg, userId);
+ SliceClientPermissions client = getClient(pkgUser);
+ int providerUserId = ContentProvider.getUserIdFromUri(uri, userId);
+ return client.hasFullAccess()
+ || client.hasPermission(ContentProvider.getUriWithoutUserId(uri), providerUserId);
+ }
+
+ @Override
+ public void onPersistableDirty(Persistable obj) {
+ mHandler.removeMessages(H.MSG_PERSIST);
+ mHandler.obtainMessage(H.MSG_ADD_DIRTY, obj).sendToTarget();
+ mHandler.sendEmptyMessageDelayed(H.MSG_PERSIST, WRITE_GRACE_PERIOD);
+ }
+
+ public void writeBackup(XmlSerializer out) throws IOException, XmlPullParserException {
+ synchronized (this) {
+ out.startTag(null, TAG_LIST);
+ out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
+
+ // Don't do anything with changes from the backup, because there shouldn't be any.
+ DirtyTracker tracker = obj -> { };
+ if (mHandler.hasMessages(H.MSG_PERSIST)) {
+ mHandler.removeMessages(H.MSG_PERSIST);
+ handlePersist();
+ }
+ for (String file : new File(mSliceDir.getAbsolutePath()).list()) {
+ if (file.isEmpty()) continue;
+ try (ParserHolder parser = getParser(file)) {
+ Persistable p;
+ while (parser.parser.getEventType() != XmlPullParser.START_TAG) {
+ parser.parser.next();
+ }
+ if (SliceClientPermissions.TAG_CLIENT.equals(parser.parser.getName())) {
+ p = SliceClientPermissions.createFrom(parser.parser, tracker);
+ } else {
+ p = SliceProviderPermissions.createFrom(parser.parser, tracker);
+ }
+ p.writeTo(out);
+ }
+ }
+
+ out.endTag(null, TAG_LIST);
+ }
+ }
+
+ public void readRestore(XmlPullParser parser) throws IOException, XmlPullParserException {
+ synchronized (this) {
+ while ((parser.getEventType() != XmlPullParser.START_TAG
+ || !TAG_LIST.equals(parser.getName()))
+ && parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ parser.next();
+ }
+ int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
+ if (xmlVersion < DB_VERSION) {
+ // No conversion support right now.
+ return;
+ }
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if (parser.getEventType() == XmlPullParser.START_TAG) {
+ if (SliceClientPermissions.TAG_CLIENT.equals(parser.getName())) {
+ SliceClientPermissions client = SliceClientPermissions.createFrom(parser,
+ this);
+ synchronized (mCachedClients) {
+ mCachedClients.put(client.getPkg(), client);
+ }
+ onPersistableDirty(client);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, client.getPkg()),
+ PERMISSION_CACHE_PERIOD);
+ } else if (SliceProviderPermissions.TAG_PROVIDER.equals(parser.getName())) {
+ SliceProviderPermissions provider = SliceProviderPermissions.createFrom(
+ parser, this);
+ synchronized (mCachedProviders) {
+ mCachedProviders.put(provider.getPkg(), provider);
+ }
+ onPersistableDirty(provider);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, provider.getPkg()),
+ PERMISSION_CACHE_PERIOD);
+ } else {
+ parser.next();
+ }
+ } else {
+ parser.next();
+ }
+ }
+ }
+ }
+
+ private SliceClientPermissions getClient(PkgUser pkgUser) {
+ SliceClientPermissions client;
+ synchronized (mCachedClients) {
+ client = mCachedClients.get(pkgUser);
+ }
+ if (client == null) {
+ try (ParserHolder parser = getParser(SliceClientPermissions.getFileName(pkgUser))) {
+ client = SliceClientPermissions.createFrom(parser.parser, this);
+ synchronized (mCachedClients) {
+ mCachedClients.put(pkgUser, client);
+ }
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_CLIENT, pkgUser),
+ PERMISSION_CACHE_PERIOD);
+ return client;
+ } catch (FileNotFoundException e) {
+ // No client exists yet.
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read client", e);
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Can't read client", e);
+ }
+ // Can't read or no permissions exist, create a clean object.
+ client = new SliceClientPermissions(pkgUser, this);
+ }
+ return client;
+ }
+
+ private SliceProviderPermissions getProvider(PkgUser pkgUser) {
+ SliceProviderPermissions provider;
+ synchronized (mCachedProviders) {
+ provider = mCachedProviders.get(pkgUser);
+ }
+ if (provider == null) {
+ try (ParserHolder parser = getParser(SliceProviderPermissions.getFileName(pkgUser))) {
+ provider = SliceProviderPermissions.createFrom(parser.parser, this);
+ synchronized (mCachedProviders) {
+ mCachedProviders.put(pkgUser, provider);
+ }
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.MSG_CLEAR_PROVIDER, pkgUser),
+ PERMISSION_CACHE_PERIOD);
+ return provider;
+ } catch (FileNotFoundException e) {
+ // No provider exists yet.
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read provider", e);
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Can't read provider", e);
+ }
+ // Can't read or no permissions exist, create a clean object.
+ provider = new SliceProviderPermissions(pkgUser, this);
+ }
+ return provider;
+ }
+
+ private ParserHolder getParser(String fileName)
+ throws FileNotFoundException, XmlPullParserException {
+ AtomicFile file = getFile(fileName);
+ ParserHolder holder = new ParserHolder();
+ holder.input = file.openRead();
+ holder.parser = XmlPullParserFactory.newInstance().newPullParser();
+ holder.parser.setInput(holder.input, Encoding.UTF_8.name());
+ return holder;
+ }
+
+ private AtomicFile getFile(String fileName) {
+ if (!mSliceDir.exists()) {
+ mSliceDir.mkdir();
+ }
+ return new AtomicFile(new File(mSliceDir, fileName));
+ }
+
+ private void handlePersist() {
+ synchronized (this) {
+ for (Persistable persistable : mDirty) {
+ AtomicFile file = getFile(persistable.getFileName());
+ final FileOutputStream stream;
+ try {
+ stream = file.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save access file", e);
+ return;
+ }
+
+ try {
+ XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
+ out.setOutput(stream, Encoding.UTF_8.name());
+
+ persistable.writeTo(out);
+
+ out.flush();
+ file.finishWrite(stream);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.w(TAG, "Failed to save access file, restoring backup", e);
+ file.failWrite(stream);
+ }
+ }
+ mDirty.clear();
+ }
+ }
+
+ private void handleRemove(PkgUser pkgUser) {
+ getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
+ getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
+ mDirty.remove(mCachedClients.remove(pkgUser));
+ mDirty.remove(mCachedProviders.remove(pkgUser));
+ }
+
+ private final class H extends Handler {
+ private static final int MSG_ADD_DIRTY = 1;
+ private static final int MSG_PERSIST = 2;
+ private static final int MSG_REMOVE = 3;
+ private static final int MSG_CLEAR_CLIENT = 4;
+ private static final int MSG_CLEAR_PROVIDER = 5;
+
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ADD_DIRTY:
+ mDirty.add((Persistable) msg.obj);
+ break;
+ case MSG_PERSIST:
+ handlePersist();
+ break;
+ case MSG_REMOVE:
+ handleRemove((PkgUser) msg.obj);
+ break;
+ case MSG_CLEAR_CLIENT:
+ synchronized (mCachedClients) {
+ mCachedClients.remove(msg.obj);
+ }
+ break;
+ case MSG_CLEAR_PROVIDER:
+ synchronized (mCachedProviders) {
+ mCachedProviders.remove(msg.obj);
+ }
+ break;
+ }
+ }
+ }
+
+ public static class PkgUser {
+ private static final String SEPARATOR = "@";
+ private static final String FORMAT = "%s" + SEPARATOR + "%d";
+ private final String mPkg;
+ private final int mUserId;
+
+ public PkgUser(String pkg, int userId) {
+ mPkg = pkg;
+ mUserId = userId;
+ }
+
+ public PkgUser(String pkgUserStr) throws IllegalArgumentException {
+ try {
+ String[] vals = pkgUserStr.split(SEPARATOR, 2);
+ mPkg = vals[0];
+ mUserId = Integer.parseInt(vals[1]);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public String getPkg() {
+ return mPkg;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPkg.hashCode() + mUserId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
+ PkgUser other = (PkgUser) obj;
+ return Objects.equals(other.mPkg, mPkg) && other.mUserId == mUserId;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(FORMAT, mPkg, mUserId);
+ }
+ }
+
+ private class ParserHolder implements AutoCloseable {
+
+ private InputStream input;
+ private XmlPullParser parser;
+
+ @Override
+ public void close() throws IOException {
+ input.close();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/slice/SliceProviderPermissions.java b/services/core/java/com/android/server/slice/SliceProviderPermissions.java
new file mode 100644
index 0000000..6e602d5
--- /dev/null
+++ b/services/core/java/com/android/server/slice/SliceProviderPermissions.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2018 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.slice;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.server.slice.DirtyTracker.Persistable;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Objects;
+
+public class SliceProviderPermissions implements DirtyTracker, Persistable {
+
+ private static final String TAG = "SliceProviderPermissions";
+
+ static final String TAG_PROVIDER = "provider";
+ private static final String TAG_AUTHORITY = "authority";
+ private static final String TAG_PKG = "pkg";
+ private static final String NAMESPACE = null;
+
+ private static final String ATTR_PKG = "pkg";
+ private static final String ATTR_AUTHORITY = "authority";
+
+ private final PkgUser mPkg;
+ private final ArrayMap<String, SliceAuthority> mAuths = new ArrayMap<>();
+ private final DirtyTracker mTracker;
+
+ public SliceProviderPermissions(@NonNull PkgUser pkg, @NonNull DirtyTracker tracker) {
+ mPkg = pkg;
+ mTracker = tracker;
+ }
+
+ public PkgUser getPkg() {
+ return mPkg;
+ }
+
+ public synchronized Collection<SliceAuthority> getAuthorities() {
+ return new ArrayList<>(mAuths.values());
+ }
+
+ public synchronized SliceAuthority getOrCreateAuthority(String authority) {
+ SliceAuthority ret = mAuths.get(authority);
+ if (ret == null) {
+ ret = new SliceAuthority(authority, this);
+ mAuths.put(authority, ret);
+ onPersistableDirty(ret);
+ }
+ return ret;
+ }
+
+ @Override
+ public void onPersistableDirty(Persistable obj) {
+ mTracker.onPersistableDirty(this);
+ }
+
+ @Override
+ public String getFileName() {
+ return getFileName(mPkg);
+ }
+
+ public synchronized void writeTo(XmlSerializer out) throws IOException {
+ out.startTag(NAMESPACE, TAG_PROVIDER);
+ out.attribute(NAMESPACE, ATTR_PKG, mPkg.toString());
+
+ final int N = mAuths.size();
+ for (int i = 0; i < N; i++) {
+ out.startTag(NAMESPACE, TAG_AUTHORITY);
+ out.attribute(NAMESPACE, ATTR_AUTHORITY, mAuths.valueAt(i).mAuthority);
+
+ mAuths.valueAt(i).writeTo(out);
+
+ out.endTag(NAMESPACE, TAG_AUTHORITY);
+ }
+
+ out.endTag(NAMESPACE, TAG_PROVIDER);
+ }
+
+ public static SliceProviderPermissions createFrom(XmlPullParser parser, DirtyTracker tracker)
+ throws XmlPullParserException, IOException {
+ // Get to the beginning of the provider.
+ while (parser.getEventType() != XmlPullParser.START_TAG
+ || !TAG_PROVIDER.equals(parser.getName())) {
+ parser.next();
+ }
+ int depth = parser.getDepth();
+ PkgUser pkgUser = new PkgUser(parser.getAttributeValue(NAMESPACE, ATTR_PKG));
+ SliceProviderPermissions provider = new SliceProviderPermissions(pkgUser, tracker);
+ parser.next();
+
+ while (parser.getDepth() > depth) {
+ if (parser.getEventType() == XmlPullParser.START_TAG
+ && TAG_AUTHORITY.equals(parser.getName())) {
+ try {
+ SliceAuthority authority = new SliceAuthority(
+ parser.getAttributeValue(NAMESPACE, ATTR_AUTHORITY), provider);
+ authority.readFrom(parser);
+ provider.mAuths.put(authority.getAuthority(), authority);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Couldn't read PkgUser", e);
+ }
+ }
+
+ parser.next();
+ }
+ return provider;
+ }
+
+ public static String getFileName(PkgUser pkg) {
+ return String.format("provider_%s", pkg.toString());
+ }
+
+ public static class SliceAuthority implements Persistable {
+ private final String mAuthority;
+ private final DirtyTracker mTracker;
+ private final ArraySet<PkgUser> mPkgs = new ArraySet<>();
+
+ public SliceAuthority(String authority, DirtyTracker tracker) {
+ mAuthority = authority;
+ mTracker = tracker;
+ }
+
+ public String getAuthority() {
+ return mAuthority;
+ }
+
+ public synchronized void addPkg(PkgUser pkg) {
+ if (mPkgs.add(pkg)) {
+ mTracker.onPersistableDirty(this);
+ }
+ }
+
+ public synchronized void removePkg(PkgUser pkg) {
+ if (mPkgs.remove(pkg)) {
+ mTracker.onPersistableDirty(this);
+ }
+ }
+
+ public synchronized Collection<PkgUser> getPkgs() {
+ return new ArraySet<>(mPkgs);
+ }
+
+ @Override
+ public String getFileName() {
+ return null;
+ }
+
+ public synchronized void writeTo(XmlSerializer out) throws IOException {
+ final int N = mPkgs.size();
+ for (int i = 0; i < N; i++) {
+ out.startTag(NAMESPACE, TAG_PKG);
+ out.text(mPkgs.valueAt(i).toString());
+ out.endTag(NAMESPACE, TAG_PKG);
+ }
+ }
+
+ public synchronized void readFrom(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ int depth = parser.getDepth();
+ while (parser.getDepth() >= depth) {
+ if (parser.getEventType() == XmlPullParser.START_TAG
+ && TAG_PKG.equals(parser.getName())) {
+ mPkgs.add(new PkgUser(parser.nextText()));
+ }
+ parser.next();
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!getClass().equals(obj != null ? obj.getClass() : null)) return false;
+ SliceAuthority other = (SliceAuthority) obj;
+ return Objects.equals(mAuthority, other.mAuthority)
+ && Objects.equals(mPkgs, other.mPkgs);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%s: %s)", mAuthority, mPkgs.toString());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 2db8039..36fa868 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1096,6 +1096,30 @@
}
@Override
+ public void onNotificationSmartRepliesAdded(String key, int replyCount)
+ throws RemoteException {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationSmartRepliesAdded(key, replyCount);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onNotificationSmartReplySent(String key, int replyIndex)
+ throws RemoteException {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void onNotificationSettingsViewed(String key) throws RemoteException {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 9a4db65..5676f58 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1312,7 +1312,8 @@
if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) {
// Entering PiP from fullscreen, reset the snap fraction
mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
- } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED) {
+ } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
+ && !isHidden()) {
// Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
// for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index bae93f3..1ee642a 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -17,19 +17,16 @@
package com.android.server.wm;
import static android.app.ActivityManagerInternal.APP_TRANSITION_RECENTS_ANIM;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
-import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
import android.annotation.IntDef;
import android.app.ActivityManager.TaskSnapshot;
@@ -41,24 +38,22 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;import android.util.proto.ProtoOutputStream;
+import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-
import com.android.internal.annotations.VisibleForTesting;
-import com.google.android.collect.Sets;
-
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import com.android.server.wm.utils.InsetUtils;
-
+import com.google.android.collect.Sets;
import java.io.PrintWriter;
import java.util.ArrayList;
+
/**
* Controls a single instance of the remote driven recents animation. In particular, this allows
* the calling SystemUI to animate the visible task windows as a part of the transition. The remote
@@ -67,8 +62,7 @@
* app if it requires the animation to be canceled at any time (ie. due to timeout, etc.)
*/
public class RecentsAnimationController implements DeathRecipient {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentsAnimationController" : TAG_WM;
- private static final boolean DEBUG = false;
+ private static final String TAG = RecentsAnimationController.class.getSimpleName();
private static final long FAILSAFE_DELAY = 1000;
public static final int REORDER_KEEP_IN_PLACE = 0;
@@ -88,7 +82,7 @@
private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
private final int mDisplayId;
private final Runnable mFailsafeRunnable = () -> {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
};
// The recents component app token that is shown behind the visibile tasks
@@ -124,7 +118,8 @@
@Override
public TaskSnapshot screenshotTask(int taskId) {
- if (DEBUG) Log.d(TAG, "screenshotTask(" + taskId + "): mCanceled=" + mCanceled);
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "screenshotTask(" + taskId + "):"
+ + " mCanceled=" + mCanceled);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mService.getWindowManagerLock()) {
@@ -153,7 +148,8 @@
@Override
public void finish(boolean moveHomeToTop) {
- if (DEBUG) Log.d(TAG, "finish(" + moveHomeToTop + "): mCanceled=" + mCanceled);
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "finish(" + moveHomeToTop + "):"
+ + " mCanceled=" + mCanceled);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mService.getWindowManagerLock()) {
@@ -190,8 +186,8 @@
@Override
public void setInputConsumerEnabled(boolean enabled) {
- if (DEBUG) Log.d(TAG, "setInputConsumerEnabled(" + enabled + "): mCanceled="
- + mCanceled);
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "setInputConsumerEnabled(" + enabled + "):"
+ + " mCanceled=" + mCanceled);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mService.getWindowManagerLock()) {
@@ -264,14 +260,14 @@
// Skip the animation if there is nothing to animate
if (mPendingAnimations.isEmpty()) {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-noVisibleTasks");
return;
}
try {
linkToDeathOfRunner();
} catch (RemoteException e) {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-failedToLinkToDeath");
return;
}
@@ -279,7 +275,8 @@
final AppWindowToken recentsComponentAppToken = dc.getStack(WINDOWING_MODE_UNDEFINED,
targetActivityType).getTopChild().getTopFullscreenAppToken();
if (recentsComponentAppToken != null) {
- if (DEBUG) Log.d(TAG, "setHomeApp(" + recentsComponentAppToken.getName() + ")");
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "setHomeApp("
+ + recentsComponentAppToken.getName() + ")");
mTargetAppToken = recentsComponentAppToken;
if (recentsComponentAppToken.windowsCanBeWallpaperTarget()) {
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
@@ -295,7 +292,7 @@
@VisibleForTesting
AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
- if (DEBUG) Log.d(TAG, "addAnimation(" + task.getName() + ")");
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "addAnimation(" + task.getName() + ")");
// TODO: Refactor this to use the task's animator
final SurfaceAnimator anim = new SurfaceAnimator(task, null /* animationFinishedCallback */,
mService);
@@ -309,14 +306,15 @@
@VisibleForTesting
void removeAnimation(TaskAnimationAdapter taskAdapter) {
- if (DEBUG) Log.d(TAG, "removeAnimation(" + taskAdapter.mTask.getName() + ")");
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "removeAnimation("
+ + taskAdapter.mTask.mTaskId + ")");
taskAdapter.mTask.setCanAffectSystemUiFlags(true);
taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter);
mPendingAnimations.remove(taskAdapter);
}
void startAnimation() {
- if (DEBUG) Log.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart
+ " mCanceled=" + mCanceled);
if (!mPendingStart || mCanceled) {
// Skip starting if we've already started or canceled the animation
@@ -336,7 +334,7 @@
// Skip the animation if there is nothing to animate
if (appAnimations.isEmpty()) {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
return;
}
@@ -354,6 +352,13 @@
: null;
mRunner.onAnimationStart_New(mController, appTargets, contentInsets,
minimizedHomeBounds);
+ if (DEBUG_RECENTS_ANIMATIONS) {
+ Slog.d(TAG, "startAnimation(): Notify animation start:");
+ for (int i = 0; i < mPendingAnimations.size(); i++) {
+ final Task task = mPendingAnimations.get(i).mTask;
+ Slog.d(TAG, "\t" + task.mTaskId);
+ }
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to start recents animation", e);
}
@@ -363,8 +368,8 @@
reasons).sendToTarget();
}
- void cancelAnimation(@ReorderMode int reorderMode) {
- if (DEBUG) Log.d(TAG, "cancelAnimation()");
+ void cancelAnimation(@ReorderMode int reorderMode, String reason) {
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation()");
synchronized (mService.getWindowManagerLock()) {
if (mCanceled) {
// We've already canceled the animation
@@ -385,8 +390,9 @@
}
void cleanupAnimation(@ReorderMode int reorderMode) {
- if (DEBUG) Log.d(TAG, "cleanupAnimation(): mPendingAnimations="
- + mPendingAnimations.size());
+ if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG,
+ "cleanupAnimation(): Notify animation finished mPendingAnimations="
+ + mPendingAnimations.size() + " reorderMode=" + reorderMode);
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
@@ -421,7 +427,7 @@
@Override
public void binderDied() {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied");
}
void checkAnimationReady(WallpaperController wallpaperController) {
@@ -550,7 +556,7 @@
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
}
@Override
@@ -572,6 +578,10 @@
} else {
pw.print(prefix); pw.println("Target: null");
}
+ pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
+ pw.println("mPosition=" + mPosition);
+ pw.println("mBounds=" + mBounds);
+ pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
}
@Override
@@ -588,6 +598,10 @@
final String innerPrefix = prefix + " ";
pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":");
pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart);
+ pw.print(innerPrefix); pw.println("mCanceled=" + mCanceled);
+ pw.print(innerPrefix); pw.println("mInputConsumerEnabled=" + mInputConsumerEnabled);
+ pw.print(innerPrefix); pw.println("mSplitScreenMinimized=" + mSplitScreenMinimized);
pw.print(innerPrefix); pw.println("mTargetAppToken=" + mTargetAppToken);
+ pw.print(innerPrefix); pw.println("isTargetOverWallpaper=" + isTargetOverWallpaper());
}
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 16f4cd0..1b06b2f 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -19,6 +19,7 @@
import static com.android.server.wm.AnimationAdapterProto.REMOTE;
import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_REMOTE_ANIMATIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -49,7 +50,9 @@
* Helper class to run app animations in a remote process.
*/
class RemoteAnimationController implements DeathRecipient {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "RemoteAnimationController" : TAG_WM;
+ private static final String TAG = TAG_WITH_CLASS_NAME
+ || (DEBUG_REMOTE_ANIMATIONS && !DEBUG_APP_TRANSITIONS)
+ ? "RemoteAnimationController" : TAG_WM;
private static final long TIMEOUT_MS = 2000;
private final WindowManagerService mService;
@@ -57,7 +60,7 @@
private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>();
private final Rect mTmpRect = new Rect();
private final Handler mHandler;
- private final Runnable mTimeoutRunnable = this::cancelAnimation;
+ private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
private FinishedCallback mFinishedCallback;
private boolean mCanceled;
@@ -80,6 +83,7 @@
*/
AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position,
Rect stackBounds) {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token=" + appWindowToken);
final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper(
appWindowToken, position, stackBounds);
mPendingAnimations.add(adapter);
@@ -90,7 +94,10 @@
* Called when the transition is ready to be started, and all leashes have been set up.
*/
void goodToGo() {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo()");
if (mPendingAnimations.isEmpty() || mCanceled) {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): Animation finished before good to go, canceled="
+ + mCanceled + " mPendingAnimations=" + mPendingAnimations.size());
onAnimationFinished();
return;
}
@@ -102,6 +109,7 @@
final RemoteAnimationTarget[] animations = createAnimations();
if (animations.length == 0) {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): No apps to animate");
onAnimationFinished();
return;
}
@@ -113,14 +121,20 @@
Slog.e(TAG, "Failed to start remote animation", e);
onAnimationFinished();
}
+ if (DEBUG_REMOTE_ANIMATIONS) {
+ Slog.d(TAG, "startAnimation(): Notify animation start:");
+ for (int i = 0; i < mPendingAnimations.size(); i++) {
+ Slog.d(TAG, "\t" + mPendingAnimations.get(i).mAppWindowToken);
+ }
+ } else if (DEBUG_APP_TRANSITIONS) {
+ writeStartDebugStatement();
+ }
});
sendRunningRemoteAnimation(true);
- if (DEBUG_APP_TRANSITIONS) {
- writeStartDebugStatement();
- }
}
- private void cancelAnimation() {
+ private void cancelAnimation(String reason) {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason);
synchronized (mService.getWindowManagerLock()) {
if (mCanceled) {
return;
@@ -143,14 +157,16 @@
}
private RemoteAnimationTarget[] createAnimations() {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final RemoteAnimationAdapterWrapper wrapper = mPendingAnimations.get(i);
- final RemoteAnimationTarget target =
- mPendingAnimations.get(i).createRemoteAppAnimation();
+ final RemoteAnimationTarget target = wrapper.createRemoteAppAnimation();
if (target != null) {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken);
targets.add(target);
} else {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token=" + wrapper.mAppWindowToken);
// We can't really start an animation but we still need to make sure to finish the
// pending animation that was started by SurfaceAnimator
@@ -164,22 +180,29 @@
}
private void onAnimationFinished() {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): mPendingAnimations="
+ + mPendingAnimations.size());
mHandler.removeCallbacks(mTimeoutRunnable);
synchronized (mService.mWindowMap) {
unlinkToDeathOfRunner();
releaseFinishedCallback();
mService.openSurfaceTransaction();
try {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): Notify animation finished:");
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i);
adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapter.mAppWindowToken);
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to finish remote animation", e);
+ throw e;
} finally {
mService.closeSurfaceTransaction("RemoteAnimationController#finished");
}
}
sendRunningRemoteAnimation(false);
- if (DEBUG_APP_TRANSITIONS) Slog.i(TAG, "Finishing remote animation");
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.i(TAG, "Finishing remote animation");
}
private void invokeAnimationCancelled() {
@@ -221,7 +244,7 @@
@Override
public void binderDied() {
- cancelAnimation();
+ cancelAnimation("binderDied");
}
private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
@@ -234,6 +257,7 @@
@Override
public void onAnimationFinished() throws RemoteException {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-onAnimationFinished(): mOuter=" + mOuter);
final long token = Binder.clearCallingIdentity();
try {
if (mOuter != null) {
@@ -253,6 +277,7 @@
* to prevent memory leak.
*/
void release() {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "app-release(): mOuter=" + mOuter);
mOuter = null;
}
};
@@ -316,6 +341,7 @@
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
OnAnimationFinishedCallback finishCallback) {
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
// Restore z-layering, position and stack crop until client has a chance to modify it.
t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex());
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 662d51d..c808c91 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -45,6 +45,7 @@
import android.util.Slog;
import android.view.Display;
import android.view.DisplayCutout;
+import android.view.DisplayCutout.ParcelableWrapper;
import android.view.IWindow;
import android.view.IWindowId;
import android.view.IWindowSession;
@@ -218,7 +219,7 @@
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
new Rect() /* outFrame */, outContentInsets, outStableInsets, null /* outOutsets */,
- null /* cutout */, null /* outInputChannel */);
+ new ParcelableWrapper() /* cutout */, null /* outInputChannel */);
}
@Override
@@ -233,16 +234,16 @@
@Override
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewFlags,
- int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
- Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
- DisplayCutout.ParcelableWrapper cutout,
- MergedConfiguration mergedConfiguration, Surface outSurface) {
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+ Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets,
+ Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
+ DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
+ Surface outSurface) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
int res = mService.relayoutWindow(this, window, seq, attrs,
- requestedWidth, requestedHeight, viewFlags, flags,
+ requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outStableInsets, outsets, outBackdropFrame, cutout,
mergedConfiguration, outSurface);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index b895cf2..67d2be8 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -215,8 +215,8 @@
currentOrientation);
window.setOuter(snapshotSurface);
try {
- session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
- tmpRect, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpRect,
+ session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
+ tmpFrame, tmpRect, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpRect,
tmpCutout, tmpMergedConfiguration, surface);
} catch (RemoteException e) {
// Local call.
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 2175c6b..ae9e8026 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -37,6 +37,7 @@
import static com.android.server.wm.StackProto.ADJUSTED_FOR_IME;
import static com.android.server.wm.StackProto.ADJUST_DIVIDER_AMOUNT;
import static com.android.server.wm.StackProto.ADJUST_IME_AMOUNT;
+import static com.android.server.wm.StackProto.ANIMATING_BOUNDS;
import static com.android.server.wm.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
import static com.android.server.wm.StackProto.BOUNDS;
import static com.android.server.wm.StackProto.DEFER_REMOVAL;
@@ -827,6 +828,14 @@
}
}
+ if (inSplitScreenSecondaryWindowingMode()) {
+ // When the stack is resized due to entering split screen secondary, offset the
+ // windows to compensate for the new stack position.
+ forAllWindows(w -> {
+ w.mWinAnimator.setOffsetPositionForStackResize(true);
+ }, true);
+ }
+
updateDisplayInfo(bounds);
updateSurfaceBounds();
}
@@ -1363,6 +1372,7 @@
proto.write(ADJUST_IME_AMOUNT, mAdjustImeAmount);
proto.write(ADJUST_DIVIDER_AMOUNT, mAdjustDividerAmount);
mAdjustedBounds.writeToProto(proto, ADJUSTED_BOUNDS);
+ proto.write(ANIMATING_BOUNDS, mBoundsAnimating);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7eaca5d..c63da77 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -620,7 +620,7 @@
// If there was a recents animation in progress, cancel that animation
if (mService.getRecentsAnimationController() != null) {
mService.getRecentsAnimationController().cancelAnimation(
- REORDER_MOVE_TO_ORIGINAL_POSITION);
+ REORDER_MOVE_TO_ORIGINAL_POSITION, "wallpaperDrawPendingTimeout");
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 9d9805a..990eb97 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -74,6 +74,9 @@
static final boolean SHOW_STACK_CRAWLS = false;
static final boolean DEBUG_WINDOW_CROP = false;
static final boolean DEBUG_UNKNOWN_APP_VISIBILITY = false;
+ // TODO (b/73188263): Reset debugging flags
+ static final boolean DEBUG_RECENTS_ANIMATIONS = true;
+ static final boolean DEBUG_REMOTE_ANIMATIONS = DEBUG_APP_TRANSITIONS || true;
static final String TAG_KEEP_SCREEN_ON = "DebugKeepScreenOn";
static final boolean DEBUG_KEEP_SCREEN_ON = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2b5620c..407312a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1835,10 +1835,9 @@
}
}
- public int relayoutWindow(Session session, IWindow client, int seq,
- LayoutParams attrs, int requestedWidth,
- int requestedHeight, int viewVisibility, int flags,
- Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+ public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
+ int requestedWidth, int requestedHeight, int viewVisibility, int flags,
+ long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
Surface outSurface) {
@@ -1865,6 +1864,7 @@
win.setRequestedSize(requestedWidth, requestedHeight);
}
+ win.setFrameNumber(frameNumber);
int attrChanges = 0;
int flagChanges = 0;
if (attrs != null) {
@@ -2729,13 +2729,19 @@
}
}
- public void cancelRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
+ /**
+ * Cancels any running recents animation. The caller should NOT hold the WM lock while calling
+ * this method, as it can call back into AM, and locking will be done in the animation
+ * controller itself.
+ */
+ public void cancelRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode,
+ String reason) {
// Note: Do not hold the WM lock, this will lock appropriately in the call which also
// calls through to AM/RecentsAnimation.onAnimationFinished()
if (mRecentsAnimationController != null) {
// This call will call through to cleanupAnimation() below after the animation is
// canceled
- mRecentsAnimationController.cancelAnimation(reorderMode);
+ mRecentsAnimationController.cancelAnimation(reorderMode, reason);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d10f005..9f2915b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -636,6 +636,11 @@
private PowerManagerWrapper mPowerManagerWrapper;
/**
+ * A frame number in which changes requested in this layout will be rendered.
+ */
+ private long mFrameNumber = -1;
+
+ /**
* Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
@@ -4655,7 +4660,7 @@
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
t.deferTransactionUntil(mSurfaceControl,
mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
- mAttrs.frameNumber);
+ getFrameNumber());
}
}
}
@@ -4771,6 +4776,14 @@
return mService.mInputMethodTarget == this;
}
+ long getFrameNumber() {
+ return mFrameNumber;
+ }
+
+ void setFrameNumber(long frameNumber) {
+ mFrameNumber = frameNumber;
+ }
+
private final class MoveAnimationSpec implements AnimationSpec {
private final long mDuration;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index e92d460..ab5e24a 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -67,7 +67,6 @@
import com.android.server.policy.WindowManagerPolicy;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
@@ -217,6 +216,12 @@
int mXOffset = 0;
int mYOffset = 0;
+ /**
+ * A flag to determine if the WSA needs to offset its position to compensate for the stack's
+ * position update before the WSA surface has resized.
+ */
+ private boolean mOffsetPositionForStackResize;
+
private final Rect mTmpSize = new Rect();
private final SurfaceControl.Transaction mReparentTransaction = new SurfaceControl.Transaction();
@@ -230,6 +235,8 @@
// once per animation.
boolean mPipAnimationStarted = false;
+ private final Point mTmpPos = new Point();
+
WindowStateAnimator(final WindowState win) {
final WindowManagerService service = win.mService;
@@ -498,6 +505,8 @@
mSurfaceController = new WindowSurfaceController(mSession.mSurfaceSession,
attrs.getTitle().toString(), width, height, format, flags, this,
windowType, ownerUid);
+
+ setOffsetPositionForStackResize(false);
mSurfaceFormat = format;
w.setHasSurface(true);
@@ -859,7 +868,8 @@
// However, this would be unsafe, as the client may be in the middle
// of producing a frame at the old size, having just completed layout
// to find the surface size changed underneath it.
- if (!w.mRelayoutCalled || w.mInRelayout) {
+ final boolean relayout = !w.mRelayoutCalled || w.mInRelayout;
+ if (relayout) {
mSurfaceResized = mSurfaceController.setSizeInTransaction(
mTmpSize.width(), mTmpSize.height(), recoveringMemory);
} else {
@@ -996,7 +1006,38 @@
mPipAnimationStarted = false;
if (!w.mSeamlesslyRotated) {
- mSurfaceController.setPositionInTransaction(mXOffset, mYOffset, recoveringMemory);
+ // Used to offset the WSA when stack position changes before a resize.
+ int xOffset = mXOffset;
+ int yOffset = mYOffset;
+ if (mOffsetPositionForStackResize) {
+ if (relayout) {
+ // Once a relayout is called, reset the offset back to 0 and defer
+ // setting it until a new frame with the updated size. This ensures that
+ // the WS position is reset (so the stack position is shown) at the same
+ // time that the buffer size changes.
+ setOffsetPositionForStackResize(false);
+ mSurfaceController.deferTransactionUntil(mSurfaceController.getHandle(),
+ mWin.getFrameNumber());
+ } else {
+ final TaskStack stack = mWin.getStack();
+ mTmpPos.x = 0;
+ mTmpPos.y = 0;
+ if (stack != null) {
+ stack.getRelativePosition(mTmpPos);
+ }
+
+ xOffset = -mTmpPos.x;
+ yOffset = -mTmpPos.y;
+
+ // Crop also needs to be extended so the bottom isn't cut off when the WSA
+ // position is moved.
+ if (clipRect != null) {
+ clipRect.right += mTmpPos.x;
+ clipRect.bottom += mTmpPos.y;
+ }
+ }
+ }
+ mSurfaceController.setPositionInTransaction(xOffset, yOffset, recoveringMemory);
}
}
@@ -1499,4 +1540,8 @@
int getLayer() {
return mLastLayer;
}
+
+ void setOffsetPositionForStackResize(boolean offsetPositionForStackResize) {
+ mOffsetPositionForStackResize = offsetPositionForStackResize;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 9663a1b..08b8af2 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -23,6 +23,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
+import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -287,4 +290,43 @@
// Verify that the stack was removed.
assertEquals(originalStackCount, defaultDisplay.getChildCount());
}
+
+ @Test
+ public void testFocusability() throws Exception {
+ final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(stack).build();
+
+ // Under split screen primary we should be focusable when not minimized
+ mService.mStackSupervisor.setDockedStackMinimized(false);
+ assertTrue(stack.isFocusable());
+ assertTrue(activity.isFocusable());
+
+ // Under split screen primary we should not be focusable when minimized
+ mService.mStackSupervisor.setDockedStackMinimized(true);
+ assertFalse(stack.isFocusable());
+ assertFalse(activity.isFocusable());
+
+ final ActivityStack pinnedStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord pinnedActivity = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(pinnedStack).build();
+
+ // We should not be focusable when in pinned mode
+ assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedActivity.isFocusable());
+
+ // Add flag forcing focusability.
+ pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
+
+ // We should not be focusable when in pinned mode
+ assertTrue(pinnedStack.isFocusable());
+ assertTrue(pinnedActivity.isFocusable());
+
+ // Without the overridding activity, stack should not be focusable.
+ pinnedStack.removeTask(pinnedActivity.getTask(), "testFocusability",
+ REMOVE_TASK_MODE_DESTROYING);
+ assertFalse(pinnedStack.isFocusable());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index c78fcd3..4b8dcc1 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -103,7 +103,42 @@
assertEquals(mStack.getResumedActivity(), r);
r.setState(PAUSING, "testResumedActivity");
assertEquals(mStack.getResumedActivity(), null);
+ }
+ @Test
+ public void testResumedActivityFromTaskReparenting() {
+ final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+ // Ensure moving task between two stacks updates resumed activity
+ r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
+ assertEquals(mStack.getResumedActivity(), r);
+
+ final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+ mTask.reparent(destStack, true /* toTop */, TaskRecord.REPARENT_KEEP_STACK_AT_FRONT,
+ false /* animate */, true /* deferResume*/,
+ "testResumedActivityFromTaskReparenting");
+
+ assertEquals(mStack.getResumedActivity(), null);
+ assertEquals(destStack.getResumedActivity(), r);
+ }
+
+ @Test
+ public void testResumedActivityFromActivityReparenting() {
+ final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+ // Ensure moving task between two stacks updates resumed activity
+ r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
+ assertEquals(mStack.getResumedActivity(), r);
+
+ final ActivityStack destStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final TaskRecord destTask = new TaskBuilder(mSupervisor).setStack(destStack).build();
+
+ mTask.removeActivity(r);
+ destTask.addActivityToTop(r);
+
+ assertEquals(mStack.getResumedActivity(), null);
+ assertEquals(destStack.getResumedActivity(), r);
}
@Test
@@ -543,5 +578,4 @@
assertEquals(expected, mStack.shouldSleepActivities());
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index d012bba..18e842f 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -23,6 +23,7 @@
import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED;
import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
+import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_SWITCHES_CANCELED;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
@@ -34,6 +35,7 @@
import android.app.ActivityOptions;
import android.app.IApplicationThread;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.WindowLayout;
@@ -74,6 +76,8 @@
import com.android.server.am.LaunchParamsController.LaunchParamsModifier;
import com.android.server.am.TaskRecord.TaskRecordFactory;
+import java.util.ArrayList;
+
/**
* Tests for the {@link ActivityStarter} class.
*
@@ -301,13 +305,14 @@
anyBoolean(), any(), any(), any());
// instrument the stack and task used.
- final ActivityStack stack = spy(mService.mStackSupervisor.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */));
- final TaskRecord task =
- spy(new TaskBuilder(mService.mStackSupervisor).setStack(stack).build());
+ final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+ .setCreateStack(false)
+ .build();
// supervisor needs a focused stack.
- mService.mStackSupervisor.mFocusedStack = task.getStack();
+ mService.mStackSupervisor.mFocusedStack = stack;
// use factory that only returns spy task.
final TaskRecordFactory factory = mock(TaskRecordFactory.class);
@@ -322,14 +327,6 @@
doReturn(stack).when(mService.mStackSupervisor)
.getLaunchStack(any(), any(), any(), anyBoolean(), anyInt());
- // ignore the start request.
- doNothing().when(stack)
- .startActivityLocked(any(), any(), anyBoolean(), anyBoolean(), any());
-
- // ignore requests to create window container.
- doNothing().when(task).createWindowContainer(anyBoolean(), anyBoolean());
-
-
final Intent intent = new Intent();
intent.addFlags(launchFlags);
intent.setComponent(ActivityBuilder.getDefaultComponent());
@@ -448,4 +445,30 @@
// Ensure result is moving task to front.
assertEquals(result, START_TASK_TO_FRONT);
}
+
+ /**
+ * Tests activity is cleaned up properly in a task mode violation.
+ */
+ @Test
+ public void testTaskModeViolation() {
+ final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+ assertNoTasks(display);
+
+ final ActivityStarter starter = prepareStarter(0);
+
+ final LockTaskController lockTaskController = mService.getLockTaskController();
+ doReturn(true).when(lockTaskController).isLockTaskModeViolation(any());
+
+ final int result = starter.setReason("testTaskModeViolation").execute();
+
+ assertEquals(START_RETURN_LOCK_TASK_MODE_VIOLATION, result);
+ assertNoTasks(display);
+ }
+
+ private void assertNoTasks(ActivityDisplay display) {
+ for (int i = display.getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = display.getChildAt(i);
+ assertTrue(stack.getAllTasks().isEmpty());
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 741901d..f5e61a1 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
+import android.app.ActivityOptions;
import com.android.server.wm.DisplayWindowController;
import org.junit.Rule;
@@ -51,6 +52,9 @@
import android.support.test.InstrumentationRegistry;
import android.testing.DexmakerShareClassLoaderRule;
+
+import com.android.internal.app.IVoiceInteractor;
+
import com.android.server.AttributeCache;
import com.android.server.wm.AppWindowContainerController;
import com.android.server.wm.PinnedStackWindowController;
@@ -62,6 +66,7 @@
import org.junit.Before;
import org.mockito.MockitoAnnotations;
+
/**
* A base class to handle common operations in activity related unit tests.
*/
@@ -215,6 +220,7 @@
private int mTaskId = 0;
private int mUserId = 0;
private IVoiceInteractionSession mVoiceSession;
+ private boolean mCreateStack = true;
private ActivityStack mStack;
@@ -232,6 +238,15 @@
return this;
}
+ /**
+ * Set to {@code true} by default, set to {@code false} to prevent the task from
+ * automatically creating a parent stack.
+ */
+ TaskBuilder setCreateStack(boolean createStack) {
+ mCreateStack = createStack;
+ return this;
+ }
+
TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
mVoiceSession = session;
return this;
@@ -258,7 +273,7 @@
}
TaskRecord build() {
- if (mStack == null) {
+ if (mStack == null && mCreateStack) {
mStack = mSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
}
@@ -276,17 +291,38 @@
intent.setComponent(mComponent);
intent.setFlags(mFlags);
- final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
+ final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo,
intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
task.userId = mUserId;
- mSupervisor.setFocusStackUnchecked("test", mStack);
- mStack.addTask(task, true, "creating test task");
- task.setStack(mStack);
- task.setWindowContainerController(mock(TaskWindowContainerController.class));
+
+ if (mStack != null) {
+ mSupervisor.setFocusStackUnchecked("test", mStack);
+ mStack.addTask(task, true, "creating test task");
+ task.setStack(mStack);
+ task.setWindowContainerController();
+ }
+
task.touchActiveTime();
return task;
}
+
+ private static class TestTaskRecord extends TaskRecord {
+ TestTaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info,
+ Intent _intent, IVoiceInteractionSession _voiceSession,
+ IVoiceInteractor _voiceInteractor) {
+ super(service, _taskId, info, _intent, _voiceSession, _voiceInteractor);
+ }
+
+ @Override
+ void createWindowContainer(boolean onTop, boolean showForAllUsers) {
+ setWindowContainerController();
+ }
+
+ private void setWindowContainerController() {
+ setWindowContainerController(mock(TaskWindowContainerController.class));
+ }
+ }
}
/**
@@ -295,6 +331,7 @@
*/
protected static class TestActivityManagerService extends ActivityManagerService {
private ClientLifecycleManager mLifecycleManager;
+ private LockTaskController mLockTaskController;
TestActivityManagerService(Context context) {
super(context);
@@ -314,6 +351,14 @@
return mLifecycleManager;
}
+ public LockTaskController getLockTaskController() {
+ if (mLockTaskController == null) {
+ mLockTaskController = spy(super.getLockTaskController());
+ }
+
+ return mLockTaskController;
+ }
+
void setLifecycleManager(ClientLifecycleManager manager) {
mLifecycleManager = manager;
}
@@ -444,7 +489,7 @@
}
/**
- * Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a
+ * Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a
* method is called. Note that its functionality depends on the implementations of the
* construction arguments.
*/
@@ -530,5 +575,11 @@
return super.supportsSplitScreenWindowingMode();
}
}
+
+ @Override
+ void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
+ boolean newTask, boolean keepCurTransition,
+ ActivityOptions options) {
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
index d37db20..4f18be7 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
@@ -26,7 +26,9 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
@@ -395,7 +397,13 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(null, packageInfo,
@@ -409,7 +417,13 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
@@ -425,7 +439,7 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[0][0];
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, packageInfo,
@@ -440,7 +454,7 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = null;
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, packageInfo,
@@ -453,7 +467,7 @@
public void signaturesMatch_disallowsUnsignedApps_bothSignaturesNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
- packageInfo.signingCertificateHistory = null;
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(null, packageInfo,
@@ -467,7 +481,7 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[0][0];
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
@@ -484,9 +498,13 @@
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {
- {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
- };
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
@@ -503,9 +521,13 @@
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {
- {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
- };
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
@@ -522,9 +544,13 @@
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {
- {signature1Copy, signature2Copy}
- };
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {signature1Copy, signature2Copy},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
@@ -541,9 +567,13 @@
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {
- {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
- };
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
@@ -560,7 +590,13 @@
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(signature1Copy,
@@ -579,7 +615,13 @@
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_2},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ new Signature[] {SIGNATURE_1, SIGNATURE_2},
+ new int[] {0, 0}));
packageInfo.applicationInfo = new ApplicationInfo();
// we know signature1Copy is in history, and we want to assume it has
@@ -601,7 +643,13 @@
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_2},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ new Signature[] {SIGNATURE_1, SIGNATURE_2},
+ new int[] {0, 0}));
packageInfo.applicationInfo = new ApplicationInfo();
// we know signature1Copy is in history, but we want to assume it does not have
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
index 5f052ce..2830a74 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java
@@ -41,7 +41,9 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.os.Bundle;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
@@ -371,7 +373,13 @@
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_2}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {FAKE_SIGNATURE_2},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
@@ -402,7 +410,13 @@
ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.SYSTEM_UID;
packageInfo.applicationInfo.backupAgentName = "backup.agent";
- packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {FAKE_SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -434,7 +448,13 @@
ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {FAKE_SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
@@ -469,7 +489,13 @@
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {FAKE_SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.versionCode = 2;
PackageManagerStub.sPackageInfo = packageInfo;
@@ -507,7 +533,13 @@
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {FAKE_SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.versionCode = 1;
PackageManagerStub.sPackageInfo = packageInfo;
@@ -541,7 +573,13 @@
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
- packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {FAKE_SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.versionCode = 1;
PackageManagerStub.sPackageInfo = packageInfo;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index fb25cf3..284d443 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -32,7 +32,9 @@
import android.os.PowerManager;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.MathUtils;
import android.util.Spline;
+import android.util.Slog;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -91,6 +93,29 @@
private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
private static final int[] EMPTY_INT_ARRAY = new int[0];
+ private static final float MAXIMUM_GAMMA = 3.0f;
+ private static final int[] GAMMA_CORRECTION_LUX = {
+ 0,
+ 100,
+ 1000,
+ 2500,
+ 4000,
+ 4900,
+ 5000
+ };
+ private static final float[] GAMMA_CORRECTION_NITS = {
+ 1.0f,
+ 10.55f,
+ 96.5f,
+ 239.75f,
+ 383.0f,
+ 468.95f,
+ 478.5f,
+ };
+ private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline(
+ new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
+ new float[] { 0.035f, 0.035f, 0.221f, 0.523f, 0.797f, 0.980f, 1.0f });
+
@Test
public void testSimpleStrategyMappingAtControlPoints() {
Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
@@ -325,7 +350,7 @@
}
// Now set the middle of the lux range to something just above the minimum.
- final float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
+ float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.01f);
// Then make sure the curve is still monotonic.
@@ -340,7 +365,8 @@
// And that the lowest lux level still gives the absolute minimum brightness. This should
// be true assuming that there are more than two lux levels in the curve since we picked a
// brightness just barely above the minimum for the middle of the curve.
- assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.001 /*tolerance*/);
+ minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction.
+ assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.01 /*tolerance*/);
}
private static float[] toFloatArray(int[] vals) {
@@ -396,6 +422,9 @@
when(mockResources.getInteger(
com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
.thenReturn(255);
+ when(mockResources.getFraction(
+ com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
+ .thenReturn(MAXIMUM_GAMMA);
return mockResources;
}
@@ -419,4 +448,121 @@
return mockArray;
}
+ // Gamma correction tests.
+ // x0 = 100 y0 = ~0.01
+ // x1 = 1000 y1 = ~0.20
+ // x2 = 2500 y2 = ~0.50
+ // x3 = 4000 y3 = ~0.80
+ // x4 = 4900 y4 = ~0.99
+
+ @Test
+ public void testGammaCorrectionLowChangeAtCenter() {
+ // If we set a user data point at (x2, y2^0.5), i.e. gamma = 0.5, it should bump the rest
+ // of the spline accordingly.
+ final int x1 = 1000;
+ final int x2 = 2500;
+ final int x3 = 4000;
+ final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
+ final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+ final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
+ Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
+ DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
+ // Let's start with a sanity check:
+ assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */);
+ assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */);
+ assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */);
+ // OK, let's roll:
+ float gamma = 0.5f;
+ strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
+ assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.01f /* tolerance */);
+ assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */);
+ assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.01f /* tolerance */);
+ // The adjustment should be +0.63 (manual calculation).
+ assertEquals(+0.63f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
+ }
+
+ @Test
+ public void testGammaCorrectionHighChangeAtCenter() {
+ // This time we set a user data point at (x2, y2^0.25), i.e. gamma = 0.3 (the minimum),
+ // which should bump the rest of the spline accordingly, and further correct x2 to hit
+ // y2^0.25 (not y2^0.3).
+ final int x1 = 1000;
+ final int x2 = 2500;
+ final int x3 = 4000;
+ final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
+ final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+ final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
+ Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
+ DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
+ // Sanity check:
+ assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */);
+ assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */);
+ assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */);
+ // Let's roll:
+ float gamma = 0.25f;
+ final float minGamma = 1.0f / MAXIMUM_GAMMA;
+ strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
+ assertEquals(MathUtils.pow(y1, minGamma), strategy.getBrightness(x1),
+ 0.01f /* tolerance */);
+ assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2),
+ 0.01f /* tolerance */);
+ assertEquals(MathUtils.pow(y3, minGamma), strategy.getBrightness(x3),
+ 0.01f /* tolerance */);
+ // The adjustment should be +1.0 (maximum adjustment).
+ assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
+ }
+
+ @Test
+ public void testGammaCorrectionExtremeChangeAtCenter() {
+ // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
+ // just make sure the adjustment reflects the change.
+ Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
+ DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
+ assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
+ strategy.addUserDataPoint(2500, 1.0f);
+ assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
+ strategy.addUserDataPoint(2500, 0.0f);
+ assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
+ }
+
+ @Test
+ public void testGammaCorrectionChangeAtEdges() {
+ // The algorithm behaves differently at the edges, because gamma correction there tends to
+ // be extreme. If we add a user data point at (x0, y0+0.3), the adjustment should be
+ // 0.3*2 = 0.6, resulting in a gamma of 3**-0.6 = ~0.52.
+ final int x0 = 100;
+ final int x2 = 2500;
+ final int x4 = 4900;
+ final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
+ final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+ final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
+ Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS,
+ DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources);
+ // Sanity, as per tradition:
+ assertEquals(y0, strategy.getBrightness(x0), 0.01f /* tolerance */);
+ assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */);
+ assertEquals(y4, strategy.getBrightness(x4), 0.01f /* tolerance */);
+ // Rollin':
+ float increase = 0.3f;
+ float adjustment = increase * 2;
+ float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
+ strategy.addUserDataPoint(x0, y0 + increase);
+ assertEquals(y0 + increase, strategy.getBrightness(x0), 0.01f /* tolerance */);
+ assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */);
+ assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.01f /* tolerance */);
+ assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
+ // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be (1-y4)*2.
+ increase = 1.0f - y4;
+ adjustment = increase * 2;
+ gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
+ strategy.addUserDataPoint(x4, 1.0f);
+ assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.01f /* tolerance */);
+ assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */);
+ assertEquals(1.0f, strategy.getBrightness(x4), 0.01f /* tolerance */);
+ assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index ee83b62..998ffa0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -58,11 +58,13 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -102,6 +104,8 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -1039,8 +1043,13 @@
pi.versionCode = version;
pi.applicationInfo.setVersionCode(version);
pi.signatures = null;
- pi.signingCertificateHistory = new Signature[][] {genSignatures(signatures)};
-
+ pi.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ genSignatures(signatures),
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
return pi;
}
@@ -1128,7 +1137,7 @@
if (getSignatures) {
ret.signatures = null;
- ret.signingCertificateHistory = pi.signingCertificateHistory;
+ ret.signingInfo = pi.signingInfo;
}
return ret;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java
index cd7feea..203b2ca 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java
@@ -76,13 +76,13 @@
mMyPackage, mMyUserId, /*signature*/ false);
assertEquals(mMyPackage, pi.packageName);
assertNull(pi.signatures);
- assertNull(pi.signingCertificateHistory);
+ assertNull(pi.signingInfo);
pi = mShortcutService.getPackageInfo(
mMyPackage, mMyUserId, /*signature*/ true);
assertEquals(mMyPackage, pi.packageName);
assertNull(pi.signatures);
- assertNotNull(pi.signingCertificateHistory);
+ assertNotNull(pi.signingInfo);
pi = mShortcutService.getPackageInfo(
"no.such.package", mMyUserId, /*signature*/ true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index 36e4753..c5cd0a3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -242,7 +242,7 @@
}
@Test
- public void testIsPackageSuspended() {
+ public void testIsPackageSuspended() throws Exception {
suspendTestPackage(null, null, null);
assertTrue("isPackageSuspended is false",
mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
@@ -297,7 +297,7 @@
intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2,
"2", 0.2);
- mPackageManager.setSuspendedPackageAppExtras(TEST_APP_PACKAGE_NAME, extras2);
+ suspendTestPackage(extras2, null, null);
intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
@@ -370,8 +370,8 @@
}
@Override
- public void onPackagesSuspended(String[] packageNames, Bundle launcherExtras,
- UserHandle user) {
+ public void onPackagesSuspended(String[] packageNames, UserHandle user,
+ Bundle launcherExtras) {
final StringBuilder errorString = new StringBuilder();
if (!Arrays.equals(packageNames, PACKAGES_TO_SUSPEND)) {
errorString.append("Received unexpected packageNames in onPackagesSuspended:");
diff --git a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
index 1ac7cb8..caa1d02 100644
--- a/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
@@ -27,8 +27,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
import android.content.pm.PackageParser.Package;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.test.MoreAsserts;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
@@ -94,7 +96,13 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = BackupUtils.signaturesMatch(null, packageInfo,
@@ -108,7 +116,13 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -125,7 +139,7 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[0][0];
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -142,7 +156,7 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = null;
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -158,7 +172,7 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = null;
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = BackupUtils.signaturesMatch(null, packageInfo,
@@ -172,7 +186,7 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[0][0];
+ packageInfo.signingInfo = null;
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -186,9 +200,13 @@
public void signaturesMatch_equalSignatures_returnsTrue() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {
- {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
- };
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -205,9 +223,13 @@
public void signaturesMatch_extraSignatureInTarget_returnsTrue() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {
- {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
- };
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -223,7 +245,13 @@
public void signaturesMatch_extraSignatureInStored_returnsFalse() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1, SIGNATURE_2}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -240,9 +268,13 @@
public void signaturesMatch_oneNonMatchingSignature_returnsFalse() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {
- {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
- };
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2, SIGNATURE_3},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
@@ -260,7 +292,13 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(SIGNATURE_HASH_1,
@@ -279,7 +317,13 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
// we know SIGNATURE_1 is in history, and we want to assume it has
@@ -301,7 +345,13 @@
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
- packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
+ packageInfo.signingInfo = new SigningInfo(
+ new PackageParser.SigningDetails(
+ new Signature[] {SIGNATURE_1, SIGNATURE_2},
+ PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null,
+ null));
packageInfo.applicationInfo = new ApplicationInfo();
// we know SIGNATURE_1 is in history, but we want to assume it does not have
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java
new file mode 100644
index 0000000..1efa415
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceClientPermissionsTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2018 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.slice;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Xml.Encoding;
+
+import com.android.server.UiServiceTestCase;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+import com.android.server.slice.SliceClientPermissions.SliceAuthority;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SliceClientPermissionsTest extends UiServiceTestCase {
+
+ @Test
+ public void testRemoveBasic() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+ Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("com.android.pkg.slices").build();
+
+ PkgUser testPkg = new PkgUser("other", 2);
+
+ client.grantUri(base.buildUpon()
+ .appendPath("first")
+ .build(), testPkg);
+ client.revokeUri(base.buildUpon()
+ .appendPath("first")
+ .build(), testPkg);
+
+ assertFalse(client.hasPermission(base.buildUpon()
+ .appendPath("first")
+ .appendPath("third")
+ .build(), testPkg.getUserId()));
+
+ ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+ assertEquals(0, authorities.get(0).getPaths().size());
+ }
+
+ @Test
+ public void testRemoveSubtrees() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+ Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("com.android.pkg.slices").build();
+
+ PkgUser testPkg = new PkgUser("other", 2);
+
+ client.grantUri(base.buildUpon()
+ .appendPath("first")
+ .appendPath("second")
+ .build(), testPkg);
+ client.grantUri(base.buildUpon()
+ .appendPath("first")
+ .appendPath("third")
+ .build(), testPkg);
+ client.revokeUri(base.buildUpon()
+ .appendPath("first")
+ .build(), testPkg);
+
+ assertFalse(client.hasPermission(base.buildUpon()
+ .appendPath("first")
+ .appendPath("fourth")
+ .build(), testPkg.getUserId()));
+
+ ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+ assertEquals(0, authorities.get(0).getPaths().size());
+ }
+
+ @Test
+ public void testAddConsolidate_addFirst() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+ Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("com.android.pkg.slices").build();
+
+ PkgUser testPkg = new PkgUser("other", 2);
+
+ client.grantUri(base.buildUpon()
+ .appendPath("first")
+ .build(), testPkg);
+ client.grantUri(base.buildUpon()
+ .appendPath("first")
+ .appendPath("second")
+ .build(), testPkg);
+
+ assertTrue(client.hasPermission(base.buildUpon()
+ .appendPath("first")
+ .appendPath("third")
+ .build(), testPkg.getUserId()));
+
+ ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+ assertEquals(1, authorities.get(0).getPaths().size());
+ }
+
+ @Test
+ public void testAddConsolidate_addSecond() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+ Uri base = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("com.android.pkg.slices").build();
+
+ PkgUser testPkg = new PkgUser("other", 2);
+
+ client.grantUri(base.buildUpon()
+ .appendPath("first")
+ .appendPath("second")
+ .build(), testPkg);
+ client.grantUri(base.buildUpon()
+ .appendPath("first")
+ .build(), testPkg);
+
+ assertTrue(client.hasPermission(base.buildUpon()
+ .appendPath("first")
+ .appendPath("third")
+ .build(), testPkg.getUserId()));
+
+ ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+ assertEquals(1, authorities.get(0).getPaths().size());
+ }
+
+ @Test
+ public void testDirty_addAuthority() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+
+ client.getOrCreateAuthority(new PkgUser("some_auth", 2), new PkgUser("com.pkg", 2));
+
+ verify(tracker).onPersistableDirty(eq(client));
+ }
+
+ @Test
+ public void testDirty_addPkg() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+
+ SliceAuthority auth = client.getOrCreateAuthority(
+ new PkgUser("some_auth", 2),
+ new PkgUser("com.pkg", 2));
+ clearInvocations(tracker);
+
+ auth.addPath(Arrays.asList("/something/"));
+
+ verify(tracker).onPersistableDirty(eq(client));
+ }
+
+ @Test
+ public void testCreation() {
+ SliceClientPermissions client = createClient();
+ ArrayList<SliceAuthority> authorities = new ArrayList<>(client.getAuthorities());
+ authorities.sort(Comparator.comparing(SliceAuthority::getAuthority));
+
+ assertEquals(2, authorities.size());
+ assertEquals("com.android.pkg", authorities.get(0).getAuthority());
+ assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority());
+
+ assertEquals(1, authorities.get(0).getPaths().size());
+ assertEquals(2, authorities.get(1).getPaths().size());
+ }
+
+ @Test
+ public void testSerialization() throws XmlPullParserException, IOException {
+ SliceClientPermissions client = createClient();
+ client.setHasFullAccess(true);
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+ serializer.setOutput(output, Encoding.UTF_8.name());
+
+ client.writeTo(serializer);
+ serializer.flush();
+
+ ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
+ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+ parser.setInput(input, Encoding.UTF_8.name());
+
+ SliceClientPermissions deser = SliceClientPermissions.createFrom(parser,
+ mock(DirtyTracker.class));
+
+ assertEquivalent(client, deser);
+ }
+
+ private void assertEquivalent(SliceClientPermissions o1, SliceClientPermissions o2) {
+ assertEquals(o1.getPkg(), o2.getPkg());
+ ArrayList<SliceAuthority> a1 = new ArrayList<>(o1.getAuthorities());
+ ArrayList<SliceAuthority> a2 = new ArrayList<>(o2.getAuthorities());
+ a1.sort(Comparator.comparing(SliceAuthority::getAuthority));
+ a2.sort(Comparator.comparing(SliceAuthority::getAuthority));
+ assertEquals(a1, a2);
+ }
+
+ private static SliceClientPermissions createClient() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 2);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceClientPermissions client = new SliceClientPermissions(pkg, tracker);
+
+ SliceAuthority auth = client.getOrCreateAuthority(
+ new PkgUser("com.android.pkg.slices", 3),
+ new PkgUser("com.android.pkg", 3));
+ auth.addPath(Arrays.asList("/something/"));
+ auth.addPath(Arrays.asList("/something/else"));
+
+ auth = client.getOrCreateAuthority(
+ new PkgUser("com.android.pkg", 3),
+ new PkgUser("com.pkg", 1));
+ auth.addPath(Arrays.asList("/somewhere"));
+ return client;
+ }
+
+}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java
new file mode 100644
index 0000000..5443e73
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SlicePermissionManagerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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.slice;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.os.FileUtils;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Log;
+import android.util.Xml.Encoding;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SlicePermissionManagerTest extends UiServiceTestCase {
+
+ @Test
+ public void testBackup() throws XmlPullParserException, IOException {
+ File sliceDir = new File(mContext.getDataDir(), "system/slices");
+ Uri uri = new Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority("authority")
+ .path("something").build();
+ SlicePermissionManager permissions = new SlicePermissionManager(mContext,
+ TestableLooper.get(this).getLooper(), sliceDir);
+
+ permissions.grantFullAccess("com.android.mypkg", 10);
+ permissions.grantSliceAccess("com.android.otherpkg", 0, "com.android.lastpkg", 1, uri);
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+ serializer.setOutput(output, Encoding.UTF_8.name());
+
+
+ TestableLooper.get(this).processAllMessages();
+ permissions.writeBackup(serializer);
+ serializer.flush();
+
+ ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
+ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+ parser.setInput(input, Encoding.UTF_8.name());
+
+ permissions = new SlicePermissionManager(mContext,
+ TestableLooper.get(this).getLooper());
+ permissions.readRestore(parser);
+
+ assertTrue(permissions.hasFullAccess("com.android.mypkg", 10));
+ assertTrue(permissions.hasPermission("com.android.otherpkg", 0,
+ ContentProvider.maybeAddUserId(uri, 1)));
+ permissions.removePkg("com.android.lastpkg", 1);
+ assertFalse(permissions.hasPermission("com.android.otherpkg", 0,
+ ContentProvider.maybeAddUserId(uri, 1)));
+
+ // Cleanup.
+ assertTrue(FileUtils.deleteContentsAndDir(sliceDir));
+ }
+
+}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java
new file mode 100644
index 0000000..5775991
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceProviderPermissionsTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 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.slice;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.Xml.Encoding;
+
+import com.android.server.UiServiceTestCase;
+import com.android.server.slice.SlicePermissionManager.PkgUser;
+import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SliceProviderPermissionsTest extends UiServiceTestCase {
+
+ @Test
+ public void testDirty_addAuthority() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
+
+ provider.getOrCreateAuthority("some_auth");
+
+ verify(tracker).onPersistableDirty(eq(provider));
+ }
+
+ @Test
+ public void testDirty_addPkg() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 0);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
+
+ SliceAuthority auth = provider.getOrCreateAuthority("some_auth");
+ clearInvocations(tracker);
+
+ auth.addPkg(new PkgUser("pkg", 0));
+
+ verify(tracker).onPersistableDirty(eq(provider));
+ }
+
+ @Test
+ public void testCreation() {
+ SliceProviderPermissions provider = createProvider();
+ ArrayList<SliceAuthority> authorities = new ArrayList<>(provider.getAuthorities());
+ authorities.sort(Comparator.comparing(SliceAuthority::getAuthority));
+
+ assertEquals(2, authorities.size());
+ assertEquals("com.android.pkg", authorities.get(0).getAuthority());
+ assertEquals("com.android.pkg.slices", authorities.get(1).getAuthority());
+
+ assertEquals(1, authorities.get(0).getPkgs().size());
+ assertEquals(2, authorities.get(1).getPkgs().size());
+ }
+
+ @Test
+ public void testSerialization() throws XmlPullParserException, IOException {
+ SliceProviderPermissions provider = createProvider();
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ XmlSerializer serializer = XmlPullParserFactory.newInstance().newSerializer();
+ serializer.setOutput(output, Encoding.UTF_8.name());
+
+ provider.writeTo(serializer);
+ serializer.flush();
+
+ ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
+ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+ parser.setInput(input, Encoding.UTF_8.name());
+
+ SliceProviderPermissions deser = SliceProviderPermissions.createFrom(parser,
+ mock(DirtyTracker.class));
+
+ assertEquivalent(provider, deser);
+ }
+
+ private void assertEquivalent(SliceProviderPermissions o1, SliceProviderPermissions o2) {
+ assertEquals(o1.getPkg(), o2.getPkg());
+ assertEquals(o1.getAuthorities(), o2.getAuthorities());
+ }
+
+ private static SliceProviderPermissions createProvider() {
+ PkgUser pkg = new PkgUser("com.android.pkg", 2);
+ DirtyTracker tracker = mock(DirtyTracker.class);
+ SliceProviderPermissions provider = new SliceProviderPermissions(pkg, tracker);
+
+ SliceAuthority auth = provider.getOrCreateAuthority("com.android.pkg.slices");
+ auth.addPkg(new PkgUser("com.example.pkg", 0));
+ auth.addPkg(new PkgUser("example.pkg.com", 10));
+
+ auth = provider.getOrCreateAuthority("com.android.pkg");
+ auth.addPkg(new PkgUser("com.example.pkg", 2));
+ return provider;
+ }
+
+}
\ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 1160943..cd524a5 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -926,25 +926,24 @@
Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were "
+ "run in last 24 hours");
}
- return;
- }
+ } else {
+ mNumOps.addOp(currentTime);
- mNumOps.addOp(currentTime);
+ // Find a free opID
+ int opId = mNumTotalOpsPerformed;
+ do {
+ mNumTotalOpsPerformed++;
+ } while (mRunningOpIds.contains(opId));
- // Find a free opID
- int opId = mNumTotalOpsPerformed;
- do {
- mNumTotalOpsPerformed++;
- } while (mRunningOpIds.contains(opId));
+ // Run OP
+ try {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
- // Run OP
- try {
- if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
-
- op.run(opId, mService);
- mRunningOpIds.add(opId);
- } catch (Exception e) {
- Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
+ op.run(opId, mService);
+ mRunningOpIds.add(opId);
+ } catch (Exception e) {
+ Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
+ }
}
// Unbind from service if no operations are left (i.e. if the operation failed)
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7cb5ce4..3b9006c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5183,7 +5183,12 @@
* {@link #AUTHTYPE_EAP_SIM}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
- * @return the response of authentication, or null if not available
+ * @return the response of authentication. This value will be null in the following cases:
+ * Authentication error, incorrect MAC
+ * Authentication error, security context not supported
+ * Key freshness failure
+ * Authentication error, no memory space available
+ * Authentication error, no memory space available in EFMUK
*/
// TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not
// READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since
@@ -5204,7 +5209,13 @@
* {@link #AUTHTYPE_EAP_SIM}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
- * @return the response of authentication, or null if not available
+ * @return the response of authentication. This value will be null in the following cases only
+ * (see 3GPP TS 31.102 7.3.1):
+ * Authentication error, incorrect MAC
+ * Authentication error, security context not supported
+ * Key freshness failure
+ * Authentication error, no memory space available
+ * Authentication error, no memory space available in EFMUK
* @hide
*/
public String getIccAuthentication(int subId, int appType, int authType, String data) {
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 602c796..9e3302b 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
@@ -184,6 +185,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public Builder setServiceId(String serviceId) {
fileServiceId = serviceId;
return this;
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
index a7bdabd..0787d82 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
@@ -106,7 +106,7 @@
mPorterDuffColor = porterDuffColor;
final PorterDuffColorFilter filter =
(PorterDuffColorFilter) mBlendPaint.getColorFilter();
- filter.setColor(mPorterDuffColor);
+ mBlendPaint.setColorFilter(new PorterDuffColorFilter(porterDuffColor, filter.getMode()));
invalidate();
}
diff --git a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
index 9174014..ae3914e 100644
--- a/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
+++ b/tests/WindowManagerStressTest/src/test/windowmanagerstresstest/MainActivity.java
@@ -102,7 +102,7 @@
Thread t = new Thread(() -> {
try {
WindowManagerGlobal.getWindowSession().relayout(window,
- window.mSeq, mLayoutParams, -1, -1, View.VISIBLE, 0, mTmpRect,
+ window.mSeq, mLayoutParams, -1, -1, View.VISIBLE, 0, -1, mTmpRect,
mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect, mTmpRect,
new DisplayCutout.ParcelableWrapper(), new MergedConfiguration(),
new Surface());
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
new file mode 100644
index 0000000..e58811b
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.SNOOZE_NEVER;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
+
+import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.usage.NetworkStatsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkTemplate;
+import android.net.StringNetworkSpecifier;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import android.util.DataUnit;
+import android.util.RecurrenceRule;
+
+import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.net.NetworkStatsManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MultipathPolicyTrackerTest {
+ private static final Network TEST_NETWORK = new Network(123);
+ private static final int POLICY_SNOOZED = -100;
+
+ @Mock private Context mContext;
+ @Mock private Resources mResources;
+ @Mock private Handler mHandler;
+ @Mock private MultipathPolicyTracker.Dependencies mDeps;
+ @Mock private Clock mClock;
+ @Mock private ConnectivityManager mCM;
+ @Mock private NetworkPolicyManager mNPM;
+ @Mock private NetworkStatsManager mStatsManager;
+ @Mock private NetworkPolicyManagerInternal mNPMI;
+ @Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal;
+ @Mock private TelephonyManager mTelephonyManager;
+ private MockContentResolver mContentResolver;
+
+ private ArgumentCaptor<BroadcastReceiver> mConfigChangeReceiverCaptor;
+
+ private MultipathPolicyTracker mTracker;
+
+ private Clock mPreviousRecurrenceRuleClock;
+ private boolean mRecurrenceRuleClockMocked;
+
+ private <T> void mockService(String serviceName, Class<T> serviceClass, T service) {
+ when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName);
+ when(mContext.getSystemService(serviceName)).thenReturn(service);
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mPreviousRecurrenceRuleClock = RecurrenceRule.sClock;
+ RecurrenceRule.sClock = mClock;
+ mRecurrenceRuleClockMocked = true;
+
+ mConfigChangeReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ when(mContext.registerReceiverAsUser(mConfigChangeReceiverCaptor.capture(),
+ any(), argThat(f -> f.hasAction(ACTION_CONFIGURATION_CHANGED)), any(), any()))
+ .thenReturn(null);
+
+ when(mDeps.getClock()).thenReturn(mClock);
+
+ when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+
+ mContentResolver = Mockito.spy(new MockContentResolver(mContext));
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ Settings.Global.clearProviderForTest();
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+
+ mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, mCM);
+ mockService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class, mNPM);
+ mockService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, mStatsManager);
+ mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager);
+
+ LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
+ LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI);
+
+ LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
+ LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal);
+
+ mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps);
+ }
+
+ @After
+ public void tearDown() {
+ // Avoid setting static clock to null (which should normally not be the case)
+ // if MockitoAnnotations.initMocks threw an exception
+ if (mRecurrenceRuleClockMocked) {
+ RecurrenceRule.sClock = mPreviousRecurrenceRuleClock;
+ }
+ mRecurrenceRuleClockMocked = false;
+ }
+
+ private void setDefaultQuotaGlobalSetting(long setting) {
+ Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
+ (int) setting);
+ }
+
+ private void testGetMultipathPreference(
+ long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit,
+ long defaultGlobalSetting, long defaultResSetting, boolean roaming) {
+
+ // TODO: tests should not use ZoneId.systemDefault() once code handles TZ correctly.
+ final ZonedDateTime now = ZonedDateTime.ofInstant(
+ Instant.parse("2017-04-02T10:11:12Z"), ZoneId.systemDefault());
+ final ZonedDateTime startOfDay = now.truncatedTo(ChronoUnit.DAYS);
+ when(mClock.millis()).thenReturn(now.toInstant().toEpochMilli());
+ when(mClock.instant()).thenReturn(now.toInstant());
+ when(mClock.getZone()).thenReturn(ZoneId.systemDefault());
+
+ // Setup plan quota
+ when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
+ .thenReturn(subscriptionQuota);
+
+ // Setup user policy warning / limit
+ if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) {
+ final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z");
+ final RecurrenceRule recurrenceRule = new RecurrenceRule(
+ ZonedDateTime.ofInstant(
+ recurrenceStart,
+ ZoneId.systemDefault()),
+ null /* end */,
+ Period.ofMonths(1));
+ final boolean snoozeWarning = policyWarning == POLICY_SNOOZED;
+ final boolean snoozeLimit = policyLimit == POLICY_SNOOZED;
+ when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] {
+ new NetworkPolicy(
+ NetworkTemplate.buildTemplateMobileWildcard(),
+ recurrenceRule,
+ snoozeWarning ? 0 : policyWarning,
+ snoozeLimit ? 0 : policyLimit,
+ snoozeWarning ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
+ snoozeLimit ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER,
+ SNOOZE_NEVER,
+ true /* metered */,
+ false /* inferred */)
+ });
+ } else {
+ when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]);
+ }
+
+ // Setup default quota in settings and resources
+ if (defaultGlobalSetting > 0) {
+ setDefaultQuotaGlobalSetting(defaultGlobalSetting);
+ }
+ when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
+ .thenReturn((int) defaultResSetting);
+
+ when(mNetworkStatsManagerInternal.getNetworkTotalBytes(
+ any(),
+ eq(startOfDay.toInstant().toEpochMilli()),
+ eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday);
+
+ ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ mTracker.start();
+ verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
+
+ // Simulate callback after capability changes
+ final NetworkCapabilities capabilities = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new StringNetworkSpecifier("234"));
+ if (!roaming) {
+ capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
+ networkCallback.getValue().onCapabilitiesChanged(
+ TEST_NETWORK,
+ capabilities);
+ }
+
+ @Test
+ public void testGetMultipathPreference_SubscriptionQuota() {
+ testGetMultipathPreference(
+ DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
+ DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
+ DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
+ LIMIT_DISABLED,
+ DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
+ 2_500_000 /* defaultResSetting */,
+ false /* roaming */);
+
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ }
+
+ @Test
+ public void testGetMultipathPreference_UserWarningQuota() {
+ testGetMultipathPreference(
+ DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
+ OPPORTUNISTIC_QUOTA_UNKNOWN,
+ // 29 days from Apr. 2nd to May 1st
+ DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */,
+ LIMIT_DISABLED,
+ DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
+ 2_500_000 /* defaultResSetting */,
+ false /* roaming */);
+
+ // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ }
+
+ @Test
+ public void testGetMultipathPreference_SnoozedWarningQuota() {
+ testGetMultipathPreference(
+ DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
+ OPPORTUNISTIC_QUOTA_UNKNOWN,
+ // 29 days from Apr. 2nd to May 1st
+ POLICY_SNOOZED /* policyWarning */,
+ DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */,
+ DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
+ 2_500_000 /* defaultResSetting */,
+ false /* roaming */);
+
+ // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ }
+
+ @Test
+ public void testGetMultipathPreference_SnoozedBothQuota() {
+ testGetMultipathPreference(
+ DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
+ OPPORTUNISTIC_QUOTA_UNKNOWN,
+ // 29 days from Apr. 2nd to May 1st
+ POLICY_SNOOZED /* policyWarning */,
+ POLICY_SNOOZED /* policyLimit */,
+ DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
+ 2_500_000 /* defaultResSetting */,
+ false /* roaming */);
+
+ // Default global setting should be used: 12 - 7 = 5
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
+ }
+
+ @Test
+ public void testGetMultipathPreference_SettingChanged() {
+ testGetMultipathPreference(
+ DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
+ OPPORTUNISTIC_QUOTA_UNKNOWN,
+ WARNING_DISABLED,
+ LIMIT_DISABLED,
+ -1 /* defaultGlobalSetting */,
+ DataUnit.MEGABYTES.toBytes(10) /* defaultResSetting */,
+ false /* roaming */);
+
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+
+ // Update setting
+ setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14));
+ mTracker.mSettingsObserver.onChange(
+ false, Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES));
+
+ // Callback must have been re-registered with new setting
+ verify(mStatsManager, times(1)).unregisterUsageCallback(any());
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ }
+
+ @Test
+ public void testGetMultipathPreference_ResourceChanged() {
+ testGetMultipathPreference(
+ DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
+ OPPORTUNISTIC_QUOTA_UNKNOWN,
+ WARNING_DISABLED,
+ LIMIT_DISABLED,
+ -1 /* defaultGlobalSetting */,
+ DataUnit.MEGABYTES.toBytes(14) /* defaultResSetting */,
+ false /* roaming */);
+
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+
+ when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
+ .thenReturn((int) DataUnit.MEGABYTES.toBytes(16));
+
+ final BroadcastReceiver configChangeReceiver = mConfigChangeReceiverCaptor.getValue();
+ assertNotNull(configChangeReceiver);
+ configChangeReceiver.onReceive(mContext, new Intent());
+
+ // Uses the new setting (16 - 2 = 14MB)
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 662e0ba..47e49d4 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -355,6 +355,7 @@
@Test
public void canRequireProvisioning() {
setupForRequiredProvisioning();
+ sendConfigurationChanged();
assertTrue(mTethering.isTetherProvisioningRequired());
}
@@ -363,6 +364,7 @@
setupForRequiredProvisioning();
when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
.thenReturn(null);
+ sendConfigurationChanged();
// Couldn't get the CarrierConfigManager, but still had a declared provisioning app.
// We therefore still require provisioning.
assertTrue(mTethering.isTetherProvisioningRequired());
@@ -372,6 +374,7 @@
public void toleratesCarrierConfigMissing() {
setupForRequiredProvisioning();
when(mCarrierConfigManager.getConfig()).thenReturn(null);
+ sendConfigurationChanged();
// We still have a provisioning app configured, so still require provisioning.
assertTrue(mTethering.isTetherProvisioningRequired());
}
@@ -411,6 +414,11 @@
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
+ private void sendConfigurationChanged() {
+ final Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
+ mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
private void verifyInterfaceServingModeStarted() throws Exception {
verify(mNMService, times(1)).getInterfaceConfig(TEST_WLAN_IFNAME);
verify(mNMService, times(1))
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 26248e5..70a47cf 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -50,6 +50,18 @@
return "\033[%sm" % (";".join(codes))
+def ident(raw):
+ """Strips superficial signature changes, giving us a strong key that
+ can be used to identify members across API levels."""
+ raw = raw.replace(" deprecated ", " ")
+ raw = raw.replace(" synchronized ", " ")
+ raw = raw.replace(" final ", " ")
+ raw = re.sub("<.+?>", "", raw)
+ if " throws " in raw:
+ raw = raw[:raw.index(" throws ")]
+ return raw
+
+
class Field():
def __init__(self, clazz, line, raw, blame):
self.clazz = clazz
@@ -69,8 +81,7 @@
self.value = raw[3].strip(';"')
else:
self.value = None
-
- self.ident = self.raw.replace(" deprecated ", " ")
+ self.ident = ident(self.raw)
def __hash__(self):
return hash(self.raw)
@@ -105,15 +116,7 @@
for r in raw[2:]:
if r == "throws": target = self.throws
else: target.append(r)
-
- # identity for compat purposes
- ident = self.raw
- ident = ident.replace(" deprecated ", " ")
- ident = ident.replace(" synchronized ", " ")
- ident = re.sub("<.+?>", "", ident)
- if " throws " in ident:
- ident = ident[:ident.index(" throws ")]
- self.ident = ident
+ self.ident = ident(self.raw)
def __hash__(self):
return hash(self.raw)
@@ -1469,6 +1472,40 @@
return failures
+def show_deprecations_at_birth(cur, prev):
+ """Show API deprecations at birth."""
+ global failures
+
+ # Remove all existing things so we're left with new
+ for prev_clazz in prev.values():
+ cur_clazz = cur[prev_clazz.fullname]
+
+ sigs = { i.ident: i for i in prev_clazz.ctors }
+ cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
+ sigs = { i.ident: i for i in prev_clazz.methods }
+ cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
+ sigs = { i.ident: i for i in prev_clazz.fields }
+ cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
+
+ # Forget about class entirely when nothing new
+ if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
+ del cur[prev_clazz.fullname]
+
+ for clazz in cur.values():
+ if " deprecated " in clazz.raw and not clazz.fullname in prev:
+ error(clazz, None, None, "Found API deprecation at birth")
+
+ for i in clazz.ctors + clazz.methods + clazz.fields:
+ if " deprecated " in i.raw:
+ error(clazz, i, None, "Found API deprecation at birth")
+
+ print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
+ format(reset=True)))
+ for f in sorted(failures):
+ print failures[f]
+ print
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Enforces common Android public API design \
patterns. It ignores lint messages from a previous API level, if provided.")
@@ -1481,6 +1518,8 @@
help="Allow references to Google")
parser.add_argument("--show-noticed", action='store_const', const=True,
help="Show API changes noticed")
+ parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
+ help="Show API deprecations at birth")
args = vars(parser.parse_args())
if args['no_color']:
@@ -1492,6 +1531,14 @@
current_file = args['current.txt']
previous_file = args['previous.txt']
+ if args['show_deprecations_at_birth']:
+ with current_file as f:
+ cur = _parse_stream(f)
+ with previous_file as f:
+ prev = _parse_stream(f)
+ show_deprecations_at_birth(cur, prev)
+ sys.exit()
+
with current_file as f:
cur_fail, cur_noticed = examine_stream(f)
if not previous_file is None:
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 057772f..4146e02 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -101,6 +101,7 @@
fprintf(out, "// This file is autogenerated\n");
fprintf(out, "\n");
+ fprintf(out, "#include <mutex>\n");
fprintf(out, "#include <chrono>\n");
fprintf(out, "#include <thread>\n");
fprintf(out, "#include <log/log_event_list.h>\n");
@@ -150,9 +151,7 @@
fprintf(out, "};\n");
fprintf(out, "\n");
- fprintf(out,
- "static std::map<int, int> "
- "getAtomUidField() {\n");
+ fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
fprintf(out, " std::map<int, int> uidField;\n");
for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
atom != atoms.decls.end(); atom++) {
@@ -206,6 +205,11 @@
"AtomsInfo::kStateAtomsFieldOptions = "
"getStateAtomFieldOptions();\n");
+
+ fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
+ fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
+ fprintf(out, "static std::mutex mLogdRetryMutex;\n");
+
// Print write methods
fprintf(out, "\n");
for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
@@ -317,7 +321,7 @@
fprintf(out, "{\n");
fprintf(out, " int ret = 0;\n");
- fprintf(out, " for(int retry = 0; retry < 3; ++retry) {\n");
+ fprintf(out, " for(int retry = 0; retry < 2; ++retry) {\n");
fprintf(out, " ret = try_stats_write(code");
argIndex = 1;
@@ -340,8 +344,15 @@
}
fprintf(out, ");\n");
fprintf(out, " if (ret >= 0) { return retry; }\n");
- fprintf(out,
- " std::this_thread::sleep_for(std::chrono::milliseconds(10 + 10 * retry));\n");
+
+
+ fprintf(out, " {\n");
+ fprintf(out, " std::lock_guard<std::mutex> lock(mLogdRetryMutex);\n");
+ fprintf(out, " if ((android::elapsedRealtimeNano() - lastRetryTimestampNs) <= "
+ "kMinRetryIntervalNs) break;\n");
+ fprintf(out, " lastRetryTimestampNs = android::elapsedRealtimeNano();\n");
+ fprintf(out, " }\n");
+ fprintf(out, " std::this_thread::sleep_for(std::chrono::milliseconds(10));\n");
fprintf(out, " }\n");
fprintf(out, " return ret;\n");
fprintf(out, "}\n");
@@ -408,7 +419,7 @@
fprintf(out, "{\n");
fprintf(out, " int ret = 0;\n");
- fprintf(out, " for(int retry = 0; retry < 3; ++retry) {\n");
+ fprintf(out, " for(int retry = 0; retry < 2; ++retry) {\n");
fprintf(out, " ret = try_stats_write_non_chained(code");
argIndex = 1;
@@ -419,8 +430,15 @@
}
fprintf(out, ");\n");
fprintf(out, " if (ret >= 0) { return retry; }\n");
- fprintf(out,
- " std::this_thread::sleep_for(std::chrono::milliseconds(10 + 10 * retry));\n");
+
+ fprintf(out, " {\n");
+ fprintf(out, " std::lock_guard<std::mutex> lock(mLogdRetryMutex);\n");
+ fprintf(out, " if ((android::elapsedRealtimeNano() - lastRetryTimestampNs) <= "
+ "kMinRetryIntervalNs) break;\n");
+ fprintf(out, " lastRetryTimestampNs = android::elapsedRealtimeNano();\n");
+ fprintf(out, " }\n");
+
+ fprintf(out, " std::this_thread::sleep_for(std::chrono::milliseconds(10));\n");
fprintf(out, " }\n");
fprintf(out, " return ret;\n");
fprintf(out, "}\n");