Merge "Device Default theming updates for Wear" into oc-mr1-dev
diff --git a/Android.mk b/Android.mk
index 960f868..1e6041f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -269,6 +269,8 @@
core/java/android/os/IRecoverySystemProgressListener.aidl \
core/java/android/os/IRemoteCallback.aidl \
core/java/android/os/ISchedulingPolicyService.aidl \
+ core/java/android/os/IThermalEventListener.aidl \
+ core/java/android/os/IThermalService.aidl \
core/java/android/os/IUpdateLock.aidl \
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 8d2e9c8..3c9cd8e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10506,6 +10506,7 @@
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
method public void removeSplit(java.lang.String) throws java.io.IOException;
method public void setStagingProgress(float);
+ method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static abstract class PackageInstaller.SessionCallback {
@@ -10523,10 +10524,16 @@
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public int getInstallLocation();
method public int getInstallReason();
method public java.lang.String getInstallerPackageName();
+ method public int getMode();
+ method public int getOriginatingUid();
+ method public android.net.Uri getOriginatingUri();
method public float getProgress();
+ method public android.net.Uri getReferrerUri();
method public int getSessionId();
+ method public long getSize();
method public boolean isActive();
method public boolean isSealed();
method public void writeToParcel(android.os.Parcel, int);
@@ -37082,6 +37089,13 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.LuhnChecksumValidator> CREATOR;
}
+ public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator {
+ ctor public RegexValidator(android.view.autofill.AutofillId, java.util.regex.Pattern);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.RegexValidator> CREATOR;
+ }
+
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
method public void onSuccess();
@@ -37122,13 +37136,6 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
- public final class SimpleRegexValidator implements android.os.Parcelable android.service.autofill.Validator {
- ctor public SimpleRegexValidator(android.view.autofill.AutofillId, java.util.regex.Pattern);
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.SimpleRegexValidator> CREATOR;
- }
-
public abstract interface Transformation {
}
diff --git a/api/system-current.txt b/api/system-current.txt
index e2754fd..6c1983d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11180,12 +11180,14 @@
method public void abandon();
method public void close();
method public void commit(android.content.IntentSender);
+ method public void commitTransferred(android.content.IntentSender);
method public void fsync(java.io.OutputStream) throws java.io.IOException;
method public java.lang.String[] getNames() throws java.io.IOException;
method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException;
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
method public void removeSplit(java.lang.String) throws java.io.IOException;
method public void setStagingProgress(float);
+ method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static abstract class PackageInstaller.SessionCallback {
@@ -11200,13 +11202,26 @@
public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
method public android.content.Intent createDetailsIntent();
method public int describeContents();
+ method public boolean getAllocateAggressive();
+ method public boolean getAllowDowngrade();
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public boolean getDontKillApp();
+ method public java.lang.String[] getGrantedRuntimePermissions();
+ method public boolean getInstallAsFullApp(boolean);
+ method public boolean getInstallAsInstantApp(boolean);
+ method public boolean getInstallAsVirtualPreload();
+ method public int getInstallLocation();
method public int getInstallReason();
method public java.lang.String getInstallerPackageName();
+ method public int getMode();
+ method public int getOriginatingUid();
+ method public android.net.Uri getOriginatingUri();
method public float getProgress();
+ method public android.net.Uri getReferrerUri();
method public int getSessionId();
+ method public long getSize();
method public boolean isActive();
method public boolean isSealed();
method public void writeToParcel(android.os.Parcel, int);
@@ -17249,10 +17264,8 @@
field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
- field public static final int IDENTIFIER_TYPE_VENDOR1_PRIMARY = 14; // 0xe
- field public static final int IDENTIFIER_TYPE_VENDOR2_PRIMARY = 15; // 0xf
- field public static final int IDENTIFIER_TYPE_VENDOR3_PRIMARY = 16; // 0x10
- field public static final int IDENTIFIER_TYPE_VENDOR4_PRIMARY = 17; // 0x11
+ field public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
+ field public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
field public static final int PROGRAM_TYPE_AM = 1; // 0x1
field public static final int PROGRAM_TYPE_AM_HD = 3; // 0x3
field public static final int PROGRAM_TYPE_DAB = 5; // 0x5
@@ -17260,10 +17273,8 @@
field public static final int PROGRAM_TYPE_FM = 2; // 0x2
field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4
field public static final int PROGRAM_TYPE_SXM = 7; // 0x7
- field public static final int PROGRAM_TYPE_VENDOR1 = 8; // 0x8
- field public static final int PROGRAM_TYPE_VENDOR2 = 9; // 0x9
- field public static final int PROGRAM_TYPE_VENDOR3 = 10; // 0xa
- field public static final int PROGRAM_TYPE_VENDOR4 = 11; // 0xb
+ field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf
+ field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8
}
public static final class ProgramSelector.Identifier implements android.os.Parcelable {
@@ -40272,6 +40283,13 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.LuhnChecksumValidator> CREATOR;
}
+ public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator {
+ ctor public RegexValidator(android.view.autofill.AutofillId, java.util.regex.Pattern);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.RegexValidator> CREATOR;
+ }
+
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
method public void onSuccess();
@@ -40312,13 +40330,6 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
- public final class SimpleRegexValidator implements android.os.Parcelable android.service.autofill.Validator {
- ctor public SimpleRegexValidator(android.view.autofill.AutofillId, java.util.regex.Pattern);
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.SimpleRegexValidator> CREATOR;
- }
-
public abstract interface Transformation {
}
diff --git a/api/test-current.txt b/api/test-current.txt
index fcb404e..839964f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -10544,6 +10544,7 @@
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
method public void removeSplit(java.lang.String) throws java.io.IOException;
method public void setStagingProgress(float);
+ method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static abstract class PackageInstaller.SessionCallback {
@@ -10561,10 +10562,16 @@
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public int getInstallLocation();
method public int getInstallReason();
method public java.lang.String getInstallerPackageName();
+ method public int getMode();
+ method public int getOriginatingUid();
+ method public android.net.Uri getOriginatingUri();
method public float getProgress();
+ method public android.net.Uri getReferrerUri();
method public int getSessionId();
+ method public long getSize();
method public boolean isActive();
method public boolean isSealed();
method public void writeToParcel(android.os.Parcel, int);
@@ -37274,6 +37281,14 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.LuhnChecksumValidator> CREATOR;
}
+ public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator {
+ ctor public RegexValidator(android.view.autofill.AutofillId, java.util.regex.Pattern);
+ method public int describeContents();
+ method public boolean isValid(android.service.autofill.ValueFinder);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.RegexValidator> CREATOR;
+ }
+
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
method public void onSuccess();
@@ -37314,14 +37329,6 @@
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
- public final class SimpleRegexValidator implements android.os.Parcelable android.service.autofill.Validator {
- ctor public SimpleRegexValidator(android.view.autofill.AutofillId, java.util.regex.Pattern);
- method public int describeContents();
- method public boolean isValid(android.service.autofill.ValueFinder);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.autofill.SimpleRegexValidator> CREATOR;
- }
-
public abstract interface Transformation {
}
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index 371cd12..f99d1a8 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -19,6 +19,7 @@
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.system.ErrnoException;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -26,6 +27,8 @@
import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;
+import libcore.io.Libcore;
+
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -151,22 +154,50 @@
// The dex path is not a secondary dex file. Nothing to do.
return;
}
- File secondaryProfile = getSecondaryProfileFile(dexPath);
+
+ File realDexPath;
try {
- // Create the profile if not already there.
- // Returns true if the file was created, false if the file already exists.
- // or throws exceptions in case of errors.
+ // Secondary dex profiles are stored in the oat directory, next to the real dex file
+ // and have the same name with 'cur.prof' appended. We use the realpath because that
+ // is what installd is using when processing the dex file.
+ // NOTE: Keep in sync with installd.
+ realDexPath = new File(Libcore.os.realpath(dexPath));
+ } catch (ErrnoException ex) {
+ Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath
+ + ":" + ex.getMessage());
+ // Do not continue with registration if we could not retrieve the real path.
+ return;
+ }
+
+ // NOTE: Keep this in sync with installd expectations.
+ File secondaryProfileDir = new File(realDexPath.getParent(), "oat");
+ File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof");
+
+ // Create the profile if not already there.
+ // Returns true if the file was created, false if the file already exists.
+ // or throws exceptions in case of errors.
+ if (!secondaryProfileDir.exists()) {
+ if (!secondaryProfileDir.mkdir()) {
+ Slog.e(TAG, "Could not create the profile directory: " + secondaryProfile);
+ // Do not continue with registration if we could not create the oat dir.
+ return;
+ }
+ }
+
+ try {
boolean created = secondaryProfile.createNewFile();
if (DEBUG && created) {
Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile);
}
} catch (IOException ex) {
- Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile +
- ":" + ex.getMessage());
- // Don't move forward with the registration if we failed to create the profile.
+ Slog.e(TAG, "Failed to create profile for secondary dex " + dexPath
+ + ":" + ex.getMessage());
+ // Do not continue with registration if we could not create the profile files.
return;
}
+ // If we got here, the dex paths is a secondary dex and we were able to create the profile.
+ // Register the path to the runtime.
VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath });
}
@@ -180,11 +211,4 @@
}
return false;
}
-
- // Secondary dex profiles are stored next to the dex file and have the same
- // name with '.prof' appended.
- // NOTE: Keep in sync with installd.
- private File getSecondaryProfileFile(String dexPath) {
- return new File(dexPath + ".prof");
- }
}
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 2a3fac3..0b16852 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -32,6 +32,7 @@
void removeSplit(String splitName);
void close();
- void commit(in IntentSender statusReceiver);
+ void commit(in IntentSender statusReceiver, boolean forTransferred);
+ void transfer(in String packageName);
void abandon();
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c3ebf55..f4fdcaa 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -38,6 +38,7 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.system.ErrnoException;
@@ -793,7 +794,7 @@
* @throws IOException if trouble opening the file for writing, such as
* lack of disk space or unavailable media.
* @throws SecurityException if called after the session has been
- * committed or abandoned.
+ * sealed or abandoned
*/
public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
long lengthBytes) throws IOException {
@@ -918,7 +919,68 @@
*/
public void commit(@NonNull IntentSender statusReceiver) {
try {
- mSession.commit(statusReceiver);
+ mSession.commit(statusReceiver, false);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempt to commit a session that has been {@link #transfer(String) transferred}.
+ *
+ * <p>If the device reboots before the session has been finalized, you may commit the
+ * session again.
+ *
+ * <p>The caller of this method is responsible to ensure the safety of the session. As the
+ * session was created by another - usually less trusted - app, it is paramount that before
+ * committing <u>all</u> public and system {@link SessionInfo properties of the session}
+ * and <u>all</u> {@link #openRead(String) APKs} are verified by the caller. It might happen
+ * that new properties are added to the session with a new API revision. In this case the
+ * callers need to be updated.
+ *
+ * @param statusReceiver Callbacks called when the state of the session changes.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)
+ public void commitTransferred(@NonNull IntentSender statusReceiver) {
+ try {
+ mSession.commit(statusReceiver, true);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Transfer the session to a new owner.
+ * <p>
+ * Only sessions that update the installing app can be transferred.
+ * <p>
+ * After the transfer to a package with a different uid all method calls on the session
+ * will cause {@link SecurityException}s.
+ * <p>
+ * Once this method is called, the session is sealed and no additional mutations beside
+ * committing it may be performed on the session.
+ *
+ * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES
+ * permission.
+ *
+ * @throws PackageManager.NameNotFoundException if the new owner could not be found.
+ * @throws SecurityException if called after the session has been committed or abandoned.
+ * @throws SecurityException if the session does not update the original installer
+ * @throws SecurityException if streams opened through
+ * {@link #openWrite(String, long, long) are still open.
+ */
+ public void transfer(@NonNull String packageName)
+ throws PackageManager.NameNotFoundException {
+ Preconditions.checkNotNull(packageName);
+
+ try {
+ mSession.transfer(packageName);
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1041,6 +1103,26 @@
}
/**
+ * Check if there are hidden options set.
+ *
+ * <p>Hidden options are those options that cannot be verified via public or system-api
+ * methods on {@link SessionInfo}.
+ *
+ * @return {@code true} if any hidden option is set.
+ *
+ * @hide
+ */
+ public boolean areHiddenOptionsSet() {
+ return (installFlags & (PackageManager.INSTALL_ALLOW_DOWNGRADE
+ | PackageManager.INSTALL_DONT_KILL_APP
+ | PackageManager.INSTALL_INSTANT_APP
+ | PackageManager.INSTALL_FULL_APP
+ | PackageManager.INSTALL_VIRTUAL_PRELOAD
+ | PackageManager.INSTALL_ALLOCATE_AGGRESSIVE)) != installFlags
+ || abiOverride != null || volumeUuid != null;
+ }
+
+ /**
* Provide value of {@link PackageInfo#installLocation}, which may be used
* to determine where the app will be staged. Defaults to
* {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
@@ -1300,6 +1382,19 @@
public CharSequence appLabel;
/** {@hide} */
+ public int installLocation;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ public int originatingUid;
+ /** {@hide} */
+ public Uri referrerUri;
+ /** {@hide} */
+ public String[] grantedRuntimePermissions;
+ /** {@hide} */
+ public int installFlags;
+
+ /** {@hide} */
public SessionInfo() {
}
@@ -1318,6 +1413,13 @@
appPackageName = source.readString();
appIcon = source.readParcelable(null);
appLabel = source.readString();
+
+ installLocation = source.readInt();
+ originatingUri = source.readParcelable(null);
+ originatingUid = source.readInt();
+ referrerUri = source.readParcelable(null);
+ grantedRuntimePermissions = source.readStringArray();
+ installFlags = source.readInt();
}
/**
@@ -1441,6 +1543,130 @@
return intent;
}
+ /**
+ * Get the mode of the session as set in the constructor of the {@link SessionParams}.
+ *
+ * @return One of {@link SessionParams#MODE_FULL_INSTALL}
+ * or {@link SessionParams#MODE_INHERIT_EXISTING}
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setInstallLocation(int)}.
+ */
+ public int getInstallLocation() {
+ return installLocation;
+ }
+
+ /**
+ * Get the value as set in {@link SessionParams#setSize(long)}.
+ *
+ * <p>The value is a hint and does not have to match the actual size.
+ */
+ public long getSize() {
+ return sizeBytes;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUri(Uri)}.
+ */
+ public @Nullable Uri getOriginatingUri() {
+ return originatingUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUid(int)}.
+ */
+ public int getOriginatingUid() {
+ return originatingUid;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setReferrerUri(Uri)}
+ */
+ public @Nullable Uri getReferrerUri() {
+ return referrerUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setGrantedRuntimePermissions(String[])}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String[] getGrantedRuntimePermissions() {
+ return grantedRuntimePermissions;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllowDowngrade() {
+ return (installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setDontKillApp(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getDontKillApp() {
+ return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code true},
+ * return true. If it was called with {@code false} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsFullApp
+ */
+ @SystemApi
+ public boolean getInstallAsInstantApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code false},
+ * return true. If it was called with {@code true} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsInstantApp
+ */
+ @SystemApi
+ public boolean getInstallAsFullApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
+ }
+
+ /**
+ * Get if {@link SessionParams#setInstallAsVirtualPreload()} was called.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getInstallAsVirtualPreload() {
+ return (installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllocateAggressive(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllocateAggressive() {
+ return (installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0;
+ }
+
+
/** {@hide} */
@Deprecated
public @Nullable Intent getDetailsIntent() {
@@ -1467,6 +1693,13 @@
dest.writeString(appPackageName);
dest.writeParcelable(appIcon, flags);
dest.writeString(appLabel != null ? appLabel.toString() : null);
+
+ dest.writeInt(installLocation);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeInt(originatingUid);
+ dest.writeParcelable(referrerUri, flags);
+ dest.writeStringArray(grantedRuntimePermissions);
+ dest.writeInt(installFlags);
}
public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ea03727..f6eaed4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2295,9 +2295,7 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
- * {@link #hasSystemFeature}: The device supports Wi-Fi Passpoint and all
- * Passpoint related APIs in {@link WifiManager} are supported. Refer to
- * {@link WifiManager#addOrUpdatePasspointConfiguration} for more info.
+ * {@link #hasSystemFeature}: The device supports Wi-Fi Passpoint.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e8eca8c..4ae1aaf 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -50,6 +50,8 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageParserCacheHelper.ReadHelper;
+import android.content.pm.PackageParserCacheHelper.WriteHelper;
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
import android.content.pm.split.SplitAssetLoader;
@@ -121,6 +123,7 @@
import java.util.List;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;
@@ -235,6 +238,11 @@
private static final boolean LOG_UNSAFE_BROADCASTS = false;
+ /**
+ * Total number of packages that were read from the cache. We use it only for logging.
+ */
+ public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger();
+
// Set of broadcast actions that are safe for manifest receivers
private static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
static {
@@ -1035,21 +1043,45 @@
}
@VisibleForTesting
- protected Package fromCacheEntry(byte[] bytes) throws IOException {
- Parcel p = Parcel.obtain();
+ protected Package fromCacheEntry(byte[] bytes) {
+ return fromCacheEntryStatic(bytes);
+ }
+
+ /** static version of {@link #fromCacheEntry} for unit tests. */
+ @VisibleForTesting
+ public static Package fromCacheEntryStatic(byte[] bytes) {
+ final Parcel p = Parcel.obtain();
p.unmarshall(bytes, 0, bytes.length);
p.setDataPosition(0);
+ final ReadHelper helper = new ReadHelper(p);
+ helper.startAndInstall();
+
PackageParser.Package pkg = new PackageParser.Package(p);
+
p.recycle();
+ sCachedPackageReadCount.incrementAndGet();
+
return pkg;
}
@VisibleForTesting
- protected byte[] toCacheEntry(Package pkg) throws IOException {
- Parcel p = Parcel.obtain();
+ protected byte[] toCacheEntry(Package pkg) {
+ return toCacheEntryStatic(pkg);
+
+ }
+
+ /** static version of {@link #toCacheEntry} for unit tests. */
+ @VisibleForTesting
+ public static byte[] toCacheEntryStatic(Package pkg) {
+ final Parcel p = Parcel.obtain();
+ final WriteHelper helper = new WriteHelper(p);
+
pkg.writeToParcel(p, 0 /* flags */);
+
+ helper.finishAndUninstall();
+
byte[] serialized = p.marshall();
p.recycle();
@@ -1146,13 +1178,7 @@
}
}
- final byte[] cacheEntry;
- try {
- cacheEntry = toCacheEntry(parsed);
- } catch (IOException ioe) {
- Slog.e(TAG, "Unable to serialize parsed package for: " + packageFile);
- return;
- }
+ final byte[] cacheEntry = toCacheEntry(parsed);
if (cacheEntry == null) {
return;
@@ -6421,8 +6447,11 @@
dest.writeStringList(requestedPermissions);
dest.writeStringList(protectedBroadcasts);
+
+ // TODO: This doesn't work: b/64295061
dest.writeParcelable(parentPackage, flags);
dest.writeParcelableList(childPackages, flags);
+
dest.writeString(staticSharedLibName);
dest.writeInt(staticSharedLibVersion);
dest.writeStringList(libraryNames);
diff --git a/core/java/android/content/pm/PackageParserCacheHelper.java b/core/java/android/content/pm/PackageParserCacheHelper.java
new file mode 100644
index 0000000..44def33
--- /dev/null
+++ b/core/java/android/content/pm/PackageParserCacheHelper.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Helper classes to read from and write to Parcel with pooled strings.
+ *
+ * @hide
+ */
+public class PackageParserCacheHelper {
+ private PackageParserCacheHelper() {
+ }
+
+ private static final String TAG = "PackageParserCacheHelper";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Parcel read helper with a string pool.
+ */
+ public static class ReadHelper extends Parcel.ReadWriteHelper {
+ private final ArrayList<String> mStrings = new ArrayList<>();
+
+ private final Parcel mParcel;
+
+ public ReadHelper(Parcel p) {
+ mParcel = p;
+ }
+
+ /**
+ * Prepare to read from a parcel, and install itself as a read-write helper.
+ *
+ * (We don't do it in the constructor to avoid calling methods before the constructor
+ * finishes.)
+ */
+ public void startAndInstall() {
+ mStrings.clear();
+
+ final int poolPosition = mParcel.readInt();
+ final int startPosition = mParcel.dataPosition();
+
+ // The pool is at the end of the parcel.
+ mParcel.setDataPosition(poolPosition);
+ mParcel.readStringList(mStrings);
+
+ // Then move back.
+ mParcel.setDataPosition(startPosition);
+
+ if (DEBUG) {
+ Log.i(TAG, "Read " + mStrings.size() + " strings");
+ for (int i = 0; i < mStrings.size(); i++) {
+ Log.i(TAG, " " + i + ": \"" + mStrings.get(i) + "\"");
+ }
+ }
+
+ mParcel.setReadWriteHelper(this);
+ }
+
+ /**
+ * Read an string index from a parcel, and returns the corresponding string from the pool.
+ */
+ @Override
+ public String readString(Parcel p) {
+ return mStrings.get(p.readInt());
+ }
+ }
+
+ /**
+ * Parcel write helper with a string pool.
+ */
+ public static class WriteHelper extends Parcel.ReadWriteHelper {
+ private final ArrayList<String> mStrings = new ArrayList<>();
+
+ private final HashMap<String, Integer> mIndexes = new HashMap<>();
+
+ private final Parcel mParcel;
+ private final int mStartPos;
+
+ /**
+ * Constructor. Prepare a parcel, and install it self as a read-write helper.
+ */
+ public WriteHelper(Parcel p) {
+ mParcel = p;
+ mStartPos = p.dataPosition();
+ mParcel.writeInt(0); // We come back later here and write the pool position.
+
+ mParcel.setReadWriteHelper(this);
+ }
+
+ /**
+ * Instead of writing a string directly to a parcel, this method adds it to the pool,
+ * and write the index in the pool to the parcel.
+ */
+ @Override
+ public void writeString(Parcel p, String s) {
+ final Integer cur = mIndexes.get(s);
+ if (cur != null) {
+ // String already in the pool. Just write the index.
+ p.writeInt(cur); // Already in the pool.
+ if (DEBUG) {
+ Log.i(TAG, "Duplicate '" + s + "' at " + cur);
+ }
+ } else {
+ // Not in the pool. Add to the pool, and write the index.
+ final int index = mStrings.size();
+ mIndexes.put(s, index);
+ mStrings.add(s);
+
+ if (DEBUG) {
+ Log.i(TAG, "New '" + s + "' at " + index);
+ }
+
+ p.writeInt(index);
+ }
+ }
+
+ /**
+ * Closes a parcel by appending the string pool at the end and updating the pool offset,
+ * which it assumes is at the first byte. It also uninstalls itself as a read-write helper.
+ */
+ public void finishAndUninstall() {
+ // Uninstall first, so that writeStringList() uses the native writeString.
+ mParcel.setReadWriteHelper(null);
+
+ final int poolPosition = mParcel.dataPosition();
+ mParcel.writeStringList(mStrings);
+
+ mParcel.setDataPosition(mStartPos);
+ mParcel.writeInt(poolPosition);
+
+ // Move back to the end.
+ mParcel.setDataPosition(mParcel.dataSize());
+ if (DEBUG) {
+ Log.i(TAG, "Wrote " + mStrings.size() + " strings");
+ }
+ }
+ }
+}
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 94c25b3..2211cee 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -17,6 +17,7 @@
package android.hardware.radio;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -73,10 +74,8 @@
/** SiriusXM Satellite Radio. */
public static final int PROGRAM_TYPE_SXM = 7;
/** Vendor-specific, not synced across devices. */
- public static final int PROGRAM_TYPE_VENDOR1 = 8;
- public static final int PROGRAM_TYPE_VENDOR2 = 9;
- public static final int PROGRAM_TYPE_VENDOR3 = 10;
- public static final int PROGRAM_TYPE_VENDOR4 = 11;
+ public static final int PROGRAM_TYPE_VENDOR_START = 1000;
+ public static final int PROGRAM_TYPE_VENDOR_END = 1999;
@IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
PROGRAM_TYPE_AM,
PROGRAM_TYPE_FM,
@@ -85,11 +84,8 @@
PROGRAM_TYPE_DAB,
PROGRAM_TYPE_DRMO,
PROGRAM_TYPE_SXM,
- PROGRAM_TYPE_VENDOR1,
- PROGRAM_TYPE_VENDOR2,
- PROGRAM_TYPE_VENDOR3,
- PROGRAM_TYPE_VENDOR4,
})
+ @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END)
@Retention(RetentionPolicy.SOURCE)
public @interface ProgramType {}
@@ -145,12 +141,12 @@
* Primary identifier for vendor-specific radio technology.
* The value format is determined by a vendor.
*
- * It must not be used in any other programType than VENDORx.
+ * It must not be used in any other programType than corresponding VENDOR
+ * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
+ * not be used in any program type other than 1015).
*/
- public static final int IDENTIFIER_TYPE_VENDOR1_PRIMARY = 14;
- public static final int IDENTIFIER_TYPE_VENDOR2_PRIMARY = 15;
- public static final int IDENTIFIER_TYPE_VENDOR3_PRIMARY = 16;
- public static final int IDENTIFIER_TYPE_VENDOR4_PRIMARY = 17;
+ public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START;
+ public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END;
@IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
IDENTIFIER_TYPE_AMFM_FREQUENCY,
IDENTIFIER_TYPE_RDS_PI,
@@ -165,11 +161,8 @@
IDENTIFIER_TYPE_DRMO_MODULATION,
IDENTIFIER_TYPE_SXM_SERVICE_ID,
IDENTIFIER_TYPE_SXM_CHANNEL,
- IDENTIFIER_TYPE_VENDOR1_PRIMARY,
- IDENTIFIER_TYPE_VENDOR2_PRIMARY,
- IDENTIFIER_TYPE_VENDOR3_PRIMARY,
- IDENTIFIER_TYPE_VENDOR4_PRIMARY,
})
+ @IntRange(from = IDENTIFIER_TYPE_VENDOR_PRIMARY_START, to = IDENTIFIER_TYPE_VENDOR_PRIMARY_END)
@Retention(RetentionPolicy.SOURCE)
public @interface IdentifierType {}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index a5763ef..5312dca 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Log;
@@ -23,6 +24,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.io.Serializable;
@@ -94,7 +96,8 @@
private ClassLoader mClassLoader;
/** {@hide} */
- int mFlags;
+ @VisibleForTesting
+ public int mFlags;
/**
* Constructs a new, empty Bundle that uses a specific ClassLoader for
@@ -218,59 +221,72 @@
*/
/* package */ void unparcel() {
synchronized (this) {
- final Parcel parcelledData = mParcelledData;
- if (parcelledData == null) {
- if (DEBUG) Log.d(TAG, "unparcel "
- + Integer.toHexString(System.identityHashCode(this))
- + ": no parcelled data");
- return;
- }
-
- if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
- Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
- + "clobber all data inside!", new Throwable());
- }
-
- if (isEmptyParcel()) {
- if (DEBUG) Log.d(TAG, "unparcel "
- + Integer.toHexString(System.identityHashCode(this)) + ": empty");
- if (mMap == null) {
- mMap = new ArrayMap<>(1);
- } else {
- mMap.erase();
- }
- mParcelledData = null;
- return;
- }
-
- int N = parcelledData.readInt();
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
- + ": reading " + N + " maps");
- if (N < 0) {
- return;
- }
- ArrayMap<String, Object> map = mMap;
- if (map == null) {
- map = new ArrayMap<>(N);
+ final Parcel source = mParcelledData;
+ if (source != null) {
+ initializeFromParcelLocked(source, /*recycleParcel=*/ true);
} else {
- map.erase();
- map.ensureCapacity(N);
- }
- try {
- parcelledData.readArrayMapInternal(map, N, mClassLoader);
- } catch (BadParcelableException e) {
- if (sShouldDefuse) {
- Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
- map.erase();
- } else {
- throw e;
+ if (DEBUG) {
+ Log.d(TAG, "unparcel "
+ + Integer.toHexString(System.identityHashCode(this))
+ + ": no parcelled data");
}
- } finally {
- mMap = map;
- parcelledData.recycle();
- mParcelledData = null;
}
- if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ }
+ }
+
+ private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel) {
+ if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
+ Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
+ + "clobber all data inside!", new Throwable());
+ }
+
+ if (isEmptyParcel(parcelledData)) {
+ if (DEBUG) {
+ Log.d(TAG, "unparcel "
+ + Integer.toHexString(System.identityHashCode(this)) + ": empty");
+ }
+ if (mMap == null) {
+ mMap = new ArrayMap<>(1);
+ } else {
+ mMap.erase();
+ }
+ mParcelledData = null;
+ return;
+ }
+
+ final int count = parcelledData.readInt();
+ if (DEBUG) {
+ Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ + ": reading " + count + " maps");
+ }
+ if (count < 0) {
+ return;
+ }
+ ArrayMap<String, Object> map = mMap;
+ if (map == null) {
+ map = new ArrayMap<>(count);
+ } else {
+ map.erase();
+ map.ensureCapacity(count);
+ }
+ try {
+ parcelledData.readArrayMapInternal(map, count, mClassLoader);
+ } catch (BadParcelableException e) {
+ if (sShouldDefuse) {
+ Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
+ map.erase();
+ } else {
+ throw e;
+ }
+ } finally {
+ mMap = map;
+ if (recycleParcel) {
+ recycleParcel(parcelledData);
+ }
+ mParcelledData = null;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ " final map: " + mMap);
}
}
@@ -286,7 +302,20 @@
* @hide
*/
public boolean isEmptyParcel() {
- return mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL;
+ return isEmptyParcel(mParcelledData);
+ }
+
+ /**
+ * @hide
+ */
+ private static boolean isEmptyParcel(Parcel p) {
+ return p == NoImagePreloadHolder.EMPTY_PARCEL;
+ }
+
+ private static void recycleParcel(Parcel p) {
+ if (p != null && !isEmptyParcel(p)) {
+ p.recycle();
+ }
}
/** @hide */
@@ -1476,6 +1505,10 @@
* @param parcel The parcel to copy this bundle to.
*/
void writeToParcelInner(Parcel parcel, int flags) {
+ // If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
+ if (parcel.hasReadWriteHelper()) {
+ unparcel();
+ }
// Keep implementation in sync with writeToParcel() in
// frameworks/native/libs/binder/PersistableBundle.cpp.
final ArrayMap<String, Object> map;
@@ -1544,6 +1577,15 @@
+ Integer.toHexString(magic));
}
+ if (parcel.hasReadWriteHelper()) {
+ // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
+ // unparcel right away.
+ synchronized (this) {
+ initializeFromParcelLocked(parcel, /*recycleParcel=*/ false);
+ }
+ return;
+ }
+
// Advance within this Parcel
int offset = parcel.dataPosition();
parcel.setDataPosition(MathUtils.addOrThrow(offset, length));
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cc6b5e1..6d44330 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -206,8 +206,15 @@
* - CPU frequency time per uid
* New in version 22:
* - BLE scan result background count, BLE unoptimized scan time
+ * - Background partial wakelock time & count
+ * New in version 23:
+ * - Logging smeared power model values
+ * New in version 24:
+ * - Fixed bugs in background timers and BLE scan time
+ * New in version 25:
+ * - Package wakeup alarms are now on screen-off timebase
*/
- static final String CHECKIN_VERSION = "24";
+ static final String CHECKIN_VERSION = "25";
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -686,7 +693,7 @@
public abstract long getSystemCpuTimeUs(int which);
/**
- * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed for a
+ * Returns the approximate cpu time (in microseconds) spent at a certain CPU speed for a
* given CPU cluster.
* @param cluster the index of the CPU cluster.
* @param step the index of the CPU speed. This is not the actual speed of the CPU.
@@ -3544,7 +3551,12 @@
if (name.indexOf(',') >= 0) {
name = name.replace(',', '_');
}
- name = name.replaceAll("[\\n|\\r]+", "");
+ if (name.indexOf('\n') >= 0) {
+ name = name.replace('\n', '_');
+ }
+ if (name.indexOf('\r') >= 0) {
+ name = name.replace('\r', '_');
+ }
dumpLine(pw, uid, category, WAKELOCK_DATA, name, sb.toString());
}
}
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 51f96ee..c58153a 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -22,6 +22,8 @@
import android.util.SizeF;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -32,9 +34,14 @@
* @see PersistableBundle
*/
public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
- private static final int FLAG_HAS_FDS = 1 << 8;
- private static final int FLAG_HAS_FDS_KNOWN = 1 << 9;
- private static final int FLAG_ALLOW_FDS = 1 << 10;
+ @VisibleForTesting
+ static final int FLAG_HAS_FDS = 1 << 8;
+
+ @VisibleForTesting
+ static final int FLAG_HAS_FDS_KNOWN = 1 << 9;
+
+ @VisibleForTesting
+ static final int FLAG_ALLOW_FDS = 1 << 10;
public static final Bundle EMPTY;
@@ -65,20 +72,42 @@
* will be unparcelled on first contact, using the assigned ClassLoader.
*
* @param parcelledData a Parcel containing a Bundle
+ *
+ * @hide
*/
- Bundle(Parcel parcelledData) {
+ @VisibleForTesting
+ public Bundle(Parcel parcelledData) {
super(parcelledData);
- mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
- if (mParcelledData.hasFileDescriptors()) {
- mFlags |= FLAG_HAS_FDS;
- }
+ mFlags = FLAG_ALLOW_FDS;
+ maybePrefillHasFds();
}
- /* package */ Bundle(Parcel parcelledData, int length) {
+ /**
+ * Constructor from a parcel for when the length is known *and is not stored in the parcel.*
+ * The other constructor that takes a parcel assumes the length is in the parcel.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public Bundle(Parcel parcelledData, int length) {
super(parcelledData, length);
- mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
- if (mParcelledData.hasFileDescriptors()) {
- mFlags |= FLAG_HAS_FDS;
+ mFlags = FLAG_ALLOW_FDS;
+ maybePrefillHasFds();
+ }
+
+ /**
+ * If {@link #mParcelledData} is not null, copy the HAS FDS bit from it because it's fast.
+ * Otherwise (if {@link #mParcelledData} is already null), leave {@link #FLAG_HAS_FDS_KNOWN}
+ * unset, because scanning a map is slower. We'll do it lazily in
+ * {@link #hasFileDescriptors()}.
+ */
+ private void maybePrefillHasFds() {
+ if (mParcelledData != null) {
+ if (mParcelledData.hasFileDescriptors()) {
+ mFlags |= FLAG_HAS_FDS | FLAG_HAS_FDS_KNOWN;
+ } else {
+ mFlags |= FLAG_HAS_FDS_KNOWN;
+ }
}
}
@@ -1213,10 +1242,8 @@
*/
public void readFromParcel(Parcel parcel) {
super.readFromParcelInner(parcel);
- mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
- if (mParcelledData.hasFileDescriptors()) {
- mFlags |= FLAG_HAS_FDS;
- }
+ mFlags = FLAG_ALLOW_FDS;
+ maybePrefillHasFds();
}
@Override
diff --git a/core/java/android/os/IThermalEventListener.aidl b/core/java/android/os/IThermalEventListener.aidl
new file mode 100644
index 0000000..9a6de60
--- /dev/null
+++ b/core/java/android/os/IThermalEventListener.aidl
@@ -0,0 +1,32 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.os.Temperature;
+
+/**
+ * Listener for thermal events.
+ * {@hide}
+ */
+oneway interface IThermalEventListener {
+ /**
+ * Called when a thermal throttling start/stop event is received.
+ * @param temperature the temperature at which the event was generated.
+ */
+ void notifyThrottling(
+ in boolean isThrottling, in Temperature temperature);
+}
diff --git a/core/java/android/os/IThermalService.aidl b/core/java/android/os/IThermalService.aidl
new file mode 100644
index 0000000..e388eda
--- /dev/null
+++ b/core/java/android/os/IThermalService.aidl
@@ -0,0 +1,51 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.os.IThermalEventListener;
+import android.os.Temperature;
+
+/**
+ * {@hide}
+ */
+interface IThermalService {
+ /**
+ * Register a listener for thermal events.
+ * @param listener the IThermalEventListener to be notified.
+ * {@hide}
+ */
+ void registerThermalEventListener(in IThermalEventListener listener);
+ /**
+ * Unregister a previously-registered listener for thermal events.
+ * @param listener the IThermalEventListener to no longer be notified.
+ * {@hide}
+ */
+ void unregisterThermalEventListener(in IThermalEventListener listener);
+ /**
+ * Send a thermal throttling start/stop notification to all listeners.
+ * @param temperature the temperature at which the event was generated.
+ * {@hide}
+ */
+ oneway void notifyThrottling(
+ in boolean isThrottling, in Temperature temperature);
+ /**
+ * Return whether system performance is currently thermal throttling.
+ * @return true if thermal throttling is currently in effect
+ * {@hide}
+ */
+ boolean isThrottling();
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2efb0f5..fae9d53 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -291,7 +291,7 @@
private static native void nativeWriteFloat(long nativePtr, float val);
@FastNative
private static native void nativeWriteDouble(long nativePtr, double val);
- private static native void nativeWriteString(long nativePtr, String val);
+ static native void nativeWriteString(long nativePtr, String val);
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
@@ -306,7 +306,7 @@
private static native float nativeReadFloat(long nativePtr);
@CriticalNative
private static native double nativeReadDouble(long nativePtr);
- private static native String nativeReadString(long nativePtr);
+ static native String nativeReadString(long nativePtr);
private static native IBinder nativeReadStrongBinder(long nativePtr);
private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
@@ -339,6 +339,33 @@
};
/**
+ * @hide
+ */
+ public static class ReadWriteHelper {
+ public static final ReadWriteHelper DEFAULT = new ReadWriteHelper();
+
+ /**
+ * Called when writing a string to a parcel. Subclasses wanting to write a string
+ * must use {@link #writeStringNoHelper(String)} to avoid
+ * infinity recursive calls.
+ */
+ public void writeString(Parcel p, String s) {
+ nativeWriteString(p.mNativePtr, s);
+ }
+
+ /**
+ * Called when reading a string to a parcel. Subclasses wanting to read a string
+ * must use {@link #readStringNoHelper()} to avoid
+ * infinity recursive calls.
+ */
+ public String readString(Parcel p) {
+ return nativeReadString(p.mNativePtr);
+ }
+ }
+
+ private ReadWriteHelper mReadWriteHelper = ReadWriteHelper.DEFAULT;
+
+ /**
* Retrieve a new Parcel object from the pool.
*/
public static Parcel obtain() {
@@ -352,6 +379,7 @@
if (DEBUG_RECYCLE) {
p.mStack = new RuntimeException();
}
+ p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
return p;
}
}
@@ -385,6 +413,25 @@
}
}
+ /**
+ * Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for
+ * example.
+ *
+ * @hide
+ */
+ public void setReadWriteHelper(ReadWriteHelper helper) {
+ mReadWriteHelper = helper != null ? helper : ReadWriteHelper.DEFAULT;
+ }
+
+ /**
+ * @return whether this parcel has a {@link ReadWriteHelper}.
+ *
+ * @hide
+ */
+ public boolean hasReadWriteHelper() {
+ return (mReadWriteHelper != null) && (mReadWriteHelper != ReadWriteHelper.DEFAULT);
+ }
+
/** @hide */
public static native long getGlobalAllocSize();
@@ -625,6 +672,17 @@
* growing dataCapacity() if needed.
*/
public final void writeString(String val) {
+ mReadWriteHelper.writeString(this, val);
+ }
+
+ /**
+ * Write a string without going though a {@link ReadWriteHelper}. Subclasses of
+ * {@link ReadWriteHelper} must use this method instead of {@link #writeString} to avoid
+ * infinity recursive calls.
+ *
+ * @hide
+ */
+ public void writeStringNoHelper(String val) {
nativeWriteString(mNativePtr, val);
}
@@ -1997,6 +2055,17 @@
* Read a string value from the parcel at the current dataPosition().
*/
public final String readString() {
+ return mReadWriteHelper.readString(this);
+ }
+
+ /**
+ * Read a string without going though a {@link ReadWriteHelper}. Subclasses of
+ * {@link ReadWriteHelper} must use this method instead of {@link #readString} to avoid
+ * infinity recursive calls.
+ *
+ * @hide
+ */
+ public String readStringNoHelper() {
return nativeReadString(mNativePtr);
}
@@ -2997,6 +3066,7 @@
if (mOwnsNativeParcelObject) {
updateNativeSize(nativeFreeBuffer(mNativePtr));
}
+ mReadWriteHelper = ReadWriteHelper.DEFAULT;
}
private void destroy() {
@@ -3007,6 +3077,7 @@
}
mNativePtr = 0;
}
+ mReadWriteHelper = null;
}
@Override
diff --git a/core/java/android/os/Temperature.aidl b/core/java/android/os/Temperature.aidl
new file mode 100644
index 0000000..708c08f
--- /dev/null
+++ b/core/java/android/os/Temperature.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+parcelable Temperature;
diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java
new file mode 100644
index 0000000..3e48493
--- /dev/null
+++ b/core/java/android/os/Temperature.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Temperature values used by IThermalService.
+ */
+
+/**
+ * @hide
+ */
+public class Temperature implements Parcelable {
+ /* Temperature value */
+ private float mValue;
+ /* A temperature type from HardwarePropertiesManager */
+ private int mType;
+
+ public Temperature() {
+ mType = Integer.MIN_VALUE;
+ mValue = HardwarePropertiesManager.UNDEFINED_TEMPERATURE;
+ }
+
+ public Temperature(float value, int type) {
+ mValue = value;
+ mType = type;
+ }
+
+ /**
+ * Return the temperature value.
+ * @return a temperature value in floating point.
+ */
+ public float getValue() {
+ return mValue;
+ }
+
+ /**
+ * Return the temperature type.
+ * @return a temperature type:
+ * HardwarePropertiesManager.DEVICE_TEMPERATURE_CPU, etc.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /*
+ * Parcel read/write code must be kept in sync with
+ * frameworks/native/services/thermalservice/aidl/android/os/
+ * Temperature.cpp
+ */
+
+ private Temperature(Parcel p) {
+ readFromParcel(p);
+ }
+
+ /**
+ * Fill in Temperature members from a Parcel.
+ * @param p the parceled Temperature object.
+ */
+ public void readFromParcel(Parcel p) {
+ mValue = p.readFloat();
+ mType = p.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel p, int flags) {
+ p.writeFloat(mValue);
+ p.writeInt(mType);
+ }
+
+ public static final Parcelable.Creator<Temperature> CREATOR =
+ new Parcelable.Creator<Temperature>() {
+ @Override
+ public Temperature createFromParcel(Parcel p) {
+ return new Temperature(p);
+ }
+
+ @Override
+ public Temperature[] newArray(int size) {
+ return new Temperature[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java
index 0edb154..3da0b5e 100644
--- a/core/java/android/service/autofill/CustomDescription.java
+++ b/core/java/android/service/autofill/CustomDescription.java
@@ -32,7 +32,7 @@
*
* <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
* for example, when the screen contains a credit card, it could display a logo of the credit card
- * bank, the last for digits of the credit card number, and its expiration number.
+ * bank, the last four digits of the credit card number, and its expiration number.
*
* <p>A custom description is made of 2 parts:
* <ul>
@@ -63,16 +63,16 @@
* // Image child - different logo for each bank, based on credit card prefix
* builder.addChild(R.id.templateccLogo,
* new ImageTransformation.Builder(ccNumberId)
- * .addOption("^4815.*$", R.drawable.ic_credit_card_logo1)
- * .addOption("^1623.*$", R.drawable.ic_credit_card_logo2)
- * .addOption("^42.*$", R.drawable.ic_credit_card_logo3);
+ * .addOption(Pattern.compile(""^4815.*$"), R.drawable.ic_credit_card_logo1)
+ * .addOption(Pattern.compile(""^1623.*$"), R.drawable.ic_credit_card_logo2)
+ * .addOption(Pattern.compile(""^42.*$"), R.drawable.ic_credit_card_logo3);
* // Masked credit card number (as .....LAST_4_DIGITS)
* builder.addChild(R.id.templateCcNumber, new CharSequenceTransformation.Builder()
- * .addField(ccNumberId, "^.*(\\d\\d\\d\\d)$", "...$1")
+ * .addField(ccNumberId, Pattern.compile(""^.*(\\d\\d\\d\\d)$"), "...$1")
* // Expiration date as MM / YYYY:
* builder.addChild(R.id.templateExpDate, new CharSequenceTransformation.Builder()
- * .addField(ccExpMonthId, "^(\\d\\d)$", "Exp: $1")
- * .addField(ccExpYearId, "^(\\d\\d)$", "/$1");
+ * .addField(ccExpMonthId, Pattern.compile(""^(\\d\\d)$"), "Exp: $1")
+ * .addField(ccExpYearId, Pattern.compile(""^(\\d\\d)$"), "/$1");
* </pre>
*
* <p>See {@link ImageTransformation}, {@link CharSequenceTransformation} for more info about these
diff --git a/core/java/android/service/autofill/SimpleRegexValidator.java b/core/java/android/service/autofill/RegexValidator.java
similarity index 79%
rename from core/java/android/service/autofill/SimpleRegexValidator.java
rename to core/java/android/service/autofill/RegexValidator.java
index ef8c52c9..9dfe78d 100644
--- a/core/java/android/service/autofill/SimpleRegexValidator.java
+++ b/core/java/android/service/autofill/RegexValidator.java
@@ -34,9 +34,9 @@
*
* <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
*/
-public final class SimpleRegexValidator extends InternalValidator implements Validator, Parcelable {
+public final class RegexValidator extends InternalValidator implements Validator, Parcelable {
- private static final String TAG = "SimpleRegexValidator";
+ private static final String TAG = "RegexValidator";
private final AutofillId mId;
private final Pattern mRegex;
@@ -49,7 +49,7 @@
* matches the contents of the field identified by {@code id}, it returns {@code true};
* otherwise, it returns {@code false}.
*/
- public SimpleRegexValidator(@NonNull AutofillId id, @NonNull Pattern regex) {
+ public RegexValidator(@NonNull AutofillId id, @NonNull Pattern regex) {
mId = Preconditions.checkNotNull(id);
mRegex = Preconditions.checkNotNull(regex);
}
@@ -76,7 +76,7 @@
public String toString() {
if (!sDebug) return super.toString();
- return "SimpleRegexValidator: [id=" + mId + ", regex=" + mRegex + "]";
+ return "RegexValidator: [id=" + mId + ", regex=" + mRegex + "]";
}
/////////////////////////////////////
@@ -93,17 +93,17 @@
parcel.writeSerializable(mRegex);
}
- public static final Parcelable.Creator<SimpleRegexValidator> CREATOR =
- new Parcelable.Creator<SimpleRegexValidator>() {
+ public static final Parcelable.Creator<RegexValidator> CREATOR =
+ new Parcelable.Creator<RegexValidator>() {
@Override
- public SimpleRegexValidator createFromParcel(Parcel parcel) {
- return new SimpleRegexValidator(parcel.readParcelable(null),
+ public RegexValidator createFromParcel(Parcel parcel) {
+ return new RegexValidator(parcel.readParcelable(null),
(Pattern) parcel.readSerializable());
}
@Override
- public SimpleRegexValidator[] newArray(int size) {
- return new SimpleRegexValidator[size];
+ public RegexValidator[] newArray(int size) {
+ return new RegexValidator[size];
}
};
}
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index f8a94d6..e0a0730 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -470,7 +470,7 @@
* <p>Validator for a credit number that must have exactly 16 digits:
*
* <pre class="prettyprint">
- * Validator validator = new SimpleRegexValidator(ccNumberId, "^\\d{16}$")
+ * Validator validator = new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$"))
* </pre>
*
* <p>Validator for a credit number that must pass a Luhn checksum and either have
@@ -483,8 +483,8 @@
* and(
* new LuhnChecksumValidator(ccNumberId),
* or(
- * new SimpleRegexValidator(ccNumberId, "^\\d{16}$"),
- * new SimpleRegexValidator(ccNumberId, "^108\\d{12}$")
+ * new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$")),
+ * new RegexValidator(ccNumberId, Pattern.compile(""^108\\d{12}$"))
* )
* );
* </pre>
@@ -496,7 +496,7 @@
* Validator validator =
* and(
* new LuhnChecksumValidator(ccNumberId),
- * new SimpleRegexValidator(ccNumberId, "^(\\d{16}|108\\d{12})$")
+ * new RegexValidator(ccNumberId, Pattern.compile(""^(\\d{16}|108\\d{12})$"))
* );
* </pre>
*
@@ -508,10 +508,10 @@
*
* Validator validator =
* and(
- * new SimpleRegexValidator(ccNumberId1, "^\\d{4}$"),
- * new SimpleRegexValidator(ccNumberId2, "^\\d{4}$"),
- * new SimpleRegexValidator(ccNumberId3, "^\\d{4}$"),
- * new SimpleRegexValidator(ccNumberId4, "^\\d{4}$")
+ * new RegexValidator(ccNumberId1, Pattern.compile(""^\\d{4}$")),
+ * new RegexValidator(ccNumberId2, Pattern.compile(""^\\d{4}$")),
+ * new RegexValidator(ccNumberId3, Pattern.compile(""^\\d{4}$")),
+ * new RegexValidator(ccNumberId4, Pattern.compile(""^\\d{4}$"))
* );
* </pre>
*
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 44c88e1..1a2968f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -65,7 +65,8 @@
private static native void nativeSetSize(long nativeObject, int w, int h);
private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
private static native void nativeSetAlpha(long nativeObject, float alpha);
- private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx, float dsdy, float dtdy);
+ private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx,
+ float dtdy, float dsdy);
private static native void nativeSetFlags(long nativeObject, int flags, int mask);
private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e59cf84..611c4b8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -119,7 +119,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 162 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 164 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -5707,7 +5707,7 @@
LongSamplingCounter mUserCpuTime;
LongSamplingCounter mSystemCpuTime;
- LongSamplingCounter[][] mCpuClusterSpeed;
+ LongSamplingCounter[][] mCpuClusterSpeedTimesUs;
LongSamplingCounterArray mCpuFreqTimeMs;
LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
@@ -6556,12 +6556,12 @@
@Override
public long getTimeAtCpuSpeed(int cluster, int step, int which) {
- if (mCpuClusterSpeed != null) {
- if (cluster >= 0 && cluster < mCpuClusterSpeed.length) {
- final LongSamplingCounter[] cpuSpeeds = mCpuClusterSpeed[cluster];
- if (cpuSpeeds != null) {
- if (step >= 0 && step < cpuSpeeds.length) {
- final LongSamplingCounter c = cpuSpeeds[step];
+ if (mCpuClusterSpeedTimesUs != null) {
+ if (cluster >= 0 && cluster < mCpuClusterSpeedTimesUs.length) {
+ final LongSamplingCounter[] cpuSpeedTimesUs = mCpuClusterSpeedTimesUs[cluster];
+ if (cpuSpeedTimesUs != null) {
+ if (step >= 0 && step < cpuSpeedTimesUs.length) {
+ final LongSamplingCounter c = cpuSpeedTimesUs[step];
if (c != null) {
return c.getCountLocked(which);
}
@@ -6712,8 +6712,8 @@
mUserCpuTime.reset(false);
mSystemCpuTime.reset(false);
- if (mCpuClusterSpeed != null) {
- for (LongSamplingCounter[] speeds : mCpuClusterSpeed) {
+ if (mCpuClusterSpeedTimesUs != null) {
+ for (LongSamplingCounter[] speeds : mCpuClusterSpeedTimesUs) {
if (speeds != null) {
for (LongSamplingCounter speed : speeds) {
if (speed != null) {
@@ -6902,8 +6902,8 @@
mUserCpuTime.detach();
mSystemCpuTime.detach();
- if (mCpuClusterSpeed != null) {
- for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
+ if (mCpuClusterSpeedTimesUs != null) {
+ for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeedTimesUs) {
if (cpuSpeeds != null) {
for (LongSamplingCounter c : cpuSpeeds) {
if (c != null) {
@@ -7156,10 +7156,10 @@
mUserCpuTime.writeToParcel(out);
mSystemCpuTime.writeToParcel(out);
- if (mCpuClusterSpeed != null) {
+ if (mCpuClusterSpeedTimesUs != null) {
out.writeInt(1);
- out.writeInt(mCpuClusterSpeed.length);
- for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeed) {
+ out.writeInt(mCpuClusterSpeedTimesUs.length);
+ for (LongSamplingCounter[] cpuSpeeds : mCpuClusterSpeedTimesUs) {
if (cpuSpeeds != null) {
out.writeInt(1);
out.writeInt(cpuSpeeds.length);
@@ -7453,7 +7453,7 @@
throw new ParcelFormatException("Incompatible number of cpu clusters");
}
- mCpuClusterSpeed = new LongSamplingCounter[numCpuClusters][];
+ mCpuClusterSpeedTimesUs = new LongSamplingCounter[numCpuClusters][];
for (int cluster = 0; cluster < numCpuClusters; cluster++) {
if (in.readInt() != 0) {
int numSpeeds = in.readInt();
@@ -7463,18 +7463,19 @@
}
final LongSamplingCounter[] cpuSpeeds = new LongSamplingCounter[numSpeeds];
- mCpuClusterSpeed[cluster] = cpuSpeeds;
+ mCpuClusterSpeedTimesUs[cluster] = cpuSpeeds;
for (int speed = 0; speed < numSpeeds; speed++) {
if (in.readInt() != 0) {
- cpuSpeeds[speed] = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+ cpuSpeeds[speed] = new LongSamplingCounter(
+ mBsi.mOnBatteryTimeBase, in);
}
}
} else {
- mCpuClusterSpeed[cluster] = null;
+ mCpuClusterSpeedTimesUs[cluster] = null;
}
}
} else {
- mCpuClusterSpeed = null;
+ mCpuClusterSpeedTimesUs = null;
}
mCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(in, mBsi.mOnBatteryTimeBase);
@@ -8043,6 +8044,7 @@
/**
* Number of times wakeup alarms have occurred for this app.
+ * On screen-off timebase starting in report v25.
*/
ArrayMap<String, Counter> mWakeupAlarms = new ArrayMap<>();
@@ -8071,7 +8073,7 @@
mWakeupAlarms.clear();
for (int i=0; i<numWA; i++) {
String tag = in.readString();
- mWakeupAlarms.put(tag, new Counter(mBsi.mOnBatteryTimeBase, in));
+ mWakeupAlarms.put(tag, new Counter(mBsi.mOnBatteryScreenOffTimeBase, in));
}
int numServs = in.readInt();
@@ -8110,7 +8112,7 @@
public void noteWakeupAlarmLocked(String tag) {
Counter c = mWakeupAlarms.get(tag);
if (c == null) {
- c = new Counter(mBsi.mOnBatteryTimeBase);
+ c = new Counter(mBsi.mOnBatteryScreenOffTimeBase);
mWakeupAlarms.put(tag, c);
}
c.stepAtomic();
@@ -10429,46 +10431,48 @@
}
}
- long totalCpuClustersTime = 0;
+ long totalCpuClustersTimeMs = 0;
// Read the time spent for each cluster at various cpu frequencies.
- final long[][] clusterSpeeds = new long[mKernelCpuSpeedReaders.length][];
+ final long[][] clusterSpeedTimesMs = new long[mKernelCpuSpeedReaders.length][];
for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
- clusterSpeeds[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
- if (clusterSpeeds[cluster] != null) {
- for (int speed = clusterSpeeds[cluster].length - 1; speed >= 0; --speed) {
- totalCpuClustersTime += clusterSpeeds[cluster][speed];
+ clusterSpeedTimesMs[cluster] = mKernelCpuSpeedReaders[cluster].readDelta();
+ if (clusterSpeedTimesMs[cluster] != null) {
+ for (int speed = clusterSpeedTimesMs[cluster].length - 1; speed >= 0; --speed) {
+ totalCpuClustersTimeMs += clusterSpeedTimesMs[cluster][speed];
}
}
}
- if (totalCpuClustersTime != 0) {
+ if (totalCpuClustersTimeMs != 0) {
// We have cpu times per freq aggregated over all uids but we need the times per uid.
// So, we distribute total time spent by an uid to different cpu freqs based on the
// amount of time cpu was running at that freq.
final int updatedUidsCount = updatedUids.size();
for (int i = 0; i < updatedUidsCount; ++i) {
final Uid u = getUidStatsLocked(updatedUids.keyAt(i));
- final long appCpuTimeMs = updatedUids.valueAt(i) / 1000;
+ final long appCpuTimeUs = updatedUids.valueAt(i);
// Add the cpu speeds to this UID.
final int numClusters = mPowerProfile.getNumCpuClusters();
- if (u.mCpuClusterSpeed == null || u.mCpuClusterSpeed.length !=
+ if (u.mCpuClusterSpeedTimesUs == null || u.mCpuClusterSpeedTimesUs.length !=
numClusters) {
- u.mCpuClusterSpeed = new LongSamplingCounter[numClusters][];
+ u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
}
- for (int cluster = 0; cluster < clusterSpeeds.length; cluster++) {
- final int speedsInCluster = clusterSpeeds[cluster].length;
- if (u.mCpuClusterSpeed[cluster] == null || speedsInCluster !=
- u.mCpuClusterSpeed[cluster].length) {
- u.mCpuClusterSpeed[cluster] = new LongSamplingCounter[speedsInCluster];
+ for (int cluster = 0; cluster < clusterSpeedTimesMs.length; cluster++) {
+ final int speedsInCluster = clusterSpeedTimesMs[cluster].length;
+ if (u.mCpuClusterSpeedTimesUs[cluster] == null || speedsInCluster !=
+ u.mCpuClusterSpeedTimesUs[cluster].length) {
+ u.mCpuClusterSpeedTimesUs[cluster]
+ = new LongSamplingCounter[speedsInCluster];
}
- final LongSamplingCounter[] cpuSpeeds = u.mCpuClusterSpeed[cluster];
+ final LongSamplingCounter[] cpuSpeeds = u.mCpuClusterSpeedTimesUs[cluster];
for (int speed = 0; speed < speedsInCluster; speed++) {
if (cpuSpeeds[speed] == null) {
cpuSpeeds[speed] = new LongSamplingCounter(mOnBatteryTimeBase);
}
- cpuSpeeds[speed].addCountLocked(appCpuTimeMs * clusterSpeeds[cluster][speed]
- / totalCpuClustersTime);
+ cpuSpeeds[speed].addCountLocked(appCpuTimeUs
+ * clusterSpeedTimesMs[cluster][speed]
+ / totalCpuClustersTimeMs);
}
}
}
@@ -11752,7 +11756,7 @@
throw new ParcelFormatException("Incompatible cpu cluster arrangement");
}
- u.mCpuClusterSpeed = new LongSamplingCounter[numClusters][];
+ u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
for (int cluster = 0; cluster < numClusters; cluster++) {
if (in.readInt() != 0) {
final int NSB = in.readInt();
@@ -11762,20 +11766,20 @@
NSB);
}
- u.mCpuClusterSpeed[cluster] = new LongSamplingCounter[NSB];
+ u.mCpuClusterSpeedTimesUs[cluster] = new LongSamplingCounter[NSB];
for (int speed = 0; speed < NSB; speed++) {
if (in.readInt() != 0) {
- u.mCpuClusterSpeed[cluster][speed] = new LongSamplingCounter(
+ u.mCpuClusterSpeedTimesUs[cluster][speed] = new LongSamplingCounter(
mOnBatteryTimeBase);
- u.mCpuClusterSpeed[cluster][speed].readSummaryFromParcelLocked(in);
+ u.mCpuClusterSpeedTimesUs[cluster][speed].readSummaryFromParcelLocked(in);
}
}
} else {
- u.mCpuClusterSpeed[cluster] = null;
+ u.mCpuClusterSpeedTimesUs[cluster] = null;
}
}
} else {
- u.mCpuClusterSpeed = null;
+ u.mCpuClusterSpeedTimesUs = null;
}
u.mCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
@@ -11867,7 +11871,7 @@
p.mWakeupAlarms.clear();
for (int iwa=0; iwa<NWA; iwa++) {
String tag = in.readString();
- Counter c = new Counter(mOnBatteryTimeBase);
+ Counter c = new Counter(mOnBatteryScreenOffTimeBase);
c.readSummaryFromParcelLocked(in);
p.mWakeupAlarms.put(tag, c);
}
@@ -12183,10 +12187,10 @@
u.mUserCpuTime.writeSummaryFromParcelLocked(out);
u.mSystemCpuTime.writeSummaryFromParcelLocked(out);
- if (u.mCpuClusterSpeed != null) {
+ if (u.mCpuClusterSpeedTimesUs != null) {
out.writeInt(1);
- out.writeInt(u.mCpuClusterSpeed.length);
- for (LongSamplingCounter[] cpuSpeeds : u.mCpuClusterSpeed) {
+ out.writeInt(u.mCpuClusterSpeedTimesUs.length);
+ for (LongSamplingCounter[] cpuSpeeds : u.mCpuClusterSpeedTimesUs) {
if (cpuSpeeds != null) {
out.writeInt(1);
out.writeInt(cpuSpeeds.length);
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 9d52e19..bb743c1 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -22,6 +22,7 @@
public class CpuPowerCalculator extends PowerCalculator {
private static final String TAG = "CpuPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private static final long MICROSEC_IN_HR = (long) 60 * 60 * 1000 * 1000;
private final PowerProfile mProfile;
public CpuPowerCalculator(PowerProfile profile) {
@@ -35,21 +36,22 @@
app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
final int numClusters = mProfile.getNumCpuClusters();
- double cpuPowerMaMs = 0;
+ double cpuPowerMaUs = 0;
for (int cluster = 0; cluster < numClusters; cluster++) {
final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
for (int speed = 0; speed < speedsForCluster; speed++) {
- final double cpuSpeedStepPower = u.getTimeAtCpuSpeed(cluster, speed, statsType) *
+ final long timeUs = u.getTimeAtCpuSpeed(cluster, speed, statsType);
+ final double cpuSpeedStepPower = timeUs *
mProfile.getAveragePowerForCpu(cluster, speed);
if (DEBUG) {
Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
- + speed + " power="
- + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+ + speed + " timeUs=" + timeUs + " power="
+ + BatteryStatsHelper.makemAh(cpuSpeedStepPower / MICROSEC_IN_HR));
}
- cpuPowerMaMs += cpuSpeedStepPower;
+ cpuPowerMaUs += cpuSpeedStepPower;
}
}
- app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);
+ app.cpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + app.cpuTimeMs + " ms power="
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index 9c7debb..757a112 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -39,8 +39,8 @@
private static final String TAG = "KernelCpuSpeedReader";
private final String mProcFile;
- private final long[] mLastSpeedTimes;
- private final long[] mDeltaSpeedTimes;
+ private final long[] mLastSpeedTimesMs;
+ private final long[] mDeltaSpeedTimesMs;
// How long a CPU jiffy is in milliseconds.
private final long mJiffyMillis;
@@ -51,8 +51,8 @@
public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
cpuNumber);
- mLastSpeedTimes = new long[numSpeedSteps];
- mDeltaSpeedTimes = new long[numSpeedSteps];
+ mLastSpeedTimesMs = new long[numSpeedSteps];
+ mDeltaSpeedTimesMs = new long[numSpeedSteps];
long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
mJiffyMillis = 1000/jiffyHz;
}
@@ -68,27 +68,27 @@
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
String line;
int speedIndex = 0;
- while (speedIndex < mLastSpeedTimes.length && (line = reader.readLine()) != null) {
+ while (speedIndex < mLastSpeedTimesMs.length && (line = reader.readLine()) != null) {
splitter.setString(line);
- Long.parseLong(splitter.next());
+ splitter.next();
long time = Long.parseLong(splitter.next()) * mJiffyMillis;
- if (time < mLastSpeedTimes[speedIndex]) {
+ if (time < mLastSpeedTimesMs[speedIndex]) {
// The stats reset when the cpu hotplugged. That means that the time
// we read is offset from 0, so the time is the delta.
- mDeltaSpeedTimes[speedIndex] = time;
+ mDeltaSpeedTimesMs[speedIndex] = time;
} else {
- mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex];
+ mDeltaSpeedTimesMs[speedIndex] = time - mLastSpeedTimesMs[speedIndex];
}
- mLastSpeedTimes[speedIndex] = time;
+ mLastSpeedTimesMs[speedIndex] = time;
speedIndex++;
}
} catch (IOException e) {
Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
- Arrays.fill(mDeltaSpeedTimes, 0);
+ Arrays.fill(mDeltaSpeedTimesMs, 0);
} finally {
StrictMode.setThreadPolicy(policy);
}
- return mDeltaSpeedTimes;
+ return mDeltaSpeedTimesMs;
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 9462a06..e6c49f1 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -67,8 +67,7 @@
private static final String TAG = "LockPatternUtils";
private static final boolean DEBUG = false;
- private static final boolean FRP_CREDENTIAL_ENABLED =
- Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.frpcredential.enable", false);
+ private static final boolean FRP_CREDENTIAL_ENABLED = true;
/**
* The key to identify when the lock pattern enabled flag is being accessed for legacy reasons.
diff --git a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml b/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml
index 4b01736..2c430e0 100644
--- a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml
+++ b/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml
@@ -40,15 +40,31 @@
<application
android:hasCode="true">
+ <meta-data android:name="key1" android:value="value1" />
+ <meta-data android:name="key2" android:value="this_is_app" />
+
<activity
android:name="com.android.frameworks.coretests.TestActivity">
+ <meta-data android:name="key1" android:value="value1" />
+ <meta-data android:name="key2" android:value="this_is_activity" />
</activity>
<provider
android:name="com.android.frameworks.coretests.TestProvider"
- android:authorities="com.android.frameworks.coretests.testprovider" />
+ android:authorities="com.android.frameworks.coretests.testprovider" >
+ <meta-data android:name="key1" android:value="value1" />
+ <meta-data android:name="key2" android:value="this_is_provider" />
+ </provider>
+
<receiver
- android:name="com.android.frameworks.coretests.TestReceiver" />
+ android:name="com.android.frameworks.coretests.TestReceiver" >
+ <meta-data android:name="key1" android:value="value1" />
+ <meta-data android:name="key2" android:value="this_is_receiver" />
+ </receiver>
+
<service
- android:name="com.android.frameworks.coretests.TestService" />
+ android:name="com.android.frameworks.coretests.TestService" >
+ <meta-data android:name="key1" android:value="value1" />
+ <meta-data android:name="key2" android:value="this_is_service" />
+ </service>
</application>
</manifest>
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
new file mode 100644
index 0000000..00be822
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.pm.PackageParserCacheHelper.ReadHelper;
+import android.content.pm.PackageParserCacheHelper.WriteHelper;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PackageParserCacheHelperTest {
+ @Test
+ public void testParcelUnparcel() throws Exception {
+ final Bundle source = new Bundle();
+ source.putInt("i1", 123);
+ source.putString("s1", "abcdef");
+ source.putString("s2", "xyz");
+ source.putString("s3", null);
+
+ final Bundle nest = new Bundle();
+ nest.putString("s1", "xyz");
+ source.putBundle("b1", nest);
+
+ final Parcel p = Parcel.obtain();
+ final WriteHelper writeHelper = new WriteHelper(p);
+
+ source.writeToParcel(p, 0);
+ writeHelper.finishAndUninstall();
+
+ p.setDataPosition(0);
+
+ final ReadHelper readHelper = new ReadHelper(p);
+ readHelper.startAndInstall();
+
+ final Bundle dest = new Bundle();
+ dest.readFromParcel(p);
+
+ dest.size(); // Unparcel so that toString() returns the content.
+
+ assertEquals(source.get("i1"), dest.get("i1"));
+ assertEquals(source.get("s1"), dest.get("s1"));
+ assertEquals(source.get("s2"), dest.get("s2"));
+ assertEquals(source.get("s3"), dest.get("s3"));
+ assertEquals(source.getBundle("b1").get("s1"), dest.getBundle("b1").get("s1"));
+ assertEquals(source.keySet().size(), dest.keySet().size());
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 73c153f..fda0f1e 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -26,6 +26,7 @@
import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.Permission;
import android.os.Build;
+import android.os.Bundle;
import android.os.FileUtils;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -39,6 +40,7 @@
import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
+import java.util.function.Function;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -271,22 +273,27 @@
assertEquals(0x0083, finalConfigChanges); // Should be 10000011.
}
+ Package parsePackage(String apkFileName, int apkResourceId) throws Exception {
+ return parsePackage(apkFileName, apkResourceId, p -> p);
+ }
+
/**
* Attempts to parse a package.
*
* APKs are put into coretests/apks/packageparser_*.
*
- * @param apkName temporary file name to store apk extracted from resources
+ * @param apkFileName temporary file name to store apk extracted from resources
* @param apkResourceId identifier of the apk as a resource
*/
- Package parsePackage(String apkFileName, int apkResourceId) throws Exception {
+ Package parsePackage(String apkFileName, int apkResourceId,
+ Function<Package, Package> converter) throws Exception {
// Copy the resource to a file.
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
File outFile = new File(context.getFilesDir(), apkFileName);
try {
InputStream is = context.getResources().openRawResource(apkResourceId);
assertTrue(FileUtils.copyToFile(is, outFile));
- return new PackageParser().parsePackage(outFile, 0 /* flags */);
+ return converter.apply(new PackageParser().parsePackage(outFile, 0 /* flags */));
} finally {
outFile.delete();
}
@@ -331,10 +338,39 @@
assertEquals(protectionLevel, permission.info.protectionLevel);
}
+ private void assertMetadata(Bundle b, String... keysAndValues) {
+ assertTrue("Odd number of elements in keysAndValues", (keysAndValues.length % 2) == 0);
+
+ assertNotNull(b);
+ assertEquals(keysAndValues.length / 2, b.size());
+
+ for (int i = 0; i < keysAndValues.length; i += 2) {
+ final String key = keysAndValues[i];
+ final String value = keysAndValues[i + 1];
+
+ assertEquals(value, b.getString(key));
+ }
+ }
+
+ // TODO Add a "_cached" test for testMultiPackageComponents() too, after fixing b/64295061.
+ // Package.writeToParcel can't handle circular package references.
+
@Test
- public void testPackageWithComponents() throws Exception {
+ public void testPackageWithComponents_no_cache() throws Exception {
+ checkPackageWithComponents(p -> p);
+ }
+
+ @Test
+ public void testPackageWithComponents_cached() throws Exception {
+ checkPackageWithComponents(p ->
+ PackageParser.fromCacheEntryStatic(PackageParser.toCacheEntryStatic(p)));
+ }
+
+ private void checkPackageWithComponents(
+ Function<Package, Package> converter) throws Exception {
Package p = parsePackage(
- "install_complete_package_info.apk", R.raw.install_complete_package_info);
+ "install_complete_package_info.apk", R.raw.install_complete_package_info,
+ converter);
String packageName = "com.android.frameworks.coretests.install_complete_package_info";
assertEquals(packageName, p.packageName);
@@ -344,6 +380,22 @@
packageName, PermissionInfo.PROTECTION_NORMAL, p.permissions.get(0));
assertOneComponentOfEachType("com.android.frameworks.coretests.Test%s", p);
+
+ assertMetadata(p.mAppMetaData,
+ "key1", "value1",
+ "key2", "this_is_app");
+ assertMetadata(p.activities.get(0).metaData,
+ "key1", "value1",
+ "key2", "this_is_activity");
+ assertMetadata(p.services.get(0).metaData,
+ "key1", "value1",
+ "key2", "this_is_service");
+ assertMetadata(p.receivers.get(0).metaData,
+ "key1", "value1",
+ "key2", "this_is_receiver");
+ assertMetadata(p.providers.get(0).metaData,
+ "key1", "value1",
+ "key2", "this_is_provider");
}
@Test
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
new file mode 100644
index 0000000..9fcf96d
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for bundle that requires accessing hidden APS. Tests that can be written only with
+ * public APIs should go in the CTS counterpart.
+ *
+ * Run with:
+ * bit FrameworksCoreTests:android.os.BundleTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BundleTest {
+ /**
+ * Create a test bundle, parcel it and return the parcel.
+ */
+ private Parcel createBundleParcel(boolean withFd) throws Exception {
+ final Bundle source = new Bundle();
+ source.putString("string", "abc");
+ source.putInt("int", 1);
+ if (withFd) {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ pipe[1].close();
+ source.putParcelable("fd", pipe[0]);
+ }
+ final Parcel p = Parcel.obtain();
+ // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+ source.writeToParcel(p, 0);
+ p.setDataPosition(0);
+
+ return p;
+ }
+
+ /**
+ * Verify a bundle generated by {@link #createBundleParcel(boolean)}.
+ */
+ private void checkBundle(Bundle b, boolean withFd) {
+ // First, do the checks without actually unparceling the bundle.
+ // (Note looking into the contents will unparcel a bundle, so we'll do it later.)
+ assertTrue("mParcelledData shouldn't be null here.", b.isParcelled());
+
+ // Make sure FLAG_HAS_FDS and FLAG_HAS_FDS_KNOWN are set/cleared properly.
+ if (withFd) {
+ // FLAG_HAS_FDS and FLAG_HAS_FDS_KNOWN should both be set.
+ assertEquals(Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN,
+ b.mFlags & (Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN));
+ } else {
+ // FLAG_HAS_FDS_KNOWN should be set, bot not FLAG_HAS_FDS.
+ assertEquals(Bundle.FLAG_HAS_FDS_KNOWN,
+ b.mFlags & (Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN));
+ }
+
+ // Then, check the contents.
+ assertEquals("abc", b.getString("string"));
+ assertEquals(1, b.getInt("int"));
+
+ // Make sure FLAG_HAS_FDS and FLAG_HAS_FDS_KNOWN are set/cleared properly.
+ if (withFd) {
+ assertEquals(ParcelFileDescriptor.class, b.getParcelable("fd").getClass());
+ assertEquals(3, b.keySet().size());
+ } else {
+ assertEquals(2, b.keySet().size());
+ }
+ assertFalse(b.isParcelled());
+ }
+
+ @Test
+ public void testCreateFromParcel() throws Exception {
+ boolean withFd;
+ Parcel p;
+ Bundle b;
+ int length;
+
+ withFd = false;
+
+ // new Bundle with p
+ p = createBundleParcel(withFd);
+ checkBundle(new Bundle(p), withFd);
+ p.recycle();
+
+ // new Bundle with p and length
+ p = createBundleParcel(withFd);
+ length = p.readInt();
+ checkBundle(new Bundle(p, length), withFd);
+ p.recycle();
+
+ // readFromParcel()
+ p = createBundleParcel(withFd);
+ b = new Bundle();
+ b.readFromParcel(p);
+ checkBundle(b, withFd);
+ p.recycle();
+
+ // Same test with FDs.
+ withFd = true;
+
+ // new Bundle with p
+ p = createBundleParcel(withFd);
+ checkBundle(new Bundle(p), withFd);
+ p.recycle();
+
+ // new Bundle with p and length
+ p = createBundleParcel(withFd);
+ length = p.readInt();
+ checkBundle(new Bundle(p, length), withFd);
+ p.recycle();
+
+ // readFromParcel()
+ p = createBundleParcel(withFd);
+ b = new Bundle();
+ b.readFromParcel(p);
+ checkBundle(b, withFd);
+ p.recycle();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index 8fd4e31..0294095 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -488,7 +488,7 @@
bi.noteStartSensorLocked(UID, SENSOR_ID);
clocks.realtime += 111;
- clocks.uptime += 1111;
+ clocks.uptime += 111;
// Timebase and timer was on so times have increased.
assertEquals(111_000, timer.getTotalTimeLocked(1000*clocks.realtime, which));
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
index 7fafef7..3a81c13 100644
--- a/data/etc/framework-sysconfig.xml
+++ b/data/etc/framework-sysconfig.xml
@@ -28,4 +28,6 @@
<backup-transport-whitelisted-service
service="android/com.android.internal.backup.LocalTransportService" />
+ <!-- Whitelist of bundled applications which all handle URLs to their websites by default -->
+ <app-link package="com.android.carrierdefaultapp" />
</config>
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 6664456..4ea4e38 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.IntDef;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
@@ -27,6 +28,8 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Map;
@@ -249,7 +252,7 @@
* @return A Bitmap containing a representative video frame, which
* can be null, if such a frame cannot be retrieved.
*/
- public Bitmap getFrameAtTime(long timeUs, int option) {
+ public Bitmap getFrameAtTime(long timeUs, @Option int option) {
if (option < OPTION_PREVIOUS_SYNC ||
option > OPTION_CLOSEST) {
throw new IllegalArgumentException("Unsupported option: " + option);
@@ -286,27 +289,27 @@
* {@link #OPTION_CLOSEST} often has larger performance overhead compared
* to the other options if there is no sync frame located at timeUs.
*
- * @param dst_width expected output bitmap width
- * @param dst_height expected output bitmap height
- * @return A Bitmap of size not larger than dst_width by dst_height containing a
+ * @param dstWidth expected output bitmap width
+ * @param dstHeight expected output bitmap height
+ * @return A Bitmap of size not larger than dstWidth by dstHeight containing a
* scaled video frame, which can be null, if such a frame cannot be retrieved.
* @throws IllegalArgumentException if passed in invalid option or width by height
* is less than or equal to 0.
*/
public Bitmap getScaledFrameAtTime(
- long timeUs, int option, int dst_width, int dst_height) {
+ long timeUs, @Option int option, int dstWidth, int dstHeight) {
if (option < OPTION_PREVIOUS_SYNC ||
option > OPTION_CLOSEST) {
throw new IllegalArgumentException("Unsupported option: " + option);
}
- if (dst_width <= 0) {
- throw new IllegalArgumentException("Invalid width: " + dst_width);
+ if (dstWidth <= 0) {
+ throw new IllegalArgumentException("Invalid width: " + dstWidth);
}
- if (dst_height <= 0) {
- throw new IllegalArgumentException("Invalid height: " + dst_height);
+ if (dstHeight <= 0) {
+ throw new IllegalArgumentException("Invalid height: " + dstHeight);
}
- return _getFrameAtTime(timeUs, option, dst_width, dst_height);
+ return _getFrameAtTime(timeUs, option, dstWidth, dstHeight);
}
/**
@@ -427,6 +430,16 @@
*/
public static final int OPTION_CLOSEST = 0x03;
+ /** @hide */
+ @IntDef(flag = true, prefix = { "OPTION_" }, value = {
+ OPTION_PREVIOUS_SYNC,
+ OPTION_NEXT_SYNC,
+ OPTION_CLOSEST_SYNC,
+ OPTION_CLOSEST,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Option {}
+
/*
* Do not change these metadata key values without updating their
* counterparts in include/media/mediametadataretriever.h!
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index c309133..1cd7b61 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,6 +34,7 @@
<intent-filter>
<action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
<action android:name="com.android.internal.telephony.CARRIER_SIGNAL_RESET" />
+ <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
</receiver>
@@ -43,10 +44,24 @@
android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
android:label="@string/action_bar_label"
android:theme="@style/AppTheme"
- android:configChanges="keyboardHidden|orientation|screenSize" >
+ android:configChanges="keyboardHidden|orientation|screenSize">
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
+
+ <activity-alias
+ android:name="com.android.carrierdefaultapp.URLHandlerActivity"
+ android:targetActivity="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
+ android:enabled="false" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+ <data android:host="*" />
+ </intent-filter>
+ </activity-alias>
</application>
</manifest>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 6194b87..b0052cc 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -20,6 +20,9 @@
import android.app.LoadedApk;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -34,6 +37,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TypedValue;
@@ -68,7 +72,7 @@
private static final boolean DBG = true;
private static final int SOCKET_TIMEOUT_MS = 10 * 1000;
- public static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000;
+ private static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000;
private URL mUrl;
private Network mNetwork;
@@ -188,16 +192,19 @@
CarrierActionUtils.applyCarrierAction(
CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(),
getApplicationContext());
-
+ CarrierActionUtils.applyCarrierAction(
+ CarrierActionUtils.CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER, getIntent(),
+ getApplicationContext());
+ CarrierActionUtils.applyCarrierAction(
+ CarrierActionUtils.CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL, getIntent(),
+ getApplicationContext());
}
finishAndRemoveTask();
}
private URL getUrlForCaptivePortal() {
String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
- if (url.isEmpty()) {
- url = mCm.getCaptivePortalServerUrl();
- }
+ if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl();
final CarrierConfigManager configManager = getApplicationContext()
.getSystemService(CarrierConfigManager.class);
final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
@@ -437,6 +444,27 @@
}
}
+ /**
+ * This alias presents the target activity, CaptivePortalLoginActivity, as a independent
+ * entity with its own intent filter to handle URL links. This alias will be enabled/disabled
+ * dynamically to handle url links based on the network conditions.
+ */
+ public static String getAlias(Context context) {
+ try {
+ PackageInfo p = context.getPackageManager().getPackageInfo(context.getPackageName(),
+ PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS);
+ for (ActivityInfo activityInfo : p.activities) {
+ String targetActivity = activityInfo.targetActivity;
+ if (CaptivePortalLoginActivity.class.getName().equals(targetActivity)) {
+ return activityInfo.name;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
private static void logd(String s) {
Rlog.d(TAG, s);
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 0213306..a2bf964 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -19,8 +19,10 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.telephony.SubscriptionManager;
@@ -49,6 +51,10 @@
public static final int CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION = 4;
public static final int CARRIER_ACTION_SHOW_NO_DATA_SERVICE_NOTIFICATION = 5;
public static final int CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS = 6;
+ public static final int CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER = 7;
+ public static final int CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER = 8;
+ public static final int CARRIER_ACTION_REGISTER_DEFAULT_NETWORK_AVAIL = 9;
+ public static final int CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL = 10;
public static void applyCarrierAction(int actionIdx, Intent intent, Context context) {
switch (actionIdx) {
@@ -73,6 +79,18 @@
case CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS:
onCancelAllNotifications(context);
break;
+ case CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER:
+ onEnableDefaultURLHandler(context);
+ break;
+ case CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER:
+ onDisableDefaultURLHandler(context);
+ break;
+ case CARRIER_ACTION_REGISTER_DEFAULT_NETWORK_AVAIL:
+ onRegisterDefaultNetworkAvail(intent, context);
+ break;
+ case CARRIER_ACTION_DEREGISTER_DEFAULT_NETWORK_AVAIL:
+ onDeregisterDefaultNetworkAvail(intent, context);
+ break;
default:
loge("unsupported carrier action index: " + actionIdx);
}
@@ -94,6 +112,38 @@
telephonyMgr.carrierActionSetMeteredApnsEnabled(subId, ENABLE);
}
+ private static void onEnableDefaultURLHandler(Context context) {
+ logd("onEnableDefaultURLHandler");
+ final PackageManager pm = context.getPackageManager();
+ pm.setComponentEnabledSetting(
+ new ComponentName(context, CaptivePortalLoginActivity.getAlias(context)),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ }
+
+ private static void onDisableDefaultURLHandler(Context context) {
+ logd("onDisableDefaultURLHandler");
+ final PackageManager pm = context.getPackageManager();
+ pm.setComponentEnabledSetting(
+ new ComponentName(context, CaptivePortalLoginActivity.getAlias(context)),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+
+ private static void onRegisterDefaultNetworkAvail(Intent intent, Context context) {
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultVoiceSubscriptionId());
+ logd("onRegisterDefaultNetworkAvail subId: " + subId);
+ final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+ telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, true);
+ }
+
+ private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultVoiceSubscriptionId());
+ logd("onDeregisterDefaultNetworkAvail subId: " + subId);
+ final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+ telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, false);
+ }
+
private static void onDisableRadio(Intent intent, Context context) {
int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.getDefaultVoiceSubscriptionId());
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index d5d0b79..02c61d7 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -22,7 +22,6 @@
import android.telephony.Rlog;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.ArrayUtils;
@@ -95,6 +94,12 @@
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
break;
+ case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
+ configs = b.getStringArray(CarrierConfigManager
+ .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE);
+ arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents
+ .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
+ break;
default:
Rlog.e(TAG, "load carrier config failure with un-configured key: " +
intent.getAction());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
index 1240e05..cc7798e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -31,4 +31,5 @@
void sendRecentsDrawnEvent();
void sendDockingTopTaskEvent(int dragMode, in Rect initialRect);
void sendLaunchRecentsEvent();
+ void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index a5c994e..d10e080 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -54,9 +54,11 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -612,6 +614,14 @@
}
});
}
+
+ // This will catch the cases when a user launches from recents to another app
+ // (and vice versa) that is not in the recents stack (such as home or bugreport) and it
+ // would not reset the wait for transition flag. This will catch it and make sure that the
+ // flag is reset.
+ if (!event.visible) {
+ mImpl.setWaitingForTransitionStart(false);
+ }
}
/**
@@ -684,6 +694,11 @@
}
}
+ public final void onBusEvent(LaunchTaskFailedEvent event) {
+ // Reset the transition when tasks fail to launch
+ mImpl.setWaitingForTransitionStart(false);
+ }
+
public final void onBusEvent(ConfigurationChangedEvent event) {
// Update the configuration for the Recents component when the activity configuration
// changes as well
@@ -711,6 +726,25 @@
}
}
+ public final void onBusEvent(SetWaitingForTransitionStartEvent event) {
+ int processUser = sSystemServicesProxy.getProcessUser();
+ if (sSystemServicesProxy.isSystemUser(processUser)) {
+ mImpl.setWaitingForTransitionStart(event.waitingForTransitionStart);
+ } else {
+ postToSystemUser(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mUserToSystemCallbacks.setWaitingForTransitionStartEvent(
+ event.waitingForTransitionStart);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ }
+ });
+ }
+ }
+
/**
* Attempts to register with the system user.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 7cd93bb..86e93fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -24,12 +24,11 @@
import android.app.ActivityManager;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -208,6 +207,20 @@
protected static RecentsTaskLoadPlan sInstanceLoadPlan;
// Stores the last pinned task time
protected static long sLastPipTime = -1;
+ // Stores whether we are waiting for a transition to/from recents to start. During this time,
+ // we disallow the user from manually toggling recents until the transition has started.
+ private static boolean mWaitingForTransitionStart = false;
+ // Stores whether or not the user toggled while we were waiting for a transition to/from
+ // recents. In this case, we defer the toggle state until then and apply it immediately after.
+ private static boolean mToggleFollowingTransitionStart = true;
+
+ private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener =
+ new OnAnimationStartedListener() {
+ @Override
+ public void onAnimationStarted() {
+ setWaitingForTransitionStart(false);
+ }
+ };
protected Context mContext;
protected Handler mHandler;
@@ -365,6 +378,11 @@
return;
}
+ if (mWaitingForTransitionStart) {
+ mToggleFollowingTransitionStart = true;
+ return;
+ }
+
mDraggingInRecents = false;
mLaunchedWhileDocking = false;
mTriggeredFromAltTab = false;
@@ -638,6 +656,18 @@
}
}
+ public void setWaitingForTransitionStart(boolean waitingForTransitionStart) {
+ if (mWaitingForTransitionStart == waitingForTransitionStart) {
+ return;
+ }
+
+ mWaitingForTransitionStart = waitingForTransitionStart;
+ if (!waitingForTransitionStart && mToggleFollowingTransitionStart) {
+ toggleRecents(DividerView.INVALID_RECENTS_GROW_TARGET);
+ }
+ mToggleFollowingTransitionStart = false;
+ }
+
/**
* Returns the preloaded load plan and invalidates it.
*/
@@ -865,8 +895,9 @@
}
AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
specs.toArray(specsArray);
+
return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- specsArray, mHandler, null, this), null);
+ specsArray, mHandler, mResetToggleFlagListener, this), null);
} else {
// Update the destination rect
Task toTask = new Task();
@@ -884,8 +915,10 @@
return Lists.newArrayList(new AppTransitionAnimationSpec(
toTask.key.id, thumbnail, rect));
});
+
return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
- mHandler, future.getFuture(), null, false /* scaleUp */), future);
+ mHandler, future.getFuture(), mResetToggleFlagListener, false /* scaleUp */),
+ future);
}
}
@@ -991,6 +1024,10 @@
launchState.launchedToTaskId = runningTaskId;
launchState.launchedWithAltTab = mTriggeredFromAltTab;
+ // Disable toggling of recents between starting the activity and it is visible and the app
+ // has started its transition into recents.
+ setWaitingForTransitionStart(useThumbnailTransition);
+
// Preload the icon (this will be a null-op if we have preloaded the icon already in
// preloadRecents())
preloadIcon(runningTaskId);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
index 3921a20..1285626 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -29,6 +29,7 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.ForegroundThread;
@@ -105,4 +106,10 @@
public void sendLaunchRecentsEvent() throws RemoteException {
EventBus.getDefault().post(new RecentsActivityStartingEvent());
}
+
+ @Override
+ public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
+ EventBus.getDefault().post(new SetWaitingForTransitionStartEvent(
+ waitingForTransitionStart));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java
new file mode 100644
index 0000000..d9cf5fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.component;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when we are setting/resetting the flag to wait for the transition to start.
+ */
+public class SetWaitingForTransitionStartEvent extends EventBus.Event {
+
+ public final boolean waitingForTransitionStart;
+
+ public SetWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
+ this.waitingForTransitionStart = waitingForTransitionStart;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index ce40f20..127822a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -54,6 +54,7 @@
import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -117,31 +118,58 @@
final Rect windowRect = Recents.getSystemServices().getWindowRect();
transitionFuture = getAppTransitionFuture(
() -> composeAnimationSpecs(task, stackView, destinationStack, windowRect));
- animStartedListener = () -> {
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
+ animStartedListener = new OnAnimationStartedListener() {
+ private boolean mHandled;
- if (screenPinningRequested) {
- // Request screen pinning after the animation runs
- mStartScreenPinningRunnable.taskId = task.key.id;
- mHandler.postDelayed(mStartScreenPinningRunnable, 350);
+ @Override
+ public void onAnimationStarted() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ if (screenPinningRequested) {
+ // Request screen pinning after the animation runs
+ mStartScreenPinningRunnable.taskId = task.key.id;
+ mHandler.postDelayed(mStartScreenPinningRunnable, 350);
+ }
+
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
}
};
} else {
// This is only the case if the task is not on screen (scrolled offscreen for example)
transitionFuture = null;
- animStartedListener = () -> {
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
- EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
- stackView.cancelAllTaskViewAnimations();
+ animStartedListener = new OnAnimationStartedListener() {
+ private boolean mHandled;
+
+ @Override
+ public void onAnimationStarted() {
+ if (mHandled) {
+ return;
+ }
+ mHandled = true;
+
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+ EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+ stackView.cancelAllTaskViewAnimations();
+
+ // Reset the state where we are waiting for the transition to start
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+ }
};
}
+ EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
mHandler, transitionFuture != null ? transitionFuture.future : null,
animStartedListener, true /* scaleUp */);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 15b682e..a633a3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -219,6 +219,7 @@
private boolean mResetToInitialStateWhenResized;
private int mLastWidth;
private int mLastHeight;
+ private boolean mStackActionButtonVisible;
// We keep track of the task view focused by user interaction and draw a frame around it in the
// grid layout.
@@ -287,6 +288,7 @@
mDividerSize = ssp.getDockedDividerSize(context);
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
mDisplayRect = ssp.getDisplayRect();
+ mStackActionButtonVisible = false;
// Create a frame to draw around the focused task view
if (Recents.getConfiguration().isGridEnabled) {
@@ -975,6 +977,9 @@
mLayoutAlgorithm.clearUnfocusedTaskOverrides();
willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask,
requestViewFocus);
+ if (willScroll) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ }
} else {
// Focus the task view
TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask);
@@ -1159,7 +1164,7 @@
Task focusedTask = getAccessibilityFocusedTask();
info.setScrollable(true);
int focusedTaskIndex = mStack.indexOfStackTask(focusedTask);
- if (focusedTaskIndex > 0) {
+ if (focusedTaskIndex > 0 || !mStackActionButtonVisible) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
if (0 <= focusedTaskIndex && focusedTaskIndex < mStack.getTaskCount() - 1) {
@@ -1812,6 +1817,18 @@
}
}
+ public final void onBusEvent(ShowStackActionButtonEvent event) {
+ if (RecentsDebugFlags.Static.EnableStackActionButton) {
+ mStackActionButtonVisible = true;
+ }
+ }
+
+ public final void onBusEvent(HideStackActionButtonEvent event) {
+ if (RecentsDebugFlags.Static.EnableStackActionButton) {
+ mStackActionButtonVisible = false;
+ }
+ }
+
public final void onBusEvent(LaunchNextTaskRequestEvent event) {
if (!mFinishedLayoutAfterStackReload) {
mLaunchNextAfterFirstMeasure = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 479b945..1d64480 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -543,7 +543,7 @@
mKeyguardFadingOutInProgress = false;
mAnimatingDozeUnlock = false;
}
- if (mWakingUpFromAodAnimationRunning) {
+ if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
mWakingUpFromAodAnimationRunning = false;
mWakingUpFromAodInProgress = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 6f710da..06cdfae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -103,7 +103,7 @@
private boolean mDeferScrimFadeOut;
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
- private PendingDismissActionRequest mPendingDismissAction;
+ private DismissWithActionRequest mPendingWakeupAction;
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@@ -174,7 +174,7 @@
private void hideBouncer(boolean destroyView) {
mBouncer.hide(destroyView);
- cancelPendingDismissAction();
+ cancelPendingWakeupAction();
}
private void showBouncer() {
@@ -187,11 +187,11 @@
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone) {
if (mShowing) {
- cancelPendingDismissAction();
+ cancelPendingWakeupAction();
// If we're dozing, this needs to be delayed until after we wake up - unless we're
// wake-and-unlocking, because there dozing will last until the end of the transition.
if (mDozing && !isWakeAndUnlocking()) {
- mPendingDismissAction = new PendingDismissActionRequest(
+ mPendingWakeupAction = new DismissWithActionRequest(
r, cancelAction, afterKeyguardGone);
return;
}
@@ -280,7 +280,7 @@
updateStates();
if (!dozing) {
- launchPendingDismissAction();
+ launchPendingWakeupAction();
}
}
}
@@ -357,7 +357,7 @@
*/
public void hide(long startTime, long fadeoutDuration) {
mShowing = false;
- launchPendingDismissAction();
+ launchPendingWakeupAction();
if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {
fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;
@@ -685,9 +685,9 @@
return mStatusBar.getStatusBarView().getViewRootImpl();
}
- public void launchPendingDismissAction() {
- PendingDismissActionRequest request = mPendingDismissAction;
- mPendingDismissAction = null;
+ public void launchPendingWakeupAction() {
+ DismissWithActionRequest request = mPendingWakeupAction;
+ mPendingWakeupAction = null;
if (request != null) {
if (mShowing) {
dismissWithAction(request.dismissAction, request.cancelAction,
@@ -698,20 +698,20 @@
}
}
- public void cancelPendingDismissAction() {
- PendingDismissActionRequest request = mPendingDismissAction;
- mPendingDismissAction = null;
+ public void cancelPendingWakeupAction() {
+ DismissWithActionRequest request = mPendingWakeupAction;
+ mPendingWakeupAction = null;
if (request != null && request.cancelAction != null) {
request.cancelAction.run();
}
}
- private static class PendingDismissActionRequest {
+ private static class DismissWithActionRequest {
final OnDismissAction dismissAction;
final Runnable cancelAction;
final boolean afterKeyguardGone;
- PendingDismissActionRequest(OnDismissAction dismissAction, Runnable cancelAction,
+ DismissWithActionRequest(OnDismissAction dismissAction, Runnable cancelAction,
boolean afterKeyguardGone) {
this.dismissAction = dismissAction;
this.cancelAction = cancelAction;
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 814e4be..c23757f 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -214,8 +214,7 @@
Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE
| Context.BIND_NOT_FOREGROUND
- | Context.BIND_IMPORTANT_BACKGROUND
- | Context.BIND_SHOWING_UI;
+ | Context.BIND_IMPORTANT_BACKGROUND;
/**
* Binding flags used only while the {@link InputMethodService} is showing window.
@@ -223,7 +222,8 @@
private static final int IME_VISIBLE_BIND_FLAGS =
Context.BIND_AUTO_CREATE
| Context.BIND_TREAT_LIKE_ACTIVITY
- | Context.BIND_FOREGROUND_SERVICE;
+ | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_SHOWING_UI;
@Retention(SOURCE)
@IntDef({HardKeyboardBehavior.WIRELESS_AFFORDANCE, HardKeyboardBehavior.WIRED_AFFORDANCE})
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 80d39f5..13e3ae6 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -598,7 +598,7 @@
Slog.w(TAG, "bind service: " + info.getId());
}
if (!bindCurrentSpellCheckerService(serviceIntent, connection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) {
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT_BACKGROUND)) {
Slog.e(TAG, "Failed to get a spell checker service.");
return null;
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 6a81d32..8d46d1e 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -18,7 +18,11 @@
import android.app.IActivityController;
import android.os.Binder;
+import android.os.Build;
import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.system.StructRlimit;
import com.android.internal.os.ZygoteConnectionConstants;
import com.android.server.am.ActivityManagerService;
@@ -45,6 +49,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -107,6 +112,7 @@
int mPhonePid;
IActivityController mController;
boolean mAllowRestart = true;
+ final OpenFdMonitor mOpenFdMonitor;
/**
* Used for checking status of handle threads and scheduling monitor callbacks.
@@ -269,6 +275,8 @@
// Initialize monitor for Binder threads.
addMonitor(new BinderThreadMonitor());
+ mOpenFdMonitor = OpenFdMonitor.create();
+
// See the notes on DEFAULT_TIMEOUT.
assert DB ||
DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
@@ -358,7 +366,7 @@
return checkers;
}
- private String describeCheckersLocked(ArrayList<HandlerChecker> checkers) {
+ private String describeCheckersLocked(List<HandlerChecker> checkers) {
StringBuilder builder = new StringBuilder(128);
for (int i=0; i<checkers.size(); i++) {
if (builder.length() > 0) {
@@ -410,7 +418,7 @@
public void run() {
boolean waitedHalf = false;
while (true) {
- final ArrayList<HandlerChecker> blockedCheckers;
+ final List<HandlerChecker> blockedCheckers;
final String subject;
final boolean allowRestart;
int debuggerWasConnected = 0;
@@ -447,30 +455,40 @@
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
}
- final int waitState = evaluateCheckerCompletionLocked();
- if (waitState == COMPLETED) {
- // The monitors have returned; reset
- waitedHalf = false;
- continue;
- } else if (waitState == WAITING) {
- // still waiting but within their configured intervals; back off and recheck
- continue;
- } else if (waitState == WAITED_HALF) {
- if (!waitedHalf) {
- // We've waited half the deadlock-detection interval. Pull a stack
- // trace and wait another half.
- ArrayList<Integer> pids = new ArrayList<Integer>();
- pids.add(Process.myPid());
- ActivityManagerService.dumpStackTraces(true, pids, null, null,
- getInterestingNativePids());
- waitedHalf = true;
- }
- continue;
+ boolean fdLimitTriggered = false;
+ if (mOpenFdMonitor != null) {
+ fdLimitTriggered = mOpenFdMonitor.monitor();
}
- // something is overdue!
- blockedCheckers = getBlockedCheckersLocked();
- subject = describeCheckersLocked(blockedCheckers);
+ if (!fdLimitTriggered) {
+ final int waitState = evaluateCheckerCompletionLocked();
+ if (waitState == COMPLETED) {
+ // The monitors have returned; reset
+ waitedHalf = false;
+ continue;
+ } else if (waitState == WAITING) {
+ // still waiting but within their configured intervals; back off and recheck
+ continue;
+ } else if (waitState == WAITED_HALF) {
+ if (!waitedHalf) {
+ // We've waited half the deadlock-detection interval. Pull a stack
+ // trace and wait another half.
+ ArrayList<Integer> pids = new ArrayList<Integer>();
+ pids.add(Process.myPid());
+ ActivityManagerService.dumpStackTraces(true, pids, null, null,
+ getInterestingNativePids());
+ waitedHalf = true;
+ }
+ continue;
+ }
+
+ // something is overdue!
+ blockedCheckers = getBlockedCheckersLocked();
+ subject = describeCheckersLocked(blockedCheckers);
+ } else {
+ blockedCheckers = Collections.emptyList();
+ subject = "Open FD high water mark reached";
+ }
allowRestart = mAllowRestart;
}
@@ -584,4 +602,87 @@
}
private native void native_dumpKernelStacks(String tracesPath);
+
+ public static final class OpenFdMonitor {
+ /**
+ * Number of FDs below the soft limit that we trigger a runtime restart at. This was
+ * chosen arbitrarily, but will need to be at least 6 in order to have a sufficient number
+ * of FDs in reserve to complete a dump.
+ */
+ private static final int FD_HIGH_WATER_MARK = 12;
+
+ private final File mDumpDir;
+ private final File mFdHighWaterMark;
+
+ public static OpenFdMonitor create() {
+ // Only run the FD monitor on debuggable builds (such as userdebug and eng builds).
+ if (!Build.IS_DEBUGGABLE) {
+ return null;
+ }
+
+ // Don't run the FD monitor on builds that have a global ANR trace file. We're using
+ // the ANR trace directory as a quick hack in order to get these traces in bugreports
+ // and we wouldn't want to overwrite something important.
+ final String dumpDirStr = SystemProperties.get("dalvik.vm.stack-trace-dir", "");
+ if (dumpDirStr.isEmpty()) {
+ return null;
+ }
+
+ final StructRlimit rlimit;
+ try {
+ rlimit = android.system.Os.getrlimit(OsConstants.RLIMIT_NOFILE);
+ } catch (ErrnoException errno) {
+ Slog.w(TAG, "Error thrown from getrlimit(RLIMIT_NOFILE)", errno);
+ return null;
+ }
+
+ // The assumption we're making here is that FD numbers are allocated (more or less)
+ // sequentially, which is currently (and historically) true since open is currently
+ // specified to always return the lowest-numbered non-open file descriptor for the
+ // current process.
+ //
+ // We do this to avoid having to enumerate the contents of /proc/self/fd in order to
+ // count the number of descriptors open in the process.
+ final File fdThreshold = new File("/proc/self/fd/" + (rlimit.rlim_cur - FD_HIGH_WATER_MARK));
+ return new OpenFdMonitor(new File(dumpDirStr), fdThreshold);
+ }
+
+ OpenFdMonitor(File dumpDir, File fdThreshold) {
+ mDumpDir = dumpDir;
+ mFdHighWaterMark = fdThreshold;
+ }
+
+ private void dumpOpenDescriptors() {
+ try {
+ File dumpFile = File.createTempFile("anr_fd_", "", mDumpDir);
+ java.lang.Process proc = new ProcessBuilder()
+ .command("/system/bin/lsof", "-p", String.valueOf(Process.myPid()))
+ .redirectErrorStream(true)
+ .redirectOutput(dumpFile)
+ .start();
+
+ int returnCode = proc.waitFor();
+ if (returnCode != 0) {
+ Slog.w(TAG, "Unable to dump open descriptors, lsof return code: "
+ + returnCode);
+ dumpFile.delete();
+ }
+ } catch (IOException | InterruptedException ex) {
+ Slog.w(TAG, "Unable to dump open descriptors: " + ex);
+ }
+ }
+
+ /**
+ * @return {@code true} if the high water mark was breached and a dump was written,
+ * {@code false} otherwise.
+ */
+ public boolean monitor() {
+ if (mFdHighWaterMark.exists()) {
+ dumpOpenDescriptors();
+ return true;
+ }
+
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bcad2fc..161bf9a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13323,7 +13323,6 @@
final ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r != null) {
final ActivityOptions activityOptions = r.pendingOptions;
- r.pendingOptions = null;
return activityOptions == null ? null : activityOptions.toBundle();
}
return null;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index a50ec49f..81bccdc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -851,6 +851,7 @@
mSystemAudioActivated = on;
mService.announceSystemAudioModeChange(on);
}
+ startArcAction(on);
}
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 11a4eb4..436ea3c 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -1054,8 +1054,15 @@
// download tasks overrun.
synchronized (mLock) {
if (mDownloadXtraWakeLock.isHeld()) {
- mDownloadXtraWakeLock.release();
- if (DEBUG) Log.d(TAG, "WakeLock released by handleDownloadXtraData()");
+ // This wakelock may have time-out, if a timeout was specified.
+ // Catch (and ignore) any timeout exceptions.
+ try {
+ mDownloadXtraWakeLock.release();
+ if (DEBUG) Log.d(TAG, "WakeLock released by handleDownloadXtraData()");
+ } catch (Exception e) {
+ Log.i(TAG, "Wakelock timeout & release race exception in "
+ + "handleDownloadXtraData()", e);
+ }
} else {
Log.e(TAG, "WakeLock expired before release in "
+ "handleDownloadXtraData()");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index bab7011..c3b93b4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,17 +16,6 @@
package com.android.server.pm;
-import static com.android.internal.util.XmlUtils.readBitmapAttribute;
-import static com.android.internal.util.XmlUtils.readBooleanAttribute;
-import static com.android.internal.util.XmlUtils.readIntAttribute;
-import static com.android.internal.util.XmlUtils.readLongAttribute;
-import static com.android.internal.util.XmlUtils.readStringAttribute;
-import static com.android.internal.util.XmlUtils.readUriAttribute;
-import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
-import static com.android.internal.util.XmlUtils.writeIntAttribute;
-import static com.android.internal.util.XmlUtils.writeLongAttribute;
-import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.internal.util.XmlUtils.writeUriAttribute;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -54,8 +43,6 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -84,9 +71,6 @@
import android.util.SparseIntArray;
import android.util.Xml;
-import java.io.CharArrayWriter;
-import libcore.io.IoUtils;
-
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageHelper;
@@ -97,10 +81,13 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -125,32 +112,6 @@
/** XML constants used in {@link #mSessionsFile} */
private static final String TAG_SESSIONS = "sessions";
- private static final String TAG_SESSION = "session";
- private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
- private static final String ATTR_SESSION_ID = "sessionId";
- private static final String ATTR_USER_ID = "userId";
- private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
- private static final String ATTR_INSTALLER_UID = "installerUid";
- private static final String ATTR_CREATED_MILLIS = "createdMillis";
- private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
- private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
- private static final String ATTR_PREPARED = "prepared";
- private static final String ATTR_SEALED = "sealed";
- private static final String ATTR_MODE = "mode";
- private static final String ATTR_INSTALL_FLAGS = "installFlags";
- private static final String ATTR_INSTALL_LOCATION = "installLocation";
- private static final String ATTR_SIZE_BYTES = "sizeBytes";
- private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
- @Deprecated
- private static final String ATTR_APP_ICON = "appIcon";
- private static final String ATTR_APP_LABEL = "appLabel";
- private static final String ATTR_ORIGINATING_URI = "originatingUri";
- private static final String ATTR_ORIGINATING_UID = "originatingUid";
- private static final String ATTR_REFERRER_URI = "referrerUri";
- private static final String ATTR_ABI_OVERRIDE = "abiOverride";
- private static final String ATTR_VOLUME_UUID = "volumeUuid";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_INSTALL_REASON = "installRason";
/** Automatically destroy sessions older than this */
private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
@@ -357,8 +318,10 @@
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
- if (TAG_SESSION.equals(tag)) {
- final PackageInstallerSession session = readSessionLocked(in);
+ if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
+ final PackageInstallerSession session = PackageInstallerSession.
+ readFromXml(in, mInternalCallback, mContext, mPm,
+ mInstallThread.getLooper(), mSessionsDir);
final long age = System.currentTimeMillis() - session.createdMillis;
final boolean valid;
@@ -397,53 +360,10 @@
session.dump(pw);
mHistoricalSessions.add(writer.toString());
+ int installerUid = session.getInstallerUid();
// Increment the number of sessions by this installerUid.
- mHistoricalSessionsByInstaller.put(
- session.installerUid,
- mHistoricalSessionsByInstaller.get(session.installerUid) + 1);
- }
-
- private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException,
- XmlPullParserException {
- final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
- final int userId = readIntAttribute(in, ATTR_USER_ID);
- final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
- final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, mPm.getPackageUid(
- installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
- final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
- final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
- final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null;
- final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
- final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
- final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
-
- final SessionParams params = new SessionParams(
- SessionParams.MODE_INVALID);
- params.mode = readIntAttribute(in, ATTR_MODE);
- params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
- params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
- params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES);
- params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME);
- params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON);
- params.appLabel = readStringAttribute(in, ATTR_APP_LABEL);
- params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
- params.originatingUid =
- readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN);
- params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
- params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
- params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
- params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
- params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
-
- final File appIconFile = buildAppIconFile(sessionId);
- if (appIconFile.exists()) {
- params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
- params.appIconLastModified = appIconFile.lastModified();
- }
-
- return new PackageInstallerSession(mInternalCallback, mContext, mPm,
- mInstallThread.getLooper(), sessionId, userId, installerPackageName, installerUid,
- params, createdMillis, stageDir, stageCid, prepared, sealed);
+ mHistoricalSessionsByInstaller.put(installerUid,
+ mHistoricalSessionsByInstaller.get(installerUid) + 1);
}
private void writeSessionsLocked() {
@@ -460,7 +380,7 @@
final int size = mSessions.size();
for (int i = 0; i < size; i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- writeSessionLocked(out, session);
+ session.write(out, mSessionsDir);
}
out.endTag(null, TAG_SESSIONS);
out.endDocument();
@@ -473,106 +393,6 @@
}
}
- private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session)
- throws IOException {
- final SessionParams params = session.params;
-
- out.startTag(null, TAG_SESSION);
-
- writeIntAttribute(out, ATTR_SESSION_ID, session.sessionId);
- writeIntAttribute(out, ATTR_USER_ID, session.userId);
- writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
- session.installerPackageName);
- writeIntAttribute(out, ATTR_INSTALLER_UID, session.installerUid);
- writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis);
- if (session.stageDir != null) {
- writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
- session.stageDir.getAbsolutePath());
- }
- if (session.stageCid != null) {
- writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid);
- }
- writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared());
- writeBooleanAttribute(out, ATTR_SEALED, session.isSealed());
-
- writeIntAttribute(out, ATTR_MODE, params.mode);
- writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags);
- writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
- writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
- writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
- writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
- writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
- writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid);
- writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
- writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
- writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
- writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
-
- // Persist app icon if changed since last written
- final File appIconFile = buildAppIconFile(session.sessionId);
- if (params.appIcon == null && appIconFile.exists()) {
- appIconFile.delete();
- } else if (params.appIcon != null
- && appIconFile.lastModified() != params.appIconLastModified) {
- if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
- FileOutputStream os = null;
- try {
- os = new FileOutputStream(appIconFile);
- params.appIcon.compress(CompressFormat.PNG, 90, os);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
- } finally {
- IoUtils.closeQuietly(os);
- }
-
- params.appIconLastModified = appIconFile.lastModified();
- }
-
- writeGrantedRuntimePermissions(out, params.grantedRuntimePermissions);
-
- out.endTag(null, TAG_SESSION);
- }
-
- private static void writeGrantedRuntimePermissions(XmlSerializer out,
- String[] grantedRuntimePermissions) throws IOException {
- if (grantedRuntimePermissions != null) {
- for (String permission : grantedRuntimePermissions) {
- out.startTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
- writeStringAttribute(out, ATTR_NAME, permission);
- out.endTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
- }
- }
- }
-
- private static String[] readGrantedRuntimePermissions(XmlPullParser in)
- throws IOException, XmlPullParserException {
- List<String> permissions = null;
-
- final int outerDepth = in.getDepth();
- int type;
- while ((type = in.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
- String permission = readStringAttribute(in, ATTR_NAME);
- if (permissions == null) {
- permissions = new ArrayList<>();
- }
- permissions.add(permission);
- }
- }
-
- if (permissions == null) {
- return null;
- }
-
- String[] permissionsArray = new String[permissions.size()];
- permissions.toArray(permissionsArray);
- return permissionsArray;
- }
-
private File buildAppIconFile(int sessionId) {
return new File(mSessionsDir, "app_icon." + sessionId + ".png");
}
@@ -885,9 +705,11 @@
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- if (Objects.equals(session.installerPackageName, installerPackageName)
+
+ SessionInfo info = session.generateInfo(false);
+ if (Objects.equals(info.getInstallerPackageName(), installerPackageName)
&& session.userId == userId) {
- result.add(session.generateInfo(false));
+ result.add(info);
}
}
}
@@ -962,7 +784,7 @@
final int size = sessions.size();
for (int i = 0; i < size; i++) {
final PackageInstallerSession session = sessions.valueAt(i);
- if (session.installerUid == installerUid) {
+ if (session.getInstallerUid() == installerUid) {
count++;
}
}
@@ -974,7 +796,7 @@
if (callingUid == Process.ROOT_UID) {
return true;
} else {
- return (session != null) && (callingUid == session.installerUid);
+ return (session != null) && (callingUid == session.getInstallerUid());
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5823771..0fd696f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -25,9 +25,22 @@
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
+import static com.android.internal.util.XmlUtils.readBitmapAttribute;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.readUriAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.internal.util.XmlUtils.writeUriAttribute;
import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
+import android.Manifest;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
@@ -45,6 +58,8 @@
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.Signature;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Bundle;
import android.os.FileBridge;
@@ -53,6 +68,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
import android.os.Process;
import android.os.RemoteException;
import android.os.RevocableFileDescriptor;
@@ -80,9 +96,14 @@
import libcore.io.IoUtils;
import libcore.io.Libcore;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileFilter;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.Certificate;
import java.util.ArrayList;
@@ -97,6 +118,34 @@
private static final int MSG_COMMIT = 0;
+ /** XML constants used for persisting a session */
+ static final String TAG_SESSION = "session";
+ private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
+ private static final String ATTR_SESSION_ID = "sessionId";
+ private static final String ATTR_USER_ID = "userId";
+ private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
+ private static final String ATTR_INSTALLER_UID = "installerUid";
+ private static final String ATTR_CREATED_MILLIS = "createdMillis";
+ private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
+ private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
+ private static final String ATTR_PREPARED = "prepared";
+ private static final String ATTR_SEALED = "sealed";
+ private static final String ATTR_MODE = "mode";
+ private static final String ATTR_INSTALL_FLAGS = "installFlags";
+ private static final String ATTR_INSTALL_LOCATION = "installLocation";
+ private static final String ATTR_SIZE_BYTES = "sizeBytes";
+ private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
+ @Deprecated
+ private static final String ATTR_APP_ICON = "appIcon";
+ private static final String ATTR_APP_LABEL = "appLabel";
+ private static final String ATTR_ORIGINATING_URI = "originatingUri";
+ private static final String ATTR_ORIGINATING_UID = "originatingUid";
+ private static final String ATTR_REFERRER_URI = "referrerUri";
+ private static final String ATTR_ABI_OVERRIDE = "abiOverride";
+ private static final String ATTR_VOLUME_UUID = "volumeUuid";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_INSTALL_REASON = "installRason";
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -104,12 +153,9 @@
private final Context mContext;
private final PackageManagerService mPm;
private final Handler mHandler;
- private final boolean mIsInstallerDeviceOwner;
final int sessionId;
final int userId;
- final String installerPackageName;
- final int installerUid;
final SessionParams params;
final long createdMillis;
final int defaultContainerGid;
@@ -122,6 +168,17 @@
private final Object mLock = new Object();
+ /** Uid of the creator of this session. */
+ private final int mOriginalInstallerUid;
+
+ /** Package of the owner of the installer session */
+ @GuardedBy("mLock")
+ private String mInstallerPackageName;
+
+ /** Uid of the owner of the installer session */
+ @GuardedBy("mLock")
+ private int mInstallerUid;
+
@GuardedBy("mLock")
private float mClientProgress = 0;
@GuardedBy("mLock")
@@ -132,18 +189,25 @@
@GuardedBy("mLock")
private float mReportedProgress = -1;
+ /** State of the session. */
@GuardedBy("mLock")
private boolean mPrepared = false;
@GuardedBy("mLock")
private boolean mSealed = false;
@GuardedBy("mLock")
- private boolean mPermissionsAccepted = false;
+ private boolean mCommitted = false;
@GuardedBy("mLock")
private boolean mRelinquished = false;
@GuardedBy("mLock")
private boolean mDestroyed = false;
+ /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */
+ @GuardedBy("mLock")
+ private boolean mPermissionsManuallyAccepted = false;
+
+ @GuardedBy("mLock")
private int mFinalStatus;
+ @GuardedBy("mLock")
private String mFinalMessage;
@GuardedBy("mLock")
@@ -155,9 +219,13 @@
private IPackageInstallObserver2 mRemoteObserver;
/** Fields derived from commit parsing */
+ @GuardedBy("mLock")
private String mPackageName;
+ @GuardedBy("mLock")
private int mVersionCode;
+ @GuardedBy("mLock")
private Signature[] mSignatures;
+ @GuardedBy("mLock")
private Certificate[][] mCertificates;
/**
@@ -205,32 +273,61 @@
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
- // Cache package manager data without the lock held
- final PackageInfo pkgInfo = mPm.getPackageInfo(
- params.appPackageName, PackageManager.GET_SIGNATURES
- | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
- final ApplicationInfo appInfo = mPm.getApplicationInfo(
- params.appPackageName, 0, userId);
-
synchronized (mLock) {
if (msg.obj != null) {
mRemoteObserver = (IPackageInstallObserver2) msg.obj;
}
-
try {
- commitLocked(pkgInfo, appInfo);
+ commitLocked();
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
dispatchSessionFinished(e.error, completeMsg, null);
}
-
- return true;
}
+
+ return true;
}
};
+ /**
+ * @return {@code true} iff the installing is app an device owner?
+ */
+ private boolean isInstallerDeviceOwnerLocked() {
+ DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+
+ return (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
+ mInstallerPackageName);
+ }
+
+ /**
+ * Checks if the permissions still need to be confirmed.
+ *
+ * <p>This is dependant on the identity of the installer, hence this cannot be cached if the
+ * installer might still {@link #transfer(String) change}.
+ *
+ * @return {@code true} iff we need to ask to confirm the permissions?
+ */
+ private boolean needToAskForPermissionsLocked() {
+ if (mPermissionsManuallyAccepted) {
+ return false;
+ }
+
+ final boolean isPermissionGranted =
+ (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES,
+ mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+ final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
+ final boolean forcePermissionPrompt =
+ (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
+
+ // Device owners are allowed to silently install packages, so the permission check is
+ // waived if the installer is the device owner.
+ return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot
+ || isInstallerDeviceOwnerLocked());
+ }
+
public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
String installerPackageName, int installerUid, SessionParams params, long createdMillis,
@@ -242,8 +339,9 @@
this.sessionId = sessionId;
this.userId = userId;
- this.installerPackageName = installerPackageName;
- this.installerUid = installerUid;
+ mOriginalInstallerUid = installerUid;
+ mInstallerPackageName = installerPackageName;
+ mInstallerUid = installerUid;
this.params = params;
this.createdMillis = createdMillis;
this.stageDir = stageDir;
@@ -257,26 +355,6 @@
mPrepared = prepared;
mSealed = sealed;
- // Device owners are allowed to silently install packages, so the permission check is
- // waived if the installer is the device owner.
- DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- final boolean isPermissionGranted =
- (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
- == PackageManager.PERMISSION_GRANTED);
- final boolean isInstallerRoot = (installerUid == Process.ROOT_UID);
- final boolean forcePermissionPrompt =
- (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
- mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
- installerPackageName);
- if ((isPermissionGranted
- || isInstallerRoot
- || mIsInstallerDeviceOwner)
- && !forcePermissionPrompt) {
- mPermissionsAccepted = true;
- } else {
- mPermissionsAccepted = false;
- }
final long identity = Binder.clearCallingIdentity();
try {
final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
@@ -295,7 +373,7 @@
final SessionInfo info = new SessionInfo();
synchronized (mLock) {
info.sessionId = sessionId;
- info.installerPackageName = installerPackageName;
+ info.installerPackageName = mInstallerPackageName;
info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
mResolvedBaseFile.getAbsolutePath() : null;
info.progress = mProgress;
@@ -310,6 +388,13 @@
info.appIcon = params.appIcon;
}
info.appLabel = params.appLabel;
+
+ info.installLocation = params.installLocation;
+ info.originatingUri = params.originatingUri;
+ info.originatingUid = params.originatingUid;
+ info.referrerUri = params.referrerUri;
+ info.grantedRuntimePermissions = params.grantedRuntimePermissions;
+ info.installFlags = params.installFlags;
}
return info;
}
@@ -326,14 +411,26 @@
}
}
- private void assertPreparedAndNotSealed(String cookie) {
- synchronized (mLock) {
- if (!mPrepared) {
- throw new IllegalStateException(cookie + " before prepared");
- }
- if (mSealed) {
- throw new SecurityException(cookie + " not allowed after commit");
- }
+ private void assertPreparedAndNotSealedLocked(String cookie) {
+ assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
+ if (mSealed) {
+ throw new SecurityException(cookie + " not allowed after sealing");
+ }
+ }
+
+ private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
+ assertPreparedAndNotDestroyedLocked(cookie);
+ if (mCommitted) {
+ throw new SecurityException(cookie + " not allowed after commit");
+ }
+ }
+
+ private void assertPreparedAndNotDestroyedLocked(String cookie) {
+ if (!mPrepared) {
+ throw new IllegalStateException(cookie + " before prepared");
+ }
+ if (mDestroyed) {
+ throw new SecurityException(cookie + " not allowed after destruction");
}
}
@@ -342,27 +439,27 @@
* might point at an ASEC mount point, which is why we delay path resolution
* until someone actively works with the session.
*/
- private File resolveStageDir() throws IOException {
- synchronized (mLock) {
- if (mResolvedStageDir == null) {
- if (stageDir != null) {
- mResolvedStageDir = stageDir;
+ private File resolveStageDirLocked() throws IOException {
+ if (mResolvedStageDir == null) {
+ if (stageDir != null) {
+ mResolvedStageDir = stageDir;
+ } else {
+ final String path = PackageHelper.getSdDir(stageCid);
+ if (path != null) {
+ mResolvedStageDir = new File(path);
} else {
- final String path = PackageHelper.getSdDir(stageCid);
- if (path != null) {
- mResolvedStageDir = new File(path);
- } else {
- throw new IOException("Failed to resolve path to container " + stageCid);
- }
+ throw new IOException("Failed to resolve path to container " + stageCid);
}
}
- return mResolvedStageDir;
}
+ return mResolvedStageDir;
}
@Override
public void setClientProgress(float progress) {
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
// Always publish first staging movement
final boolean forcePublish = (mClientProgress == 0);
mClientProgress = progress;
@@ -373,6 +470,8 @@
@Override
public void addClientProgress(float progress) {
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
setClientProgress(mClientProgress + progress);
}
}
@@ -390,11 +489,15 @@
@Override
public String[] getNames() {
- assertPreparedAndNotSealed("getNames");
- try {
- return resolveStageDir().list();
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("getNames");
+
+ try {
+ return resolveStageDirLocked().list();
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
@@ -403,20 +506,26 @@
if (TextUtils.isEmpty(params.appPackageName)) {
throw new IllegalStateException("Must specify package name to remove a split");
}
- try {
- createRemoveSplitMarker(splitName);
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("removeSplit");
+
+ try {
+ createRemoveSplitMarkerLocked(splitName);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
- private void createRemoveSplitMarker(String splitName) throws IOException {
+ private void createRemoveSplitMarkerLocked(String splitName) throws IOException {
try {
final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION;
if (!FileUtils.isValidExtFilename(markerName)) {
throw new IllegalArgumentException("Invalid marker: " + markerName);
}
- final File target = new File(resolveStageDir(), markerName);
+ final File target = new File(resolveStageDirLocked(), markerName);
target.createNewFile();
Os.chmod(target.getAbsolutePath(), 0 /*mode*/);
} catch (ErrnoException e) {
@@ -440,8 +549,10 @@
// will block any attempted install transitions.
final RevocableFileDescriptor fd;
final FileBridge bridge;
+ final File stageDir;
synchronized (mLock) {
- assertPreparedAndNotSealed("openWrite");
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("openWrite");
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
fd = new RevocableFileDescriptor();
@@ -452,6 +563,8 @@
bridge = new FileBridge();
mBridges.add(bridge);
}
+
+ stageDir = resolveStageDirLocked();
}
try {
@@ -462,7 +575,7 @@
final File target;
final long identity = Binder.clearCallingIdentity();
try {
- target = new File(resolveStageDir(), name);
+ target = new File(stageDir, name);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -500,55 +613,108 @@
@Override
public ParcelFileDescriptor openRead(String name) {
- try {
- return openReadInternal(name);
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
+ try {
+ return openReadInternalLocked(name);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
- private ParcelFileDescriptor openReadInternal(String name) throws IOException {
- assertPreparedAndNotSealed("openRead");
-
+ private ParcelFileDescriptor openReadInternalLocked(String name) throws IOException {
try {
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid name: " + name);
}
- final File target = new File(resolveStageDir(), name);
-
+ final File target = new File(resolveStageDirLocked(), name);
final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
return new ParcelFileDescriptor(targetFd);
-
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
+ /**
+ * Check if the caller is the owner of this session. Otherwise throw a
+ * {@link SecurityException}.
+ */
+ private void assertCallerIsOwnerOrRootLocked() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid) {
+ throw new SecurityException("Session does not belong to uid " + callingUid);
+ }
+ }
+
+ /**
+ * If anybody is reading or writing data of the session, throw an {@link SecurityException}.
+ */
+ private void assertNoWriteFileTransfersOpenLocked() {
+ // Verify that all writers are hands-off
+ for (RevocableFileDescriptor fd : mFds) {
+ if (!fd.isRevoked()) {
+ throw new SecurityException("Files still open");
+ }
+ }
+ for (FileBridge bridge : mBridges) {
+ if (!bridge.isClosed()) {
+ throw new SecurityException("Files still open");
+ }
+ }
+ }
+
@Override
- public void commit(IntentSender statusReceiver) {
+ public void commit(IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkNotNull(statusReceiver);
+ // Cache package manager data without the lock held
+ final PackageInfo installedPkgInfo = mPm.getPackageInfo(
+ params.appPackageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
final boolean wasSealed;
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotDestroyedLocked("commit");
+
+ if (forTransfer) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, null);
+
+ if (mInstallerUid == mOriginalInstallerUid) {
+ throw new IllegalArgumentException("Session has not been transferred");
+ }
+ } else {
+ if (mInstallerUid != mOriginalInstallerUid) {
+ throw new IllegalArgumentException("Session has been transferred");
+ }
+ }
+
wasSealed = mSealed;
if (!mSealed) {
- // Verify that all writers are hands-off
- for (RevocableFileDescriptor fd : mFds) {
- if (!fd.isRevoked()) {
- throw new SecurityException("Files still open");
- }
+ try {
+ sealAndValidateLocked(installedPkgInfo);
+ } catch (PackageManagerException e) {
+ // Do now throw an exception here to stay compatible with O and older
+ destroyInternal();
+ dispatchSessionFinished(e.error, ExceptionUtils.getCompleteMessage(e), null);
+ return;
}
- for (FileBridge bridge : mBridges) {
- if (!bridge.isClosed()) {
- throw new SecurityException("Files still open");
- }
- }
- mSealed = true;
}
// Client staging is fully done at this point
mClientProgress = 1f;
computeProgressLocked(true);
+
+ // This ongoing commit should keep session active, even though client
+ // will probably close their end.
+ mActiveCount.incrementAndGet();
+
+ mCommitted = true;
+ final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(
+ mContext, statusReceiver, sessionId, isInstallerDeviceOwnerLocked(), userId);
+ mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
if (!wasSealed) {
@@ -557,17 +723,82 @@
// the session lock, since otherwise it's a lock inversion.
mCallback.onSessionSealedBlocking(this);
}
-
- // This ongoing commit should keep session active, even though client
- // will probably close their end.
- mActiveCount.incrementAndGet();
-
- final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
- statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
- mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
- private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
+ /**
+ * Seal the session to prevent further modification and validate the contents of it.
+ *
+ * <p>The session will be sealed after calling this method even if it failed.
+ *
+ * @param pkgInfo The package info for {@link #params}.packagename
+ */
+ private void sealAndValidateLocked(PackageInfo pkgInfo)
+ throws PackageManagerException {
+ assertNoWriteFileTransfersOpenLocked();
+
+ mSealed = true;
+
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ validateInstallLocked(pkgInfo);
+
+ // Read transfers from the original owner stay open, but as the session's data
+ // cannot be modified anymore, there is no leak of information.
+ }
+
+ @Override
+ public void transfer(String packageName) {
+ Preconditions.checkNotNull(packageName);
+
+ ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId);
+ if (newOwnerAppInfo == null) {
+ throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
+ }
+
+ if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission(
+ Manifest.permission.INSTALL_PACKAGES, newOwnerAppInfo.uid)) {
+ throw new SecurityException("Destination package " + packageName + " does not have "
+ + "the " + Manifest.permission.INSTALL_PACKAGES + " permission");
+ }
+
+ // Cache package manager data without the lock held
+ final PackageInfo installedPkgInfo = mPm.getPackageInfo(
+ params.appPackageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
+ // Only install flags that can be verified by the app the session is transferred to are
+ // allowed. The parameters can be read via PackageInstaller.SessionInfo.
+ if (!params.areHiddenOptionsSet()) {
+ throw new SecurityException("Can only transfer sessions that use public options");
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("transfer");
+
+ try {
+ sealAndValidateLocked(installedPkgInfo);
+ } catch (PackageManagerException e) {
+ throw new IllegalArgumentException("Package is not valid", e);
+ }
+
+ if (!mPackageName.equals(mInstallerPackageName)) {
+ throw new SecurityException("Can only transfer sessions that update the original "
+ + "installer");
+ }
+
+ mInstallerPackageName = packageName;
+ mInstallerUid = newOwnerAppInfo.uid;
+ }
+
+ // Persist the fact that we've sealed ourselves to prevent
+ // mutations of any hard links we create. We do this without holding
+ // the session lock, since otherwise it's a lock inversion.
+ mCallback.onSessionSealedBlocking(this);
+ }
+
+ private void commitLocked()
throws PackageManagerException {
if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
@@ -577,22 +808,17 @@
}
try {
- resolveStageDir();
+ resolveStageDirLocked();
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
"Failed to resolve stage location", e);
}
- // Verify that stage looks sane with respect to existing application.
- // This currently only ensures packageName, versionCode, and certificate
- // consistency.
- validateInstallLocked(pkgInfo, appInfo);
-
Preconditions.checkNotNull(mPackageName);
Preconditions.checkNotNull(mSignatures);
Preconditions.checkNotNull(mResolvedBaseFile);
- if (!mPermissionsAccepted) {
+ if (needToAskForPermissionsLocked()) {
// User needs to accept permissions; give installer an intent they
// can use to involve user.
final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
@@ -622,7 +848,7 @@
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
try {
final List<File> fromFiles = mResolvedInheritedFiles;
- final File toDir = resolveStageDir();
+ final File toDir = resolveStageDirLocked();
if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
@@ -683,7 +909,7 @@
mRelinquished = true;
mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
- installerPackageName, installerUid, user, mCertificates);
+ mInstallerPackageName, mInstallerUid, user, mCertificates);
}
/**
@@ -698,8 +924,7 @@
* Note that upgrade compatibility is still performed by
* {@link PackageManagerService}.
*/
- private void validateInstallLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
- throws PackageManagerException {
+ private void validateInstallLocked(PackageInfo pkgInfo) throws PackageManagerException {
mPackageName = null;
mVersionCode = -1;
mSignatures = null;
@@ -752,7 +977,7 @@
mCertificates = apk.certificates;
}
- assertApkConsistent(String.valueOf(addedFile), apk);
+ assertApkConsistentLocked(String.valueOf(addedFile), apk);
// Take this opportunity to enforce uniform naming
final String targetName;
@@ -807,13 +1032,14 @@
} else {
// Partial installs must be consistent with existing install
- if (appInfo == null) {
+ if (pkgInfo == null || pkgInfo.applicationInfo == null) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Missing existing base package for " + mPackageName);
}
final PackageLite existing;
final ApkLite existingBase;
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
try {
existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0);
existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()),
@@ -822,7 +1048,7 @@
throw PackageManagerException.from(e);
}
- assertApkConsistent("Existing base", existingBase);
+ assertApkConsistentLocked("Existing base", existingBase);
// Inherit base if not overridden
if (mResolvedBaseFile == null) {
@@ -878,7 +1104,7 @@
}
}
- private void assertApkConsistent(String tag, ApkLite apk)
+ private void assertApkConsistentLocked(String tag, ApkLite apk)
throws PackageManagerException {
if (!mPackageName.equals(apk.packageName)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
@@ -959,6 +1185,15 @@
return true;
}
+ /**
+ * @return the uid of the owner this session
+ */
+ public int getInstallerUid() {
+ synchronized (mLock) {
+ return mInstallerUid;
+ }
+ }
+
private static String getRelativePath(File file, File base) throws IOException {
final String pathStr = file.getAbsolutePath();
final String baseStr = base.getAbsolutePath();
@@ -1106,9 +1341,9 @@
if (accepted) {
// Mark and kick off another install pass
synchronized (mLock) {
- mPermissionsAccepted = true;
+ mPermissionsManuallyAccepted = true;
+ mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
- mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
} else {
destroyInternal();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
@@ -1120,7 +1355,9 @@
mCallback.onSessionActiveChanged(this, true);
}
+ boolean wasPrepared;
synchronized (mLock) {
+ wasPrepared = mPrepared;
if (!mPrepared) {
if (stageDir != null) {
prepareStageDir(stageDir);
@@ -1141,13 +1378,20 @@
}
mPrepared = true;
- mCallback.onSessionPrepared(this);
}
}
+
+ if (!wasPrepared) {
+ mCallback.onSessionPrepared(this);
+ }
}
@Override
public void close() {
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ }
+
if (mActiveCount.decrementAndGet() == 0) {
mCallback.onSessionActiveChanged(this, false);
}
@@ -1155,21 +1399,33 @@
@Override
public void abandon() {
- if (mRelinquished) {
- Slog.d(TAG, "Ignoring abandon after commit relinquished control");
- return;
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
+ if (mRelinquished) {
+ Slog.d(TAG, "Ignoring abandon after commit relinquished control");
+ return;
+ }
+ destroyInternal();
}
- destroyInternal();
+
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
}
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
- mFinalStatus = returnCode;
- mFinalMessage = msg;
+ IPackageInstallObserver2 observer;
+ String packageName;
+ synchronized (mLock) {
+ mFinalStatus = returnCode;
+ mFinalMessage = msg;
- if (mRemoteObserver != null) {
+ observer = mRemoteObserver;
+ packageName = mPackageName;
+ }
+
+ if (observer != null) {
try {
- mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
+ observer.onPackageInstalled(packageName, returnCode, msg, extras);
} catch (RemoteException ignored) {
}
}
@@ -1220,8 +1476,9 @@
pw.increaseIndent();
pw.printPair("userId", userId);
- pw.printPair("installerPackageName", installerPackageName);
- pw.printPair("installerUid", installerUid);
+ pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
+ pw.printPair("mInstallerPackageName", mInstallerPackageName);
+ pw.printPair("mInstallerUid", mInstallerUid);
pw.printPair("createdMillis", createdMillis);
pw.printPair("stageDir", stageDir);
pw.printPair("stageCid", stageCid);
@@ -1232,7 +1489,7 @@
pw.printPair("mClientProgress", mClientProgress);
pw.printPair("mProgress", mProgress);
pw.printPair("mSealed", mSealed);
- pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
+ pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
pw.printPair("mRelinquished", mRelinquished);
pw.printPair("mDestroyed", mDestroyed);
pw.printPair("mFds", mFds.size());
@@ -1243,4 +1500,170 @@
pw.decreaseIndent();
}
+
+ private static void writeGrantedRuntimePermissionsLocked(XmlSerializer out,
+ String[] grantedRuntimePermissions) throws IOException {
+ if (grantedRuntimePermissions != null) {
+ for (String permission : grantedRuntimePermissions) {
+ out.startTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
+ writeStringAttribute(out, ATTR_NAME, permission);
+ out.endTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
+ }
+ }
+ }
+
+ private static File buildAppIconFile(int sessionId, @NonNull File sessionsDir) {
+ return new File(sessionsDir, "app_icon." + sessionId + ".png");
+ }
+
+ /**
+ * Write this session to a {@link XmlSerializer}.
+ *
+ * @param out Where to write the session to
+ * @param sessionsDir The directory containing the sessions
+ */
+ void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException {
+ synchronized (mLock) {
+ out.startTag(null, TAG_SESSION);
+
+ writeIntAttribute(out, ATTR_SESSION_ID, sessionId);
+ writeIntAttribute(out, ATTR_USER_ID, userId);
+ writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
+ mInstallerPackageName);
+ writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid);
+ writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis);
+ if (stageDir != null) {
+ writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
+ stageDir.getAbsolutePath());
+ }
+ if (stageCid != null) {
+ writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid);
+ }
+ writeBooleanAttribute(out, ATTR_PREPARED, isPrepared());
+ writeBooleanAttribute(out, ATTR_SEALED, isSealed());
+
+ writeIntAttribute(out, ATTR_MODE, params.mode);
+ writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags);
+ writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
+ writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
+ writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
+ writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
+ writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
+ writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid);
+ writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
+ writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
+ writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
+ writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
+
+ // Persist app icon if changed since last written
+ File appIconFile = buildAppIconFile(sessionId, sessionsDir);
+ if (params.appIcon == null && appIconFile.exists()) {
+ appIconFile.delete();
+ } else if (params.appIcon != null
+ && appIconFile.lastModified() != params.appIconLastModified) {
+ if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
+ FileOutputStream os = null;
+ try {
+ os = new FileOutputStream(appIconFile);
+ params.appIcon.compress(Bitmap.CompressFormat.PNG, 90, os);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
+ } finally {
+ IoUtils.closeQuietly(os);
+ }
+
+ params.appIconLastModified = appIconFile.lastModified();
+ }
+
+ writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
+ }
+
+ out.endTag(null, TAG_SESSION);
+ }
+
+ private static String[] readGrantedRuntimePermissions(XmlPullParser in)
+ throws IOException, XmlPullParserException {
+ List<String> permissions = null;
+
+ final int outerDepth = in.getDepth();
+ int type;
+ while ((type = in.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
+ String permission = readStringAttribute(in, ATTR_NAME);
+ if (permissions == null) {
+ permissions = new ArrayList<>();
+ }
+ permissions.add(permission);
+ }
+ }
+
+ if (permissions == null) {
+ return null;
+ }
+
+ String[] permissionsArray = new String[permissions.size()];
+ permissions.toArray(permissionsArray);
+ return permissionsArray;
+ }
+
+ /**
+ * Read new session from a {@link XmlPullParser xml description} and create it.
+ *
+ * @param in The source of the description
+ * @param callback Callback the session uses to notify about changes of it's state
+ * @param context Context to be used by the session
+ * @param pm PackageManager to use by the session
+ * @param installerThread Thread to be used for callbacks of this session
+ * @param sessionsDir The directory the sessions are stored in
+ *
+ * @return The newly created session
+ */
+ public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in,
+ @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
+ @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir)
+ throws IOException, XmlPullParserException {
+ final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
+ final int userId = readIntAttribute(in, ATTR_USER_ID);
+ final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
+ final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, pm.getPackageUid(
+ installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
+ final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
+ final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
+ final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null;
+ final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
+ final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
+ final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
+
+ final SessionParams params = new SessionParams(
+ SessionParams.MODE_INVALID);
+ params.mode = readIntAttribute(in, ATTR_MODE);
+ params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
+ params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
+ params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES);
+ params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME);
+ params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON);
+ params.appLabel = readStringAttribute(in, ATTR_APP_LABEL);
+ params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
+ params.originatingUid =
+ readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN);
+ params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
+ params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
+ params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
+ params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
+ params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
+
+ final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
+ if (appIconFile.exists()) {
+ params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
+ params.appIconLastModified = appIconFile.lastModified();
+ }
+
+ return new PackageInstallerSession(callback, context, pm,
+ installerThread, sessionId, userId, installerPackageName, installerUid,
+ params, createdMillis, stageDir, stageCid, prepared, sealed);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aead98b..b68630e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2722,14 +2722,17 @@
//delete tmp files
deleteTempPackageFiles();
+ final int cachedSystemApps = PackageParser.sCachedPackageReadCount.get();
+
// Remove any shared userIDs that have no associated packages
mSettings.pruneSharedUsersLPw();
final long systemScanTime = SystemClock.uptimeMillis() - startTime;
final int systemPackagesCount = mPackages.size();
Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime
+ " ms, packageCount: " + systemPackagesCount
- + " ms, timePerPackage: "
- + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount));
+ + " , timePerPackage: "
+ + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)
+ + " , cached: " + cachedSystemApps);
if (mIsUpgrade && systemPackagesCount > 0) {
MetricsLogger.histogram(null, "ota_package_manager_system_app_avg_scan_time",
((int) systemScanTime) / systemPackagesCount);
@@ -2820,12 +2823,16 @@
// This must be done last to ensure all stubs are replaced or disabled.
decompressSystemApplications(stubSystemApps, scanFlags);
+ final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get()
+ - cachedSystemApps;
+
final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;
final int dataPackagesCount = mPackages.size() - systemPackagesCount;
Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
+ " ms, packageCount: " + dataPackagesCount
- + " ms, timePerPackage: "
- + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount));
+ + " , timePerPackage: "
+ + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
+ + " , cached: " + cachedNonSystemApps);
if (mIsUpgrade && dataPackagesCount > 0) {
MetricsLogger.histogram(null, "ota_package_manager_data_app_avg_scan_time",
((int) dataScanTime) / dataPackagesCount);
diff --git a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
index 0508fdf..a12c2c4 100644
--- a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
+++ b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
@@ -32,7 +32,7 @@
// the surface control.
//
// See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side.
-class RemoteSurfaceTrace extends SurfaceControl {
+class RemoteSurfaceTrace extends SurfaceControlWithBackground {
static final String TAG = "RemoteSurfaceTrace";
final FileDescriptor mWriteFd;
@@ -41,7 +41,8 @@
final WindowManagerService mService;
final WindowState mWindow;
- RemoteSurfaceTrace(FileDescriptor fd, SurfaceControl wrapped, WindowState window) {
+ RemoteSurfaceTrace(FileDescriptor fd, SurfaceControlWithBackground wrapped,
+ WindowState window) {
super(wrapped);
mWriteFd = fd;
diff --git a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
new file mode 100644
index 0000000..f5ef2e6
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * SurfaceControl extension that has background sized to match its container.
+ */
+class SurfaceControlWithBackground extends SurfaceControl {
+ // SurfaceControl that holds the background behind opaque letterboxed app windows.
+ private SurfaceControl mBackgroundControl;
+
+ // Flags that define whether the background should be shown.
+ private boolean mOpaque;
+ private boolean mVisible;
+
+ // Way to communicate with corresponding window.
+ private WindowSurfaceController mWindowSurfaceController;
+
+ // Rect to hold task bounds when computing metrics for background.
+ private Rect mTmpContainerRect = new Rect();
+
+ // Last metrics applied to the main SurfaceControl.
+ private float mLastWidth, mLastHeight;
+ private float mLastDsDx = 1, mLastDsDy = 1;
+ private float mLastX, mLastY;
+
+ public SurfaceControlWithBackground(SurfaceControlWithBackground other) {
+ super(other);
+ mBackgroundControl = other.mBackgroundControl;
+ mOpaque = other.mOpaque;
+ mVisible = other.mVisible;
+ mWindowSurfaceController = other.mWindowSurfaceController;
+ }
+
+ public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format,
+ int flags, int windowType, int ownerUid,
+ WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
+ super(s, name, w, h, format, flags, windowType, ownerUid);
+
+ // We should only show background when the window is letterboxed in a task.
+ if (!windowSurfaceController.mAnimator.mWin.isLetterboxedAppWindow()) {
+ return;
+ }
+ mWindowSurfaceController = windowSurfaceController;
+ mLastWidth = w;
+ mLastHeight = h;
+ mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+ mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+ mBackgroundControl = new SurfaceControl(s, "Background for - " + name,
+ mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE,
+ flags | SurfaceControl.FX_SURFACE_DIM);
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.setAlpha(alpha);
+ }
+
+ @Override
+ public void setLayer(int zorder) {
+ super.setLayer(zorder);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ // TODO: Use setRelativeLayer(Integer.MIN_VALUE) when it's fixed.
+ mBackgroundControl.setLayer(zorder - 1);
+ }
+
+ @Override
+ public void setPosition(float x, float y) {
+ super.setPosition(x, y);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mLastX = x;
+ mLastY = y;
+ updateBgPosition();
+ }
+
+ private void updateBgPosition() {
+ mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+ final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
+ final float offsetX = (mTmpContainerRect.left - winFrame.left) * mLastDsDx;
+ final float offsetY = (mTmpContainerRect.top - winFrame.top) * mLastDsDy;
+ mBackgroundControl.setPosition(mLastX + offsetX, mLastY + offsetY);
+ }
+
+ @Override
+ public void setSize(int w, int h) {
+ super.setSize(w, h);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mLastWidth = w;
+ mLastHeight = h;
+ mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+ mBackgroundControl.setSize(mTmpContainerRect.width(), mTmpContainerRect.height());
+ }
+
+ @Override
+ public void setWindowCrop(Rect crop) {
+ super.setWindowCrop(crop);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ if (crop.width() < mLastWidth || crop.height() < mLastHeight) {
+ // We're animating and cropping window, compute the appropriate crop for background.
+ calculateBgCrop(crop);
+ mBackgroundControl.setWindowCrop(mTmpContainerRect);
+ } else {
+ // When not animating just set crop to container rect.
+ mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+ mBackgroundControl.setWindowCrop(mTmpContainerRect);
+ }
+ }
+
+ @Override
+ public void setFinalCrop(Rect crop) {
+ super.setFinalCrop(crop);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ if (crop.width() < mLastWidth || crop.height() < mLastHeight) {
+ // We're animating and cropping window, compute the appropriate crop for background.
+ calculateBgCrop(crop);
+ mBackgroundControl.setFinalCrop(mTmpContainerRect);
+ } else {
+ // When not animating just set crop to container rect.
+ mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+ mBackgroundControl.setFinalCrop(mTmpContainerRect);
+ }
+ }
+
+ /** Compute background crop based on current animation progress for main surface control. */
+ private void calculateBgCrop(Rect crop) {
+ // Track overall progress of animation by computing cropped portion of status bar.
+ final Rect contentInsets = mWindowSurfaceController.mAnimator.mWin.mContentInsets;
+ float d = contentInsets.top == 0 ? 0 : (float) crop.top / contentInsets.top;
+
+ // Compute additional offset for the background when app window is positioned not at (0,0).
+ // E.g. landscape with navigation bar on the left.
+ final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
+ final int offsetX = (int) (winFrame.left * mLastDsDx * d + 0.5);
+ final int offsetY = (int) (winFrame.top * mLastDsDy * d + 0.5);
+
+ // Compute new scaled width and height for background that will depend on current animation
+ // progress. Those consist of current crop rect for the main surface + scaled areas outside
+ // of letterboxed area.
+ mWindowSurfaceController.getContainerRect(mTmpContainerRect);
+ final int backgroundWidth =
+ (int) (crop.width() + (mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
+ final int backgroundHeight =
+ (int) (crop.height() + (mTmpContainerRect.height() - mLastHeight) * (1 - d) + 0.5);
+
+ mTmpContainerRect.set(crop);
+ // Make sure that part of background to left/top is visible and scaled.
+ mTmpContainerRect.offset(offsetX, offsetY);
+ // Set correct width/height, so that area to right/bottom is cropped properly.
+ mTmpContainerRect.right = mTmpContainerRect.left + backgroundWidth;
+ mTmpContainerRect.bottom = mTmpContainerRect.top + backgroundHeight;
+ }
+
+ @Override
+ public void setLayerStack(int layerStack) {
+ super.setLayerStack(layerStack);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.setLayerStack(layerStack);
+ }
+
+ @Override
+ public void setOpaque(boolean isOpaque) {
+ super.setOpaque(isOpaque);
+ mOpaque = isOpaque;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void setSecure(boolean isSecure) {
+ super.setSecure(isSecure);
+ }
+
+ @Override
+ public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
+ super.setMatrix(dsdx, dtdx, dtdy, dsdy);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
+ mLastDsDx = dsdx;
+ mLastDsDy = dsdy;
+ updateBgPosition();
+ }
+
+ @Override
+ public void hide() {
+ super.hide();
+ mVisible = false;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ mVisible = true;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.destroy();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.release();
+ }
+
+ @Override
+ public void setTransparentRegionHint(Region region) {
+ super.setTransparentRegionHint(region);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.setTransparentRegionHint(region);
+ }
+
+ @Override
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ super.deferTransactionUntil(handle, frame);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.deferTransactionUntil(handle, frame);
+ }
+
+ @Override
+ public void deferTransactionUntil(Surface barrier, long frame) {
+ super.deferTransactionUntil(barrier, frame);
+
+ if (mBackgroundControl == null) {
+ return;
+ }
+ mBackgroundControl.deferTransactionUntil(barrier, frame);
+ }
+
+ private void updateBackgroundVisibility() {
+ if (mBackgroundControl == null) {
+ return;
+ }
+ if (mOpaque && mVisible) {
+ mBackgroundControl.show();
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0c217ca..90a2892 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3244,6 +3244,15 @@
return !isInMultiWindowMode();
}
+ /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
+ boolean isLetterboxedAppWindow() {
+ final Task task = getTask();
+ final boolean taskIsFullscreen = task != null && task.isFullscreen();
+ final boolean appWindowIsFullscreen = mAppToken != null && !mAppToken.hasBounds();
+
+ return taskIsFullscreen && !appWindowIsFullscreen;
+ }
+
/** Returns the appropriate bounds to use for computing frames. */
private void getContainerBounds(Rect outBounds) {
if (isInMultiWindowMode()) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0cc505e..86265c29 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1200,7 +1200,8 @@
if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Applying decor to crop win=" + w + " mDecorFrame="
+ w.mDecorFrame + " mSystemDecorRect=" + mSystemDecorRect);
- final boolean fullscreen = w.fillsDisplay();
+ final Task task = w.getTask();
+ final boolean fullscreen = w.fillsDisplay() || (task != null && task.isFullscreen());
final boolean isFreeformResizing =
w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
@@ -1526,6 +1527,19 @@
}
}
+ /**
+ * Get rect of the task this window is currently in. If there is no task, rect will be set to
+ * empty.
+ */
+ void getContainerRect(Rect rect) {
+ final Task task = mWin.getTask();
+ if (task != null) {
+ task.getDimBounds(rect);
+ } else {
+ rect.left = rect.top = rect.right = rect.bottom = 0;
+ }
+ }
+
void prepareSurfaceLocked(final boolean recoveringMemory) {
final WindowState w = mWin;
if (!hasSurface()) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 1728cfb..110d5cb 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -50,7 +50,7 @@
final WindowStateAnimator mAnimator;
- private SurfaceControl mSurfaceControl;
+ private SurfaceControlWithBackground mSurfaceControl;
// Should only be set from within setShown().
private boolean mSurfaceShown = false;
@@ -97,15 +97,10 @@
mWindowType = windowType;
mWindowSession = win.mSession;
- if (DEBUG_SURFACE_TRACE) {
- mSurfaceControl = new SurfaceTrace(
- s, name, w, h, format, flags, windowType, ownerUid);
- } else {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- mSurfaceControl = new SurfaceControl(
- s, name, w, h, format, flags, windowType, ownerUid);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+ mSurfaceControl = new SurfaceControlWithBackground(
+ s, name, w, h, format, flags, windowType, ownerUid, this);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (mService.mRoot.mSurfaceTraceEnabled) {
mSurfaceControl = new RemoteSurfaceTrace(
@@ -118,7 +113,7 @@
}
void removeRemoteTrace() {
- mSurfaceControl = new SurfaceControl(mSurfaceControl);
+ mSurfaceControl = new SurfaceControlWithBackground(mSurfaceControl);
}
@@ -291,30 +286,30 @@
mSurfaceControl.setGeometryAppliesWithResize();
}
- void setMatrixInTransaction(float dsdx, float dtdx, float dsdy, float dtdy,
+ void setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy,
boolean recoveringMemory) {
final boolean matrixChanged = mLastDsdx != dsdx || mLastDtdx != dtdx ||
- mLastDsdy != dsdy || mLastDtdy != dtdy;
+ mLastDtdy != dtdy || mLastDsdy != dsdy;
if (!matrixChanged) {
return;
}
mLastDsdx = dsdx;
mLastDtdx = dtdx;
- mLastDsdy = dsdy;
mLastDtdy = dtdy;
+ mLastDsdy = dsdy;
try {
if (SHOW_TRANSACTIONS) logSurface(
- "MATRIX [" + dsdx + "," + dtdx + "," + dsdy + "," + dtdy + "]", null);
+ "MATRIX [" + dsdx + "," + dtdx + "," + dtdy + "," + dsdy + "]", null);
mSurfaceControl.setMatrix(
- dsdx, dtdx, dsdy, dtdy);
+ dsdx, dtdx, dtdy, dsdy);
} catch (RuntimeException e) {
// If something goes wrong with the surface (such
// as running out of memory), don't take down the
// entire system.
Slog.e(TAG, "Error setting matrix on surface surface" + title
- + " MATRIX [" + dsdx + "," + dtdx + "," + dsdy + "," + dtdy + "]", null);
+ + " MATRIX [" + dsdx + "," + dtdx + "," + dtdy + "," + dsdy + "]", null);
if (!recoveringMemory) {
mAnimator.reclaimSomeSurfaceMemory("matrix", true);
}
@@ -421,6 +416,10 @@
}
}
+ void getContainerRect(Rect rect) {
+ mAnimator.getContainerRect(rect);
+ }
+
boolean showRobustlyInTransaction() {
if (SHOW_TRANSACTIONS) logSurface(
"SHOW (performLayout)", null);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a78c261..23db6ac 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1036,6 +1036,26 @@
*/
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET =
"carrier_default_actions_on_reset_string_array";
+
+ /**
+ * Defines carrier-specific actions which act upon
+ * com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE,
+ * used for customization of the default carrier app
+ * Format:
+ * {
+ * "true : CARRIER_ACTION_IDX_1",
+ * "false: CARRIER_ACTION_IDX_2"
+ * }
+ * Where {@code true} is a boolean indicates default network available/unavailable
+ * Where {@code CARRIER_ACTION_IDX} is an integer defined in
+ * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * Example:
+ * {@link com.android.carrierdefaultapp.CarrierActionUtils
+ * #CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER enable the app as the default URL handler}
+ * @hide
+ */
+ public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE =
+ "carrier_default_actions_on_default_network_available_string_array";
/**
* Defines a list of acceptable redirection url for default carrier app
* @hides
@@ -1712,9 +1732,10 @@
sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, "");
sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
new String[]{
- "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:" +
- "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED," +
- "com.android.internal.telephony.CARRIER_SIGNAL_RESET"
+ "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:"
+ + "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED,"
+ + "com.android.internal.telephony.CARRIER_SIGNAL_RESET,"
+ + "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE"
});
sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
@@ -1722,12 +1743,22 @@
// Default carrier app configurations
sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY,
new String[]{
- "4, 1"
+ "9, 4, 1"
+ //9: CARRIER_ACTION_REGISTER_NETWORK_AVAIL
//4: CARRIER_ACTION_DISABLE_METERED_APNS
//1: CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
});
sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET, new String[]{
- "6" //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+ "6, 8"
+ //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+ //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER
+ });
+ sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE,
+ new String[] {
+ String.valueOf(false) + ": 7",
+ //7: CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER
+ String.valueOf(true) + ": 8"
+ //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER
});
sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cee77fb..6a9d00e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6673,6 +6673,25 @@
}
/**
+ * Action set from carrier signalling broadcast receivers to start/stop reporting default
+ * network available events
+ * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+ * @param subId the subscription ID that this action applies to.
+ * @param report control start/stop reporting network status.
+ * @hide
+ */
+ public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ service.carrierActionReportDefaultNetworkStatus(subId, report);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#carrierActionReportDefaultNetworkStatus", e);
+ }
+ }
+
+ /**
* Get aggregated video call data usage since boot.
* Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
*
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 654adb2..9262ec5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1303,6 +1303,16 @@
void carrierActionSetRadioEnabled(int subId, boolean enabled);
/**
+ * Action set from carrier signalling broadcast receivers to start/stop reporting default
+ * network conditions.
+ * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+ * @param subId the subscription ID that this action applies to.
+ * @param report control start/stop reporting default network events.
+ * @hide
+ */
+ void carrierActionReportDefaultNetworkStatus(int subId, boolean report);
+
+ /**
* Get aggregated video call data usage since boot.
* Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
*
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 0343890..f29d993c 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -447,6 +447,20 @@
"com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
/**
+ * <p>Broadcast Action: when system default network available/unavailable with
+ * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
+ * other network becomes system default network, Wi-Fi for example.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd>
+ * <li>subId</li><dd>Sub Id which associated the default data.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system. </p>
+ */
+ public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
+ "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+
+ /**
* <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
* intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
* The intent will have the following extra values:</p>
@@ -465,7 +479,7 @@
public static final String EXTRA_APN_PROTO_KEY = "apnProto";
public static final String EXTRA_PCO_ID_KEY = "pcoId";
public static final String EXTRA_PCO_VALUE_KEY = "pcoValue";
-
+ public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable";
/**
* Broadcast action to trigger CI OMA-DM Session.
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9a37913..a5783a5 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -268,8 +268,7 @@
continue;
}
- if (!parser->element_namespace().empty() ||
- parser->element_name() != "resources") {
+ if (!parser->element_namespace().empty() || parser->element_name() != "resources") {
diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
<< "root element must be <resources>");
return false;
@@ -328,8 +327,7 @@
parsed_resource.comment = std::move(comment);
// Extract the product name if it exists.
- if (Maybe<StringPiece> maybe_product =
- xml::FindNonEmptyAttribute(parser, "product")) {
+ if (Maybe<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
parsed_resource.product = maybe_product.value().to_string();
}
@@ -348,10 +346,8 @@
for (const ResourceName& stripped_resource : stripped_resources) {
if (!table_->FindResource(stripped_resource)) {
// Failed to find the resource.
- diag_->Error(DiagMessage(source_)
- << "resource '" << stripped_resource
- << "' "
- "was filtered out but no product variant remains");
+ diag_->Error(DiagMessage(source_) << "resource '" << stripped_resource
+ << "' was filtered out but no product variant remains");
error = true;
}
}
@@ -589,7 +585,7 @@
// This can only be a StyledString.
std::unique_ptr<StyledString> styled_string =
util::make_unique<StyledString>(table_->string_pool.MakeRef(
- style_string, StringPool::Context(StringPool::Context::kStylePriority, config_)));
+ style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_)));
styled_string->untranslatable_sections = std::move(untranslatable_sections);
return std::move(styled_string);
}
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index d47a529..1683c64 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -117,7 +117,7 @@
StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
ASSERT_THAT(str, NotNull());
- EXPECT_THAT(*str->value->str, Eq("This is my aunt\u2019s fickle string"));
+ EXPECT_THAT(str->value->value, Eq("This is my aunt\u2019s fickle string"));
EXPECT_THAT(str->value->spans, SizeIs(2));
EXPECT_THAT(str->untranslatable_sections, IsEmpty());
@@ -190,7 +190,7 @@
StyledString* str = test::GetValue<StyledString>(&table_, "string/foo");
ASSERT_THAT(str, NotNull());
- EXPECT_THAT(*str->value->str, Eq("There are %1$d apples"));
+ EXPECT_THAT(str->value->value, Eq("There are %1$d apples"));
ASSERT_THAT(str->untranslatable_sections, SizeIs(1));
// We expect indices and lengths that span to include the whitespace
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 6e6a2ba..f193fe0 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -700,7 +700,7 @@
spans++;
}
return util::make_unique<StyledString>(dst_pool->MakeRef(
- style_str, StringPool::Context(StringPool::Context::kStylePriority, config)));
+ style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));
} else {
if (type != ResourceType::kString && util::StartsWith(str, "res/")) {
// This must be a FileReference.
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 947e091..eb59175 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -253,10 +253,9 @@
}
void StyledString::Print(std::ostream* out) const {
- *out << "(styled string) \"" << *value->str << "\"";
+ *out << "(styled string) \"" << value->value << "\"";
for (const StringPool::Span& span : value->spans) {
- *out << " " << *span.name << ":" << span.first_char << ","
- << span.last_char;
+ *out << " " << *span.name << ":" << span.first_char << "," << span.last_char;
}
}
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 57da5f0..705b1ab 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -27,7 +27,7 @@
#include "util/BigBuffer.h"
#include "util/Util.h"
-using android::StringPiece;
+using ::android::StringPiece;
namespace aapt {
@@ -75,9 +75,14 @@
return &entry_->value;
}
-const std::string& StringPool::Ref::operator*() const { return entry_->value; }
+const std::string& StringPool::Ref::operator*() const {
+ return entry_->value;
+}
-size_t StringPool::Ref::index() const { return entry_->index; }
+size_t StringPool::Ref::index() const {
+ // Account for the styles, which *always* come first.
+ return entry_->pool_->styles_.size() + entry_->index_;
+}
const StringPool::Context& StringPool::Ref::GetContext() const {
return entry_->context;
@@ -104,8 +109,7 @@
}
}
-StringPool::StyleRef& StringPool::StyleRef::operator=(
- const StringPool::StyleRef& rhs) {
+StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
if (rhs.entry_ != nullptr) {
rhs.entry_->ref_++;
}
@@ -118,7 +122,7 @@
}
bool StringPool::StyleRef::operator==(const StyleRef& rhs) const {
- if (entry_->str != rhs.entry_->str) {
+ if (entry_->value != rhs.entry_->value) {
return false;
}
@@ -137,7 +141,9 @@
return true;
}
-bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const { return !operator==(rhs); }
+bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const {
+ return !operator==(rhs);
+}
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
return entry_;
@@ -147,23 +153,24 @@
return *entry_;
}
-size_t StringPool::StyleRef::index() const { return entry_->str.index(); }
+size_t StringPool::StyleRef::index() const {
+ return entry_->index_;
+}
const StringPool::Context& StringPool::StyleRef::GetContext() const {
- return entry_->str.GetContext();
+ return entry_->context;
}
StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
return MakeRefImpl(str, Context{}, true);
}
-StringPool::Ref StringPool::MakeRef(const StringPiece& str,
- const Context& context) {
+StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
return MakeRefImpl(str, context, true);
}
-StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str,
- const Context& context, bool unique) {
+StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
+ bool unique) {
if (unique) {
auto iter = indexed_strings_.find(str);
if (iter != std::end(indexed_strings_)) {
@@ -171,82 +178,87 @@
}
}
- Entry* entry = new Entry();
+ std::unique_ptr<Entry> entry(new Entry());
entry->value = str.to_string();
entry->context = context;
- entry->index = strings_.size();
+ entry->index_ = strings_.size();
entry->ref_ = 0;
- strings_.emplace_back(entry);
- indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
- return Ref(entry);
+ entry->pool_ = this;
+
+ Entry* borrow = entry.get();
+ strings_.emplace_back(std::move(entry));
+ indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow));
+ return Ref(borrow);
}
StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
return MakeRef(str, Context{});
}
-StringPool::StyleRef StringPool::MakeRef(const StyleString& str,
- const Context& context) {
- Entry* entry = new Entry();
+StringPool::StyleRef StringPool::MakeRef(const StyleString& str, const Context& context) {
+ std::unique_ptr<StyleEntry> entry(new StyleEntry());
entry->value = str.str;
entry->context = context;
- entry->index = strings_.size();
+ entry->index_ = styles_.size();
entry->ref_ = 0;
- strings_.emplace_back(entry);
- indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
-
- StyleEntry* style_entry = new StyleEntry();
- style_entry->str = Ref(entry);
for (const aapt::Span& span : str.spans) {
- style_entry->spans.emplace_back(
- Span{MakeRef(span.name), span.first_char, span.last_char});
+ entry->spans.emplace_back(Span{MakeRef(span.name), span.first_char, span.last_char});
}
- style_entry->ref_ = 0;
- styles_.emplace_back(style_entry);
- return StyleRef(style_entry);
+
+ StyleEntry* borrow = entry.get();
+ styles_.emplace_back(std::move(entry));
+ return StyleRef(borrow);
}
StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) {
- Entry* entry = new Entry();
- entry->value = *ref.entry_->str;
- entry->context = ref.entry_->str.entry_->context;
- entry->index = strings_.size();
+ std::unique_ptr<StyleEntry> entry(new StyleEntry());
+ entry->value = ref.entry_->value;
+ entry->context = ref.entry_->context;
+ entry->index_ = styles_.size();
entry->ref_ = 0;
- strings_.emplace_back(entry);
- indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry));
-
- StyleEntry* style_entry = new StyleEntry();
- style_entry->str = Ref(entry);
for (const Span& span : ref.entry_->spans) {
- style_entry->spans.emplace_back(
- Span{MakeRef(*span.name), span.first_char, span.last_char});
+ entry->spans.emplace_back(Span{MakeRef(*span.name), span.first_char, span.last_char});
}
- style_entry->ref_ = 0;
- styles_.emplace_back(style_entry);
- return StyleRef(style_entry);
+
+ StyleEntry* borrow = entry.get();
+ styles_.emplace_back(std::move(entry));
+ return StyleRef(borrow);
+}
+
+void StringPool::ReAssignIndices() {
+ // Assign the style indices.
+ const size_t style_len = styles_.size();
+ for (size_t index = 0; index < style_len; index++) {
+ styles_[index]->index_ = index;
+ }
+
+ // Assign the string indices.
+ const size_t string_len = strings_.size();
+ for (size_t index = 0; index < string_len; index++) {
+ strings_[index]->index_ = index;
+ }
}
void StringPool::Merge(StringPool&& pool) {
- indexed_strings_.insert(pool.indexed_strings_.begin(),
- pool.indexed_strings_.end());
- pool.indexed_strings_.clear();
- std::move(pool.strings_.begin(), pool.strings_.end(),
- std::back_inserter(strings_));
- pool.strings_.clear();
- std::move(pool.styles_.begin(), pool.styles_.end(),
- std::back_inserter(styles_));
- pool.styles_.clear();
-
- // Assign the indices.
- const size_t len = strings_.size();
- for (size_t index = 0; index < len; index++) {
- strings_[index]->index = index;
+ // First, change the owning pool for the incoming strings.
+ for (std::unique_ptr<Entry>& entry : pool.strings_) {
+ entry->pool_ = this;
}
+
+ // Now move the styles, strings, and indices over.
+ std::move(pool.styles_.begin(), pool.styles_.end(), std::back_inserter(styles_));
+ pool.styles_.clear();
+ std::move(pool.strings_.begin(), pool.strings_.end(), std::back_inserter(strings_));
+ pool.strings_.clear();
+ indexed_strings_.insert(pool.indexed_strings_.begin(), pool.indexed_strings_.end());
+ pool.indexed_strings_.clear();
+
+ ReAssignIndices();
}
-void StringPool::HintWillAdd(size_t stringCount, size_t styleCount) {
- strings_.reserve(strings_.size() + stringCount);
- styles_.reserve(styles_.size() + styleCount);
+void StringPool::HintWillAdd(size_t string_count, size_t style_count) {
+ strings_.reserve(strings_.size() + string_count);
+ styles_.reserve(styles_.size() + style_count);
}
void StringPool::Prune() {
@@ -262,47 +274,42 @@
auto end_iter2 =
std::remove_if(strings_.begin(), strings_.end(),
- [](const std::unique_ptr<Entry>& entry) -> bool {
- return entry->ref_ <= 0;
- });
+ [](const std::unique_ptr<Entry>& entry) -> bool { return entry->ref_ <= 0; });
+ auto end_iter3 = std::remove_if(
+ styles_.begin(), styles_.end(),
+ [](const std::unique_ptr<StyleEntry>& entry) -> bool { return entry->ref_ <= 0; });
- auto end_iter3 =
- std::remove_if(styles_.begin(), styles_.end(),
- [](const std::unique_ptr<StyleEntry>& entry) -> bool {
- return entry->ref_ <= 0;
- });
-
- // Remove the entries at the end or else we'll be accessing
- // a deleted string from the StyleEntry.
+ // Remove the entries at the end or else we'll be accessing a deleted string from the StyleEntry.
strings_.erase(end_iter2, strings_.end());
styles_.erase(end_iter3, styles_.end());
- // Reassign the indices.
- const size_t len = strings_.size();
- for (size_t index = 0; index < len; index++) {
- strings_[index]->index = index;
+ ReAssignIndices();
+}
+
+template <typename E>
+static void SortEntries(
+ std::vector<std::unique_ptr<E>>& entries,
+ const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) {
+ using UEntry = std::unique_ptr<E>;
+
+ if (cmp != nullptr) {
+ std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool {
+ int r = cmp(a->context, b->context);
+ if (r == 0) {
+ r = a->value.compare(b->value);
+ }
+ return r < 0;
+ });
+ } else {
+ std::sort(entries.begin(), entries.end(),
+ [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; });
}
}
-void StringPool::Sort(
- const std::function<bool(const Entry&, const Entry&)>& cmp) {
- std::sort(
- strings_.begin(), strings_.end(),
- [&cmp](const std::unique_ptr<Entry>& a,
- const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); });
-
- // Assign the indices.
- const size_t len = strings_.size();
- for (size_t index = 0; index < len; index++) {
- strings_[index]->index = index;
- }
-
- // Reorder the styles.
- std::sort(styles_.begin(), styles_.end(),
- [](const std::unique_ptr<StyleEntry>& lhs,
- const std::unique_ptr<StyleEntry>& rhs) -> bool {
- return lhs->str.index() < rhs->str.index();
- });
+void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) {
+ SortEntries(styles_, cmp);
+ SortEntries(strings_, cmp);
+ ReAssignIndices();
}
template <typename T>
@@ -327,60 +334,31 @@
return length > kMaxSize ? 2 : 1;
}
-bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
- const size_t start_index = out->size();
- android::ResStringPool_header* header =
- out->NextBlock<android::ResStringPool_header>();
- header->header.type = android::RES_STRING_POOL_TYPE;
- header->header.headerSize = sizeof(*header);
- header->stringCount = pool.size();
+static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out) {
if (utf8) {
- header->flags |= android::ResStringPool_header::UTF8_FLAG;
- }
+ const std::string& encoded = str;
+ const ssize_t utf16_length =
+ utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+ CHECK(utf16_length >= 0);
- uint32_t* indices =
- pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr;
+ const size_t total_size = EncodedLengthUnits<char>(utf16_length) +
+ EncodedLengthUnits<char>(encoded.length()) + encoded.size() + 1;
- uint32_t* style_indices = nullptr;
- if (!pool.styles_.empty()) {
- header->styleCount = pool.styles_.back()->str.index() + 1;
- style_indices = out->NextBlock<uint32_t>(header->styleCount);
- }
+ char* data = out->NextBlock<char>(total_size);
- const size_t before_strings_index = out->size();
- header->stringsStart = before_strings_index - start_index;
+ // First encode the UTF16 string length.
+ data = EncodeLength(data, utf16_length);
- for (const auto& entry : pool) {
- *indices = out->size() - before_strings_index;
- indices++;
-
- if (utf8) {
- const std::string& encoded = entry->value;
- const ssize_t utf16_length = utf8_to_utf16_length(
- reinterpret_cast<const uint8_t*>(entry->value.data()),
- entry->value.size());
- CHECK(utf16_length >= 0);
-
- const size_t total_size = EncodedLengthUnits<char>(utf16_length) +
- EncodedLengthUnits<char>(encoded.length()) +
- encoded.size() + 1;
-
- char* data = out->NextBlock<char>(total_size);
-
- // First encode the UTF16 string length.
- data = EncodeLength(data, utf16_length);
-
- // Now encode the size of the real UTF8 string.
- data = EncodeLength(data, encoded.length());
- strncpy(data, encoded.data(), encoded.size());
+ // Now encode the size of the real UTF8 string.
+ data = EncodeLength(data, encoded.length());
+ strncpy(data, encoded.data(), encoded.size());
} else {
- const std::u16string encoded = util::Utf8ToUtf16(entry->value);
+ const std::u16string encoded = util::Utf8ToUtf16(str);
const ssize_t utf16_length = encoded.size();
// Total number of 16-bit words to write.
- const size_t total_size =
- EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1;
+ const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1;
char16_t* data = out->NextBlock<char16_t>(total_size);
@@ -395,31 +373,55 @@
// The null-terminating character is already here due to the block of data
// being set to 0s on allocation.
}
+}
+
+bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) {
+ const size_t start_index = out->size();
+ android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>();
+ header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE);
+ header->header.headerSize = util::HostToDevice16(sizeof(*header));
+ header->stringCount = util::HostToDevice32(pool.size());
+ header->styleCount = util::HostToDevice32(pool.styles_.size());
+ if (utf8) {
+ header->flags |= android::ResStringPool_header::UTF8_FLAG;
+ }
+
+ uint32_t* indices = pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr;
+ uint32_t* style_indices =
+ pool.styles_.size() != 0 ? out->NextBlock<uint32_t>(pool.styles_.size()) : nullptr;
+
+ const size_t before_strings_index = out->size();
+ header->stringsStart = before_strings_index - start_index;
+
+ // Styles always come first.
+ for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) {
+ *indices++ = out->size() - before_strings_index;
+ EncodeString(entry->value, utf8, out);
+ }
+
+ for (const std::unique_ptr<Entry>& entry : pool.strings_) {
+ *indices++ = out->size() - before_strings_index;
+ EncodeString(entry->value, utf8, out);
}
out->Align4();
- if (!pool.styles_.empty()) {
+ if (style_indices != nullptr) {
const size_t before_styles_index = out->size();
- header->stylesStart = before_styles_index - start_index;
+ header->stylesStart = util::HostToDevice32(before_styles_index - start_index);
- size_t current_index = 0;
- for (const auto& entry : pool.styles_) {
- while (entry->str.index() > current_index) {
- style_indices[current_index++] = out->size() - before_styles_index;
+ for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) {
+ *style_indices++ = out->size() - before_styles_index;
- uint32_t* span_offset = out->NextBlock<uint32_t>();
- *span_offset = android::ResStringPool_span::END;
- }
- style_indices[current_index++] = out->size() - before_styles_index;
-
- android::ResStringPool_span* span =
- out->NextBlock<android::ResStringPool_span>(entry->spans.size());
- for (const auto& s : entry->spans) {
- span->name.index = s.name.index();
- span->firstChar = s.first_char;
- span->lastChar = s.last_char;
- span++;
+ if (!entry->spans.empty()) {
+ android::ResStringPool_span* span =
+ out->NextBlock<android::ResStringPool_span>(entry->spans.size());
+ for (const Span& s : entry->spans) {
+ span->name.index = util::HostToDevice32(s.name.index());
+ span->firstChar = util::HostToDevice32(s.first_char);
+ span->lastChar = util::HostToDevice32(s.last_char);
+ span++;
+ }
}
uint32_t* spanEnd = out->NextBlock<uint32_t>();
@@ -436,7 +438,7 @@
memset(padding, 0xff, padding_length);
out->Align4();
}
- header->header.size = out->size() - start_index;
+ header->header.size = util::HostToDevice32(out->size() - start_index);
return true;
}
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index d1232a2..8350d0d 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -42,12 +42,16 @@
std::vector<Span> spans;
};
+// A StringPool for storing the value of String and StyledString resources.
+// Styles and Strings are stored separately, since the runtime variant of this
+// class -- ResStringPool -- requires that styled strings *always* appear first, since their
+// style data is stored as an array indexed by the same indices as the main string pool array.
+// Otherwise, the style data array would have to be sparse and take up more space.
class StringPool {
public:
class Context {
public:
enum : uint32_t {
- kStylePriority = 0u,
kHighPriority = 1u,
kNormalPriority = 0x7fffffffu,
kLowPriority = 0xffffffffu,
@@ -58,8 +62,8 @@
Context() = default;
Context(uint32_t p, const ConfigDescription& c) : priority(p), config(c) {}
explicit Context(uint32_t p) : priority(p) {}
- explicit Context(const ConfigDescription& c)
- : priority(kNormalPriority), config(c) {}
+ explicit Context(const ConfigDescription& c) : priority(kNormalPriority), config(c) {
+ }
};
class Entry;
@@ -116,13 +120,14 @@
public:
std::string value;
Context context;
- size_t index;
private:
friend class StringPool;
friend class Ref;
+ size_t index_;
int ref_;
+ const StringPool* pool_;
};
struct Span {
@@ -133,18 +138,18 @@
class StyleEntry {
public:
- Ref str;
+ std::string value;
+ Context context;
std::vector<Span> spans;
private:
friend class StringPool;
friend class StyleRef;
+ size_t index_;
int ref_;
};
- using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
-
static bool FlattenUtf8(BigBuffer* out, const StringPool& pool);
static bool FlattenUtf16(BigBuffer* out, const StringPool& pool);
@@ -152,92 +157,61 @@
StringPool(StringPool&&) = default;
StringPool& operator=(StringPool&&) = default;
- /**
- * Adds a string to the pool, unless it already exists. Returns
- * a reference to the string in the pool.
- */
+ // Adds a string to the pool, unless it already exists. Returns a reference to the string in the
+ // pool.
Ref MakeRef(const android::StringPiece& str);
- /**
- * Adds a string to the pool, unless it already exists, with a context
- * object that can be used when sorting the string pool. Returns
- * a reference to the string in the pool.
- */
+ // Adds a string to the pool, unless it already exists, with a context object that can be used
+ // when sorting the string pool. Returns a reference to the string in the pool.
Ref MakeRef(const android::StringPiece& str, const Context& context);
- /**
- * Adds a style to the string pool and returns a reference to it.
- */
+ // Adds a style to the string pool and returns a reference to it.
StyleRef MakeRef(const StyleString& str);
- /**
- * Adds a style to the string pool with a context object that
- * can be used when sorting the string pool. Returns a reference
- * to the style in the string pool.
- */
+ // Adds a style to the string pool with a context object that can be used when sorting the string
+ // pool. Returns a reference to the style in the string pool.
StyleRef MakeRef(const StyleString& str, const Context& context);
- /**
- * Adds a style from another string pool. Returns a reference to the
- * style in the string pool.
- */
+ // Adds a style from another string pool. Returns a reference to the style in the string pool.
StyleRef MakeRef(const StyleRef& ref);
- /**
- * Moves pool into this one without coalescing strings. When this
- * function returns, pool will be empty.
- */
+ // Moves pool into this one without coalescing strings. When this function returns, pool will be
+ // empty.
void Merge(StringPool&& pool);
- /**
- * Returns the number of strings in the table.
- */
- inline size_t size() const;
+ inline const std::vector<std::unique_ptr<Entry>>& strings() const {
+ return strings_;
+ }
- /**
- * Reserves space for strings and styles as an optimization.
- */
+ // Returns the number of strings in the table.
+ inline size_t size() const {
+ return styles_.size() + strings_.size();
+ }
+
+ // Reserves space for strings and styles as an optimization.
void HintWillAdd(size_t string_count, size_t style_count);
- /**
- * Sorts the strings according to some comparison function.
- */
- void Sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
+ // Sorts the strings according to their Context using some comparison function.
+ // Equal Contexts are further sorted by string value, lexicographically.
+ // If no comparison function is provided, values are only sorted lexicographically.
+ void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr);
- /**
- * Removes any strings that have no references.
- */
+ // Removes any strings that have no references.
void Prune();
private:
DISALLOW_COPY_AND_ASSIGN(StringPool);
- friend const_iterator begin(const StringPool& pool);
- friend const_iterator end(const StringPool& pool);
-
static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8);
Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+ void ReAssignIndices();
std::vector<std::unique_ptr<Entry>> strings_;
std::vector<std::unique_ptr<StyleEntry>> styles_;
std::unordered_multimap<android::StringPiece, Entry*> indexed_strings_;
};
-//
-// Inline implementation
-//
-
-inline size_t StringPool::size() const { return strings_.size(); }
-
-inline StringPool::const_iterator begin(const StringPool& pool) {
- return pool.strings_.begin();
-}
-
-inline StringPool::const_iterator end(const StringPool& pool) {
- return pool.strings_.end();
-}
-
} // namespace aapt
#endif // AAPT_STRING_POOL_H
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index f64a8cf..b1e5ce2 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -23,8 +23,12 @@
#include "test/Test.h"
#include "util/Util.h"
-using android::StringPiece;
-using android::StringPiece16;
+using ::android::StringPiece;
+using ::android::StringPiece16;
+using ::testing::Eq;
+using ::testing::Ne;
+using ::testing::NotNull;
+using ::testing::Pointee;
namespace aapt {
@@ -32,129 +36,127 @@
StringPool pool;
StringPool::Ref ref = pool.MakeRef("wut");
- EXPECT_EQ(*ref, "wut");
+ EXPECT_THAT(*ref, Eq("wut"));
}
TEST(StringPoolTest, InsertTwoUniqueStrings) {
StringPool pool;
- StringPool::Ref ref = pool.MakeRef("wut");
- StringPool::Ref ref2 = pool.MakeRef("hey");
+ StringPool::Ref ref_a = pool.MakeRef("wut");
+ StringPool::Ref ref_b = pool.MakeRef("hey");
- EXPECT_EQ(*ref, "wut");
- EXPECT_EQ(*ref2, "hey");
+ EXPECT_THAT(*ref_a, Eq("wut"));
+ EXPECT_THAT(*ref_b, Eq("hey"));
}
TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
StringPool pool;
- StringPool::Ref ref = pool.MakeRef("wut");
- StringPool::Ref ref2 = pool.MakeRef("wut");
+ StringPool::Ref ref_a = pool.MakeRef("wut");
+ StringPool::Ref ref_b = pool.MakeRef("wut");
- EXPECT_EQ(*ref, "wut");
- EXPECT_EQ(*ref2, "wut");
- EXPECT_EQ(1u, pool.size());
+ EXPECT_THAT(*ref_a, Eq("wut"));
+ EXPECT_THAT(*ref_b, Eq("wut"));
+ EXPECT_THAT(pool.size(), Eq(1u));
}
TEST(StringPoolTest, MaintainInsertionOrderIndex) {
StringPool pool;
- StringPool::Ref ref = pool.MakeRef("z");
- StringPool::Ref ref2 = pool.MakeRef("a");
- StringPool::Ref ref3 = pool.MakeRef("m");
+ StringPool::Ref ref_a = pool.MakeRef("z");
+ StringPool::Ref ref_b = pool.MakeRef("a");
+ StringPool::Ref ref_c = pool.MakeRef("m");
- EXPECT_EQ(0u, ref.index());
- EXPECT_EQ(1u, ref2.index());
- EXPECT_EQ(2u, ref3.index());
+ EXPECT_THAT(ref_a.index(), Eq(0u));
+ EXPECT_THAT(ref_b.index(), Eq(1u));
+ EXPECT_THAT(ref_c.index(), Eq(2u));
}
TEST(StringPoolTest, PruneStringsWithNoReferences) {
StringPool pool;
- StringPool::Ref refA = pool.MakeRef("foo");
- {
- StringPool::Ref ref = pool.MakeRef("wut");
- EXPECT_EQ(*ref, "wut");
- EXPECT_EQ(2u, pool.size());
- }
- StringPool::Ref refB = pool.MakeRef("bar");
+ StringPool::Ref ref_a = pool.MakeRef("foo");
- EXPECT_EQ(3u, pool.size());
+ {
+ StringPool::Ref ref_b = pool.MakeRef("wut");
+ EXPECT_THAT(*ref_b, Eq("wut"));
+ EXPECT_THAT(pool.size(), Eq(2u));
+ pool.Prune();
+ EXPECT_THAT(pool.size(), Eq(2u));
+ }
+ EXPECT_THAT(pool.size(), Eq(2u));
+
+ {
+ StringPool::Ref ref_c = pool.MakeRef("bar");
+ EXPECT_THAT(pool.size(), Eq(3u));
+
+ pool.Prune();
+ EXPECT_THAT(pool.size(), Eq(2u));
+ }
+ EXPECT_THAT(pool.size(), Eq(2u));
+
pool.Prune();
- EXPECT_EQ(2u, pool.size());
- StringPool::const_iterator iter = begin(pool);
- EXPECT_EQ((*iter)->value, "foo");
- EXPECT_LT((*iter)->index, 2u);
- ++iter;
- EXPECT_EQ((*iter)->value, "bar");
- EXPECT_LT((*iter)->index, 2u);
+ EXPECT_THAT(pool.size(), Eq(1u));
}
-TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
+TEST(StringPoolTest, SortAndMaintainIndexesInStringReferences) {
StringPool pool;
- StringPool::Ref ref = pool.MakeRef("z");
- StringPool::StyleRef ref2 = pool.MakeRef(StyleString{{"a"}});
- StringPool::Ref ref3 = pool.MakeRef("m");
+ StringPool::Ref ref_a = pool.MakeRef("z");
+ StringPool::Ref ref_b = pool.MakeRef("a");
+ StringPool::Ref ref_c = pool.MakeRef("m");
- EXPECT_EQ(*ref, "z");
- EXPECT_EQ(0u, ref.index());
+ EXPECT_THAT(*ref_a, Eq("z"));
+ EXPECT_THAT(ref_a.index(), Eq(0u));
- EXPECT_EQ(*(ref2->str), "a");
- EXPECT_EQ(1u, ref2.index());
+ EXPECT_THAT(*ref_b, Eq("a"));
+ EXPECT_THAT(ref_b.index(), Eq(1u));
- EXPECT_EQ(*ref3, "m");
- EXPECT_EQ(2u, ref3.index());
+ EXPECT_THAT(*ref_c, Eq("m"));
+ EXPECT_THAT(ref_c.index(), Eq(2u));
- pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.value < b.value;
- });
+ pool.Sort();
- EXPECT_EQ(*ref, "z");
- EXPECT_EQ(2u, ref.index());
+ EXPECT_THAT(*ref_a, Eq("z"));
+ EXPECT_THAT(ref_a.index(), Eq(2u));
- EXPECT_EQ(*(ref2->str), "a");
- EXPECT_EQ(0u, ref2.index());
+ EXPECT_THAT(*ref_b, Eq("a"));
+ EXPECT_THAT(ref_b.index(), Eq(0u));
- EXPECT_EQ(*ref3, "m");
- EXPECT_EQ(1u, ref3.index());
+ EXPECT_THAT(*ref_c, Eq("m"));
+ EXPECT_THAT(ref_c.index(), Eq(1u));
}
TEST(StringPoolTest, SortAndStillDedupe) {
StringPool pool;
- StringPool::Ref ref = pool.MakeRef("z");
- StringPool::Ref ref2 = pool.MakeRef("a");
- StringPool::Ref ref3 = pool.MakeRef("m");
+ StringPool::Ref ref_a = pool.MakeRef("z");
+ StringPool::Ref ref_b = pool.MakeRef("a");
+ StringPool::Ref ref_c = pool.MakeRef("m");
- pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.value < b.value;
- });
+ pool.Sort();
- StringPool::Ref ref4 = pool.MakeRef("z");
- StringPool::Ref ref5 = pool.MakeRef("a");
- StringPool::Ref ref6 = pool.MakeRef("m");
+ StringPool::Ref ref_d = pool.MakeRef("z");
+ StringPool::Ref ref_e = pool.MakeRef("a");
+ StringPool::Ref ref_f = pool.MakeRef("m");
- EXPECT_EQ(ref4.index(), ref.index());
- EXPECT_EQ(ref5.index(), ref2.index());
- EXPECT_EQ(ref6.index(), ref3.index());
+ EXPECT_THAT(ref_d.index(), Eq(ref_a.index()));
+ EXPECT_THAT(ref_e.index(), Eq(ref_b.index()));
+ EXPECT_THAT(ref_f.index(), Eq(ref_c.index()));
}
TEST(StringPoolTest, AddStyles) {
StringPool pool;
- StyleString str{{"android"}, {Span{{"b"}, 2, 6}}};
-
- StringPool::StyleRef ref = pool.MakeRef(str);
-
- EXPECT_EQ(0u, ref.index());
- EXPECT_EQ(std::string("android"), *(ref->str));
- ASSERT_EQ(1u, ref->spans.size());
+ StringPool::StyleRef ref = pool.MakeRef(StyleString{{"android"}, {Span{{"b"}, 2, 6}}});
+ EXPECT_THAT(ref.index(), Eq(0u));
+ EXPECT_THAT(ref->value, Eq("android"));
+ ASSERT_THAT(ref->spans.size(), Eq(1u));
const StringPool::Span& span = ref->spans.front();
- EXPECT_EQ(*(span.name), "b");
- EXPECT_EQ(2u, span.first_char);
- EXPECT_EQ(6u, span.last_char);
+ EXPECT_THAT(*span.name, Eq("b"));
+ EXPECT_THAT(span.first_char, Eq(2u));
+ EXPECT_THAT(span.last_char, Eq(6u));
}
TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
@@ -163,9 +165,25 @@
StringPool::Ref ref = pool.MakeRef("android");
StyleString str{{"android"}};
- StringPool::StyleRef styleRef = pool.MakeRef(str);
+ StringPool::StyleRef style_ref = pool.MakeRef(StyleString{{"android"}});
- EXPECT_NE(ref.index(), styleRef.index());
+ EXPECT_THAT(ref.index(), Ne(style_ref.index()));
+}
+
+TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) {
+ StringPool pool;
+
+ StringPool::StyleRef ref_a = pool.MakeRef(StyleString{{"beta"}});
+ StringPool::Ref ref_b = pool.MakeRef("alpha");
+ StringPool::StyleRef ref_c = pool.MakeRef(StyleString{{"alpha"}});
+
+ EXPECT_THAT(ref_b.index(), Ne(ref_c.index()));
+
+ pool.Sort();
+
+ EXPECT_THAT(ref_c.index(), Eq(0u));
+ EXPECT_THAT(ref_a.index(), Eq(1u));
+ EXPECT_THAT(ref_b.index(), Eq(2u));
}
TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
@@ -177,7 +195,7 @@
std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
ResStringPool test;
- ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
+ ASSERT_THAT(test.setTo(data.get(), buffer.size()), Eq(NO_ERROR));
}
TEST(StringPoolTest, FlattenOddCharactersUtf16) {
@@ -193,9 +211,9 @@
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
size_t len = 0;
const char16_t* str = test.stringAt(0, &len);
- EXPECT_EQ(1u, len);
- EXPECT_EQ(u'\u093f', *str);
- EXPECT_EQ(0u, str[1]);
+ EXPECT_THAT(len, Eq(1u));
+ EXPECT_THAT(str, Pointee(Eq(u'\u093f')));
+ EXPECT_THAT(str[1], Eq(0u));
}
constexpr const char* sLongString =
@@ -210,18 +228,20 @@
StringPool pool;
- StringPool::Ref ref1 = pool.MakeRef("hello");
- StringPool::Ref ref2 = pool.MakeRef("goodbye");
- StringPool::Ref ref3 = pool.MakeRef(sLongString);
- StringPool::Ref ref4 = pool.MakeRef("");
- StringPool::StyleRef ref5 = pool.MakeRef(
- StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}});
+ StringPool::Ref ref_a = pool.MakeRef("hello");
+ StringPool::Ref ref_b = pool.MakeRef("goodbye");
+ StringPool::Ref ref_c = pool.MakeRef(sLongString);
+ StringPool::Ref ref_d = pool.MakeRef("");
+ StringPool::StyleRef ref_e =
+ pool.MakeRef(StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}});
- EXPECT_EQ(0u, ref1.index());
- EXPECT_EQ(1u, ref2.index());
- EXPECT_EQ(2u, ref3.index());
- EXPECT_EQ(3u, ref4.index());
- EXPECT_EQ(4u, ref5.index());
+ // Styles are always first.
+ EXPECT_THAT(ref_e.index(), Eq(0u));
+
+ EXPECT_THAT(ref_a.index(), Eq(1u));
+ EXPECT_THAT(ref_b.index(), Eq(2u));
+ EXPECT_THAT(ref_c.index(), Eq(3u));
+ EXPECT_THAT(ref_d.index(), Eq(4u));
BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)};
StringPool::FlattenUtf8(&buffers[0], pool);
@@ -234,38 +254,37 @@
ResStringPool test;
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
- EXPECT_EQ(std::string("hello"), util::GetString(test, 0));
- EXPECT_EQ(StringPiece16(u"hello"), util::GetString16(test, 0));
+ EXPECT_THAT(util::GetString(test, 1), Eq("hello"));
+ EXPECT_THAT(util::GetString16(test, 1), Eq(u"hello"));
- EXPECT_EQ(std::string("goodbye"), util::GetString(test, 1));
- EXPECT_EQ(StringPiece16(u"goodbye"), util::GetString16(test, 1));
+ EXPECT_THAT(util::GetString(test, 2), Eq("goodbye"));
+ EXPECT_THAT(util::GetString16(test, 2), Eq(u"goodbye"));
- EXPECT_EQ(StringPiece(sLongString), util::GetString(test, 2));
- EXPECT_EQ(util::Utf8ToUtf16(sLongString), util::GetString16(test, 2).to_string());
+ EXPECT_THAT(util::GetString(test, 3), Eq(sLongString));
+ EXPECT_THAT(util::GetString16(test, 3), Eq(util::Utf8ToUtf16(sLongString)));
size_t len;
- EXPECT_TRUE(test.stringAt(3, &len) != nullptr ||
- test.string8At(3, &len) != nullptr);
+ EXPECT_TRUE(test.stringAt(4, &len) != nullptr || test.string8At(4, &len) != nullptr);
- EXPECT_EQ(std::string("style"), util::GetString(test, 4));
- EXPECT_EQ(StringPiece16(u"style"), util::GetString16(test, 4));
+ EXPECT_THAT(util::GetString(test, 0), Eq("style"));
+ EXPECT_THAT(util::GetString16(test, 0), Eq(u"style"));
- const ResStringPool_span* span = test.styleAt(4);
- ASSERT_NE(nullptr, span);
- EXPECT_EQ(std::string("b"), util::GetString(test, span->name.index));
- EXPECT_EQ(StringPiece16(u"b"), util::GetString16(test, span->name.index));
- EXPECT_EQ(0u, span->firstChar);
- EXPECT_EQ(1u, span->lastChar);
+ const ResStringPool_span* span = test.styleAt(0);
+ ASSERT_THAT(span, NotNull());
+ EXPECT_THAT(util::GetString(test, span->name.index), Eq("b"));
+ EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"b"));
+ EXPECT_THAT(span->firstChar, Eq(0u));
+ EXPECT_THAT(span->lastChar, Eq(1u));
span++;
- ASSERT_NE(ResStringPool_span::END, span->name.index);
- EXPECT_EQ(std::string("i"), util::GetString(test, span->name.index));
- EXPECT_EQ(StringPiece16(u"i"), util::GetString16(test, span->name.index));
- EXPECT_EQ(2u, span->firstChar);
- EXPECT_EQ(3u, span->lastChar);
+ ASSERT_THAT(span->name.index, Ne(ResStringPool_span::END));
+ EXPECT_THAT(util::GetString(test, span->name.index), Eq("i"));
+ EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"i"));
+ EXPECT_THAT(span->firstChar, Eq(2u));
+ EXPECT_THAT(span->lastChar, Eq(3u));
span++;
- EXPECT_EQ(ResStringPool_span::END, span->name.index);
+ EXPECT_THAT(span->name.index, Eq(ResStringPool_span::END));
}
}
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 8741b7b..e1c45d6 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -179,6 +179,13 @@
xml::Attribute{"", "configForSplit", app_info.split_name.value()});
}
+ // Splits may contain more configurations than originally desired (fallback densities, etc.).
+ // This makes programmatic discovery of split targetting difficult. Encode the original
+ // split constraints intended for this split.
+ std::stringstream target_config_str;
+ target_config_str << util::Joiner(constraints.configs, ",");
+ manifest_el->attributes.push_back(xml::Attribute{"", "targetConfig", target_config_str.str()});
+
std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>();
application_el->name = "application";
application_el->attributes.push_back(
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index a031ea4..871ed4f 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -120,7 +120,7 @@
// All Span indices are UTF-16 based, according to the resources.arsc format expected by the
// runtime. So we will do all our processing in UTF-16, then convert back.
- const std::u16string text16 = util::Utf8ToUtf16(*string->value->str);
+ const std::u16string text16 = util::Utf8ToUtf16(string->value->value);
// Convenient wrapper around the text that allows us to work with StringPieces.
const StringPiece16 text(text16);
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index b08e1da..711558a 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -31,7 +31,7 @@
util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
Pseudolocalizer::Method::kNone, &pool);
- EXPECT_EQ(original_style.str, *new_string->value->str);
+ EXPECT_EQ(original_style.str, new_string->value->value);
ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size());
EXPECT_EQ(std::string("i"), *new_string->value->spans[0].name);
@@ -52,7 +52,7 @@
util::make_unique<StyledString>(pool.MakeRef(original_style)).get(),
Pseudolocalizer::Method::kAccent, &pool);
- EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str);
+ EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), new_string->value->value);
ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size());
EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
@@ -79,7 +79,7 @@
Pseudolocalizer::Method::kAccent, &pool);
ASSERT_NE(nullptr, new_string);
ASSERT_EQ(2u, new_string->value->spans.size());
- EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str);
+ EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value);
EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
@@ -101,7 +101,7 @@
Pseudolocalizer::Method::kAccent, &pool);
ASSERT_NE(nullptr, new_string);
ASSERT_EQ(2u, new_string->value->spans.size());
- EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str);
+ EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value);
EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char);
@@ -126,7 +126,7 @@
ASSERT_EQ(4u, new_string->value->spans.size());
EXPECT_EQ(std::string(
"[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]"),
- *new_string->value->str);
+ new_string->value->value);
EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name);
EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš").size(), new_string->value->spans[0].first_char);
@@ -165,7 +165,7 @@
ASSERT_NE(nullptr, new_string);
ASSERT_EQ(2u, new_string->value->spans.size());
EXPECT_EQ(std::string("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]"),
- *new_string->value->str);
+ new_string->value->value);
EXPECT_EQ(std::string("em"), *new_string->value->spans[0].name);
EXPECT_EQ(std::u16string(u"[Ţĥîš").size(), new_string->value->spans[0].first_char);
@@ -265,7 +265,7 @@
ASSERT_NE(nullptr, new_styled_string);
// "world" should be untranslated.
- EXPECT_NE(std::string::npos, new_styled_string->value->str->find("world"));
+ EXPECT_NE(std::string::npos, new_styled_string->value->value.find("world"));
String* new_string = test::GetValueForConfig<String>(table.get(), "android:string/bar",
test::ParseConfigOrDie("en-rXA"));
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index f4d0226..e5993a6 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -557,19 +557,15 @@
} // namespace
bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
- // We must do this before writing the resources, since the string pool IDs may
- // change.
- table->string_pool.Sort(
- [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- int diff = a.context.priority - b.context.priority;
- if (diff < 0) return true;
- if (diff > 0) return false;
- diff = a.context.config.compare(b.context.config);
- if (diff < 0) return true;
- if (diff > 0) return false;
- return a.value < b.value;
- });
+ // We must do this before writing the resources, since the string pool IDs may change.
table->string_pool.Prune();
+ table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
+ int diff = util::compare(a.priority, b.priority);
+ if (diff == 0) {
+ diff = a.config.compare(b.config);
+ }
+ return diff;
+ });
// Write the ResTable header.
ChunkWriter table_writer(buffer_);
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index bfebedef..331ef78 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -180,8 +180,7 @@
flatNode->lineNumber = util::HostToDevice32(node->line_number);
flatNode->comment.index = util::HostToDevice32(-1);
- ResXMLTree_namespaceExt* flat_ns =
- writer.NextBlock<ResXMLTree_namespaceExt>();
+ ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>();
AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix);
AddString(node->namespace_uri, kLowPriority, &flat_ns->uri);
@@ -289,8 +288,7 @@
BigBuffer* buffer_;
XmlFlattenerOptions options_;
- // Scratch vector to filter attributes. We avoid allocations
- // making this a member.
+ // Scratch vector to filter attributes. We avoid allocations making this a member.
std::vector<xml::Attribute*> filtered_attrs_;
};
@@ -307,10 +305,9 @@
}
// Sort the string pool so that attribute resource IDs show up first.
- visitor.pool.Sort(
- [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- return a.context.priority < b.context.priority;
- });
+ visitor.pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
+ return util::compare(a.priority, b.priority);
+ });
// Now we flatten the string pool references into the correct places.
for (const auto& ref_entry : visitor.string_refs) {
@@ -328,15 +325,13 @@
// Write the array of resource IDs, indexed by StringPool order.
ChunkWriter res_id_map_writer(buffer_);
res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
- for (const auto& str : visitor.pool) {
- ResourceId id = {str->context.priority};
- if (id.id == kLowPriority || !id.is_valid()) {
- // When we see the first non-resource ID,
- // we're done.
+ for (const auto& str : visitor.pool.strings()) {
+ ResourceId id(str->context.priority);
+ if (str->context.priority == kLowPriority || !id.is_valid()) {
+ // When we see the first non-resource ID, we're done.
break;
}
-
- *res_id_map_writer.NextBlock<uint32_t>() = id.id;
+ *res_id_map_writer.NextBlock<uint32_t>() = util::HostToDevice32(id.id);
}
res_id_map_writer.Finish();
}
diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp
index 38bf4e3..6b21364 100644
--- a/tools/aapt2/proto/ProtoHelpers.cpp
+++ b/tools/aapt2/proto/ProtoHelpers.cpp
@@ -18,8 +18,7 @@
namespace aapt {
-void SerializeStringPoolToPb(const StringPool& pool,
- pb::StringPool* out_pb_pool) {
+void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) {
BigBuffer buffer(1024);
StringPool::FlattenUtf8(&buffer, pool);
@@ -28,14 +27,12 @@
size_t offset = 0;
for (const BigBuffer::Block& block : buffer) {
- data->insert(data->begin() + offset, block.buffer.get(),
- block.buffer.get() + block.size);
+ data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size);
offset += block.size;
}
}
-void SerializeSourceToPb(const Source& source, StringPool* src_pool,
- pb::Source* out_pb_source) {
+void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source) {
StringPool::Ref ref = src_pool->MakeRef(source.path);
out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index()));
if (source.line) {
@@ -43,8 +40,7 @@
}
}
-void DeserializeSourceFromPb(const pb::Source& pb_source,
- const android::ResStringPool& src_pool,
+void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool,
Source* out_source) {
if (pb_source.has_path_idx()) {
out_source->path = util::GetString(src_pool, pb_source.path_idx());
@@ -80,8 +76,7 @@
return SymbolState::kUndefined;
}
-void SerializeConfig(const ConfigDescription& config,
- pb::ConfigDescription* out_pb_config) {
+void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config) {
android::ResTable_config flat_config = config;
flat_config.size = sizeof(flat_config);
flat_config.swapHtoD();
@@ -99,8 +94,7 @@
return false;
}
- config = reinterpret_cast<const android::ResTable_config*>(
- pb_config.data().data());
+ config = reinterpret_cast<const android::ResTable_config*>(pb_config.data().data());
out_config->copyFromDtoH(*config);
return true;
}
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 4b56192..37d5ed0 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -195,8 +195,7 @@
spans++;
}
return util::make_unique<StyledString>(pool->MakeRef(
- style_str,
- StringPool::Context(StringPool::Context::kStylePriority, config)));
+ style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));
}
return util::make_unique<String>(
pool->MakeRef(str, StringPool::Context(config)));
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index d87d64e..730442c 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -87,7 +87,9 @@
pb_prim->set_data(val.data);
}
- void VisitItem(Item* item) override { LOG(FATAL) << "unimplemented item"; }
+ void VisitItem(Item* item) override {
+ LOG(FATAL) << "unimplemented item";
+ }
void Visit(Attribute* attr) override {
pb::Attribute* pb_attr = pb_compound_value()->mutable_attr();
@@ -207,19 +209,15 @@
} // namespace
std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {
- // We must do this before writing the resources, since the string pool IDs may
- // change.
- table->string_pool.Sort(
- [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
- int diff = a.context.priority - b.context.priority;
- if (diff < 0) return true;
- if (diff > 0) return false;
- diff = a.context.config.compare(b.context.config);
- if (diff < 0) return true;
- if (diff > 0) return false;
- return a.value < b.value;
- });
+ // We must do this before writing the resources, since the string pool IDs may change.
table->string_pool.Prune();
+ table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
+ int diff = util::compare(a.priority, b.priority);
+ if (diff == 0) {
+ diff = a.config.compare(b.config);
+ }
+ return diff;
+ });
auto pb_table = util::make_unique<pb::ResourceTable>();
SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool());
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index f311670..728d1f4 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -38,20 +38,17 @@
using namespace android;
-using android::base::StringPrintf;
+using ::android::base::StringPrintf;
namespace {
-/*
- * Visitor that converts a reference's resource ID to a resource name,
- * given a mapping from resource ID to resource name.
- */
+// Visitor that converts a reference's resource ID to a resource name, given a mapping from
+// resource ID to resource name.
class ReferenceIdToNameVisitor : public ValueVisitor {
public:
using ValueVisitor::Visit;
- explicit ReferenceIdToNameVisitor(
- const std::map<ResourceId, ResourceName>* mapping)
+ explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping)
: mapping_(mapping) {
CHECK(mapping_ != nullptr);
}
@@ -99,7 +96,7 @@
if (parser.chunk()->type != android::RES_TABLE_TYPE) {
context_->GetDiagnostics()->Error(DiagMessage(source_)
<< StringPrintf("unknown chunk of type 0x%02x",
- (int)parser.chunk()->type));
+ static_cast<int>(parser.chunk()->type)));
return false;
}
@@ -115,7 +112,7 @@
context_->GetDiagnostics()->Warn(
DiagMessage(source_) << StringPrintf(
"unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
- (int)parser.chunk()->type));
+ static_cast<int>(parser.chunk()->type)));
}
}
return true;
@@ -165,9 +162,8 @@
default:
context_->GetDiagnostics()->Warn(
- DiagMessage(source_)
- << "unexpected chunk type "
- << (int)util::DeviceToHost16(parser.chunk()->type));
+ DiagMessage(source_) << "unexpected chunk type "
+ << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
break;
}
}
@@ -245,8 +241,7 @@
return false;
}
} else {
- context_->GetDiagnostics()->Warn(DiagMessage(source_)
- << "unexpected string pool");
+ context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool");
}
break;
@@ -270,9 +265,8 @@
default:
context_->GetDiagnostics()->Warn(
- DiagMessage(source_)
- << "unexpected chunk type "
- << (int)util::DeviceToHost16(parser.chunk()->type));
+ DiagMessage(source_) << "unexpected chunk type "
+ << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
break;
}
}
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index b9ada77..ad3989e 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -70,12 +70,6 @@
android::StringPiece TrimWhitespace(const android::StringPiece& str);
/**
- * UTF-16 isspace(). It basically checks for lower range characters that are
- * whitespace.
- */
-inline bool isspace16(char16_t c) { return c < 0x0080 && isspace(c); }
-
-/**
* Returns an iterator to the first character that is not alpha-numeric and that
* is not in the allowedChars set.
*/
@@ -104,6 +98,16 @@
Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
const android::StringPiece& class_name);
+template <typename T>
+typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T& a, const T& b) {
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ }
+ return 0;
+}
+
/**
* Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
* This will be present in C++14 and can be removed then.