Merge "Allow apps to know what users think of their notifications." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 033f924..1d891a6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -198,7 +198,6 @@
public static final class R.attr {
ctor public R.attr();
- field public static final int abiOverride = 16844054; // 0x1010516
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -1365,6 +1364,7 @@
field public static final deprecated int unfocusedMonthDateColor = 16843588; // 0x1010344
field public static final int unselectedAlpha = 16843278; // 0x101020e
field public static final int updatePeriodMillis = 16843344; // 0x1010250
+ field public static final int use32bitAbi = 16844054; // 0x1010516
field public static final int useDefaultMargins = 16843641; // 0x1010379
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
@@ -34047,6 +34047,7 @@
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method public byte[] getAttestationChallenge();
method public java.lang.String[] getBlockModes();
method public java.util.Date getCertificateNotAfter();
method public java.util.Date getCertificateNotBefore();
@@ -34071,6 +34072,7 @@
ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
method public android.security.keystore.KeyGenParameterSpec build();
method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
@@ -40663,6 +40665,21 @@
method public static android.view.FocusFinder getInstance();
}
+ public final class FrameMetrics {
+ ctor public FrameMetrics(android.view.FrameMetrics);
+ method public long getMetric(int);
+ field public static final int ANIMATION_DURATION = 2; // 0x2
+ field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+ field public static final int DRAW_DURATION = 4; // 0x4
+ field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+ field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
+ field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
+ field public static final int SWAP_BUFFERS_DURATION = 7; // 0x7
+ field public static final int SYNC_DURATION = 5; // 0x5
+ field public static final int TOTAL_DURATION = 8; // 0x8
+ field public static final int UNKNOWN_DELAY_DURATION = 0; // 0x0
+ }
+
public abstract class FrameStats {
ctor public FrameStats();
method public final long getEndTimeNano();
@@ -43166,6 +43183,7 @@
ctor public Window(android.content.Context);
method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
method public void addFlags(int);
+ method public final void addFrameMetricsListener(android.view.Window.FrameMetricsListener, android.os.Handler);
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
@@ -43217,6 +43235,7 @@
method public abstract boolean performContextMenuIdentifierAction(int, int);
method public abstract boolean performPanelIdentifierAction(int, int, int);
method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
+ method public final void removeFrameMetricsListener(android.view.Window.FrameMetricsListener);
method public boolean requestFeature(int);
method public abstract void restoreHierarchyState(android.os.Bundle);
method public abstract android.os.Bundle saveHierarchyState();
@@ -43342,6 +43361,10 @@
method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
}
+ public static abstract interface Window.FrameMetricsListener {
+ method public abstract void onMetricsAvailable(android.view.Window, android.view.FrameMetrics, int);
+ }
+
public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index cc74f67..1ab5837 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -293,7 +293,6 @@
public static final class R.attr {
ctor public R.attr();
- field public static final int abiOverride = 16844054; // 0x1010516
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -1464,6 +1463,7 @@
field public static final deprecated int unfocusedMonthDateColor = 16843588; // 0x1010344
field public static final int unselectedAlpha = 16843278; // 0x101020e
field public static final int updatePeriodMillis = 16843344; // 0x1010250
+ field public static final int use32bitAbi = 16844054; // 0x1010516
field public static final int useDefaultMargins = 16843641; // 0x1010379
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
@@ -36530,6 +36530,7 @@
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method public byte[] getAttestationChallenge();
method public java.lang.String[] getBlockModes();
method public java.util.Date getCertificateNotAfter();
method public java.util.Date getCertificateNotBefore();
@@ -36554,6 +36555,7 @@
ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
method public android.security.keystore.KeyGenParameterSpec build();
method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
@@ -43415,6 +43417,21 @@
method public static android.view.FocusFinder getInstance();
}
+ public final class FrameMetrics {
+ ctor public FrameMetrics(android.view.FrameMetrics);
+ method public long getMetric(int);
+ field public static final int ANIMATION_DURATION = 2; // 0x2
+ field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+ field public static final int DRAW_DURATION = 4; // 0x4
+ field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+ field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
+ field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
+ field public static final int SWAP_BUFFERS_DURATION = 7; // 0x7
+ field public static final int SYNC_DURATION = 5; // 0x5
+ field public static final int TOTAL_DURATION = 8; // 0x8
+ field public static final int UNKNOWN_DELAY_DURATION = 0; // 0x0
+ }
+
public abstract class FrameStats {
ctor public FrameStats();
method public final long getEndTimeNano();
@@ -45918,6 +45935,7 @@
ctor public Window(android.content.Context);
method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
method public void addFlags(int);
+ method public final void addFrameMetricsListener(android.view.Window.FrameMetricsListener, android.os.Handler);
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
@@ -45969,6 +45987,7 @@
method public abstract boolean performContextMenuIdentifierAction(int, int);
method public abstract boolean performPanelIdentifierAction(int, int, int);
method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
+ method public final void removeFrameMetricsListener(android.view.Window.FrameMetricsListener);
method public boolean requestFeature(int);
method public abstract void restoreHierarchyState(android.os.Bundle);
method public abstract android.os.Bundle saveHierarchyState();
@@ -46095,6 +46114,10 @@
method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
}
+ public static abstract interface Window.FrameMetricsListener {
+ method public abstract void onMetricsAvailable(android.view.Window, android.view.FrameMetrics, int);
+ }
+
public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 1a2703c..49b8ffe 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -198,7 +198,6 @@
public static final class R.attr {
ctor public R.attr();
- field public static final int abiOverride = 16844054; // 0x1010516
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -1365,6 +1364,7 @@
field public static final deprecated int unfocusedMonthDateColor = 16843588; // 0x1010344
field public static final int unselectedAlpha = 16843278; // 0x101020e
field public static final int updatePeriodMillis = 16843344; // 0x1010250
+ field public static final int use32bitAbi = 16844054; // 0x1010516
field public static final int useDefaultMargins = 16843641; // 0x1010379
field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
field public static final int useLevel = 16843167; // 0x101019f
@@ -34062,6 +34062,7 @@
public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+ method public byte[] getAttestationChallenge();
method public java.lang.String[] getBlockModes();
method public java.util.Date getCertificateNotAfter();
method public java.util.Date getCertificateNotBefore();
@@ -34086,6 +34087,7 @@
ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
method public android.security.keystore.KeyGenParameterSpec build();
method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
@@ -40680,6 +40682,21 @@
method public static android.view.FocusFinder getInstance();
}
+ public final class FrameMetrics {
+ ctor public FrameMetrics(android.view.FrameMetrics);
+ method public long getMetric(int);
+ field public static final int ANIMATION_DURATION = 2; // 0x2
+ field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+ field public static final int DRAW_DURATION = 4; // 0x4
+ field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+ field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
+ field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
+ field public static final int SWAP_BUFFERS_DURATION = 7; // 0x7
+ field public static final int SYNC_DURATION = 5; // 0x5
+ field public static final int TOTAL_DURATION = 8; // 0x8
+ field public static final int UNKNOWN_DELAY_DURATION = 0; // 0x0
+ }
+
public abstract class FrameStats {
ctor public FrameStats();
method public final long getEndTimeNano();
@@ -43183,6 +43200,7 @@
ctor public Window(android.content.Context);
method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
method public void addFlags(int);
+ method public final void addFrameMetricsListener(android.view.Window.FrameMetricsListener, android.os.Handler);
method public void clearFlags(int);
method public abstract void closeAllPanels();
method public abstract void closePanel(int);
@@ -43234,6 +43252,7 @@
method public abstract boolean performContextMenuIdentifierAction(int, int);
method public abstract boolean performPanelIdentifierAction(int, int, int);
method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
+ method public final void removeFrameMetricsListener(android.view.Window.FrameMetricsListener);
method public boolean requestFeature(int);
method public abstract void restoreHierarchyState(android.os.Bundle);
method public abstract android.os.Bundle saveHierarchyState();
@@ -43359,6 +43378,10 @@
method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
}
+ public static abstract interface Window.FrameMetricsListener {
+ method public abstract void onMetricsAvailable(android.view.Window, android.view.FrameMetrics, int);
+ }
+
public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1954774..99852b8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2882,6 +2882,11 @@
reply.writeInt(isForeground ? 1 : 0);
return true;
}
+ case NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -6732,5 +6737,15 @@
return isForeground;
};
+ @Override
+ public void notifyPinnedStackAnimationEnded() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION, data, reply, 0);
+ data.recycle();
+ reply.recycle();
+ };
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index c071162..df4b7d1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -429,6 +429,16 @@
}
}
+ /** @hide */
+ @Override
+ public @Nullable String getServicesSystemSharedLibraryPackageName() {
+ try {
+ return mPM.getServicesSystemSharedLibraryPackageName();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
+
@Override
public FeatureInfo[] getSystemAvailableFeatures() {
try {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b5ca6ee..98ce273 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -597,6 +597,8 @@
public boolean supportsLocalVoiceInteraction() throws RemoteException;
+ public void notifyPinnedStackAnimationEnded() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -972,4 +974,5 @@
int START_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 363;
int STOP_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 364;
int SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 365;
+ int NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 366;
}
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index fa11234..6432558 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -30,4 +30,9 @@
* brought to the front or a new Intent is delivered to it.
*/
void onPinnedActivityRestartAttempt();
+
+ /**
+ * Called whenever the pinned stack is done animating a resize.
+ */
+ void onPinnedStackAnimationEnded();
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index da52c1e..8717353 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -447,7 +447,8 @@
IPackageManager pm = ActivityThread.getPackageManager();
android.content.pm.PackageInfo pi;
try {
- pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());
+ pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.myUserId());
} catch (RemoteException e) {
throw new IllegalStateException("Unable to get package info for "
+ mPackageName + "; is system dying?", e);
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index c7d2140..a952915 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -363,7 +363,8 @@
String suggestProviderPackage = null;
if (mSuggestAuthority != null) {
PackageManager pm = activityContext.getPackageManager();
- ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority, 0);
+ ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority,
+ PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
if (pi != null) {
suggestProviderPackage = pi.packageName;
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 593fe2a..3863857 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -534,4 +534,6 @@
boolean isEphemeralApplication(String packageName, int userId);
boolean setRequiredForSystemUser(String packageName, boolean systemUserApp);
+
+ String getServicesSystemSharedLibraryPackageName();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 08deedb..bf0d4de 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2327,6 +2327,28 @@
public static final int MASK_PERMISSION_FLAGS = 0xFF;
/**
+ * This is a library that contains components apps can invoke. For
+ * example, a services for apps to bind to, or standard chooser UI,
+ * etc. This library is versioned and backwards compatible. Clients
+ * should check its version via {@link android.ext.services.Version
+ * #getVersionCode()} and avoid calling APIs added in later versions.
+ *
+ * @hide
+ */
+ public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services";
+
+ /**
+ * This is a library that contains components apps can dynamically
+ * load. For example, new widgets, helper classes, etc. This library
+ * is versioned and backwards compatible. Clients should check its
+ * version via {@link android.ext.shared.Version#getVersionCode()}
+ * and avoid calling APIs added in later versions.
+ *
+ * @hide
+ */
+ public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
+
+ /**
* Retrieve overall information about an application package that is
* installed on the system.
*
@@ -3365,6 +3387,15 @@
public abstract String[] getSystemSharedLibraryNames();
/**
+ * Get the name of the package hosting the services shared library.
+ *
+ * @return The library host package.
+ *
+ * @hide
+ */
+ public abstract @Nullable String getServicesSystemSharedLibraryPackageName();
+
+ /**
* Get a list of features that are available on the
* system.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 72ace15..5dddebd 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -337,7 +337,7 @@
public final boolean coreApp;
public final boolean multiArch;
- public final String abiOverride;
+ public final boolean use32bitAbi;
public final boolean extractNativeLibs;
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
@@ -354,7 +354,7 @@
this.splitRevisionCodes = splitRevisionCodes;
this.coreApp = baseApk.coreApp;
this.multiArch = baseApk.multiArch;
- this.abiOverride = baseApk.abiOverride;
+ this.use32bitAbi = baseApk.use32bitAbi;
this.extractNativeLibs = baseApk.extractNativeLibs;
}
@@ -382,12 +382,12 @@
public final Signature[] signatures;
public final boolean coreApp;
public final boolean multiArch;
- public final String abiOverride;
+ public final boolean use32bitAbi;
public final boolean extractNativeLibs;
public ApkLite(String codePath, String packageName, String splitName, int versionCode,
int revisionCode, int installLocation, List<VerifierInfo> verifiers,
- Signature[] signatures, boolean coreApp, boolean multiArch, String abiOverride,
+ Signature[] signatures, boolean coreApp, boolean multiArch, boolean use32bitAbi,
boolean extractNativeLibs) {
this.codePath = codePath;
this.packageName = packageName;
@@ -399,7 +399,7 @@
this.signatures = signatures;
this.coreApp = coreApp;
this.multiArch = multiArch;
- this.abiOverride = abiOverride;
+ this.use32bitAbi = use32bitAbi;
this.extractNativeLibs = extractNativeLibs;
}
}
@@ -843,8 +843,7 @@
}
pkg.setCodePath(packageDir.getAbsolutePath());
- pkg.setCpuAbiOverride(lite.abiOverride);
-
+ pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
IoUtils.closeQuietly(assets);
@@ -875,7 +874,7 @@
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.setCodePath(apkFile.getAbsolutePath());
- pkg.setCpuAbiOverride(lite.abiOverride);
+ pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
IoUtils.closeQuietly(assets);
@@ -1380,7 +1379,7 @@
int revisionCode = 0;
boolean coreApp = false;
boolean multiArch = false;
- String abiOverride = null;
+ boolean use32bitAbi = false;
boolean extractNativeLibs = true;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
@@ -1421,8 +1420,8 @@
if ("multiArch".equals(attr)) {
multiArch = attrs.getAttributeBooleanValue(i, false);
}
- if ("abiOverride".equals(attr)) {
- abiOverride = attrs.getAttributeValue(i);
+ if ("use32bitAbi".equals(attr)) {
+ use32bitAbi = attrs.getAttributeBooleanValue(i, false);
}
if ("extractNativeLibs".equals(attr)) {
extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
@@ -1433,7 +1432,7 @@
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
revisionCode, installLocation, verifiers, signatures, coreApp, multiArch,
- abiOverride, extractNativeLibs);
+ use32bitAbi, extractNativeLibs);
}
/**
@@ -4740,6 +4739,12 @@
* and prods fields out of {@code this.applicationInfo}.
*/
public String cpuAbiOverride;
+ /**
+ * The install time abi override to choose 32bit abi's when multiple abi's
+ * are present. This is only meaningfull for multiarch applications.
+ * The use32bitAbi attribute is ignored if cpuAbiOverride is also set.
+ */
+ public boolean use32bitAbi;
public Package(String packageName) {
this.packageName = packageName;
@@ -4872,12 +4877,12 @@
}
}
- public void setCpuAbiOverride(String cpuAbiOverride) {
- this.cpuAbiOverride = cpuAbiOverride;
+ public void setUse32bitAbi(boolean use32bitAbi) {
+ this.use32bitAbi = use32bitAbi;
if (childPackages != null) {
final int packageCount = childPackages.size();
for (int i = 0; i < packageCount; i++) {
- childPackages.get(i).cpuAbiOverride = cpuAbiOverride;
+ childPackages.get(i).use32bitAbi = use32bitAbi;
}
}
}
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 7cf1d71..8689dce 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -19,6 +19,7 @@
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterBlob;
import android.security.keymaster.OperationResult;
import android.security.KeystoreArguments;
@@ -74,4 +75,5 @@
int addAuthToken(in byte[] authToken);
int onUserAdded(int userId, int parentId);
int onUserRemoved(int userId);
+ int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain);
}
diff --git a/libs/hwui/FrameStatsObserver.h b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
similarity index 68%
copy from libs/hwui/FrameStatsObserver.h
copy to core/java/android/security/keymaster/KeymasterCertificateChain.aidl
index 7abc9f1..dc1876a 100644
--- a/libs/hwui/FrameStatsObserver.h
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
@@ -14,19 +14,7 @@
* limitations under the License.
*/
-#pragma once
+package android.security.keymaster;
-#include <utils/RefBase.h>
-
-#include "BufferPool.h"
-
-namespace android {
-namespace uirenderer {
-
-class FrameStatsObserver : public VirtualLightRefBase {
-public:
- virtual void notify(BufferPool::Buffer* buffer);
-};
-
-}; // namespace uirenderer
-}; // namespace android
+/* @hide */
+parcelable KeymasterCertificateChain;
diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.java b/core/java/android/security/keymaster/KeymasterCertificateChain.java
new file mode 100644
index 0000000..243b9fe
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for the Java side of keystore-generated certificate chains.
+ *
+ * Serialization code for this must be kept in sync with system/security/keystore
+ * @hide
+ */
+public class KeymasterCertificateChain implements Parcelable {
+
+ private List<byte[]> mCertificates;
+
+ public static final Parcelable.Creator<KeymasterCertificateChain> CREATOR = new
+ Parcelable.Creator<KeymasterCertificateChain>() {
+ public KeymasterCertificateChain createFromParcel(Parcel in) {
+ return new KeymasterCertificateChain(in);
+ }
+ public KeymasterCertificateChain[] newArray(int size) {
+ return new KeymasterCertificateChain[size];
+ }
+ };
+
+ public KeymasterCertificateChain() {
+ mCertificates = null;
+ }
+
+ public KeymasterCertificateChain(List<byte[]> mCertificates) {
+ this.mCertificates = mCertificates;
+ }
+
+ private KeymasterCertificateChain(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public List<byte[]> getCertificates() {
+ return mCertificates;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mCertificates == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(mCertificates.size());
+ for (byte[] arg : mCertificates) {
+ out.writeByteArray(arg);
+ }
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ int length = in.readInt();
+ mCertificates = new ArrayList<byte[]>(length);
+ for (int i = 0; i < length; i++) {
+ mCertificates.add(in.createByteArray());
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 04d5952..e01f2a0 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -58,6 +58,8 @@
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
+ public static final int KM_TAG_INCLUDE_UNIQUE_ID = KM_BOOL | 202;
+
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
@@ -74,11 +76,12 @@
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
- public static final int KM_TAG_APPLICATION_DATA = KM_BYTES | 700;
public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
+ public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
+ public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
new file mode 100644
index 0000000..8e66f86
--- /dev/null
+++ b/core/java/android/view/FrameMetrics.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.view.Window;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class containing timing data for various milestones in a frame
+ * lifecycle reported by the rendering subsystem.
+ * <p>
+ * Supported metrics can be queried via their corresponding identifier.
+ * </p>
+ */
+public final class FrameMetrics {
+
+ /**
+ * Metric identifier for unknown delay.
+ * <p>
+ * Represents the number of nanoseconds elapsed waiting for the
+ * UI thread to become responsive and process the frame. This
+ * should be 0 most of the time.
+ * </p>
+ */
+ public static final int UNKNOWN_DELAY_DURATION = 0;
+
+ /**
+ * Metric identifier for input handling duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed issuing
+ * input handling callbacks.
+ * </p>
+ */
+ public static final int INPUT_HANDLING_DURATION = 1;
+
+ /**
+ * Metric identifier for animation callback duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed issuing
+ * animation callbacks.
+ * </p>
+ */
+ public static final int ANIMATION_DURATION = 2;
+
+ /**
+ * Metric identifier for layout/measure duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed measuring
+ * and laying out the invalidated pieces of the view hierarchy.
+ * </p>
+ */
+ public static final int LAYOUT_MEASURE_DURATION = 3;
+ /**
+ * Metric identifier for draw duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed computing
+ * DisplayLists for transformations applied to the view
+ * hierarchy.
+ * </p>
+ */
+ public static final int DRAW_DURATION = 4;
+
+ /**
+ * Metric identifier for sync duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed
+ * synchronizing the computed display lists with the render
+ * thread.
+ * </p>
+ */
+ public static final int SYNC_DURATION = 5;
+
+ /**
+ * Metric identifier for command issue duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed
+ * issuing draw commands to the GPU.
+ * </p>
+ */
+ public static final int COMMAND_ISSUE_DURATION = 6;
+
+ /**
+ * Metric identifier for swap buffers duration.
+ * <p>
+ * Represents the number of nanoseconds elapsed issuing
+ * the frame buffer for this frame to the display
+ * subsystem.
+ * </p>
+ */
+ public static final int SWAP_BUFFERS_DURATION = 7;
+
+ /**
+ * Metric identifier for total frame duration.
+ * <p>
+ * Represents the total time in nanoseconds this frame took to render
+ * and be issued to the display subsystem.
+ * </p>
+ * <p>
+ * Equal to the sum of the values of all other time-valued metric
+ * identifiers.
+ * </p>
+ */
+ public static final int TOTAL_DURATION = 8;
+
+ /**
+ * Metric identifier for a boolean value determining whether this frame was
+ * the first to draw in a new Window layout.
+ * <p>
+ * {@link #getMetric(int)} will return 0 for false, 1 for true.
+ * </p>
+ * <p>
+ * First draw frames are expected to be slow and should usually be exempt
+ * from display jank calculations as they do not cause skips in animations
+ * and are usually hidden by window animations or other tricks.
+ * </p>
+ */
+ public static final int FIRST_DRAW_FRAME = 9;
+
+ private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
+
+ /**
+ * Identifiers for metrics available for each frame.
+ *
+ * {@see {@link #getMetric(int)}}
+ * @hide
+ */
+ @IntDef({
+ UNKNOWN_DELAY_DURATION,
+ INPUT_HANDLING_DURATION,
+ ANIMATION_DURATION,
+ LAYOUT_MEASURE_DURATION,
+ DRAW_DURATION,
+ SYNC_DURATION,
+ COMMAND_ISSUE_DURATION,
+ SWAP_BUFFERS_DURATION,
+ TOTAL_DURATION,
+ FIRST_DRAW_FRAME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Metric {}
+
+ /**
+ * Timestamp indices for frame milestones.
+ *
+ * May change from release to release.
+ *
+ * Must be kept in sync with frameworks/base/libs/hwui/FrameInfo.h.
+ *
+ * @hide
+ */
+ @IntDef ({
+ Index.FLAGS,
+ Index.INTENDED_VSYNC,
+ Index.VSYNC,
+ Index.OLDEST_INPUT_EVENT,
+ Index.NEWEST_INPUT_EVENT,
+ Index.HANDLE_INPUT_START,
+ Index.ANIMATION_START,
+ Index.PERFORM_TRAVERSALS_START,
+ Index.DRAW_START,
+ Index.SYNC_QUEUED,
+ Index.SYNC_START,
+ Index.ISSUE_DRAW_COMMANDS_START,
+ Index.SWAP_BUFFERS,
+ Index.FRAME_COMPLETED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Index {
+ int FLAGS = 0;
+ int INTENDED_VSYNC = 1;
+ int VSYNC = 2;
+ int OLDEST_INPUT_EVENT = 3;
+ int NEWEST_INPUT_EVENT = 4;
+ int HANDLE_INPUT_START = 5;
+ int ANIMATION_START = 6;
+ int PERFORM_TRAVERSALS_START = 7;
+ int DRAW_START = 8;
+ int SYNC_QUEUED = 9;
+ int SYNC_START = 10;
+ int ISSUE_DRAW_COMMANDS_START = 11;
+ int SWAP_BUFFERS = 12;
+ int FRAME_COMPLETED = 13;
+
+ int FRAME_STATS_COUNT = 14; // must always be last
+ }
+
+ /*
+ * Bucket endpoints for each Metric defined above.
+ *
+ * Each defined metric *must* have a corresponding entry
+ * in this list.
+ */
+ private static final int[] DURATIONS = new int[] {
+ // UNKNOWN_DELAY
+ Index.INTENDED_VSYNC, Index.HANDLE_INPUT_START,
+ // INPUT_HANDLING
+ Index.HANDLE_INPUT_START, Index.ANIMATION_START,
+ // ANIMATION
+ Index.ANIMATION_START, Index.PERFORM_TRAVERSALS_START,
+ // LAYOUT_MEASURE
+ Index.PERFORM_TRAVERSALS_START, Index.DRAW_START,
+ // DRAW
+ Index.DRAW_START, Index.SYNC_QUEUED,
+ // SYNC
+ Index.SYNC_START, Index.ISSUE_DRAW_COMMANDS_START,
+ // COMMAND_ISSUE
+ Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS,
+ // SWAP_BUFFERS
+ Index.SWAP_BUFFERS, Index.FRAME_COMPLETED,
+ // TOTAL_DURATION
+ Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
+ };
+
+ /* package */ final long[] mTimingData;
+
+ /**
+ * Constructs a FrameMetrics object as a copy.
+ * <p>
+ * Use this method to copy out metrics reported by
+ * {@link Window.FrameMetricsListener#onMetricsAvailable(Window, FrameMetrics, int)}
+ * </p>
+ * @param other the FrameMetrics object to copy.
+ */
+ public FrameMetrics(FrameMetrics other) {
+ mTimingData = new long[Index.FRAME_STATS_COUNT];
+ System.arraycopy(other.mTimingData, 0, mTimingData, 0, mTimingData.length);
+ }
+
+ /**
+ * @hide
+ */
+ FrameMetrics() {
+ mTimingData = new long[Index.FRAME_STATS_COUNT];
+ }
+
+ /**
+ * Retrieves the value associated with Metric identifier {@code id}
+ * for this frame.
+ * <p>
+ * Boolean metrics are represented in [0,1], with 0 corresponding to
+ * false, and 1 corresponding to true.
+ * </p>
+ * @param id the metric to retrieve
+ * @return the value of the metric or -1 if it is not available.
+ */
+ public long getMetric(@Metric int id) {
+ if (id < UNKNOWN_DELAY_DURATION || id > FIRST_DRAW_FRAME) {
+ return -1;
+ }
+
+ if (mTimingData == null) {
+ return -1;
+ }
+
+ if (id == FIRST_DRAW_FRAME) {
+ return (mTimingData[Index.FLAGS] & FRAME_INFO_FLAG_FIRST_DRAW) != 0 ? 1 : 0;
+ }
+
+ int durationsIdx = 2 * id;
+ return mTimingData[DURATIONS[durationsIdx + 1]]
+ - mTimingData[DURATIONS[durationsIdx]];
+ }
+}
+
diff --git a/core/java/android/view/FrameMetricsObserver.java b/core/java/android/view/FrameMetricsObserver.java
new file mode 100644
index 0000000..f38f8b7
--- /dev/null
+++ b/core/java/android/view/FrameMetricsObserver.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.util.Log;
+import android.os.Looper;
+import android.os.MessageQueue;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.lang.NullPointerException;
+import java.lang.ref.WeakReference;
+import java.lang.SuppressWarnings;
+
+/**
+ * Provides streaming access to frame stats information from the rendering
+ * subsystem to apps.
+ *
+ * @hide
+ */
+public class FrameMetricsObserver {
+ private MessageQueue mMessageQueue;
+
+ private WeakReference<Window> mWindow;
+
+ private FrameMetrics mFrameMetrics;
+
+ /* package */ Window.FrameMetricsListener mListener;
+ /* package */ VirtualRefBasePtr mNative;
+
+ /**
+ * Creates a FrameMetricsObserver
+ *
+ * @param looper the looper to use when invoking callbacks
+ */
+ FrameMetricsObserver(@NonNull Window window, @NonNull Looper looper,
+ @NonNull Window.FrameMetricsListener listener) {
+ if (looper == null) {
+ throw new NullPointerException("looper cannot be null");
+ }
+
+ mMessageQueue = looper.getQueue();
+ if (mMessageQueue == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameMetrics = new FrameMetrics();
+ mWindow = new WeakReference<>(window);
+ mListener = listener;
+ }
+
+ // Called by native on the provided Handler
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable(int dropCount) {
+ final Window window = mWindow.get();
+ if (window != null) {
+ mListener.onMetricsAvailable(window, mFrameMetrics, dropCount);
+ }
+ }
+}
diff --git a/core/java/android/view/FrameStatsObserver.java b/core/java/android/view/FrameStatsObserver.java
deleted file mode 100644
index 0add607..0000000
--- a/core/java/android/view/FrameStatsObserver.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.NonNull;
-import android.util.Log;
-import android.os.Looper;
-import android.os.MessageQueue;
-
-import com.android.internal.util.VirtualRefBasePtr;
-
-import java.lang.NullPointerException;
-import java.lang.ref.WeakReference;
-import java.lang.SuppressWarnings;
-
-/**
- * Provides streaming access to frame stats information from the rendering
- * subsystem to apps.
- *
- * @hide
- */
-public abstract class FrameStatsObserver {
- private static final String TAG = "FrameStatsObserver";
-
- private MessageQueue mMessageQueue;
- private long[] mBuffer;
-
- private FrameStats mFrameStats;
-
- /* package */ ThreadedRenderer mRenderer;
- /* package */ VirtualRefBasePtr mNative;
-
- /**
- * Containing class for frame statistics reported
- * by the rendering subsystem.
- */
- public static class FrameStats {
- /**
- * Precise timing data for various milestones in a frame
- * lifecycle.
- *
- * This data is exactly the same as what is returned by
- * `adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats`
- *
- * The fields reported may change from release to release.
- *
- * @see {@link http://developer.android.com/training/testing/performance.html}
- * for a description of the fields present.
- */
- public long[] mTimingData;
- }
-
- /**
- * Creates a FrameStatsObserver
- *
- * @param looper the looper to use when invoking callbacks
- */
- public FrameStatsObserver(@NonNull Looper looper) {
- if (looper == null) {
- throw new NullPointerException("looper cannot be null");
- }
-
- mMessageQueue = looper.getQueue();
- if (mMessageQueue == null) {
- throw new IllegalStateException("invalid looper, null message queue\n");
- }
-
- mFrameStats = new FrameStats();
- }
-
- /**
- * Called on provided looper when frame stats data is available
- * for the previous frame.
- *
- * Clients of this class must do as little work as possible within
- * this callback, as the buffer is shared between the producer and consumer.
- *
- * If the consumer is still executing within this method when there is new
- * data available that data will be dropped. The producer cannot
- * wait on the consumer.
- *
- * @param data the newly available data
- */
- public abstract void onDataAvailable(FrameStats data);
-
- /**
- * Returns the number of reports dropped as a result of a slow
- * consumer.
- */
- public long getDroppedReportCount() {
- if (mRenderer == null) {
- return 0;
- }
-
- return mRenderer.getDroppedFrameReportCount();
- }
-
- public boolean isRegistered() {
- return mRenderer != null && mNative != null;
- }
-
- // === called by native === //
- @SuppressWarnings("unused")
- private void notifyDataAvailable() {
- mFrameStats.mTimingData = mBuffer;
- onDataAvailable(mFrameStats);
- }
-}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 8b06ecf..ca41d78 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -354,8 +354,6 @@
private boolean mEnabled;
private boolean mRequested = true;
- private HashSet<FrameStatsObserver> mFrameStatsObservers;
-
ThreadedRenderer(Context context, boolean translucent) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -964,29 +962,14 @@
}
}
- void addFrameStatsObserver(FrameStatsObserver fso) {
- if (mFrameStatsObservers == null) {
- mFrameStatsObservers = new HashSet<>();
- }
-
- long nativeFso = nAddFrameStatsObserver(mNativeProxy, fso);
- fso.mRenderer = this;
- fso.mNative = new VirtualRefBasePtr(nativeFso);
- mFrameStatsObservers.add(fso);
+ void addFrameMetricsObserver(FrameMetricsObserver observer) {
+ long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
+ observer.mNative = new VirtualRefBasePtr(nativeObserver);
}
- void removeFrameStatsObserver(FrameStatsObserver fso) {
- if (!mFrameStatsObservers.remove(fso)) {
- throw new IllegalArgumentException("attempt to remove FrameStatsObserver that was never added");
- }
-
- nRemoveFrameStatsObserver(mNativeProxy, fso.mNative.get());
- fso.mRenderer = null;
- fso.mNative = null;
- }
-
- long getDroppedFrameReportCount() {
- return nGetDroppedFrameReportCount(mNativeProxy);
+ void removeFrameMetricsObserver(FrameMetricsObserver observer) {
+ nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
+ observer.mNative = null;
}
static native void setupShadersDiskCache(String cacheFile);
@@ -1044,7 +1027,6 @@
private static native void nSetContentDrawBounds(long nativeProxy, int left,
int top, int right, int bottom);
- private static native long nAddFrameStatsObserver(long nativeProxy, FrameStatsObserver fso);
- private static native void nRemoveFrameStatsObserver(long nativeProxy, long nativeFso);
- private static native long nGetDroppedFrameReportCount(long nativeProxy);
+ private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer);
+ private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f52b2907..2612ab2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3703,9 +3703,9 @@
private ViewPropertyAnimator mAnimator = null;
/**
- * List of FrameStatsObservers pending registration when mAttachInfo is null.
+ * List of registered FrameMetricsObservers.
*/
- private ArrayList<FrameStatsObserver> mPendingFrameStatsObservers;
+ private ArrayList<FrameMetricsObserver> mFrameMetricsObservers;
/**
* Flag indicating that a drag can cross window boundaries. When
@@ -5479,19 +5479,29 @@
*
* @hide
*/
- public void addFrameStatsObserver(FrameStatsObserver fso) {
+ public void addFrameMetricsListener(Window window, Window.FrameMetricsListener listener,
+ Handler handler) {
if (mAttachInfo != null) {
if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.addFrameStatsObserver(fso);
+ if (mFrameMetricsObservers == null) {
+ mFrameMetricsObservers = new ArrayList<>();
+ }
+
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window,
+ handler.getLooper(), listener);
+ mFrameMetricsObservers.add(fmo);
+ mAttachInfo.mHardwareRenderer.addFrameMetricsObserver(fmo);
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
}
} else {
- if (mPendingFrameStatsObservers == null) {
- mPendingFrameStatsObservers = new ArrayList<>();
+ if (mFrameMetricsObservers == null) {
+ mFrameMetricsObservers = new ArrayList<>();
}
- mPendingFrameStatsObservers.add(fso);
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window,
+ handler.getLooper(), listener);
+ mFrameMetricsObservers.add(fmo);
}
}
@@ -5500,32 +5510,45 @@
*
* @hide
*/
- public void removeFrameStatsObserver(FrameStatsObserver fso) {
+ public void removeFrameMetricsListener(Window.FrameMetricsListener listener) {
ThreadedRenderer renderer = getHardwareRenderer();
-
- if (mPendingFrameStatsObservers != null) {
- mPendingFrameStatsObservers.remove(fso);
+ FrameMetricsObserver fmo = findFrameMetricsObserver(listener);
+ if (fmo == null) {
+ throw new IllegalArgumentException("attempt to remove FrameMetricsListener that was never added");
}
- if (renderer != null) {
- renderer.removeFrameStatsObserver(fso);
+ if (mFrameMetricsObservers != null) {
+ mFrameMetricsObservers.remove(fmo);
+ if (renderer != null) {
+ renderer.removeFrameMetricsObserver(fmo);
+ }
}
}
- private void registerPendingFrameStatsObservers() {
- if (mPendingFrameStatsObservers != null) {
+ private void registerPendingFrameMetricsObservers() {
+ if (mFrameMetricsObservers != null) {
ThreadedRenderer renderer = getHardwareRenderer();
if (renderer != null) {
- for (FrameStatsObserver fso : mPendingFrameStatsObservers) {
- renderer.addFrameStatsObserver(fso);
+ for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
+ renderer.addFrameMetricsObserver(fmo);
}
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
}
- mPendingFrameStatsObservers = null;
}
}
+ private FrameMetricsObserver findFrameMetricsObserver(Window.FrameMetricsListener listener) {
+ for (int i = 0; i < mFrameMetricsObservers.size(); i++) {
+ FrameMetricsObserver observer = mFrameMetricsObservers.get(i);
+ if (observer.mListener == listener) {
+ return observer;
+ }
+ }
+
+ return null;
+ }
+
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
@@ -15160,7 +15183,7 @@
mFloatingTreeObserver = null;
}
- registerPendingFrameStatsObservers();
+ registerPendingFrameMetricsObservers();
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index c68a740..9f05990 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,6 +34,7 @@
import android.media.session.MediaController;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -604,6 +605,34 @@
void onRestrictedCaptionAreaChanged(Rect rect);
}
+ /**
+ * Callback for clients that want frame timing information for each
+ * frame rendered by the Window.
+ */
+ public interface FrameMetricsListener {
+ /**
+ * Called when information is available for the previously rendered frame.
+ *
+ * Reports can be dropped if this callback takes too
+ * long to execute, as the report producer cannot wait for the consumer to
+ * complete.
+ *
+ * It is highly recommended that clients copy the passed in FrameMetrics
+ * via {@link FrameMetrics#FrameMetrics(FrameMetrics)} within this method and defer
+ * additional computation or storage to another thread to avoid unnecessarily
+ * dropping reports.
+ *
+ * @param window The {@link Window} on which the frame was displayed.
+ * @param frameMetrics the available metrics. This object is reused on every call
+ * and thus <strong>this reference is not valid outside the scope of this method</strong>.
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * this callback was invoked.
+ */
+ void onMetricsAvailable(Window window, FrameMetrics frameMetrics,
+ int dropCountSinceLastInvocation);
+ }
+
+
public Window(Context context) {
mContext = context;
mFeatures = mLocalFeatures = getDefaultFeatures(context);
@@ -798,33 +827,28 @@
* Set an observer to collect frame stats for each frame rendererd in this window.
*
* Must be in hardware rendering mode.
- * @hide
*/
- public final void addFrameStatsObserver(@NonNull FrameStatsObserver fso) {
+ public final void addFrameMetricsListener(@NonNull FrameMetricsListener listener,
+ Handler handler) {
final View decorView = getDecorView();
if (decorView == null) {
throw new IllegalStateException("can't observe a Window without an attached view");
}
- if (fso == null) {
- throw new NullPointerException("FrameStatsObserver cannot be null");
+ if (listener == null) {
+ throw new NullPointerException("listener cannot be null");
}
- if (fso.isRegistered()) {
- throw new IllegalStateException("FrameStatsObserver already registered on a Window.");
- }
-
- decorView.addFrameStatsObserver(fso);
+ decorView.addFrameMetricsListener(this, listener, handler);
}
/**
* Remove observer and stop listening to frame stats for this window.
- * @hide
*/
- public final void removeFrameStatsObserver(FrameStatsObserver fso) {
+ public final void removeFrameMetricsListener(FrameMetricsListener listener) {
final View decorView = getDecorView();
if (decorView != null) {
- getDecorView().removeFrameStatsObserver(fso);
+ getDecorView().removeFrameMetricsListener(listener);
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 43306d0..d97f8af 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -38,7 +38,6 @@
import android.util.Slog;
import android.util.Xml;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
-import android.view.inputmethod.InputMethodSubtypeArray;
import java.io.IOException;
import java.util.ArrayList;
@@ -122,7 +121,7 @@
* @param context The Context in which we are parsing the input method.
* @param service The ResolveInfo returned from the package manager about
* this input method's component.
- * @param additionalSubtypes additional subtypes being added to this InputMethodInfo
+ * @param additionalSubtypesMap additional subtypes being added to this InputMethodInfo
* @hide
*/
public InputMethodInfo(Context context, ResolveInfo service,
@@ -429,6 +428,18 @@
}
}
+ /**
+ * @return {@code true} if the {@link android.inputmethodservice.InputMethodService} is marked
+ * to be Encryption-Aware.
+ * @hide
+ */
+ public boolean isEncryptionAware() {
+ if (mService == null || mService.serviceInfo == null) {
+ return false;
+ }
+ return mService.serviceInfo.encryptionAware;
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index b2ae835..88af920 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -86,6 +86,7 @@
import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -615,7 +616,7 @@
if (h > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(h, heightSize), EXACTLY);
- } else if ((mWindow.getAttributes().flags & FLAG_FULLSCREEN) == 0) {
+ } else if ((mWindow.getAttributes().flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST);
mApplyFloatingVerticalInsets = true;
@@ -890,10 +891,11 @@
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
final WindowManager.LayoutParams attrs = mWindow.getAttributes();
mFloatingInsets.setEmpty();
- if ((attrs.flags & FLAG_FULLSCREEN) == 0) {
+ if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
// For dialog windows we want to make sure they don't go over the status bar or nav bar.
// We consume the system insets and we will reuse them later during the measure phase.
- // We allow the app to ignore this and handle insets itself by using FLAG_FULLSCREEN.
+ // We allow the app to ignore this and handle insets itself by using
+ // FLAG_LAYOUT_IN_SCREEN.
if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
mFloatingInsets.top = insets.getSystemWindowInsetTop();
mFloatingInsets.bottom = insets.getSystemWindowInsetBottom();
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index acd0501..dd0e456 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -41,6 +41,7 @@
#include <Animator.h>
#include <AnimationContext.h>
#include <FrameInfo.h>
+#include <FrameMetricsObserver.h>
#include <IContextFactory.h>
#include <JankTracker.h>
#include <RenderNode.h>
@@ -56,10 +57,11 @@
using namespace android::uirenderer::renderthread;
struct {
- jfieldID buffer;
+ jfieldID frameMetrics;
+ jfieldID timingDataBuffer;
jfieldID messageQueue;
- jmethodID notifyData;
-} gFrameStatsObserverClassInfo;
+ jmethodID callback;
+} gFrameMetricsObserverClassInfo;
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
@@ -239,31 +241,46 @@
mBuffer = buffer;
}
+ void setDropCount(int dropCount) {
+ mDropCount = dropCount;
+ }
+
virtual void handleMessage(const Message& message);
private:
JavaVM* mVm;
sp<ObserverProxy> mObserver;
- BufferPool::Buffer* mBuffer;
+ BufferPool::Buffer* mBuffer = nullptr;
+ int mDropCount = 0;
};
-class ObserverProxy : public FrameStatsObserver {
+static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
+ jobject frameMetrics = env->GetObjectField(
+ observer, gFrameMetricsObserverClassInfo.frameMetrics);
+ LOG_ALWAYS_FATAL_IF(frameMetrics == nullptr, "unable to retrieve data sink object");
+ jobject buffer = env->GetObjectField(
+ frameMetrics, gFrameMetricsObserverClassInfo.timingDataBuffer);
+ LOG_ALWAYS_FATAL_IF(buffer == nullptr, "unable to retrieve data sink buffer");
+ return reinterpret_cast<jlongArray>(buffer);
+}
+
+class ObserverProxy : public FrameMetricsObserver {
public:
- ObserverProxy(JavaVM *vm, jobject fso) : mVm(vm) {
+ ObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
JNIEnv* env = getenv(mVm);
- jlongArray longArrayLocal = env->NewLongArray(kBufferSize);
- LOG_ALWAYS_FATAL_IF(longArrayLocal == nullptr,
- "OOM: can't allocate frame stats buffer");
- env->SetObjectField(fso, gFrameStatsObserverClassInfo.buffer, longArrayLocal);
-
- mFsoWeak = env->NewWeakGlobalRef(fso);
- LOG_ALWAYS_FATAL_IF(mFsoWeak == nullptr,
+ mObserverWeak = env->NewWeakGlobalRef(observer);
+ LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
"unable to create frame stats observer reference");
- jobject messageQueueLocal =
- env->GetObjectField(fso, gFrameStatsObserverClassInfo.messageQueue);
+ jlongArray buffer = get_metrics_buffer(env, observer);
+ jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(buffer));
+ LOG_ALWAYS_FATAL_IF(bufferSize != kBufferSize,
+ "Mismatched Java/Native FrameMetrics data format.");
+
+ jobject messageQueueLocal = env->GetObjectField(
+ observer, gFrameMetricsObserverClassInfo.messageQueue);
mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
@@ -274,17 +291,18 @@
~ObserverProxy() {
JNIEnv* env = getenv(mVm);
- env->DeleteWeakGlobalRef(mFsoWeak);
+ env->DeleteWeakGlobalRef(mObserverWeak);
}
- jweak getJavaObjectRef() {
- return mFsoWeak;
+ jweak getObserverReference() {
+ return mObserverWeak;
}
- virtual void notify(BufferPool::Buffer* buffer) {
+ virtual void notify(BufferPool::Buffer* buffer, int dropCount) {
buffer->incRef();
mMessageHandler->setBuffer(buffer);
mMessageHandler->setObserver(this);
+ mMessageHandler->setDropCount(dropCount);
mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
}
@@ -292,26 +310,27 @@
static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
JavaVM* mVm;
- jweak mFsoWeak;
+ jweak mObserverWeak;
+ jobject mJavaBufferGlobal;
sp<MessageQueue> mMessageQueue;
sp<NotifyHandler> mMessageHandler;
Message mMessage;
+
};
void NotifyHandler::handleMessage(const Message& message) {
JNIEnv* env = getenv(mVm);
- jobject target = env->NewLocalRef(mObserver->getJavaObjectRef());
+ jobject target = env->NewLocalRef(mObserver->getObserverReference());
if (target != nullptr) {
- jobject javaBuffer = env->GetObjectField(target, gFrameStatsObserverClassInfo.buffer);
- if (javaBuffer != nullptr) {
- env->SetLongArrayRegion(reinterpret_cast<jlongArray>(javaBuffer),
- 0, mBuffer->getSize(), mBuffer->getBuffer());
- env->CallVoidMethod(target, gFrameStatsObserverClassInfo.notifyData);
- env->DeleteLocalRef(target);
- }
+ jlongArray javaBuffer = get_metrics_buffer(env, target);
+ env->SetLongArrayRegion(javaBuffer,
+ 0, mBuffer->getSize(), mBuffer->getBuffer());
+ env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback,
+ mDropCount);
+ env->DeleteLocalRef(target);
}
mBuffer->release();
@@ -579,10 +598,10 @@
}
// ----------------------------------------------------------------------------
-// FrameStatsObserver
+// FrameMetricsObserver
// ----------------------------------------------------------------------------
-static jlong android_view_ThreadedRenderer_addFrameStatsObserver(JNIEnv* env,
+static jlong android_view_ThreadedRenderer_addFrameMetricsObserver(JNIEnv* env,
jclass clazz, jlong proxyPtr, jobject fso) {
JavaVM* vm = nullptr;
if (env->GetJavaVM(&vm) != JNI_OK) {
@@ -593,25 +612,18 @@
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
- FrameStatsObserver* observer = new ObserverProxy(vm, fso);
- renderProxy->addFrameStatsObserver(observer);
+ FrameMetricsObserver* observer = new ObserverProxy(vm, fso);
+ renderProxy->addFrameMetricsObserver(observer);
return reinterpret_cast<jlong>(observer);
}
-static void android_view_ThreadedRenderer_removeFrameStatsObserver(JNIEnv* env, jclass clazz,
+static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env, jclass clazz,
jlong proxyPtr, jlong observerPtr) {
- FrameStatsObserver* observer = reinterpret_cast<FrameStatsObserver*>(observerPtr);
+ FrameMetricsObserver* observer = reinterpret_cast<FrameMetricsObserver*>(observerPtr);
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
- renderProxy->removeFrameStatsObserver(observer);
-}
-
-static jint android_view_ThreadedRenderer_getDroppedFrameReportCount(JNIEnv* env, jclass clazz,
- jlong proxyPtr) {
- renderthread::RenderProxy* renderProxy =
- reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
- return renderProxy->getDroppedFrameReportCount();
+ renderProxy->removeFrameMetricsObserver(observer);
}
// ----------------------------------------------------------------------------
@@ -684,25 +696,26 @@
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
{ "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
- { "nAddFrameStatsObserver",
- "(JLandroid/view/FrameStatsObserver;)J",
- (void*)android_view_ThreadedRenderer_addFrameStatsObserver },
- { "nRemoveFrameStatsObserver",
+ { "nAddFrameMetricsObserver",
+ "(JLandroid/view/FrameMetricsObserver;)J",
+ (void*)android_view_ThreadedRenderer_addFrameMetricsObserver },
+ { "nRemoveFrameMetricsObserver",
"(JJ)V",
- (void*)android_view_ThreadedRenderer_removeFrameStatsObserver },
- { "nGetDroppedFrameReportCount",
- "(J)J",
- (void*)android_view_ThreadedRenderer_getDroppedFrameReportCount },
+ (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver },
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
- jclass clazz = FindClassOrDie(env, "android/view/FrameStatsObserver");
- gFrameStatsObserverClassInfo.messageQueue =
- GetFieldIDOrDie(env, clazz, "mMessageQueue", "Landroid/os/MessageQueue;");
- gFrameStatsObserverClassInfo.buffer =
- GetFieldIDOrDie(env, clazz, "mBuffer", "[J");
- gFrameStatsObserverClassInfo.notifyData =
- GetMethodIDOrDie(env, clazz, "notifyDataAvailable", "()V");
+ jclass observerClass = FindClassOrDie(env, "android/view/FrameMetricsObserver");
+ gFrameMetricsObserverClassInfo.frameMetrics = GetFieldIDOrDie(
+ env, observerClass, "mFrameMetrics", "Landroid/view/FrameMetrics;");
+ gFrameMetricsObserverClassInfo.messageQueue = GetFieldIDOrDie(
+ env, observerClass, "mMessageQueue", "Landroid/os/MessageQueue;");
+ gFrameMetricsObserverClassInfo.callback = GetMethodIDOrDie(
+ env, observerClass, "notifyDataAvailable", "(I)V");
+
+ jclass metricsClass = FindClassOrDie(env, "android/view/FrameMetrics");
+ gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
+ env, metricsClass, "mTimingData", "[J");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1db75e6..99daab4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -425,6 +425,8 @@
<protected-broadcast android:name="android.intent.action.DYNAMIC_SENSOR_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_RADIO_OFF" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
diff --git a/core/res/res/layout/language_picker_section_header.xml b/core/res/res/layout/language_picker_section_header.xml
index c4d3069..b12ec8c 100644
--- a/core/res/res/layout/language_picker_section_header.xml
+++ b/core/res/res/layout/language_picker_section_header.xml
@@ -19,7 +19,8 @@
style="?android:attr/preferenceCategoryStyle"
android:layout_width="match_parent"
android:layout_height="36dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
+ android:gravity="center_vertical"
+ android:paddingStart="18dp"
+ android:paddingEnd="18dp"
android:textColor="?android:attr/colorAccent"
tools:text="@string/language_picker_section_all"/>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 3a5336c..1496d09 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -429,8 +429,10 @@
sets. -->
<attr name="multiArch" format ="boolean" />
- <!-- Specify abiOverride for multiArch application. -->
- <attr name="abiOverride" />
+ <!-- Specify whether the 32 bit version of the ABI should be used in a
+ multiArch application. If both abioverride flag (i.e. using abi option of abd install)
+ and use32bitAbi are used, then use32bit is ignored.-->
+ <attr name="use32bitAbi" />
<!-- Specify whether a component is allowed to have multiple instances
of itself running in different processes. Use with the activity
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5c5aff0..69d005c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2696,7 +2696,7 @@
<public type="attr" name="endX" />
<public type="attr" name="endY" />
<public type="attr" name="offset" />
- <public type="attr" name="abiOverride" />
+ <public type="attr" name="use32bitAbi" />
<public type="attr" name="bitmap" />
<public type="attr" name="hotSpotX" />
<public type="attr" name="hotSpotY" />
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
index d133a12..93581db 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
@@ -20,11 +20,13 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
import android.os.Parcel;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.LocaleList;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
import android.view.inputmethod.InputMethodSubtype;
@@ -230,7 +232,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_EN_US, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_EN_US))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(autoSubtype, result.get(0));
}
@@ -251,8 +256,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_EN_US, imi);
- assertEquals(1, result.size());
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_EN_US))
+ .getResources(),
+ imi);
verifyEquality(nonAutoEnUS, result.get(0));
}
@@ -271,7 +278,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_EN_GB, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_EN_GB))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoEnGB, result.get(0));
}
@@ -292,7 +302,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FR, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_FR))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoFrCA, result.get(0));
}
@@ -309,7 +322,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FR_CA, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_FR_CA))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoFrCA, result.get(0));
}
@@ -327,7 +343,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_JA_JP, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_JA_JP))
+ .getResources(),
+ imi);
assertEquals(3, result.size());
verifyEquality(nonAutoJa, result.get(0));
verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1));
@@ -344,7 +363,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FIL_PH, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_FIL_PH))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoFil, result.get(0));
}
@@ -361,7 +383,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FI, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_FI))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoJa, result.get(0));
}
@@ -376,7 +401,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_IN, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_IN))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoIn, result.get(0));
}
@@ -389,7 +417,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_ID, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_ID))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoIn, result.get(0));
}
@@ -402,7 +433,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_IN, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_IN))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoId, result.get(0));
}
@@ -415,7 +449,10 @@
"com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_ID, imi);
+ InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ createTargetContextWithLocales(new LocaleList(LOCALE_ID))
+ .getResources(),
+ imi);
assertEquals(1, result.size());
verifyEquality(nonAutoId, result.get(0));
}
@@ -568,24 +605,11 @@
}
}
- private ArrayList<InputMethodSubtype> callGetImplicitlyApplicableSubtypesLockedWithLocale(
- final Locale locale, final InputMethodInfo imi) {
- final Context context = getInstrumentation().getTargetContext();
- final Locale initialLocale = context.getResources().getConfiguration().locale;
- try {
- context.getResources().getConfiguration().setLocale(locale);
- return InputMethodUtils.getImplicitlyApplicableSubtypesLocked(context.getResources(),
- imi);
- } finally {
- context.getResources().getConfiguration().setLocale(initialLocale);
- }
- }
-
private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
final Locale systemLocale, final boolean isSystemReady, String... expectedImeNames) {
- final Context context = getInstrumentation().getTargetContext();
- final String[] actualImeNames = getPackageNames(callGetDefaultEnabledImesWithLocale(
- context, isSystemReady, preinstalledImes, systemLocale));
+ final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
+ final String[] actualImeNames = getPackageNames(
+ InputMethodUtils.getDefaultEnabledImes(context, isSystemReady, preinstalledImes));
assertEquals(expectedImeNames.length, actualImeNames.length);
for (int i = 0; i < expectedImeNames.length; ++i) {
assertEquals(expectedImeNames[i], actualImeNames[i]);
@@ -606,16 +630,12 @@
}
}
- private static ArrayList<InputMethodInfo> callGetDefaultEnabledImesWithLocale(
- final Context context, final boolean isSystemReady,
- final ArrayList<InputMethodInfo> imis, final Locale locale) {
- final Locale initialLocale = context.getResources().getConfiguration().locale;
- try {
- context.getResources().getConfiguration().setLocale(locale);
- return InputMethodUtils.getDefaultEnabledImes(context, isSystemReady, imis);
- } finally {
- context.getResources().getConfiguration().setLocale(initialLocale);
- }
+ private Context createTargetContextWithLocales(final LocaleList locales) {
+ final Configuration resourceConfiguration = new Configuration();
+ resourceConfiguration.setLocales(locales);
+ return getInstrumentation()
+ .getTargetContext()
+ .createConfigurationContext(resourceConfiguration);
}
private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index daf2581..bffbc75 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -641,16 +641,22 @@
@Override
public void setTintList(ColorStateList tint) {
- mBitmapState.mTint = tint;
- mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
- invalidateSelf();
+ final BitmapState state = mBitmapState;
+ if (state.mTint != tint) {
+ state.mTint = tint;
+ mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
+ invalidateSelf();
+ }
}
@Override
public void setTintMode(PorterDuff.Mode tintMode) {
- mBitmapState.mTintMode = tintMode;
- mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
- invalidateSelf();
+ final BitmapState state = mBitmapState;
+ if (state.mTintMode != tintMode) {
+ state.mTintMode = tintMode;
+ mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
+ invalidateSelf();
+ }
}
/**
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index c8333c8..302b0bd 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -20,9 +20,11 @@
import android.content.Context;
import android.content.Intent;
import android.util.Log;
+
import com.android.org.bouncycastle.util.io.pem.PemObject;
import com.android.org.bouncycastle.util.io.pem.PemReader;
import com.android.org.bouncycastle.util.io.pem.PemWriter;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -147,20 +149,23 @@
Reader reader = new InputStreamReader(bai, StandardCharsets.US_ASCII);
PemReader pr = new PemReader(reader);
- CertificateFactory cf = CertificateFactory.getInstance("X509");
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X509");
- List<X509Certificate> result = new ArrayList<X509Certificate>();
- PemObject o;
- while ((o = pr.readPemObject()) != null) {
- if (o.getType().equals("CERTIFICATE")) {
- Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
- result.add((X509Certificate) c);
- } else {
- throw new IllegalArgumentException("Unknown type " + o.getType());
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ PemObject o;
+ while ((o = pr.readPemObject()) != null) {
+ if (o.getType().equals("CERTIFICATE")) {
+ Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
+ result.add((X509Certificate) c);
+ } else {
+ throw new IllegalArgumentException("Unknown type " + o.getType());
+ }
}
+ return result;
+ } finally {
+ pr.close();
}
- pr.close();
- return result;
}
private static Credentials singleton;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 1b87a41..3090ac1 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,7 +19,6 @@
import android.app.ActivityThread;
import android.app.Application;
import android.app.KeyguardManager;
-
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
@@ -32,6 +31,7 @@
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterBlob;
+import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import android.security.keymaster.OperationResult;
import android.security.keystore.KeyExpiredException;
@@ -615,6 +615,17 @@
return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
}
+ public int attestKey(
+ String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
+ try {
+ return mBinder.attestKey(alias, params, outChain);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
+
+
/**
* Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
* code.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 65460b5..3a0ff1c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -22,6 +22,7 @@
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
@@ -46,6 +47,8 @@
import libcore.util.EmptyArray;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
@@ -57,14 +60,17 @@
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -166,6 +172,7 @@
mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
+ @SuppressWarnings("deprecation")
@Override
public void initialize(int keysize, SecureRandom random) {
throw new IllegalArgumentException(
@@ -173,6 +180,7 @@
+ " required to initialize this KeyPairGenerator");
}
+ @SuppressWarnings("deprecation")
@Override
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
@@ -447,6 +455,69 @@
+ ", but the user has not yet entered the credential");
}
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, (mKeySizeBits + 7) / 8);
+
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
+ final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
+ boolean success = false;
+ try {
+ generateKeystoreKeyPair(
+ privateKeyAlias, constructKeyGenerationArguments(), additionalEntropy, flags);
+ KeyPair keyPair = loadKeystoreKeyPair(privateKeyAlias);
+
+ storeCertificateChain(flags, createCertificateChain(privateKeyAlias, keyPair));
+
+ success = true;
+ return keyPair;
+ } finally {
+ if (!success) {
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
+ }
+ }
+ }
+
+ private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
+ throws ProviderException {
+ byte[] challenge = mSpec.getAttestationChallenge();
+ if (challenge != null) {
+ KeymasterArguments args = new KeymasterArguments();
+ args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge);
+ return getAttestationChain(privateKeyAlias, keyPair, args);
+ }
+
+ // Very short certificate chain in the non-attestation case.
+ return Collections.singleton(generateSelfSignedCertificateBytes(keyPair));
+ }
+
+ private void generateKeystoreKeyPair(final String privateKeyAlias, KeymasterArguments args,
+ byte[] additionalEntropy, final int flags) throws ProviderException {
+ KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+ int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy,
+ mEntryUid, flags, resultingKeyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException(
+ "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+ }
+ }
+
+ private KeyPair loadKeystoreKeyPair(final String privateKeyAlias) throws ProviderException {
+ try {
+ KeyPair result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
+ mKeyStore, privateKeyAlias, mEntryUid);
+ if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
+ throw new ProviderException(
+ "Generated key pair algorithm does not match requested algorithm: "
+ + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
+ }
+ return result;
+ } catch (UnrecoverableKeyException e) {
+ throw new ProviderException("Failed to load generated key pair from keystore", e);
+ }
+ }
+
+ private KeymasterArguments constructKeyGenerationArguments() {
KeymasterArguments args = new KeymasterArguments();
args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -466,73 +537,72 @@
mSpec.getKeyValidityForConsumptionEnd());
addAlgorithmSpecificParameters(args);
- byte[] additionalEntropy =
- KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
- mRng, (mKeySizeBits + 7) / 8);
+ if (mSpec.isUniqueIdIncluded())
+ args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
- boolean success = false;
- try {
- Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
- KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
- int errorCode = mKeyStore.generateKey(
- privateKeyAlias,
- args,
- additionalEntropy,
- mEntryUid,
- flags,
- resultingKeyCharacteristics);
- if (errorCode != KeyStore.NO_ERROR) {
- throw new ProviderException(
- "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
- }
+ return args;
+ }
- KeyPair result;
- try {
- result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
- mKeyStore, privateKeyAlias, mEntryUid);
- } catch (UnrecoverableKeyException e) {
- throw new ProviderException("Failed to load generated key pair from keystore", e);
- }
+ private void storeCertificateChain(final int flags, Iterable<byte[]> iterable)
+ throws ProviderException {
+ Iterator<byte[]> iter = iterable.iterator();
+ storeCertificate(
+ Credentials.USER_CERTIFICATE, iter.next(), flags, "Failed to store certificate");
- if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
- throw new ProviderException(
- "Generated key pair algorithm does not match requested algorithm: "
- + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
- }
-
- final X509Certificate cert;
- try {
- cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic());
- } catch (Exception e) {
- throw new ProviderException("Failed to generate self-signed certificate", e);
- }
-
- byte[] certBytes;
- try {
- certBytes = cert.getEncoded();
- } catch (CertificateEncodingException e) {
- throw new ProviderException(
- "Failed to obtain encoded form of self-signed certificate", e);
- }
-
- int insertErrorCode = mKeyStore.insert(
- Credentials.USER_CERTIFICATE + mEntryAlias,
- certBytes,
- mEntryUid,
- flags);
- if (insertErrorCode != KeyStore.NO_ERROR) {
- throw new ProviderException("Failed to store self-signed certificate",
- KeyStore.getKeyStoreException(insertErrorCode));
- }
-
- success = true;
- return result;
- } finally {
- if (!success) {
- Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
- }
+ if (!iter.hasNext()) {
+ return;
}
+
+ ByteArrayOutputStream certificateConcatenationStream = new ByteArrayOutputStream();
+ while (iter.hasNext()) {
+ byte[] data = iter.next();
+ certificateConcatenationStream.write(data, 0, data.length);
+ }
+
+ storeCertificate(Credentials.CA_CERTIFICATE, certificateConcatenationStream.toByteArray(),
+ flags, "Failed to store attestation CA certificate");
+ }
+
+ private void storeCertificate(String prefix, byte[] certificateBytes, final int flags,
+ String failureMessage) throws ProviderException {
+ int insertErrorCode = mKeyStore.insert(
+ prefix + mEntryAlias,
+ certificateBytes,
+ mEntryUid,
+ flags);
+ if (insertErrorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException(failureMessage,
+ KeyStore.getKeyStoreException(insertErrorCode));
+ }
+ }
+
+ private byte[] generateSelfSignedCertificateBytes(KeyPair keyPair) throws ProviderException {
+ try {
+ return generateSelfSignedCertificate(keyPair.getPrivate(), keyPair.getPublic())
+ .getEncoded();
+ } catch (IOException | CertificateParsingException e) {
+ throw new ProviderException("Failed to generate self-signed certificate", e);
+ } catch (CertificateEncodingException e) {
+ throw new ProviderException(
+ "Failed to obtain encoded form of self-signed certificate", e);
+ }
+ }
+
+ private Iterable<byte[]> getAttestationChain(String privateKeyAlias,
+ KeyPair keyPair, KeymasterArguments args)
+ throws ProviderException {
+ KeymasterCertificateChain outChain = new KeymasterCertificateChain();
+ int errorCode = mKeyStore.attestKey(privateKeyAlias, args, outChain);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException("Failed to generate attestation certificate chain",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+ Collection<byte[]> chain = outChain.getCertificates();
+ if (chain.size() < 2) {
+ throw new ProviderException("Attestation certificate chain contained "
+ + chain.size() + " entries. At least two are required.");
+ }
+ return chain;
}
private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) {
@@ -548,8 +618,8 @@
}
}
- private X509Certificate generateSelfSignedCertificate(
- PrivateKey privateKey, PublicKey publicKey) throws Exception {
+ private X509Certificate generateSelfSignedCertificate(PrivateKey privateKey,
+ PublicKey publicKey) throws CertificateParsingException, IOException {
String signatureAlgorithm =
getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec);
if (signatureAlgorithm == null) {
@@ -587,7 +657,7 @@
@SuppressWarnings("deprecation")
private X509Certificate generateSelfSignedCertificateWithFakeSignature(
- PublicKey publicKey) throws Exception {
+ PublicKey publicKey) throws IOException, CertificateParsingException {
V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator();
ASN1ObjectIdentifier sigAlgOid;
AlgorithmIdentifier sigAlgId;
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index add199f..f3fd129 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -250,6 +250,8 @@
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final byte[] mAttestationChallenge;
+ private final boolean mUniqueIdIncluded;
/**
* @hide should be built with Builder
@@ -273,7 +275,9 @@
@KeyProperties.BlockModeEnum String[] blockModes,
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
- int userAuthenticationValidityDurationSeconds) {
+ int userAuthenticationValidityDurationSeconds,
+ byte[] attestationChallenge,
+ boolean uniqueIdIncluded) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -315,6 +319,8 @@
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
+ mUniqueIdIncluded = uniqueIdIncluded;
}
/**
@@ -539,6 +545,48 @@
}
/**
+ * Returns the attestation challenge value that will be placed in attestation certificate for
+ * this key pair.
+ *
+ * <p>If this method returns non-{@code null}, the public key certificate for this key pair will
+ * contain an extension that describes the details of the key's configuration and
+ * authorizations, including the content of the attestation challenge value. If the key is in
+ * secure hardware, and if the secure hardware supports attestation, the certificate will be
+ * signed by a chain of certificates rooted at a trustworthy CA key. Otherwise the chain will
+ * be rooted at an untrusted certificate.
+ *
+ * <p>If this method returns {@code null}, and the spec is used to generate an asymmetric (RSA
+ * or EC) key pair, the public key will have a self-signed certificate if it has purpose {@link
+ * KeyProperties#PURPOSE_SIGN} (see {@link #KeyGenParameterSpec(String, int)). If does not have
+ * purpose {@link KeyProperties#PURPOSE_SIGN}, it will have a fake certificate.
+ *
+ * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a
+ * {@link KeyGenParameterSpec} with {@link #hasAttestationCertificate()} returning
+ * non-{@code null} is used to generate a symmetric (AES or HMAC) key,
+ * {@link KeyGenerator#generateKey())} will throw
+ * {@link java.security.InvalidAlgorithmParameterException}.
+ *
+ * @see Builder#setAttestationChallenge(byte[])
+ */
+ /*
+ * TODO(swillden): Update this documentation to describe the hardware and software root keys,
+ * including information about CRL/OCSP services for discovering revocations, and to link to
+ * documentation of the extension format and content.
+ */
+ public byte[] getAttestationChallenge() {
+ return Utils.cloneIfNotNull(mAttestationChallenge);
+ }
+
+ /**
+ * @hide This is a system-only API
+ *
+ * Returns {@code true} if the attestation certificate will contain a unique ID field.
+ */
+ public boolean isUniqueIdIncluded() {
+ return mUniqueIdIncluded;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -562,6 +610,8 @@
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
private int mUserAuthenticationValidityDurationSeconds = -1;
+ private byte[] mAttestationChallenge = null;
+ private boolean mUniqueIdIncluded = false;
/**
* Creates a new instance of the {@code Builder}.
@@ -957,6 +1007,59 @@
return this;
}
+ /*
+ * TODO(swillden): Update this documentation to describe the hardware and software root
+ * keys, including information about CRL/OCSP services for discovering revocations, and to
+ * link to documentation of the extension format and content.
+ */
+ /**
+ * Sets whether an attestation certificate will be generated for this key pair, and what
+ * challenge value will be placed in the certificate. The attestation certificate chain
+ * can be retrieved with with {@link java.security.KeyStore#getCertificateChain(String)}.
+ *
+ * <p>If {@code attestationChallenge} is not {@code null}, the public key certificate for
+ * this key pair will contain an extension that describes the details of the key's
+ * configuration and authorizations, including the {@code attestationChallenge} value. If
+ * the key is in secure hardware, and if the secure hardware supports attestation, the
+ * certificate will be signed by a chain of certificates rooted at a trustworthy CA key.
+ * Otherwise the chain will be rooted at an untrusted certificate.
+ *
+ * <p>The purpose of the challenge value is to enable relying parties to verify that the key
+ * was created in response to a specific request. If attestation is desired but no
+ * challenged is needed, any non-{@code null} value may be used, including an empty byte
+ * array.
+ *
+ * <p>If {@code attestationChallenge} is {@code null}, and this spec is used to generate an
+ * asymmetric (RSA or EC) key pair, the public key certificate will be self-signed if the
+ * key has purpose {@link KeyProperties#PURPOSE_SIGN} (see
+ * {@link #KeyGenParameterSpec(String, int)). If the key does not have purpose
+ * {@link KeyProperties#PURPOSE_SIGN}, it is not possible to use the key to sign a
+ * certificate, so the public key certificate will contain a dummy signature.
+ *
+ * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a
+ * {@code getAttestationChallenge} returns non-{@code null} and the spec is used to
+ * generate a symmetric (AES or HMAC) key, {@link KeyGenerator#generateKey()} will throw
+ * {@link java.security.InvalidAlgorithmParameterException}.
+ *
+ * @see Builder#setAttestationChallenge(String attestationChallenge)
+ */
+ @NonNull
+ public Builder setAttestationChallenge(byte[] attestationChallenge) {
+ mAttestationChallenge = attestationChallenge;
+ return this;
+ }
+
+ /**
+ * @hide Only system apps can use this method.
+ *
+ * Sets whether to include a temporary unique ID field in the attestation certificate.
+ */
+ @NonNull
+ public Builder setUniqueIdIncluded(boolean uniqueIdIncluded) {
+ mUniqueIdIncluded = uniqueIdIncluded;
+ return this;
+ }
+
/**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@@ -981,7 +1084,9 @@
mBlockModes,
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
- mUserAuthenticationValidityDurationSeconds);
+ mUserAuthenticationValidityDurationSeconds,
+ mAttestationChallenge,
+ mUniqueIdIncluded);
}
}
}
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index 9bec682..5722c7b 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -29,4 +29,8 @@
static Date cloneIfNotNull(Date value) {
return (value != null) ? (Date) value.clone() : null;
}
+
+ static byte[] cloneIfNotNull(byte[] value) {
+ return (value != null) ? value.clone() : null;
+ }
}
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 9c08b4d..e368537 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -361,17 +361,21 @@
"expect RectangleList to be trivially destructible");
if (mLastSerialization == nullptr) {
+ ClipBase* serialization = nullptr;
switch (mMode) {
case ClipMode::Rectangle:
- mLastSerialization = allocator.create<ClipRect>(mClipRect);
+ serialization = allocator.create<ClipRect>(mClipRect);
break;
case ClipMode::RectangleList:
- mLastSerialization = allocator.create<ClipRectList>(mRectangleList);
+ serialization = allocator.create<ClipRectList>(mRectangleList);
+ serialization->rect = mRectangleList.calculateBounds();
break;
case ClipMode::Region:
- mLastSerialization = allocator.create<ClipRegion>(mClipRegion);
+ serialization = allocator.create<ClipRegion>(mClipRegion);
+ serialization->rect.set(mClipRegion.getBounds());
break;
}
+ mLastSerialization = serialization;
}
return mLastSerialization;
}
diff --git a/libs/hwui/FrameStatsObserver.h b/libs/hwui/FrameMetricsObserver.h
similarity index 86%
rename from libs/hwui/FrameStatsObserver.h
rename to libs/hwui/FrameMetricsObserver.h
index 7abc9f1..2b42a80 100644
--- a/libs/hwui/FrameStatsObserver.h
+++ b/libs/hwui/FrameMetricsObserver.h
@@ -23,9 +23,9 @@
namespace android {
namespace uirenderer {
-class FrameStatsObserver : public VirtualLightRefBase {
+class FrameMetricsObserver : public VirtualLightRefBase {
public:
- virtual void notify(BufferPool::Buffer* buffer);
+ virtual void notify(BufferPool::Buffer* buffer, int dropCount);
};
}; // namespace uirenderer
diff --git a/libs/hwui/FrameStatsReporter.h b/libs/hwui/FrameMetricsReporter.h
similarity index 83%
rename from libs/hwui/FrameStatsReporter.h
rename to libs/hwui/FrameMetricsReporter.h
index b8a9432..0831d24 100644
--- a/libs/hwui/FrameStatsReporter.h
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -21,7 +21,7 @@
#include "BufferPool.h"
#include "FrameInfo.h"
-#include "FrameStatsObserver.h"
+#include "FrameMetricsObserver.h"
#include <string.h>
#include <vector>
@@ -29,18 +29,18 @@
namespace android {
namespace uirenderer {
-class FrameStatsReporter {
+class FrameMetricsReporter {
public:
- FrameStatsReporter() {
+ FrameMetricsReporter() {
mBufferPool = new BufferPool(kBufferSize, kBufferCount);
LOG_ALWAYS_FATAL_IF(mBufferPool.get() == nullptr, "OOM: unable to allocate buffer pool");
}
- void addObserver(FrameStatsObserver* observer) {
+ void addObserver(FrameMetricsObserver* observer) {
mObservers.push_back(observer);
}
- bool removeObserver(FrameStatsObserver* observer) {
+ bool removeObserver(FrameMetricsObserver* observer) {
for (size_t i = 0; i < mObservers.size(); i++) {
if (mObservers[i].get() == observer) {
mObservers.erase(mObservers.begin() + i);
@@ -54,7 +54,7 @@
return mObservers.size() > 0;
}
- void reportFrameStats(const int64_t* stats) {
+ void reportFrameMetrics(const int64_t* stats) {
BufferPool::Buffer* statsBuffer = mBufferPool->acquire();
if (statsBuffer != nullptr) {
@@ -63,11 +63,12 @@
// notify on requested threads
for (size_t i = 0; i < mObservers.size(); i++) {
- mObservers[i]->notify(statsBuffer);
+ mObservers[i]->notify(statsBuffer, mDroppedReports);
}
// drop our reference
statsBuffer->release();
+ mDroppedReports = 0;
} else {
mDroppedReports++;
}
@@ -79,7 +80,7 @@
static const size_t kBufferCount = 3;
static const size_t kBufferSize = static_cast<size_t>(FrameInfoIndex::NumIndexes);
- std::vector< sp<FrameStatsObserver> > mObservers;
+ std::vector< sp<FrameMetricsObserver> > mObservers;
sp<BufferPool> mBufferPool;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ea702c0..4f528b1 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -507,8 +507,8 @@
mJankTracker.addFrame(*mCurrentFrameInfo);
mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
- if (CC_UNLIKELY(mFrameStatsReporter.get() != nullptr)) {
- mFrameStatsReporter->reportFrameStats(mCurrentFrameInfo->data());
+ if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
+ mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
}
GpuMemoryTracker::onFrameCompleted();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 168166e..1f81970 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -20,7 +20,7 @@
#include "DamageAccumulator.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
-#include "FrameStatsReporter.h"
+#include "FrameMetricsReporter.h"
#include "IContextFactory.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
@@ -142,26 +142,26 @@
return mRenderThread.renderState();
}
- void addFrameStatsObserver(FrameStatsObserver* observer) {
- if (mFrameStatsReporter.get() == nullptr) {
- mFrameStatsReporter.reset(new FrameStatsReporter());
+ void addFrameMetricsObserver(FrameMetricsObserver* observer) {
+ if (mFrameMetricsReporter.get() == nullptr) {
+ mFrameMetricsReporter.reset(new FrameMetricsReporter());
}
- mFrameStatsReporter->addObserver(observer);
+ mFrameMetricsReporter->addObserver(observer);
}
- void removeFrameStatsObserver(FrameStatsObserver* observer) {
- if (mFrameStatsReporter.get() != nullptr) {
- mFrameStatsReporter->removeObserver(observer);
- if (!mFrameStatsReporter->hasObservers()) {
- mFrameStatsReporter.reset(nullptr);
+ void removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+ if (mFrameMetricsReporter.get() != nullptr) {
+ mFrameMetricsReporter->removeObserver(observer);
+ if (!mFrameMetricsReporter->hasObservers()) {
+ mFrameMetricsReporter.reset(nullptr);
}
}
}
long getDroppedFrameReportCount() {
- if (mFrameStatsReporter.get() != nullptr) {
- return mFrameStatsReporter->getDroppedReports();
+ if (mFrameMetricsReporter.get() != nullptr) {
+ return mFrameMetricsReporter->getDroppedReports();
}
return 0;
@@ -215,7 +215,7 @@
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
- std::unique_ptr<FrameStatsReporter> mFrameStatsReporter;
+ std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter;
std::set<RenderNode*> mPrefetechedLayers;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 7c6cd7e..04223a7 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -568,17 +568,17 @@
post(task);
}
-CREATE_BRIDGE2(addFrameStatsObserver, CanvasContext* context,
- FrameStatsObserver* frameStatsObserver) {
- args->context->addFrameStatsObserver(args->frameStatsObserver);
+CREATE_BRIDGE2(addFrameMetricsObserver, CanvasContext* context,
+ FrameMetricsObserver* frameStatsObserver) {
+ args->context->addFrameMetricsObserver(args->frameStatsObserver);
if (args->frameStatsObserver != nullptr) {
args->frameStatsObserver->decStrong(args->context);
}
return nullptr;
}
-void RenderProxy::addFrameStatsObserver(FrameStatsObserver* observer) {
- SETUP_TASK(addFrameStatsObserver);
+void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observer) {
+ SETUP_TASK(addFrameMetricsObserver);
args->context = mContext;
args->frameStatsObserver = observer;
if (observer != nullptr) {
@@ -587,17 +587,17 @@
post(task);
}
-CREATE_BRIDGE2(removeFrameStatsObserver, CanvasContext* context,
- FrameStatsObserver* frameStatsObserver) {
- args->context->removeFrameStatsObserver(args->frameStatsObserver);
+CREATE_BRIDGE2(removeFrameMetricsObserver, CanvasContext* context,
+ FrameMetricsObserver* frameStatsObserver) {
+ args->context->removeFrameMetricsObserver(args->frameStatsObserver);
if (args->frameStatsObserver != nullptr) {
args->frameStatsObserver->decStrong(args->context);
}
return nullptr;
}
-void RenderProxy::removeFrameStatsObserver(FrameStatsObserver* observer) {
- SETUP_TASK(removeFrameStatsObserver);
+void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+ SETUP_TASK(removeFrameMetricsObserver);
args->context = mContext;
args->frameStatsObserver = observer;
if (observer != nullptr) {
@@ -606,16 +606,6 @@
post(task);
}
-CREATE_BRIDGE1(getDroppedFrameReportCount, CanvasContext* context) {
- return (void*) args->context->getDroppedFrameReportCount();
-}
-
-long RenderProxy::getDroppedFrameReportCount() {
- SETUP_TASK(getDroppedFrameReportCount);
- args->context = mContext;
- return (long) postAndWait(task);
-}
-
void RenderProxy::post(RenderTask* task) {
mRenderThread.queue(task);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 178724a..8d65a82 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -29,7 +29,7 @@
#include <utils/StrongPointer.h>
#include "../Caches.h"
-#include "../FrameStatsObserver.h"
+#include "../FrameMetricsObserver.h"
#include "../IContextFactory.h"
#include "CanvasContext.h"
#include "DrawFrameTask.h"
@@ -113,8 +113,8 @@
ANDROID_API void drawRenderNode(RenderNode* node);
ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
- ANDROID_API void addFrameStatsObserver(FrameStatsObserver* observer);
- ANDROID_API void removeFrameStatsObserver(FrameStatsObserver* observer);
+ ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer);
+ ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer);
ANDROID_API long getDroppedFrameReportCount();
private:
diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp
index 4cae737..679569e 100644
--- a/libs/hwui/tests/unit/ClipAreaTests.cpp
+++ b/libs/hwui/tests/unit/ClipAreaTests.cpp
@@ -133,7 +133,7 @@
ASSERT_NE(nullptr, serializedClip);
ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode);
auto clipRect = reinterpret_cast<const ClipRect*>(serializedClip);
- ASSERT_EQ(Rect(200, 200), clipRect->rect);
+ EXPECT_EQ(Rect(200, 200), clipRect->rect);
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
<< "Requery of clip on unmodified ClipArea must return same pointer.";
}
@@ -147,7 +147,10 @@
ASSERT_NE(nullptr, serializedClip);
ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode);
auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip);
- ASSERT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+ EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+ EXPECT_FALSE(clipRectList->rect.isEmpty());
+ EXPECT_FLOAT_EQ(199.87817f, clipRectList->rect.right)
+ << "Right side should be clipped by rotated rect";
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
<< "Requery of clip on unmodified ClipArea must return same pointer.";
}
@@ -161,8 +164,9 @@
ASSERT_NE(nullptr, serializedClip);
ASSERT_EQ(ClipMode::Region, serializedClip->mode);
auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip);
- ASSERT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
+ EXPECT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
<< "Clip region should be 200x200";
+ EXPECT_EQ(Rect(200, 200), clipRegion->rect);
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
<< "Requery of clip on unmodified ClipArea must return same pointer.";
}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 800b914..e342385 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1014,9 +1014,12 @@
* Reads audio data from the audio hardware for recording into a byte array.
* The format specified in the AudioRecord constructor should be
* {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
+ * The format can be {@link AudioFormat#ENCODING_PCM_16BIT}, but this is deprecated.
* @param audioData the array to which the recorded audio data is written.
- * @param offsetInBytes index in audioData from which the data is written expressed in bytes.
+ * @param offsetInBytes index in audioData to which the data is written expressed in bytes.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInBytes the number of requested bytes.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
* <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
* is read.
@@ -1025,7 +1028,8 @@
* @return the number of bytes that were read or {@link #ERROR_INVALID_OPERATION}
* if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
* the parameters don't resolve to valid data and indexes.
- * The number of bytes will not exceed sizeInBytes.
+ * The number of bytes will be a multiple of the frame size in bytes
+ * not to exceed sizeInBytes.
*/
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
@ReadMode int readMode) {
@@ -1053,12 +1057,14 @@
* The format specified in the AudioRecord constructor should be
* {@link AudioFormat#ENCODING_PCM_16BIT} to correspond to the data in the array.
* @param audioData the array to which the recorded audio data is written.
- * @param offsetInShorts index in audioData from which the data is written expressed in shorts.
+ * @param offsetInShorts index in audioData to which the data is written expressed in shorts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInShorts the number of requested shorts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @return the number of shorts that were read or {@link #ERROR_INVALID_OPERATION}
* if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
* the parameters don't resolve to valid data and indexes.
- * The number of shorts will not exceed sizeInShorts.
+ * The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
*/
public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) {
return read(audioData, offsetInShorts, sizeInShorts, READ_BLOCKING);
@@ -1070,7 +1076,9 @@
* {@link AudioFormat#ENCODING_PCM_16BIT} to correspond to the data in the array.
* @param audioData the array to which the recorded audio data is written.
* @param offsetInShorts index in audioData from which the data is written expressed in shorts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInShorts the number of requested shorts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
* <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
* is read.
@@ -1079,7 +1087,7 @@
* @return the number of shorts that were read or {@link #ERROR_INVALID_OPERATION}
* if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
* the parameters don't resolve to valid data and indexes.
- * The number of shorts will not exceed sizeInShorts.
+ * The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
*/
public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
@ReadMode int readMode) {
@@ -1108,7 +1116,9 @@
* {@link AudioFormat#ENCODING_PCM_FLOAT} to correspond to the data in the array.
* @param audioData the array to which the recorded audio data is written.
* @param offsetInFloats index in audioData from which the data is written.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInFloats the number of requested floats.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
* <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
* is read.
@@ -1117,7 +1127,7 @@
* @return the number of floats that were read or {@link #ERROR_INVALID_OPERATION}
* if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
* the parameters don't resolve to valid data and indexes.
- * The number of floats will not exceed sizeInFloats.
+ * The number of floats will be a multiple of the channel count not to exceed sizeInFloats.
*/
public int read(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
@ReadMode int readMode) {
@@ -1154,6 +1164,7 @@
* The representation of the data in the buffer will depend on the format specified in
* the AudioRecord constructor, and will be native endian.
* @param audioBuffer the direct buffer to which the recorded audio data is written.
+ * Data is written to audioBuffer.position().
* @param sizeInBytes the number of requested bytes. It is recommended but not enforced
* that the number of bytes requested be a multiple of the frame size (sample size in
* bytes multiplied by the channel count).
@@ -1161,7 +1172,7 @@
* if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
* the parameters don't resolve to valid data and indexes.
* The number of bytes will not exceed sizeInBytes.
- * The number of bytes read will truncated to be a multiple of the frame size.
+ * The number of bytes read will be truncated to be a multiple of the frame size.
*/
public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) {
return read(audioBuffer, sizeInBytes, READ_BLOCKING);
@@ -1175,6 +1186,7 @@
* The representation of the data in the buffer will depend on the format specified in
* the AudioRecord constructor, and will be native endian.
* @param audioBuffer the direct buffer to which the recorded audio data is written.
+ * Data is written to audioBuffer.position().
* @param sizeInBytes the number of requested bytes. It is recommended but not enforced
* that the number of bytes requested be a multiple of the frame size (sample size in
* bytes multiplied by the channel count).
@@ -1187,7 +1199,7 @@
* if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
* the parameters don't resolve to valid data and indexes.
* The number of bytes will not exceed sizeInBytes.
- * The number of bytes read will truncated to be a multiple of the frame size.
+ * The number of bytes read will be truncated to be a multiple of the frame size.
*/
public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes, @ReadMode int readMode) {
if (mState != STATE_INITIALIZED) {
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index b26b310..bdf6d9f 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1774,6 +1774,7 @@
* or copies audio data for later playback (static buffer mode).
* The format specified in the AudioTrack constructor should be
* {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
+ * The format can be {@link AudioFormat#ENCODING_PCM_16BIT}, but this is deprecated.
* <p>
* In streaming mode, the write will normally block until all the data has been enqueued for
* playback, and will return a full transfer count. However, if the track is stopped or paused
@@ -1786,7 +1787,9 @@
* @param audioData the array that holds the data to play.
* @param offsetInBytes the offset expressed in bytes in audioData where the data to write
* starts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInBytes the number of bytes to write in audioData after the offset.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @return zero or the positive number of bytes that were written, or
* {@link #ERROR_INVALID_OPERATION}
* if the track isn't properly initialized, or {@link #ERROR_BAD_VALUE} if
@@ -1795,6 +1798,8 @@
* needs to be recreated.
* The dead object error code is not returned if some data was successfully transferred.
* In this case, the error is returned at the next write().
+ * The number of bytes will be a multiple of the frame size in bytes
+ * not to exceed sizeInBytes.
*
* This is equivalent to {@link #write(byte[], int, int, int)} with <code>writeMode</code>
* set to {@link #WRITE_BLOCKING}.
@@ -1808,6 +1813,7 @@
* or copies audio data for later playback (static buffer mode).
* The format specified in the AudioTrack constructor should be
* {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
+ * The format can be {@link AudioFormat#ENCODING_PCM_16BIT}, but this is deprecated.
* <p>
* In streaming mode, the blocking behavior depends on the write mode. If the write mode is
* {@link #WRITE_BLOCKING}, the write will normally block until all the data has been enqueued
@@ -1823,7 +1829,9 @@
* @param audioData the array that holds the data to play.
* @param offsetInBytes the offset expressed in bytes in audioData where the data to write
* starts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInBytes the number of bytes to write in audioData after the offset.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
* effect in static mode.
* <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -1838,6 +1846,8 @@
* needs to be recreated.
* The dead object error code is not returned if some data was successfully transferred.
* In this case, the error is returned at the next write().
+ * The number of bytes will be a multiple of the frame size in bytes
+ * not to exceed sizeInBytes.
*/
public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
@WriteMode int writeMode) {
@@ -1887,7 +1897,9 @@
* @param audioData the array that holds the data to play.
* @param offsetInShorts the offset expressed in shorts in audioData where the data to play
* starts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInShorts the number of shorts to read in audioData after the offset.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @return zero or the positive number of shorts that were written, or
* {@link #ERROR_INVALID_OPERATION}
* if the track isn't properly initialized, or {@link #ERROR_BAD_VALUE} if
@@ -1896,6 +1908,7 @@
* needs to be recreated.
* The dead object error code is not returned if some data was successfully transferred.
* In this case, the error is returned at the next write().
+ * The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
*
* This is equivalent to {@link #write(short[], int, int, int)} with <code>writeMode</code>
* set to {@link #WRITE_BLOCKING}.
@@ -1923,7 +1936,9 @@
* @param audioData the array that holds the data to write.
* @param offsetInShorts the offset expressed in shorts in audioData where the data to write
* starts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInShorts the number of shorts to read in audioData after the offset.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
* effect in static mode.
* <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -1938,6 +1953,7 @@
* needs to be recreated.
* The dead object error code is not returned if some data was successfully transferred.
* In this case, the error is returned at the next write().
+ * The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
*/
public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
@WriteMode int writeMode) {
@@ -1999,7 +2015,9 @@
* to provide samples values within the nominal range.
* @param offsetInFloats the offset, expressed as a number of floats,
* in audioData where the data to write starts.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param sizeInFloats the number of floats to write in audioData after the offset.
+ * Must not be negative, or cause the data access to go out of bounds of the array.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
* effect in static mode.
* <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -2014,6 +2032,7 @@
* needs to be recreated.
* The dead object error code is not returned if some data was successfully transferred.
* In this case, the error is returned at the next write().
+ * The number of floats will be a multiple of the channel count not to exceed sizeInFloats.
*/
public int write(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
@WriteMode int writeMode) {
@@ -2075,7 +2094,9 @@
* <BR>Note that upon return, the buffer position (<code>audioData.position()</code>) will
* have been advanced to reflect the amount of data that was successfully written to
* the AudioTrack.
- * @param sizeInBytes number of bytes to write.
+ * @param sizeInBytes number of bytes to write. It is recommended but not enforced
+ * that the number of bytes requested be a multiple of the frame size (sample size in
+ * bytes multiplied by the channel count).
* <BR>Note this may differ from <code>audioData.remaining()</code>, but cannot exceed it.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
* effect in static mode.
@@ -2142,7 +2163,9 @@
* <BR>Note that upon return, the buffer position (<code>audioData.position()</code>) will
* have been advanced to reflect the amount of data that was successfully written to
* the AudioTrack.
- * @param sizeInBytes number of bytes to write.
+ * @param sizeInBytes number of bytes to write. It is recommended but not enforced
+ * that the number of bytes requested be a multiple of the frame size (sample size in
+ * bytes multiplied by the channel count).
* <BR>Note this may differ from <code>audioData.remaining()</code>, but cannot exceed it.
* @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}.
* <BR>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 62a01dc..82c5a8e 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -27,6 +27,7 @@
import android.provider.BaseColumns;
import android.util.ArraySet;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -1020,22 +1021,28 @@
*
* <p>Use the same language appeared in the underlying broadcast standard, if applicable.
* (For example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or
- * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty.
+ * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty. Use
+ * {@link Genres#encode} to create a text that can be stored in this column. Use
+ * {@link Genres#decode} to get the broadcast genre strings from the text stored in the
+ * column.
*
* <p>Type: TEXT
+ * @see Genres#encode
+ * @see Genres#decode
*/
public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre";
/**
* The comma-separated canonical genre string of this TV program.
*
- * <p>Canonical genres are defined in {@link Genres}. Use
- * {@link Genres#encode Genres.encode()} to create a text that can be stored in this column.
- * Use {@link Genres#decode Genres.decode()} to get the canonical genre strings from the
- * text stored in this column.
+ * <p>Canonical genres are defined in {@link Genres}. Use {@link Genres#encode} to create a
+ * text that can be stored in this column. Use {@link Genres#decode} to get the canonical
+ * genre strings from the text stored in the column.
*
* <p>Type: TEXT
* @see Genres
+ * @see Genres#encode
+ * @see Genres#decode
*/
public static final String COLUMN_CANONICAL_GENRE = "canonical_genre";
@@ -1303,34 +1310,87 @@
CANONICAL_GENRES.add(TECH_SCIENCE);
}
+ private static final char DOUBLE_QUOTE = '"';
+ private static final char COMMA = ',';
+ private static final String DELIMITER = ",";
+
private Genres() {}
/**
- * Encodes canonical genre strings to a text that can be put into the database.
+ * Encodes genre strings to a text that can be put into the database.
*
- * @param genres Canonical genre strings. Use the strings defined in this class.
+ * @param genres Genre strings.
* @return an encoded genre string that can be inserted into the
- * {@link #COLUMN_CANONICAL_GENRE} column.
+ * {@link #COLUMN_BROADCAST_GENRE} or {@link #COLUMN_CANONICAL_GENRE} column.
*/
public static String encode(String... genres) {
StringBuilder sb = new StringBuilder();
String separator = "";
for (String genre : genres) {
- sb.append(separator).append(genre);
- separator = ",";
+ sb.append(separator).append(encodeToCsv(genre));
+ separator = DELIMITER;
+ }
+ return sb.toString();
+ }
+
+ private static String encodeToCsv(String genre) {
+ StringBuilder sb = new StringBuilder();
+ int length = genre.length();
+ for (int i = 0; i < length; ++i) {
+ char c = genre.charAt(i);
+ switch (c) {
+ case DOUBLE_QUOTE:
+ sb.append(DOUBLE_QUOTE);
+ break;
+ case COMMA:
+ sb.append(DOUBLE_QUOTE);
+ break;
+ }
+ sb.append(c);
}
return sb.toString();
}
/**
- * Decodes the canonical genre strings from the text stored in the database.
+ * Decodes the genre strings from the text stored in the database.
*
* @param genres The encoded genre string retrieved from the
- * {@link #COLUMN_CANONICAL_GENRE} column.
- * @return canonical genre strings.
+ * {@link #COLUMN_BROADCAST_GENRE} or {@link #COLUMN_CANONICAL_GENRE} column.
+ * @return genre strings.
*/
public static String[] decode(String genres) {
- return genres.split("\\s*,\\s*");
+ StringBuilder sb = new StringBuilder();
+ List<String> results = new ArrayList<>();
+ int length = genres.length();
+ boolean escape = false;
+ for (int i = 0; i < length; ++i) {
+ char c = genres.charAt(i);
+ switch (c) {
+ case DOUBLE_QUOTE:
+ if (!escape) {
+ escape = true;
+ continue;
+ }
+ break;
+ case COMMA:
+ if (!escape) {
+ String string = sb.toString().trim();
+ if (string.length() > 0) {
+ results.add(string);
+ }
+ sb = new StringBuilder();
+ continue;
+ }
+ break;
+ }
+ sb.append(c);
+ escape = false;
+ }
+ String string = sb.toString().trim();
+ if (string.length() > 0) {
+ results.add(string);
+ }
+ return results.toArray(new String[results.size()]);
}
/**
@@ -1441,7 +1501,10 @@
*
* <p>Use the same language appeared in the underlying broadcast standard, if applicable.
* (For example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or
- * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty.
+ * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty. Use
+ * {@link Genres#encode Genres.encode()} to create a text that can be stored in this column.
+ * Use {@link Genres#decode Genres.decode()} to get the broadcast genre strings from the
+ * text stored in the column.
*
* <p>Type: TEXT
* @see Programs#COLUMN_BROADCAST_GENRE
@@ -1454,7 +1517,7 @@
* <p>Canonical genres are defined in {@link Programs.Genres}. Use
* {@link Programs.Genres#encode Genres.encode()} to create a text that can be stored in
* this column. Use {@link Programs.Genres#decode Genres.decode()} to get the canonical
- * genre strings from the text stored in this column.
+ * genre strings from the text stored in the column.
*
* <p>Type: TEXT
* @see Programs#COLUMN_CANONICAL_GENRE
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 3c21a21..475387b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -216,11 +216,13 @@
return state;
}
- void onStackRestored(boolean restored, boolean external) {}
+ public void setRootsDrawerOpen(boolean open) {
+ mNavigator.revealRootsDrawer(open);
+ }
void onRootPicked(RootInfo root) {
- // Skip refreshing if root didn't change
- if(root.equals(getCurrentRoot())) {
+ // Skip refreshing if root nor directory didn't change
+ if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
return;
}
@@ -539,7 +541,8 @@
// Do some "do what a I want" drawer fiddling, but don't
// do it if user already hit back recently and we recently
// did some fiddling.
- if ((System.currentTimeMillis() - mDrawerLastFiddled) > DRAWER_NO_FIDDLE_DELAY) {
+ if (mDrawer.isPresent()
+ && (System.currentTimeMillis() - mDrawerLastFiddled) > DRAWER_NO_FIDDLE_DELAY) {
// Close drawer if it is open.
if (mDrawer.isOpen()) {
mDrawer.setOpen(false);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 3485fe4..80bdfda 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -32,7 +32,6 @@
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -141,8 +140,7 @@
}
}
- @Override
- void onStackRestored(boolean restored, boolean external) {
+ private void onStackRestored(boolean restored, boolean external) {
// Show drawer when no stack restored, but only when requesting
// non-visual content. However, if we last used an external app,
// drawer is always shown.
@@ -329,10 +327,6 @@
mNavigator.revealRootsDrawer(false);
}
- public void setRootsDrawerOpen(boolean open) {
- mNavigator.revealRootsDrawer(open);
- }
-
@Override
public void onDocumentPicked(DocumentInfo doc, SiblingProvider siblings) {
final FragmentManager fm = getFragmentManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 99b425e..14d4e2d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -91,6 +91,8 @@
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_MOVE_HOME:
case KeyEvent.KEYCODE_MOVE_END:
+ case KeyEvent.KEYCODE_PAGE_UP:
+ case KeyEvent.KEYCODE_PAGE_DOWN:
return true;
default:
return false;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index d141de6..139fb45 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -16,12 +16,16 @@
package com.android.documentsui;
+import static com.android.documentsui.Shared.DEBUG;
+
import android.annotation.IntDef;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import android.util.SparseArray;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
@@ -35,6 +39,8 @@
public class State implements android.os.Parcelable {
+ private static final String TAG = "State";
+
public static final int ACTION_OPEN = 1;
public static final int ACTION_CREATE = 2;
public static final int ACTION_GET_CONTENT = 3;
@@ -85,6 +91,8 @@
/** Current user navigation stack; empty implies recents. */
public DocumentStack stack = new DocumentStack();
private boolean mStackTouched;
+ private boolean mInitialRootChanged;
+ private boolean mInitialDocChanged;
/** Currently active search, overriding any stack. */
public String currentSearch;
@@ -92,8 +100,11 @@
/** Instance state for every shown directory */
public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
+ /** UI selection */
+ public Selection selectedDocuments = new Selection();
+
/** Currently copying file */
- public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<DocumentInfo>();
+ public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<>();
/** Name of the package that started DocsUI */
public List<String> excludedAuthorities = new ArrayList<>();
@@ -108,22 +119,32 @@
}
public void onRootChanged(RootInfo root) {
+ if (DEBUG) Log.d(TAG, "Root changed to: " + root);
+ if (!mInitialRootChanged && stack.root != null && !root.equals(stack.root)) {
+ mInitialRootChanged = true;
+ }
stack.root = root;
stack.clear();
mStackTouched = true;
}
public void pushDocument(DocumentInfo info) {
+ if (DEBUG) Log.d(TAG, "Adding doc to stack: " + info);
+ if (!mInitialDocChanged && stack.size() > 0 && !info.equals(stack.peek())) {
+ mInitialDocChanged = true;
+ }
stack.push(info);
mStackTouched = true;
}
public void popDocument() {
+ if (DEBUG) Log.d(TAG, "Popping doc off stack.");
stack.pop();
mStackTouched = true;
}
public void setStack(DocumentStack stack) {
+ if (DEBUG) Log.d(TAG, "Setting the whole darn stack to: " + stack);
this.stack = stack;
mStackTouched = true;
}
@@ -132,6 +153,10 @@
return mStackTouched;
}
+ public boolean initialiLocationHasChanged() {
+ return mInitialRootChanged || mInitialDocChanged;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -152,10 +177,13 @@
DurableUtils.writeToParcel(out, stack);
out.writeString(currentSearch);
out.writeMap(dirState);
+ out.writeParcelable(selectedDocuments, 0);
out.writeList(selectedDocumentsForCopy);
out.writeList(excludedAuthorities);
out.writeInt(openableOnly ? 1 : 0);
out.writeInt(mStackTouched ? 1 : 0);
+ out.writeInt(mInitialRootChanged ? 1 : 0);
+ out.writeInt(mInitialDocChanged ? 1 : 0);
}
public static final ClassLoaderCreator<State> CREATOR = new ClassLoaderCreator<State>() {
@@ -180,10 +208,13 @@
DurableUtils.readFromParcel(in, state.stack);
state.currentSearch = in.readString();
in.readMap(state.dirState, loader);
+ state.selectedDocuments = in.readParcelable(loader);
in.readList(state.selectedDocumentsForCopy, loader);
in.readList(state.excludedAuthorities, loader);
state.openableOnly = in.readInt() != 0;
state.mStackTouched = in.readInt() != 0;
+ state.mInitialRootChanged = in.readInt() != 0;
+ state.mInitialDocChanged = in.readInt() != 0;
return state;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index d21b157..7fe881e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -27,6 +27,7 @@
import static com.android.internal.util.Preconditions.checkState;
import static com.google.common.base.Preconditions.checkArgument;
+import android.annotation.IntDef;
import android.annotation.StringRes;
import android.app.Activity;
import android.app.ActivityManager;
@@ -53,9 +54,7 @@
import android.support.design.widget.Snackbar;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
-import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.LayoutManager;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
import android.support.v7.widget.RecyclerView.RecyclerListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
@@ -94,14 +93,19 @@
import com.android.documentsui.Shared;
import com.android.documentsui.Snackbars;
import com.android.documentsui.State;
+import com.android.documentsui.State.ViewMode;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService;
+import com.android.documentsui.services.FileOperationService.OpType;
import com.android.documentsui.services.FileOperations;
+
import com.google.common.collect.Lists;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -111,6 +115,13 @@
*/
public class DirectoryFragment extends Fragment implements DocumentsAdapter.Environment {
+ @IntDef(flag = true, value = {
+ TYPE_NORMAL,
+ TYPE_SEARCH,
+ TYPE_RECENT_OPEN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultType {}
public static final int TYPE_NORMAL = 1;
public static final int TYPE_SEARCH = 2;
public static final int TYPE_RECENT_OPEN = 3;
@@ -147,7 +158,7 @@
private RecyclerView mRecView;
private ListeningGestureDetector mGestureDetector;
- private int mType = TYPE_NORMAL;
+ private @ResultType int mType = TYPE_NORMAL;
private String mStateKey;
private int mLastSortOrder = SORT_ORDER_UNKNOWN;
@@ -155,9 +166,7 @@
private LoaderCallbacks<DirectoryResult> mCallbacks;
private FragmentTuner mTuner;
private DocumentClipper mClipper;
- // These are lazily initialized.
- private LinearLayoutManager mListLayout;
- private GridLayoutManager mGridLayout;
+ private GridLayoutManager mLayout;
private int mColumnCount = 1; // This will get updated when layout changes.
private MessageBar mMessageBar;
@@ -182,22 +191,6 @@
}
});
- // TODO: Rather than update columns on layout changes, push this
- // code (or something like it) into GridLayoutManager.
- mRecView.addOnLayoutChangeListener(
- new View.OnLayoutChangeListener() {
-
- @Override
- public void onLayoutChange(
- View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- mColumnCount = calculateColumnCount();
- if (mGridLayout != null) {
- mGridLayout.setSpanCount(mColumnCount);
- }
- }
- });
-
mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
// TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
@@ -218,9 +211,6 @@
final View view = mRecView.getChildAt(i);
cancelThumbnailTask(view);
}
-
- // Clear any outstanding selection
- mSelectionManager.clearSelection();
}
@Override
@@ -232,6 +222,7 @@
final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
+ mStateKey = buildStateKey(root, doc);
mIconHelper = new IconHelper(context, MODE_GRID);
@@ -240,10 +231,24 @@
mRecView.setAdapter(mAdapter);
+ mLayout = new GridLayoutManager(getContext(), mColumnCount);
+ SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
+ if (lookup != null) {
+ mLayout.setSpanSizeLookup(lookup);
+ }
+ mRecView.setLayoutManager(mLayout);
+
mGestureDetector = new ListeningGestureDetector(this.getContext(), new GestureListener());
mRecView.addOnItemTouchListener(mGestureDetector);
+ // final here because we'll manually bump the listener iwhen we had an initial selection,
+ // but only after the model is fully loaded.
+ final SelectionModeListener selectionListener = new SelectionModeListener();
+ final Selection initialSelection = state.selectedDocuments.hasDirectoryKey(mStateKey)
+ ? state.selectedDocuments
+ : null;
+
// TODO: instead of inserting the view into the constructor, extract listener-creation code
// and set the listener on the view after the fact. Then the view doesn't need to be passed
// into the selection manager.
@@ -252,17 +257,18 @@
mAdapter,
state.allowMultiple
? MultiSelectManager.MODE_MULTIPLE
- : MultiSelectManager.MODE_SINGLE);
- mSelectionManager.addCallback(new SelectionModeListener());
+ : MultiSelectManager.MODE_SINGLE,
+ initialSelection);
+
+ mSelectionManager.addCallback(selectionListener);
mModel = new Model();
mModel.addUpdateListener(mAdapter);
mModel.addUpdateListener(mModelUpdateListener);
mType = getArguments().getInt(EXTRA_TYPE);
- mStateKey = buildStateKey(root, doc);
- mTuner = FragmentTuner.pick(state);
+ mTuner = FragmentTuner.pick(getContext(), state);
mClipper = new DocumentClipper(context);
boolean hideGridTitles;
@@ -320,10 +326,8 @@
updateDisplayState();
- // When launched into empty recents, show drawer
- if (mType == TYPE_RECENT_OPEN && mModel.isEmpty() && !state.hasLocationChanged() &&
- context instanceof DocumentsActivity) {
- ((DocumentsActivity) context).setRootsDrawerOpen(true);
+ if (initialSelection != null) {
+ selectionListener.onSelectionChanged();
}
// Restore any previous instance state
@@ -338,6 +342,8 @@
}
mLastSortOrder = state.derivedSortOrder;
+
+ mTuner.onModelLoaded(mModel, mType);
}
@Override
@@ -351,6 +357,18 @@
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ State state = getDisplayState();
+ if (mSelectionManager.hasSelection()) {
+ mSelectionManager.getSelection(state.selectedDocuments);
+ state.selectedDocuments.setDirectoryKey(mStateKey);
+ if (!state.selectedDocuments.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "Persisted selection: " + state.selectedDocuments);
+ }
+ }
+ }
+
+ @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// There's only one request code right now. Replace this with a switch statement or
// something more scalable when more codes are added.
@@ -432,41 +450,28 @@
}
/**
- * Returns a {@code LayoutManager} for {@code mode}, lazily initializing
- * classes as needed.
+ * Updates the layout after the view mode switches.
+ * @param mode The new view mode.
*/
- private void updateLayout(int mode) {
- final LayoutManager layout;
- switch (mode) {
- case MODE_GRID:
- if (mGridLayout == null) {
- mGridLayout = new GridLayoutManager(getContext(), mColumnCount);
- SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
- if (lookup != null) {
- mGridLayout.setSpanSizeLookup(lookup);
- }
- }
- layout = mGridLayout;
- break;
- case MODE_LIST:
- if (mListLayout == null) {
- mListLayout = new LinearLayoutManager(getContext());
- }
- layout = mListLayout;
- break;
- default:
- throw new IllegalArgumentException("Unsupported layout mode: " + mode);
+ private void updateLayout(@ViewMode int mode) {
+ mColumnCount = calculateColumnCount(mode);
+ if (mLayout != null) {
+ mLayout.setSpanCount(mColumnCount);
}
int pad = getDirectoryPadding(mode);
mRecView.setPadding(pad, pad, pad, pad);
- // setting layout manager automatically invalidates existing ViewHolders.
- mRecView.setLayoutManager(layout);
+ mRecView.requestLayout();
mSelectionManager.handleLayoutChanged(); // RecyclerView doesn't do this for us
mIconHelper.setViewMode(mode);
}
- private int calculateColumnCount() {
+ private int calculateColumnCount(@ViewMode int mode) {
+ if (mode == MODE_LIST) {
+ // List mode is a "grid" with 1 column.
+ return 1;
+ }
+
int cellWidth = getResources().getDimensionPixelSize(R.dimen.grid_width);
int cellMargin = 2 * getResources().getDimensionPixelSize(R.dimen.grid_item_margin);
int viewPadding = mRecView.getPaddingLeft() + mRecView.getPaddingRight();
@@ -478,14 +483,12 @@
return columnCount;
}
- private int getDirectoryPadding(int mode) {
+ private int getDirectoryPadding(@ViewMode int mode) {
switch (mode) {
case MODE_GRID:
- return getResources().getDimensionPixelSize(
- R.dimen.grid_container_padding);
+ return getResources().getDimensionPixelSize(R.dimen.grid_container_padding);
case MODE_LIST:
- return getResources().getDimensionPixelSize(
- R.dimen.list_container_padding);
+ return getResources().getDimensionPixelSize(R.dimen.list_container_padding);
default:
throw new IllegalArgumentException("Unsupported layout mode: " + mode);
}
@@ -784,7 +787,7 @@
.show();
}
- private void transferDocuments(final Selection selected, final int mode) {
+ private void transferDocuments(final Selection selected, final @OpType int mode) {
// Pop up a dialog to pick a destination. This is inadequate but works for now.
// TODO: Implement a picker that is to spec.
final Intent intent = new Intent(
@@ -1280,12 +1283,14 @@
* @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION.
*/
private int findTargetPosition(View view, int keyCode) {
- if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
- return 0;
- }
-
- if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
- return mAdapter.getItemCount() - 1;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ return 0;
+ case KeyEvent.KEYCODE_MOVE_END:
+ return mAdapter.getItemCount() - 1;
+ case KeyEvent.KEYCODE_PAGE_UP:
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ return findTargetPositionByPage(view, keyCode);
}
// Find a navigation target based on the arrow key that the user pressed.
@@ -1321,6 +1326,50 @@
}
/**
+ * Given a PgUp/PgDn event and the current view, find the position of the target view.
+ * This returns:
+ * <li>The position of the topmost (or bottom-most) visible item, if the current item is not
+ * the top- or bottom-most visible item.
+ * <li>The position of an item that is one page's worth of items up (or down) if the current
+ * item is the top- or bottom-most visible item.
+ * <li>The first (or last) item, if paging up (or down) would go past those limits.
+ * @param view The view that received the key event.
+ * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN.
+ * @return The adapter position of the target item.
+ */
+ private int findTargetPositionByPage(View view, int keyCode) {
+ int first = mLayout.findFirstVisibleItemPosition();
+ int last = mLayout.findLastVisibleItemPosition();
+ int current = mRecView.getChildAdapterPosition(view);
+ int pageSize = last - first + 1;
+
+ if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
+ if (current > first) {
+ // If the current item isn't the first item, target the first item.
+ return first;
+ } else {
+ // If the current item is the first item, target the item one page up.
+ int target = current - pageSize;
+ return target < 0 ? 0 : target;
+ }
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
+ if (current < last) {
+ // If the current item isn't the last item, target the last item.
+ return last;
+ } else {
+ // If the current item is the last item, target the item one page down.
+ int target = current + pageSize;
+ int max = mAdapter.getItemCount() - 1;
+ return target < max ? target : max;
+ }
+ }
+
+ throw new IllegalArgumentException("Unsupported keyCode: " + keyCode);
+ }
+
+ /**
* Requests focus for the item in the given adapter position, scrolling the RecyclerView if
* necessary.
*
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index 43c2f63..0930c22 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -27,7 +27,6 @@
import com.android.documentsui.State;
-import java.nio.channels.UnsupportedAddressTypeException;
import java.util.List;
/**
@@ -87,7 +86,7 @@
* we adjust sizes.
*/
GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
- throw new UnsupportedAddressTypeException();
+ throw new UnsupportedOperationException();
}
static boolean isDirectory(Cursor cursor) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index a295ab2..3f51e538 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -16,6 +16,7 @@
package com.android.documentsui.dirlist;
+import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.State.ACTION_BROWSE;
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_GET_CONTENT;
@@ -24,15 +25,20 @@
import static com.android.documentsui.State.ACTION_OPEN_TREE;
import static com.android.internal.util.Preconditions.checkArgument;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.documentsui.DocumentsActivity;
+import com.android.documentsui.FilesActivity;
import com.android.documentsui.Menus;
import com.android.documentsui.MimePredicate;
import com.android.documentsui.R;
import com.android.documentsui.State;
-
-import android.os.SystemProperties;
-import android.provider.DocumentsContract.Document;
-import android.view.Menu;
-import android.view.MenuItem;
+import com.android.documentsui.dirlist.DirectoryFragment.ResultType;
/**
* Providers support for specializing the DirectoryFragment to the "host" Activity.
@@ -40,20 +46,22 @@
*/
public abstract class FragmentTuner {
+ final Context mContext;
final State mState;
- public FragmentTuner(State state) {
+ public FragmentTuner(Context context, State state) {
+ mContext = context;
mState = state;
}
- public static FragmentTuner pick(State state) {
+ public static FragmentTuner pick(Context context, State state) {
switch (state.action) {
case ACTION_BROWSE:
- return new FilesTuner(state);
+ return new FilesTuner(context, state);
case ACTION_MANAGE:
- return new DownloadsTuner(state);
+ return new DownloadsTuner(context, state);
default:
- return new DocumentsTuner(state);
+ return new DocumentsTuner(context, state);
}
}
@@ -76,13 +84,15 @@
return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
}
+ abstract void onModelLoaded(Model model, @ResultType int resultType);
+
/**
* Provides support for Platform specific specializations of DirectoryFragment.
*/
private static final class DocumentsTuner extends FragmentTuner {
- public DocumentsTuner(State state) {
- super(state);
+ public DocumentsTuner(Context context, State state) {
+ super(context, state);
}
@Override
@@ -154,6 +164,16 @@
moveTo.setEnabled(moveEnabled);
rename.setVisible(false);
}
+
+ @Override
+ void onModelLoaded(Model model, @ResultType int resultType) {
+ // When launched into empty recents, show drawer
+ if (resultType == DirectoryFragment.TYPE_RECENT_OPEN
+ && model.isEmpty()
+ && !mState.hasLocationChanged()) {
+ ((DocumentsActivity) mContext).setRootsDrawerOpen(true);
+ }
+ }
}
/**
@@ -161,8 +181,8 @@
*/
private static final class DownloadsTuner extends FragmentTuner {
- public DownloadsTuner(State state) {
- super(state);
+ public DownloadsTuner(Context context, State state) {
+ super(context, state);
}
@Override
@@ -189,6 +209,9 @@
moveTo.setEnabled(moveEnabled);
rename.setVisible(false);
}
+
+ @Override
+ void onModelLoaded(Model model, @ResultType int resultType) {}
}
/**
@@ -196,8 +219,10 @@
*/
private static final class FilesTuner extends FragmentTuner {
- public FilesTuner(State state) {
- super(state);
+ private static final String TAG = "FilesTuner";
+
+ public FilesTuner(Context context, State state) {
+ super(context, state);
}
@Override
@@ -221,9 +246,23 @@
Menus.disableHiddenItems(menu, copy, paste);
}
+
+ @Override
+ void onModelLoaded(Model model, @ResultType int resultType) {
+ if (DEBUG) Log.d(TAG, "Handling model loaded. Has Location shcnage: " + mState.initialiLocationHasChanged());
+ // When launched into empty root, open drawer.
+ if (model.isEmpty() && !mState.initialiLocationHasChanged()) {
+ if (DEBUG) Log.d(TAG, "Showing roots drawer cuz stuffs empty.");
+
+ // This noops on layouts without drawer, so no need to guard.
+ ((FilesActivity) mContext).setRootsDrawerOpen(true);
+ }
+ if (DEBUG) Log.d(TAG, "Donezo.");
+ }
}
private static boolean isDirectory(String mimeType) {
return Document.MIME_TYPE_DIR.equals(mimeType);
}
+
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 516b25e..d60825b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -23,9 +23,12 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
+import android.annotation.IntDef;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.GridLayoutManager;
@@ -41,12 +44,13 @@
import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -58,10 +62,13 @@
*/
public final class MultiSelectManager {
- /** Selection mode for multiple select. **/
+ @IntDef(flag = true, value = {
+ MODE_MULTIPLE,
+ MODE_SINGLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SelectionMode {}
public static final int MODE_MULTIPLE = 0;
-
- /** Selection mode for multiple select. **/
public static final int MODE_SINGLE = 1;
private static final String TAG = "MultiSelectManager";
@@ -79,14 +86,19 @@
/**
- * @param recyclerView
- * @param mode Selection mode
+ * @param mode Selection single or multiple selection mode.
+ * @param initialSelection selection state probably preserved in external state.
*/
public MultiSelectManager(
- final RecyclerView recyclerView, DocumentsAdapter adapter, int mode) {
- this(new RuntimeSelectionEnvironment(recyclerView), adapter, mode);
+ final RecyclerView recyclerView,
+ DocumentsAdapter adapter,
+ @SelectionMode int mode,
+ @Nullable Selection initialSelection) {
+
+ this(new RuntimeSelectionEnvironment(recyclerView), adapter, mode, initialSelection);
if (mode == MODE_MULTIPLE) {
+ // TODO: Don't load this on low memory devices.
mBandManager = new BandController();
}
@@ -116,10 +128,18 @@
* @hide
*/
@VisibleForTesting
- MultiSelectManager(SelectionEnvironment environment, DocumentsAdapter adapter, int mode) {
+ MultiSelectManager(
+ SelectionEnvironment environment,
+ DocumentsAdapter adapter,
+ @SelectionMode int mode,
+ @Nullable Selection initialSelection) {
+
mEnvironment = checkNotNull(environment, "'environment' cannot be null.");
mAdapter = checkNotNull(adapter, "'adapter' cannot be null.");
mSingleSelect = mode == MODE_SINGLE;
+ if (initialSelection != null) {
+ mSelection.copyFrom(initialSelection);
+ }
mAdapter.registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
@@ -203,6 +223,13 @@
}
/**
+ * Updates selection to include items in {@code selection}.
+ */
+ public void updateSelection(Selection selection) {
+ setItemsSelected(selection.toList(), true);
+ }
+
+ /**
* Sets the selected state of the specified items. Note that the callback will NOT
* be consulted to see if an item can be selected.
*
@@ -615,7 +642,7 @@
* Object representing the current selection. Provides read only access
* public access, and private write access.
*/
- public static final class Selection {
+ public static final class Selection implements Parcelable {
// This class tracks selected items by managing two sets: the saved selection, and the total
// selection. Saved selections are those which have been completed by tapping an item or by
@@ -628,8 +655,9 @@
// item A is tapped (and selected), then an in-progress band select covers A then uncovers
// A, A should still be selected as it has been saved. To ensure this behavior, the saved
// selection must be tracked separately.
- private Set<String> mSavedSelection = new HashSet<>();
- private Set<String> mTotalSelection = new HashSet<>();
+ private Set<String> mSelection = new HashSet<>();
+ private Set<String> mProvisionalSelection = new HashSet<>();
+ private String mDirectoryKey;
@VisibleForTesting
public Selection(String... ids) {
@@ -643,53 +671,70 @@
* @return true if the position is currently selected.
*/
public boolean contains(@Nullable String id) {
- return mTotalSelection.contains(id);
+ return mSelection.contains(id) || mProvisionalSelection.contains(id);
}
/**
* Returns an unordered array of selected positions.
*/
public String[] getAll() {
- return mTotalSelection.toArray(new String[0]);
+ return toList().toArray(new String[0]);
+ }
+
+ /**
+ * Returns an unordered array of selected positions (including any
+ * provisional selections current in effect).
+ */
+ private List<String> toList() {
+ ArrayList<String> selection = new ArrayList<String>(mSelection);
+ selection.addAll(mProvisionalSelection);
+ return selection;
}
/**
* @return size of the selection.
*/
public int size() {
- return mTotalSelection.size();
+ return mSelection.size() + mProvisionalSelection.size();
}
/**
* @return true if the selection is empty.
*/
public boolean isEmpty() {
- return mTotalSelection.isEmpty();
+ return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
}
/**
* Sets the provisional selection, which is a temporary selection that can be saved,
* canceled, or adjusted at a later time. When a new provision selection is applied, the old
* one (if it exists) is abandoned.
- * @return Array with entry for each position added or removed. Entries which were added
- * contain a value of true, and entries which were removed contain a value of false.
+ * @return Map of ids added or removed. Added ids have a value of true, removed are false.
*/
@VisibleForTesting
- protected Map<String, Boolean> setProvisionalSelection(Set<String> provisionalSelection) {
+ protected Map<String, Boolean> setProvisionalSelection(Set<String> newSelection) {
Map<String, Boolean> delta = new HashMap<>();
- for (String id: mTotalSelection) {
+ for (String id: mProvisionalSelection) {
// Mark each item that used to be in the selection but is unsaved and not in the new
// provisional selection.
- if (!provisionalSelection.contains(id) && !mSavedSelection.contains(id)) {
+ if (!newSelection.contains(id) && !mSelection.contains(id)) {
delta.put(id, false);
}
}
- for (String id: provisionalSelection) {
+ for (String id: mSelection) {
+ // Mark each item that used to be in the selection but is unsaved and not in the new
+ // provisional selection.
+ if (!newSelection.contains(id)) {
+ delta.put(id, false);
+ }
+ }
+
+ for (String id: newSelection) {
// Mark each item that was not previously in the selection but is in the new
// provisional selection.
- if (!mTotalSelection.contains(id)) {
+ if (!mSelection.contains(id) && !mProvisionalSelection.contains(id)) {
delta.put(id, true);
}
}
@@ -700,9 +745,9 @@
for (Map.Entry<String, Boolean> entry: delta.entrySet()) {
String id = entry.getKey();
if (entry.getValue()) {
- mTotalSelection.add(id);
+ mProvisionalSelection.add(id);
} else {
- mTotalSelection.remove(id);
+ mProvisionalSelection.remove(id);
}
}
@@ -716,7 +761,8 @@
*/
@VisibleForTesting
protected void applyProvisionalSelection() {
- mSavedSelection = new HashSet<>(mTotalSelection);
+ mSelection.addAll(mProvisionalSelection);
+ mProvisionalSelection.clear();
}
/**
@@ -725,15 +771,14 @@
*/
@VisibleForTesting
void cancelProvisionalSelection() {
- mTotalSelection = new HashSet<>(mSavedSelection);
+ mProvisionalSelection.clear();
}
/** @hide */
@VisibleForTesting
boolean add(String id) {
- if (!mTotalSelection.contains(id)) {
- mTotalSelection.add(id);
- mSavedSelection.add(id);
+ if (!mSelection.contains(id)) {
+ mSelection.add(id);
return true;
}
return false;
@@ -742,31 +787,29 @@
/** @hide */
@VisibleForTesting
boolean remove(String id) {
- if (mTotalSelection.contains(id)) {
- mTotalSelection.remove(id);
- mSavedSelection.remove(id);
+ if (mSelection.contains(id)) {
+ mSelection.remove(id);
return true;
}
return false;
}
public void clear() {
- mSavedSelection.clear();
- mTotalSelection.clear();
+ mSelection.clear();
}
/**
* Trims this selection to be the intersection of itself with the set of given IDs.
*/
public void intersect(Collection<String> ids) {
- mSavedSelection.retainAll(ids);
- mTotalSelection.retainAll(ids);
+ mSelection.retainAll(ids);
+ mProvisionalSelection.retainAll(ids);
}
@VisibleForTesting
void copyFrom(Selection source) {
- mSavedSelection = new HashSet<>(source.mSavedSelection);
- mTotalSelection = new HashSet<>(source.mTotalSelection);
+ mSelection = new HashSet<>(source.mSelection);
+ mProvisionalSelection = new HashSet<>(source.mProvisionalSelection);
}
@Override
@@ -775,24 +818,19 @@
return "size=0, items=[]";
}
- StringBuilder buffer = new StringBuilder(mTotalSelection.size() * 28);
- buffer.append("{size=")
- .append(mTotalSelection.size())
- .append(", ")
- .append("items=[");
- for (Iterator<String> i = mTotalSelection.iterator(); i.hasNext(); ) {
- buffer.append(i.next());
- if (i.hasNext()) {
- buffer.append(", ");
- }
- }
- buffer.append("]}");
+ StringBuilder buffer = new StringBuilder(size() * 28);
+ buffer.append("Selection{")
+ .append("applied{size=" + mSelection.size())
+ .append(", entries=" + mSelection)
+ .append("}, provisional{size=" + mProvisionalSelection.size())
+ .append(", entries=" + mProvisionalSelection)
+ .append("}}");
return buffer.toString();
}
@Override
public int hashCode() {
- return mSavedSelection.hashCode() ^ mTotalSelection.hashCode();
+ return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
}
@Override
@@ -805,8 +843,38 @@
return false;
}
- return mSavedSelection.equals(((Selection) that).mSavedSelection) &&
- mTotalSelection.equals(((Selection) that).mTotalSelection);
+ return mSelection.equals(((Selection) that).mSelection) &&
+ mProvisionalSelection.equals(((Selection) that).mProvisionalSelection);
+ }
+
+ /**
+ * Sets the state key for this selection, which allows us to match selections
+ * to particular states (of DirectoryFragment). Basically this lets us avoid
+ * loading a persisted selection in the wrong directory.
+ */
+ public void setDirectoryKey(String key) {
+ mDirectoryKey = key;
+ }
+
+ /**
+ * Sets the state key for this selection, which allows us to match selections
+ * to particular states (of DirectoryFragment). Basically this lets us avoid
+ * loading a persisted selection in the wrong directory.
+ */
+ public boolean hasDirectoryKey(String key) {
+ return key.equals(mDirectoryKey);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mDirectoryKey);
+ dest.writeList(new ArrayList<>(mSelection));
+ // We don't include provisional selection since it is
+ // typically coupled to some other runtime state (like a band).
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
index 737b376..c51f927 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -16,35 +16,18 @@
package com.android.documentsui;
-import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
import static com.android.documentsui.StubProvider.ROOT_0_ID;
-import android.app.Instrumentation;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
-import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.documentsui.model.RootInfo;
@LargeTest
public class DownloadsActivityUiTest extends ActivityTest<DownloadsActivity> {
- private static final int TIMEOUT = 5000;
- private static final String TAG = "DownloadsActivityUiTest";
- private static final String TARGET_PKG = "com.android.documentsui";
- private static final String LAUNCHER_PKG = "com.android.launcher";
-
public DownloadsActivityUiTest() {
super(DownloadsActivity.class);
}
@@ -83,7 +66,7 @@
mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
- device.waitForIdle();
+ bot.waitForDocument("Ham & Cheese.sandwich");
bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
}
@@ -108,4 +91,11 @@
device.waitForIdle();
assertNotNull(bot.menuShare());
}
+
+ public void testClosesOnBack() throws Exception {
+ DownloadsActivity activity = getActivity();
+ device.pressBack();
+ device.wait(Until.gone(By.text(ROOT_0_ID)), TIMEOUT); // wait for the window to go away
+ assertTrue(activity.isDestroyed());
+ }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 7fd2416..77f16d9 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -16,34 +16,15 @@
package com.android.documentsui;
-import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
import static com.android.documentsui.StubProvider.ROOT_0_ID;
import static com.android.documentsui.StubProvider.ROOT_1_ID;
-import android.app.Instrumentation;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
import android.os.RemoteException;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.documentsui.model.RootInfo;
@LargeTest
public class FilesActivityUiTest extends ActivityTest<FilesActivity> {
- private static final int TIMEOUT = 5000;
- private static final String TAG = "FilesActivityUiTest";
- private static final String TARGET_PKG = "com.android.documentsui";
- private static final String LAUNCHER_PKG = "com.android.launcher";
-
public FilesActivityUiTest() {
super(FilesActivity.class);
}
@@ -102,7 +83,7 @@
bot.openRoot(ROOT_0_ID);
mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
- device.waitForIdle();
+ bot.waitForDocument("Ham & Cheese.sandwich");
bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
index 303f2d1..1069a66 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
@@ -18,9 +18,6 @@
import static com.android.documentsui.StubProvider.ROOT_0_ID;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@LargeTest
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java
new file mode 100644
index 0000000..1d1d3b5
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+@LargeTest
+public class RootUiTest extends ActivityTest<FilesActivity> {
+
+ private static final String TAG = "RootUiTest";
+
+ public RootUiTest() {
+ super(FilesActivity.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ initTestFiles();
+ bot.openRoot(ROOT_0_ID);
+ }
+
+ public void testRootTapped_GoToRootFromChildDir() throws Exception {
+ bot.openDocument(dirName1);
+ bot.assertWindowTitle(dirName1);
+ bot.openRoot(ROOT_0_ID);
+ bot.assertWindowTitle(ROOT_0_ID);
+ assertDefaultContentOfTestDir0();
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
index 61da3df..b8d8795 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -19,8 +19,6 @@
import static com.android.documentsui.StubProvider.ROOT_0_ID;
import static com.android.documentsui.StubProvider.ROOT_1_ID;
-import android.support.test.uiautomator.UiObject;
-import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@LargeTest
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 417fd24..4534c40 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -21,9 +21,11 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.assertFalse;
+import android.app.Activity;
import android.content.Context;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObject2;
@@ -32,6 +34,7 @@
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;
import junit.framework.Assert;
@@ -192,6 +195,14 @@
assertNotNull(getSnackbar(mContext.getString(id)));
}
+ void openDocument(String label) throws UiObjectNotFoundException {
+ int toolType = Configurator.getInstance().getToolType();
+ Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
+ UiObject doc = findDocument(label);
+ doc.click();
+ Configurator.getInstance().setToolType(toolType);
+ }
+
void clickDocument(String label) throws UiObjectNotFoundException {
findDocument(label).click();
}
@@ -247,6 +258,10 @@
mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
}
+ void waitForDocument(String label) throws UiObjectNotFoundException {
+ findDocument(label).waitForExists(mTimeout);
+ }
+
void switchViewMode() {
UiObject2 mode = menuGridMode();
if (mode != null) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index b1cb29e..d95fb49 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -50,7 +50,7 @@
mCallback = new TestCallback();
mEnv = new TestSelectionEnvironment(items);
mAdapter = new TestDocumentsAdapter(items);
- mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_MULTIPLE);
+ mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_MULTIPLE, null);
mManager.addCallback(mCallback);
}
@@ -174,7 +174,7 @@
}
public void testSingleSelectMode() {
- mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
+ mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
mManager.addCallback(mCallback);
longPress(20);
tap(13);
@@ -182,7 +182,7 @@
}
public void testSingleSelectMode_ShiftTap() {
- mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
+ mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
mManager.addCallback(mCallback);
longPress(13);
shiftTap(20);
@@ -198,24 +198,73 @@
provisional.append(2, true);
s.setProvisionalSelection(getItemIds(provisional));
assertSelection(items.get(1), items.get(2));
+ }
- provisional.delete(1);
- provisional.append(3, true);
+ public void testProvisionalSelection_Replace() {
+ Selection s = mManager.getSelection();
+
+ SparseBooleanArray provisional = new SparseBooleanArray();
+ provisional.append(1, true);
+ provisional.append(2, true);
s.setProvisionalSelection(getItemIds(provisional));
- assertSelection(items.get(2), items.get(3));
-
- s.applyProvisionalSelection();
- assertSelection(items.get(2), items.get(3));
provisional.clear();
provisional.append(3, true);
provisional.append(4, true);
s.setProvisionalSelection(getItemIds(provisional));
- assertSelection(items.get(2), items.get(3), items.get(4));
+ assertSelection(items.get(3), items.get(4));
+ }
- provisional.delete(3);
+ public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
+ Selection s = mManager.getSelection();
+
+ SparseBooleanArray provisional = new SparseBooleanArray();
+ provisional.append(1, true);
+ provisional.append(2, true);
s.setProvisionalSelection(getItemIds(provisional));
- assertSelection(items.get(2), items.get(3), items.get(4));
+
+ provisional.clear();
+ provisional.append(1, true);
+ s.setProvisionalSelection(getItemIds(provisional));
+ assertSelection(items.get(1));
+ }
+
+ public void testProvisionalSelection_Apply() {
+ Selection s = mManager.getSelection();
+
+ SparseBooleanArray provisional = new SparseBooleanArray();
+ provisional.append(1, true);
+ provisional.append(2, true);
+ s.setProvisionalSelection(getItemIds(provisional));
+ s.applyProvisionalSelection();
+ assertSelection(items.get(1), items.get(2));
+ }
+
+ public void testProvisionalSelection_Cancel() {
+ mManager.toggleSelection(items.get(1));
+ mManager.toggleSelection(items.get(2));
+ Selection s = mManager.getSelection();
+
+ SparseBooleanArray provisional = new SparseBooleanArray();
+ provisional.append(3, true);
+ provisional.append(4, true);
+ s.setProvisionalSelection(getItemIds(provisional));
+ s.cancelProvisionalSelection();
+
+ // Original selection should remain.
+ assertSelection(items.get(1), items.get(2));
+ }
+
+ public void testProvisionalSelection_IntersectsAppliedSelection() {
+ mManager.toggleSelection(items.get(1));
+ mManager.toggleSelection(items.get(2));
+ Selection s = mManager.getSelection();
+
+ SparseBooleanArray provisional = new SparseBooleanArray();
+ provisional.append(2, true);
+ provisional.append(3, true);
+ s.setProvisionalSelection(getItemIds(provisional));
+ assertSelection(items.get(1), items.get(2), items.get(3));
}
private static Set<String> getItemIds(SparseBooleanArray selection) {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index ef1e8e2..90b5c09 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -18,7 +18,6 @@
import android.content.ContentResolver;
import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -262,31 +261,39 @@
if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
return;
}
- if (mNumLoaded == 0) {
- mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
- }
- try {
+ try{
+ if (mNumLoaded == 0) {
+ mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
+ }
mDatabase.getMapper().putChildDocuments(
mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
mNumLoaded += objectInfoList.length;
- } catch (SQLiteException exp) {
- mError = exp;
- mNumLoaded = 0;
- }
- if (getState() != STATE_LOADING) {
- mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+ if (getState() != STATE_LOADING) {
+ mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+ }
+ } catch (FileNotFoundException exception) {
+ setErrorInternal(exception);
}
}
- void setError(Exception message) {
+ void setError(Exception error) {
final int lastState = getState();
- mError = message;
- mNumLoaded = 0;
+ setErrorInternal(error);
if (lastState == STATE_LOADING) {
- mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+ try {
+ mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+ } catch (FileNotFoundException exception) {
+ setErrorInternal(exception);
+ }
}
}
+ private void setErrorInternal(Exception error) {
+ Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error);
+ mError = error;
+ mNumLoaded = 0;
+ }
+
private Uri createUri() {
return DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 3faa8f4..cf1e1f6 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -27,9 +27,9 @@
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
@@ -56,11 +56,12 @@
/**
* Puts device information to database.
+ *
* @return If device is added to the database.
+ * @throws FileNotFoundException
*/
- synchronized boolean putDeviceDocument(MtpDeviceRecord device) {
+ synchronized boolean putDeviceDocument(MtpDeviceRecord device) throws FileNotFoundException {
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
- Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null));
database.beginTransaction();
try {
final ContentValues[] valuesList = new ContentValues[1];
@@ -69,11 +70,11 @@
extraValuesList[0] = new ContentValues();
MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device);
final boolean changed = putDocuments(
+ null,
valuesList,
extraValuesList,
COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
EMPTY_ARGS,
- /* heuristic */ false,
COLUMN_DEVICE_ID);
database.setTransactionSuccessful();
return changed;
@@ -84,24 +85,24 @@
/**
* Puts root information to database.
+ *
* @param parentDocumentId Document ID of device document.
* @param roots List of root information.
* @return If roots are added or removed from the database.
+ * @throws FileNotFoundException
*/
- synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) {
+ synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots)
+ throws FileNotFoundException {
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
database.beginTransaction();
try {
- final boolean heuristic;
final String mapColumn;
Preconditions.checkState(mMappingMode.containsKey(parentDocumentId));
switch (mMappingMode.get(parentDocumentId)) {
case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
mapColumn = COLUMN_STORAGE_ID;
break;
case MAP_BY_NAME:
- heuristic = true;
mapColumn = Document.COLUMN_DISPLAY_NAME;
break;
default:
@@ -116,11 +117,11 @@
valuesList[i], extraValuesList[i], parentDocumentId, roots[i]);
}
final boolean changed = putDocuments(
+ parentDocumentId,
valuesList,
extraValuesList,
COLUMN_PARENT_DOCUMENT_ID + "=?",
strings(parentDocumentId),
- heuristic,
mapColumn);
database.setTransactionSuccessful();
@@ -132,21 +133,21 @@
/**
* Puts document information to database.
+ *
* @param deviceId Device ID
* @param parentId Parent document ID.
* @param documents List of document information.
+ * @throws FileNotFoundException
*/
- synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
- final boolean heuristic;
+ synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents)
+ throws FileNotFoundException {
final String mapColumn;
Preconditions.checkState(mMappingMode.containsKey(parentId));
switch (mMappingMode.get(parentId)) {
case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
mapColumn = COLUMN_OBJECT_HANDLE;
break;
case MAP_BY_NAME:
- heuristic = true;
mapColumn = Document.COLUMN_DISPLAY_NAME;
break;
default:
@@ -159,21 +160,18 @@
valuesList[i], deviceId, parentId, documents[i]);
}
putDocuments(
+ parentId,
valuesList,
null,
COLUMN_PARENT_DOCUMENT_ID + "=?",
strings(parentId),
- heuristic,
mapColumn);
}
- @VisibleForTesting
void clearMapping() {
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
database.beginTransaction();
try {
- mDatabase.deleteDocumentsAndRootsRecursively(
- COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
final ContentValues values = new ContentValues();
values.putNull(COLUMN_OBJECT_HANDLE);
values.putNull(COLUMN_STORAGE_ID);
@@ -193,9 +191,9 @@
* a corresponding existing row. Otherwise it does heuristic.
*
* @param parentDocumentId Parent document ID or NULL for root documents.
+ * @throws FileNotFoundException
*/
- void startAddingDocuments(@Nullable String parentDocumentId) {
- Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
+ void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException {
final String selection;
final String[] args;
if (parentDocumentId != null) {
@@ -209,10 +207,8 @@
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
database.beginTransaction();
try {
- // Delete all pending rows.
- mDatabase.deleteDocumentsAndRootsRecursively(
- selection + " AND " + COLUMN_ROW_STATE + "=?",
- DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_PENDING)));
+ getParentOrHaltMapping(parentDocumentId);
+ Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
// Set all documents as invalidated.
final ContentValues values = new ContentValues();
@@ -241,24 +237,27 @@
* {@link #stopAddingDocuments(String)} turns the pending rows into 'valid'
* rows. If the methods adds rows to database, it updates valueList with correct document ID.
*
+ * @param parentId Parent document ID.
* @param valuesList Values for documents to be stored in the database.
* @param rootExtraValuesList Values for root extra to be stored in the database.
* @param selection SQL where closure to select rows that shares the same parent.
* @param args Argument for selection SQL.
- * @param heuristic Whether the mapping mode is heuristic.
- * @return Whether the method adds new rows.
+ * @return Whether it adds at least one new row that is not mapped with existing document ID.
+ * @throws FileNotFoundException When parentId is not registered in the database.
*/
private boolean putDocuments(
+ String parentId,
ContentValues[] valuesList,
@Nullable ContentValues[] rootExtraValuesList,
String selection,
String[] args,
- boolean heuristic,
- String mappingKey) {
+ String mappingKey) throws FileNotFoundException {
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
boolean added = false;
database.beginTransaction();
try {
+ getParentOrHaltMapping(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(parentId));
for (int i = 0; i < valuesList.length; i++) {
final ContentValues values = valuesList[i];
final ContentValues rootExtraValues;
@@ -285,7 +284,7 @@
if (candidateCursor.getCount() == 0) {
rowId = database.insert(TABLE_DOCUMENTS, null, values);
added = true;
- } else if (!heuristic) {
+ } else {
candidateCursor.moveToNext();
rowId = candidateCursor.getLong(0);
database.update(
@@ -293,9 +292,6 @@
values,
SELECTION_DOCUMENT_ID,
strings(rowId));
- } else {
- values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
- rowId = database.insertOrThrow(TABLE_DOCUMENTS, null, values);
}
// Document ID is a primary integer key of the table. So the returned row
// IDs should be same with the document ID.
@@ -320,11 +316,12 @@
* Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
* If the database does not find corresponding 'invalidated' document, it just removes
* 'invalidated' document from the database.
+ *
* @param parentId Parent document ID or null for root documents.
* @return Whether the methods adds or removed visible rows.
+ * @throws FileNotFoundException
*/
- boolean stopAddingDocuments(@Nullable String parentId) {
- Preconditions.checkState(mMappingMode.containsKey(parentId));
+ boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException {
final String selection;
final String[] args;
if (parentId != null) {
@@ -334,86 +331,15 @@
selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
args = EMPTY_ARGS;
}
- final String groupKey;
- switch (mMappingMode.get(parentId)) {
- case MAP_BY_MTP_IDENTIFIER:
- groupKey = parentId != null ? COLUMN_OBJECT_HANDLE : COLUMN_STORAGE_ID;
- break;
- case MAP_BY_NAME:
- groupKey = Document.COLUMN_DISPLAY_NAME;
- break;
- default:
- throw new Error("Unexpected mapping state.");
- }
- mMappingMode.remove(parentId);
+
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
database.beginTransaction();
try {
- // Get 1-to-1 mapping of invalidated document and pending document.
- final String invalidatedIdQuery = createStateFilter(
- ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
- final String pendingIdQuery = createStateFilter(
- ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
- // SQL should be like:
- // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
- // group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
- // WHERE device_id = ? AND parent_document_id IS NULL
- // GROUP BY display_name
- // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
- // count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
- final Cursor mergingCursor = database.query(
- TABLE_DOCUMENTS,
- new String[] {
- "group_concat(" + invalidatedIdQuery + ")",
- "group_concat(" + pendingIdQuery + ")"
- },
- selection,
- args,
- groupKey,
- "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
- null);
-
- final ContentValues values = new ContentValues();
- while (mergingCursor.moveToNext()) {
- final String invalidatedId = mergingCursor.getString(0);
- final String pendingId = mergingCursor.getString(1);
-
- // Obtain the new values including the latest object handle from mapping row.
- getFirstRow(
- TABLE_DOCUMENTS,
- SELECTION_DOCUMENT_ID,
- new String[] { pendingId },
- values);
- values.remove(Document.COLUMN_DOCUMENT_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- database.update(
- TABLE_DOCUMENTS,
- values,
- SELECTION_DOCUMENT_ID,
- new String[] { invalidatedId });
-
- getFirstRow(
- TABLE_ROOT_EXTRA,
- SELECTION_ROOT_ID,
- new String[] { pendingId },
- values);
- if (values.size() > 0) {
- values.remove(Root.COLUMN_ROOT_ID);
- database.update(
- TABLE_ROOT_EXTRA,
- values,
- SELECTION_ROOT_ID,
- new String[] { invalidatedId });
- }
-
- // Delete 'pending' row.
- mDatabase.deleteDocumentsAndRootsRecursively(
- SELECTION_DOCUMENT_ID, new String[] { pendingId });
- }
- mergingCursor.close();
+ getParentOrHaltMapping(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(parentId));
+ mMappingMode.remove(parentId);
boolean changed = false;
-
// Delete all invalidated rows that cannot be mapped.
if (mDatabase.deleteDocumentsAndRootsRecursively(
COLUMN_ROW_STATE + " = ? AND " + selection,
@@ -421,18 +347,6 @@
changed = true;
}
- // The database cannot find old document ID for the pending rows.
- // Turn the all pending rows into valid state, which means the rows become to be
- // valid with new document ID.
- values.clear();
- values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- if (database.update(
- TABLE_DOCUMENTS,
- values,
- COLUMN_ROW_STATE + " = ? AND " + selection,
- DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) {
- changed = true;
- }
database.setTransactionSuccessful();
return changed;
} finally {
@@ -441,38 +355,23 @@
}
/**
- * Obtains values of the first row for the query.
- * @param values ContentValues that the values are stored to.
- * @param table Target table.
- * @param selection Query to select rows.
- * @param args Argument for query.
+ * Returns the parent identifier from parent document ID if the parent ID is found in the
+ * database. Otherwise it halts mapping and throws FileNotFoundException.
+ *
+ * @param parentId Parent document ID
+ * @return Parent identifier
+ * @throws FileNotFoundException
*/
- private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
- final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
- values.clear();
- final Cursor cursor = database.query(table, null, selection, args, null, null, null, "1");
- try {
- if (cursor.getCount() == 0) {
- return;
- }
- cursor.moveToNext();
- DatabaseUtils.cursorRowToContentValues(cursor, values);
- } finally {
- cursor.close();
+ private @Nullable Identifier getParentOrHaltMapping(
+ @Nullable String parentId) throws FileNotFoundException {
+ if (parentId == null) {
+ return null;
}
- }
-
- /**
- * Gets SQL expression that represents the given value or NULL depends on the row state.
- * You must pass static constants to this methods otherwise you may be suffered from SQL
- * injections.
- * @param state Expected row state.
- * @param a SQL value.
- * @return Expression that represents a if the row state is expected one, and represents NULL
- * otherwise.
- */
- private static String createStateFilter(int state, String a) {
- return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
- " THEN " + a + " ELSE NULL END";
+ try {
+ return mDatabase.createIdentifier(parentId);
+ } catch (FileNotFoundException error) {
+ mMappingMode.remove(parentId);
+ throw error;
+ }
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 3cfb82f..33687cb 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -78,14 +78,6 @@
static final int ROW_STATE_INVALIDATED = 1;
/**
- * The state represents the raw has a valid object handle but it may be going to be mapped with
- * another rows invalidated. After fetching all documents under the parent, the database tries
- * to map the pending documents and the invalidated documents in order to keep old document ID
- * alive.
- */
- static final int ROW_STATE_PENDING = 2;
-
- /**
* Mapping mode that uses MTP identifier to find corresponding rows.
*/
static final int MAP_BY_MTP_IDENTIFIER = 0;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index a4c3cf4..cec2b4d 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -22,6 +22,7 @@
import android.provider.DocumentsContract;
import android.util.Log;
+import java.io.FileNotFoundException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -123,14 +124,22 @@
// Update devices.
final MtpDeviceRecord[] devices = mManager.getDevices();
- mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
- for (final MtpDeviceRecord device : devices) {
- if (mDatabase.getMapper().putDeviceDocument(device)) {
+ try {
+ mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
+ for (final MtpDeviceRecord device : devices) {
+ if (mDatabase.getMapper().putDeviceDocument(device)) {
+ changed = true;
+ }
+ }
+ if (mDatabase.getMapper().stopAddingDocuments(
+ null /* parentDocumentId */)) {
changed = true;
}
- }
- if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) {
- changed = true;
+ } catch (FileNotFoundException exception) {
+ // The top root (ID is null) must exist always.
+ // FileNotFoundException is unexpected.
+ Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException", exception);
+ throw new AssertionError("Unexpected exception for the top parent", exception);
}
// Update roots.
@@ -139,12 +148,17 @@
if (documentId == null) {
continue;
}
- mDatabase.getMapper().startAddingDocuments(documentId);
- if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) {
- changed = true;
- }
- if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
- changed = true;
+ try {
+ mDatabase.getMapper().startAddingDocuments(documentId);
+ if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) {
+ changed = true;
+ }
+ if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
+ changed = true;
+ }
+ } catch (FileNotFoundException exception) {
+ Log.e(MtpDocumentsProvider.TAG, "Parent document is gone.", exception);
+ continue;
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index af1fed4..6e28e33 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -36,16 +36,23 @@
private TestContentResolver mResolver;
private DocumentLoader mLoader;
final private Identifier mParentIdentifier = new Identifier(
- 0, 0, 0, "1", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE);
+ 0, 0, 0, "2", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE);
@Override
- public void setUp() {
+ public void setUp() throws Exception {
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+
+ mDatabase.getMapper().startAddingDocuments(null);
+ mDatabase.getMapper().putDeviceDocument(
+ new MtpDeviceRecord(1, "Device", true, new MtpRoot[0], null, null));
+ mDatabase.getMapper().stopAddingDocuments(null);
+
+ mDatabase.getMapper().startAddingDocuments("1");
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 0, "Storage", 1000, 1000, "")
});
- mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+ mDatabase.getMapper().stopAddingDocuments("1");
+
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index a49dc67..ea52957 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -138,8 +138,10 @@
}
public void testPutStorageDocuments() throws Exception {
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ addTestDevice();
+
+ mDatabase.getMapper().startAddingDocuments("1");
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 1, "Storage", 1000, 2000, ""),
new MtpRoot(0, 2, "Storage", 2000, 4000, ""),
new MtpRoot(0, 3, "/@#%&<>Storage", 3000, 6000,"")
@@ -150,7 +152,7 @@
assertEquals(3, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
@@ -165,11 +167,11 @@
MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE));
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals("/@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
@@ -186,18 +188,21 @@
}
public void testPutChildDocuments() throws Exception {
- mDatabase.getMapper().startAddingDocuments("parentId");
- mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ addTestDevice();
+ addTestStorage("1");
+
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
});
- final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "parentId");
+ final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "2");
assertEquals(3, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals(100, getInt(cursor, COLUMN_OBJECT_HANDLE));
@@ -216,7 +221,7 @@
MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals(101, getInt(cursor, COLUMN_OBJECT_HANDLE));
@@ -235,7 +240,7 @@
MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
cursor.moveToNext();
- assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals(102, getInt(cursor, COLUMN_OBJECT_HANDLE));
@@ -263,8 +268,10 @@
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ // Add device and two storages.
+ addTestDevice();
+ mDatabase.getMapper().startAddingDocuments("1");
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 100, "Storage A", 1000, 0, ""),
new MtpRoot(0, 101, "Storage B", 1001, 0, "")
});
@@ -273,34 +280,37 @@
final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(101, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
+ // Clear mapping and add a device.
mDatabase.getMapper().clearMapping();
+ addTestDevice();
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ // Add two storages, but one's name is different from previous one.
+ mDatabase.getMapper().startAddingDocuments("1");
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 200, "Storage A", 2000, 0, ""),
new MtpRoot(0, 202, "Storage C", 2002, 0, "")
});
@@ -309,11 +319,11 @@
final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(3, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
- assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
+ assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
@@ -323,13 +333,13 @@
cursor.close();
}
- mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+ mDatabase.getMapper().stopAddingDocuments("1");
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
@@ -346,69 +356,64 @@
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- mDatabase.getMapper().startAddingDocuments("parentId");
- mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+
+ addTestDevice();
+ addTestStorage("1");
+
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
});
mDatabase.getMapper().clearMapping();
+ addTestDevice();
+ addTestStorage("1");
+
{
- final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "2");
assertEquals(3, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
assertEquals("image.jpg", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
assertEquals("music.mp3", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
}
- mDatabase.getMapper().startAddingDocuments("parentId");
- mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
});
+ mDatabase.getMapper().stopAddingDocuments("2");
{
- final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
- assertEquals(4, cursor.getCount());
-
- cursor.moveToPosition(3);
- assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
- assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
- assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
-
- cursor.close();
- }
-
- mDatabase.getMapper().stopAddingDocuments("parentId");
-
- {
- final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "2");
assertEquals(2, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
+
cursor.close();
}
}
@@ -424,10 +429,10 @@
Root.COLUMN_AVAILABLE_BYTES
};
mDatabase.getMapper().startAddingDocuments(null);
- mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device A", true, new MtpRoot[0], null, null));
- mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(1, "Device B", true, new MtpRoot[0], null, null));
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device A", true, new MtpRoot[0], null, null));
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 1, "Device B", true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
@@ -467,6 +472,13 @@
mDatabase.getMapper().clearMapping();
+ mDatabase.getMapper().startAddingDocuments(null);
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device A", true, new MtpRoot[0], null, null));
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 1, "Device B", true, new MtpRoot[0], null, null));
+ mDatabase.getMapper().stopAddingDocuments(null);
+
mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().startAddingDocuments("2");
mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
@@ -511,45 +523,71 @@
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
};
- mDatabase.getMapper().startAddingDocuments("parentId1");
- mDatabase.getMapper().startAddingDocuments("parentId2");
- mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ // Add device, storage, and two directories.
+ addTestDevice();
+ addTestStorage("1");
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
+ createDocument(50, "A", MtpConstants.FORMAT_ASSOCIATION, 0),
+ createDocument(51, "B", MtpConstants.FORMAT_ASSOCIATION, 0),
+ });
+ mDatabase.getMapper().stopAddingDocuments("2");
+
+ // Put note.txt in each directory.
+ mDatabase.getMapper().startAddingDocuments("3");
+ mDatabase.getMapper().startAddingDocuments("4");
+ mDatabase.getMapper().putChildDocuments(0, "3", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "4", new MtpObjectInfo[] {
createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
+
+ // Clear mapping.
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingDocuments("parentId1");
- mDatabase.getMapper().startAddingDocuments("parentId2");
- mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ // Add device, storage, and two directories again.
+ addTestDevice();
+ addTestStorage("1");
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
+ createDocument(50, "A", MtpConstants.FORMAT_ASSOCIATION, 0),
+ createDocument(51, "B", MtpConstants.FORMAT_ASSOCIATION, 0),
+ });
+ mDatabase.getMapper().stopAddingDocuments("2");
+
+ // Add note.txt in each directory again.
+ mDatabase.getMapper().startAddingDocuments("3");
+ mDatabase.getMapper().startAddingDocuments("4");
+ mDatabase.getMapper().putChildDocuments(0, "3", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "4", new MtpObjectInfo[] {
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.getMapper().stopAddingDocuments("parentId1");
+ mDatabase.getMapper().stopAddingDocuments("3");
+ mDatabase.getMapper().stopAddingDocuments("4");
+ // Check if the two note.txt are mapped correctly.
{
- final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "3");
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
cursor.close();
}
{
- final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2");
+ final Cursor cursor = mDatabase.queryChildDocuments(columns, "4");
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
- assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+ assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(201, getInt(cursor, COLUMN_OBJECT_HANDLE));
cursor.close();
}
}
- public void testClearMtpIdentifierBeforeResolveRootDocuments() {
+ public void testClearMtpIdentifierBeforeResolveRootDocuments() throws Exception {
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -561,8 +599,8 @@
};
mDatabase.getMapper().startAddingDocuments(null);
- mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], null, null));
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device", false, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
@@ -571,12 +609,28 @@
});
mDatabase.getMapper().clearMapping();
+ mDatabase.getMapper().startAddingDocuments(null);
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device", false, new MtpRoot[0], null, null));
+ mDatabase.getMapper().stopAddingDocuments(null);
+
+ try (final Cursor cursor = mDatabase.queryRoots(resources, rootColumns)) {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("1", getString(cursor, Root.COLUMN_ROOT_ID));
+ }
+
mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 200, "Storage", 2000, 0, ""),
});
mDatabase.getMapper().clearMapping();
+ mDatabase.getMapper().startAddingDocuments(null);
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device", false, new MtpRoot[0], null, null));
+ mDatabase.getMapper().stopAddingDocuments(null);
+
mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 300, "Storage", 3000, 0, ""),
@@ -609,38 +663,47 @@
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
- new MtpRoot(0, 100, "Storage", 0, 0, ""),
- });
+ // Add a device and a storage.
+ addTestDevice();
+ addTestStorage("1");
+
+ // Disconnect devices.
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ // Add a device and two storages that has same name.
+ addTestDevice();
+ mDatabase.getMapper().startAddingDocuments("1");
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 200, "Storage", 2000, 0, ""),
new MtpRoot(0, 201, "Storage", 2001, 0, ""),
});
- mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+ mDatabase.getMapper().stopAddingDocuments("1");
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
+
+ // First storage reuse document ID of previous storage.
cursor.moveToNext();
+ // One reuses exisitng document ID 1.
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+
+ // Second one has new document ID.
cursor.moveToNext();
assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+
cursor.close();
}
}
- public void testReplaceExistingRoots() {
+ public void testReplaceExistingRoots() throws Exception {
mDatabase.getMapper().startAddingDocuments(null);
- mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], null, null));
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device", true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
// The client code should be able to replace existing rows with new information.
@@ -686,66 +749,62 @@
}
}
- public void testFailToReplaceExisitingUnmappedRoots() {
+ public void testFailToReplaceExisitingUnmappedRoots() throws Exception {
// The client code should not be able to replace rows before resolving 'unmapped' rows.
// Add one.
- mDatabase.getMapper().startAddingDocuments(null);
- mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], null, null));
- mDatabase.getMapper().stopAddingDocuments(null);
-
+ addTestDevice();
mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
mDatabase.getMapper().clearMapping();
- final Cursor oldCursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_ROOT_ID));
- assertEquals(1, oldCursor.getCount());
- // Add one.
- mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
- new MtpRoot(0, 101, "Storage B", 1000, 1000, ""),
- });
- // Add one more before resolving unmapped documents.
- mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
- new MtpRoot(0, 102, "Storage B", 1000, 1000, ""),
- });
- mDatabase.getMapper().stopAddingDocuments("1");
+ addTestDevice();
+ try (final Cursor oldCursor =
+ mDatabase.queryRoots(resources, strings(Root.COLUMN_ROOT_ID))) {
+ assertEquals(1, oldCursor.getCount());
+ oldCursor.moveToNext();
+ assertEquals("1", getString(oldCursor, Root.COLUMN_ROOT_ID));
- // Because the roots shares the same name, the roots should have new IDs.
- final Cursor newCursor = mDatabase.queryChildDocuments(
- strings(Document.COLUMN_DOCUMENT_ID), "1");
- assertEquals(2, newCursor.getCount());
- oldCursor.moveToNext();
- newCursor.moveToNext();
- assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
- newCursor.moveToNext();
- assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+ // Add one.
+ mDatabase.getMapper().startAddingDocuments("1");
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 101, "Storage B", 1000, 1000, ""),
+ });
+ // Add one more before resolving unmapped documents.
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+ new MtpRoot(0, 102, "Storage B", 1000, 1000, ""),
+ });
+ mDatabase.getMapper().stopAddingDocuments("1");
- oldCursor.close();
- newCursor.close();
+ // Because the roots shares the same name, the roots should have new IDs.
+ try (final Cursor newCursor = mDatabase.queryChildDocuments(
+ strings(Document.COLUMN_DOCUMENT_ID), "1")) {
+ assertEquals(2, newCursor.getCount());
+ newCursor.moveToNext();
+ assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+ newCursor.moveToNext();
+ assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+ }
+ }
}
- public void testQueryDocuments() {
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
- new MtpRoot(0, 100, "Storage A", 0, 0, ""),
- });
- mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+ public void testQueryDocuments() throws Exception {
+ addTestDevice();
+ addTestStorage("1");
- final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
+ final Cursor cursor = mDatabase.queryDocument("2", strings(Document.COLUMN_DISPLAY_NAME));
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME));
+ assertEquals("Storage", getString(cursor, Document.COLUMN_DISPLAY_NAME));
cursor.close();
}
- public void testQueryRoots() {
+ public void testQueryRoots() throws Exception {
// Add device document.
mDatabase.getMapper().startAddingDocuments(null);
- mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], null, null));
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device", false, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
// It the device does not have storages, it shows a device root.
@@ -790,38 +849,12 @@
}
public void testGetParentId() throws FileNotFoundException {
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
- new MtpRoot(0, 100, "Storage A", 0, 0, ""),
- });
- mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+ addTestDevice();
mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putChildDocuments(
- 0,
- "1",
- new MtpObjectInfo[] {
- createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
- });
- mDatabase.getMapper().stopAddingDocuments("1");
-
- assertEquals("1", mDatabase.getParentIdentifier("2").mDocumentId);
- }
-
- public void testDeleteDocument() {
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+ mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
new MtpRoot(0, 100, "Storage A", 0, 0, ""),
});
- mDatabase.getMapper().stopAddingDocuments("deviceDocId");
-
- mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.getMapper().putChildDocuments(
- 0,
- "1",
- new MtpObjectInfo[] {
- createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
- });
mDatabase.getMapper().stopAddingDocuments("1");
mDatabase.getMapper().startAddingDocuments("2");
@@ -833,12 +866,37 @@
});
mDatabase.getMapper().stopAddingDocuments("2");
- mDatabase.deleteDocument("2");
+ assertEquals("2", mDatabase.getParentIdentifier("3").mDocumentId);
+ }
+
+ public void testDeleteDocument() throws Exception {
+ addTestDevice();
+ addTestStorage("1");
+
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "2",
+ new MtpObjectInfo[] {
+ createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
+ });
+ mDatabase.getMapper().stopAddingDocuments("2");
+
+ mDatabase.getMapper().startAddingDocuments("3");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "3",
+ new MtpObjectInfo[] {
+ createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
+ });
+ mDatabase.getMapper().stopAddingDocuments("3");
+
+ mDatabase.deleteDocument("3");
{
// Do not query deleted documents.
final Cursor cursor =
- mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
assertEquals(0, cursor.getCount());
cursor.close();
}
@@ -846,64 +904,58 @@
{
// Child document should be deleted also.
final Cursor cursor =
- mDatabase.queryDocument("3", strings(Document.COLUMN_DOCUMENT_ID));
+ mDatabase.queryDocument("4", strings(Document.COLUMN_DOCUMENT_ID));
assertEquals(0, cursor.getCount());
cursor.close();
}
}
- public void testPutNewDocument() {
- mDatabase.getMapper().startAddingDocuments("deviceDocId");
- mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
- new MtpRoot(0, 100, "Storage A", 0, 0, ""),
- });
- mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+ public void testPutNewDocument() throws Exception {
+ addTestDevice();
+ addTestStorage("1");
assertEquals(
- "2",
+ "3",
mDatabase.putNewDocument(
- 0, "1", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024)));
+ 0, "2", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024)));
{
final Cursor cursor =
- mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
- assertEquals(1, cursor.getCount());
- cursor.moveToNext();
- assertEquals("2", cursor.getString(0));
- cursor.close();
- }
-
- // The new document should not be mapped with existing invalidated document.
- mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingDocuments("1");
- mDatabase.putNewDocument(
- 0,
- "1",
- createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
- mDatabase.getMapper().stopAddingDocuments("1");
-
- {
- final Cursor cursor =
- mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("3", cursor.getString(0));
cursor.close();
}
+
+ // The new document should not be mapped with existing invalidated document.
+ mDatabase.getMapper().clearMapping();
+ mDatabase.getMapper().startAddingDocuments("2");
+ mDatabase.putNewDocument(
+ 0,
+ "2",
+ createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
+ mDatabase.getMapper().stopAddingDocuments("2");
+
+ {
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("4", cursor.getString(0));
+ cursor.close();
+ }
}
- public void testGetDocumentIdForDevice() {
- mDatabase.getMapper().startAddingDocuments(null);
- mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(100, "Device", true, new MtpRoot[0], null, null));
- mDatabase.getMapper().stopAddingDocuments(null);
- assertEquals("1", mDatabase.getDocumentIdForDevice(100));
+ public void testGetDocumentIdForDevice() throws Exception {
+ addTestDevice();
+ assertEquals("1", mDatabase.getDocumentIdForDevice(0));
}
- public void testGetClosedDevice() {
+ public void testGetClosedDevice() throws Exception {
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
- 0, "Device", /* opened is */ false , new MtpRoot[0], null, null));
+ 0, "Device", /* opened is */ false, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
final String[] columns = new String [] {
@@ -919,4 +971,19 @@
assertTrue(cursor.isNull(2));
}
}
+
+ private void addTestDevice() throws FileNotFoundException {
+ mDatabase.getMapper().startAddingDocuments(null);
+ mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+ 0, "Device", /* opened is */ true, new MtpRoot[0], null, null));
+ mDatabase.getMapper().stopAddingDocuments(null);
+ }
+
+ private void addTestStorage(String parentId) throws FileNotFoundException {
+ mDatabase.getMapper().startAddingDocuments(parentId);
+ mDatabase.getMapper().putStorageDocuments(parentId, new MtpRoot[] {
+ new MtpRoot(0, 100, "Storage", 1024, 1024, ""),
+ });
+ mDatabase.getMapper().stopAddingDocuments(parentId);
+ }
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 32699eb..6d047d0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -416,6 +416,8 @@
<string name="legacy_dhcp_client">Use legacy DHCP client</string>
<!-- Setting Checkbox title whether to always keep cellular data active. [CHAR LIMIT=80] -->
<string name="mobile_data_always_on">Cellular data always active</string>
+ <!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
+ <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
<!-- setting Checkbox summary whether to show options for wireless display certification -->
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
@@ -456,6 +458,8 @@
<string name="verify_apps_over_usb_title">Verify apps over USB</string>
<!-- Summary of checkbox setting to perform package verification on apps installed over USB/ADT/ADB [CHAR LIMIT=NONE] -->
<string name="verify_apps_over_usb_summary">Check apps installed via ADB/ADT for harmful behavior.</string>
+ <!-- Summary of checkbox for disabling Bluetooth absolute volume -->
+ <string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
<!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
<string name="enable_terminal_title">Local terminal</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index f03e94d..231fc69 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -63,14 +63,15 @@
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
+ final int appOpsCount = appOps != null ? appOps.size() : 0;
+
// Process the AppOps list and generate a preference list.
- ArrayList<Request> requests = new ArrayList<>(appOps.size());
+ ArrayList<Request> requests = new ArrayList<>(appOpsCount);
final long now = System.currentTimeMillis();
final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
final List<UserHandle> profiles = um.getUserProfiles();
- final int appOpsN = appOps.size();
- for (int i = 0; i < appOpsN; ++i) {
+ for (int i = 0; i < appOpsCount; ++i) {
AppOpsManager.PackageOps ops = appOps.get(i);
// Don't show the Android System in the list - it's not actionable for the user.
// Also don't show apps belonging to background users except managed users.
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e5e5710..6702cef 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -282,9 +282,6 @@
<!-- The padding between freeform workspace tasks -->
<dimen name="recents_freeform_workspace_task_padding">8dp</dimen>
- <!-- Space reserved for the cards behind the top card in the top stack -->
- <dimen name="top_stack_peek_amount">12dp</dimen>
-
<!-- Space reserved for the cards behind the top card in the bottom stack -->
<dimen name="bottom_stack_peek_amount">12dp</dimen>
@@ -295,9 +292,6 @@
<!-- The height of the area before the bottom stack in which the notifications slow down -->
<dimen name="bottom_stack_slow_down_length">12dp</dimen>
- <!-- The height of the area before the top stack in which the notifications slow down -->
- <dimen name="top_stack_slow_down_length">12dp</dimen>
-
<!-- Z distance between notifications if they are in the stack -->
<dimen name="z_distance_between_notifications">0.5dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 5890b5f..9da5c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -124,6 +124,10 @@
public void onPinnedActivityRestartAttempt() {
}
+ @Override
+ public void onPinnedStackAnimationEnded() {
+ }
+
/** Preloads the next task */
public void run() {
RecentsConfiguration config = Recents.getConfiguration();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index 42ebfa9..f3201d0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -318,13 +318,13 @@
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP: {
SystemServicesProxy ssp = Recents.getSystemServices();
- PipManager.getInstance().showPipMenu();
+ PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_MENU);
ssp.focusPinnedStack();
return true;
}
case KeyEvent.KEYCODE_DPAD_DOWN: {
SystemServicesProxy ssp = Recents.getSystemServices();
- PipManager.getInstance().showPipOverlay(false);
+ PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
ssp.focusHomeStack();
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 7f1316f..84b2031 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -947,6 +947,10 @@
}
}
+ public boolean mustStayOnScreen() {
+ return mIsHeadsUp;
+ }
+
private void updateClearability() {
// public versions cannot be dismissed
mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index a0fb34a..8042b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -399,6 +399,10 @@
return false;
}
+ public boolean mustStayOnScreen() {
+ return false;
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index e20936b..08cd053 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -109,6 +109,10 @@
}
@Override
+ public void onPinnedStackAnimationEnded() {
+ }
+
+ @Override
public void onTaskStackChanged() {
mHandler.removeCallbacks(this);
mHandler.post(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index b5b7f43..79c21f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -79,7 +79,8 @@
mTouchingHeadsUpView = false;
if (child instanceof ExpandableNotificationRow) {
mPickedChild = (ExpandableNotificationRow) child;
- mTouchingHeadsUpView = mPickedChild.isHeadsUp() && mPickedChild.isPinned();
+ mTouchingHeadsUpView = !mStackScroller.isExpanded()
+ && mPickedChild.isHeadsUp() && mPickedChild.isPinned();
}
break;
case MotionEvent.ACTION_POINTER_UP:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5f5974e..0febbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -738,9 +738,9 @@
+ (offscreen ? " OFFSCREEN!" : ""));
pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s",
- getResourceName(mCurrentView.getId()),
- mCurrentView.getWidth(), mCurrentView.getHeight(),
- visibilityToString(mCurrentView.getVisibility())));
+ getResourceName(getCurrentView().getId()),
+ getCurrentView().getWidth(), getCurrentView().getHeight(),
+ visibilityToString(getCurrentView().getVisibility())));
pw.println(String.format(" disabled=0x%08x vertical=%s menu=%s",
mDisabledFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 09a7bf0..50a49a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -519,7 +519,7 @@
*/
protected boolean mStartedGoingToSleep;
- private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_CARD
+ private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_HUN
| StackViewState.LOCATION_MAIN_AREA;
private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index cc0e67d..49e9c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -75,7 +75,7 @@
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
- private static final String TAG = "NotificationStackScrollLayout";
+ private static final String TAG = "StackScroller";
private static final boolean DEBUG = false;
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
@@ -136,7 +136,7 @@
private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
private AmbientState mAmbientState = new AmbientState();
private NotificationGroupManager mGroupManager;
- private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
+ private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
@@ -474,6 +474,7 @@
* modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
*/
private void updateChildren() {
+ updateScrollStateForAddedChildren();
mAmbientState.setScrollY(mOwnScrollY);
mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
if (!isCurrentlyAnimating() && !mNeedsAnimation) {
@@ -483,6 +484,28 @@
}
}
+ private void updateScrollStateForAddedChildren() {
+ if (mChildrenToAddAnimated.isEmpty()) {
+ return;
+ }
+ for (int i = 0; i < getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (mChildrenToAddAnimated.contains(child)) {
+ int startingPosition = getPositionInLinearLayout(child);
+ int padding = child.needsIncreasedPadding()
+ ? mIncreasedPaddingBetweenElements :
+ mPaddingBetweenElements;
+ int childHeight = getIntrinsicHeight(child) + padding;
+ if (startingPosition < mOwnScrollY) {
+ // This child starts off screen, so let's keep it offscreen to keep the others visible
+
+ mOwnScrollY += childHeight;
+ }
+ }
+ }
+ clampScrollPosition();
+ }
+
private void requestChildrenUpdate() {
if (!mChildrenUpdateRequested) {
getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
@@ -1648,12 +1671,17 @@
bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
bottom = Math.min(bottom, getHeight());
}
- } else if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
- top = mTopPadding;
+ } else {
+ top = (int) (mTopPadding + mStackTranslation);
bottom = top;
}
- mBackgroundBounds.top = Math.max(0, top);
- mBackgroundBounds.bottom = Math.min(getHeight(), bottom);
+ if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
+ mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top);
+ } else {
+ // otherwise the animation from the shade to the keyguard will jump as it's maxed
+ mBackgroundBounds.top = Math.max(0, top);
+ }
+ mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
}
private ActivatableNotificationView getFirstPinnedHeadsUp() {
@@ -3206,6 +3234,10 @@
}
}
+ public boolean isExpanded() {
+ return mIsExpanded;
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index f6959f0..e87b363 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -39,17 +39,14 @@
private static final String LOG_TAG = "StackScrollAlgorithm";
private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
- private static final int MAX_ITEMS_IN_TOP_STACK = 3;
private int mPaddingBetweenElements;
private int mIncreasedPaddingBetweenElements;
private int mCollapsedSize;
- private int mTopStackPeekSize;
private int mBottomStackPeekSize;
private int mZDistanceBetweenElements;
private int mZBasicHeight;
- private StackIndentationFunctor mTopStackIndentationFunctor;
private StackIndentationFunctor mBottomStackIndentationFunctor;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
@@ -58,12 +55,8 @@
private boolean mIsExpanded;
private ExpandableView mFirstChildWhileExpanding;
private boolean mExpandedOnStart;
- private int mTopStackTotalSize;
private int mBottomStackSlowDownLength;
- private int mTopStackSlowDownLength;
private int mCollapseSecondCardPadding;
- private ExpandableView mFirstChild;
- private int mFirstChildMinHeight;
public StackScrollAlgorithm(Context context) {
initView(context);
@@ -71,22 +64,6 @@
public void initView(Context context) {
initConstants(context);
- updatePadding();
- }
-
- private void updatePadding() {
- mTopStackTotalSize = mTopStackSlowDownLength + mPaddingBetweenElements
- + mTopStackPeekSize;
- mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
- MAX_ITEMS_IN_TOP_STACK,
- mTopStackPeekSize,
- mTopStackTotalSize - mTopStackPeekSize,
- 0.5f);
- mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
- MAX_ITEMS_IN_BOTTOM_STACK,
- mBottomStackPeekSize,
- getBottomStackSlowDownLength(),
- 0.5f);
}
public int getBottomStackSlowDownLength() {
@@ -100,8 +77,6 @@
.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
mCollapsedSize = context.getResources()
.getDimensionPixelSize(R.dimen.notification_min_height);
- mTopStackPeekSize = context.getResources()
- .getDimensionPixelSize(R.dimen.top_stack_peek_amount);
mBottomStackPeekSize = context.getResources()
.getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
mZDistanceBetweenElements = Math.max(1, context.getResources()
@@ -109,10 +84,13 @@
mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
mBottomStackSlowDownLength = context.getResources()
.getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
- mTopStackSlowDownLength = context.getResources()
- .getDimensionPixelSize(R.dimen.top_stack_slow_down_length);
mCollapseSecondCardPadding = context.getResources().getDimensionPixelSize(
R.dimen.notification_collapse_second_card_padding);
+ mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
+ MAX_ITEMS_IN_BOTTOM_STACK,
+ mBottomStackPeekSize,
+ getBottomStackSlowDownLength(),
+ 0.5f);
}
public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
@@ -123,32 +101,13 @@
// First we reset the view states to their default values.
resultState.resetViewStates();
- algorithmState.itemsInTopStack = 0.0f;
- algorithmState.partialInTop = 0.0f;
- algorithmState.lastTopStackIndex = 0;
- algorithmState.scrolledPixelsTop = 0;
- algorithmState.itemsInBottomStack = 0.0f;
- algorithmState.partialInBottom = 0.0f;
- mFirstChildMinHeight = mFirstChild == null ? 0 : mFirstChild.getMinHeight();
- float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
+ initAlgorithmState(resultState, algorithmState, ambientState);
- int scrollY = ambientState.getScrollY();
-
- // Due to the overScroller, the stackscroller can have negative scroll state. This is
- // already accounted for by the top padding and doesn't need an additional adaption
- scrollY = Math.max(0, scrollY);
- algorithmState.scrollY = (int) (scrollY + mFirstChildMinHeight + bottomOverScroll);
-
- initAlgorithmState(resultState, algorithmState);
-
- // Phase 1:
- findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState, ambientState);
-
- // Phase 2:
updatePositionsForState(resultState, algorithmState, ambientState);
- // Phase 3:
- updateZValuesForState(resultState, algorithmState);
+ updateZValuesForState(resultState, algorithmState, ambientState);
+
+ updateHeadsUpStates(resultState, algorithmState, ambientState);
handleDraggedViews(ambientState, resultState, algorithmState);
updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
@@ -185,6 +144,7 @@
private void updateClipping(StackScrollState resultState,
StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
boolean dismissAllInProgress = ambientState.isDismissAllInProgress();
+ float drawStart = ambientState.getTopPadding() + ambientState.getStackTranslation();
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
boolean previousNotificationIsSwiped = false;
@@ -192,6 +152,10 @@
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState state = resultState.getViewStateForView(child);
+ if (!child.mustStayOnScreen()) {
+ previousNotificationEnd = Math.max(drawStart, previousNotificationEnd);
+ previousNotificationStart = Math.max(drawStart, previousNotificationStart);
+ }
float newYTranslation = state.yTranslation;
float newHeight = state.height;
// apply clipping and shadow
@@ -222,7 +186,7 @@
} else {
previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child);
previousNotificationEnd = newNotificationEnd;
- previousNotificationStart = newYTranslation + state.clipTopAmount;
+ previousNotificationStart =newYTranslation + state.clipTopAmount;
}
}
}
@@ -314,8 +278,20 @@
/**
* Initialize the algorithm state like updating the visible children.
*/
- private void initAlgorithmState(StackScrollState resultState,
- StackScrollAlgorithmState state) {
+ private void initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state,
+ AmbientState ambientState) {
+ state.itemsInBottomStack = 0.0f;
+ state.partialInBottom = 0.0f;
+ float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
+
+ int scrollY = ambientState.getScrollY();
+
+ // Due to the overScroller, the stackscroller can have negative scroll state. This is
+ // already accounted for by the top padding and doesn't need an additional adaption
+ scrollY = Math.max(0, scrollY);
+ state.scrollY = (int) (scrollY + bottomOverScroll);
+
+ //now init the visible children and update paddings
ViewGroup hostView = resultState.getHostView();
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
@@ -383,15 +359,9 @@
float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength;
// The y coordinate of the current child.
- float currentYPosition = 0.0f;
-
- // How far in is the element currently transitioning into the bottom stack.
- float yPositionInScrollView = 0.0f;
+ float currentYPosition = -algorithmState.scrollY;
int childCount = algorithmState.visibleChildren.size();
- int numberOfElementsCompletelyIn = algorithmState.partialInTop == 1.0f
- ? algorithmState.lastTopStackIndex
- : (int) algorithmState.itemsInTopStack;
int paddingAfterChild;
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
@@ -400,47 +370,16 @@
paddingAfterChild = getPaddingAfterChild(algorithmState, child);
int childHeight = getMaxAllowedChildHeight(child);
int minHeight = child.getMinHeight();
- float yPositionInScrollViewAfterElement = yPositionInScrollView
- + childHeight
- + paddingAfterChild;
- float scrollOffset = yPositionInScrollView - algorithmState.scrollY +
- mFirstChildMinHeight;
-
- if (i == algorithmState.lastTopStackIndex + 1) {
- // Normally the position of this child is the position in the regular scrollview,
- // but if the two stacks are very close to each other,
- // then have have to push it even more upwards to the position of the bottom
- // stack start.
- currentYPosition = Math.min(scrollOffset, bottomStackStart);
- }
childViewState.yTranslation = currentYPosition;
+ if (i == 0) {
+ updateFirstChildHeight(child, childViewState, childHeight, ambientState);
+ }
// The y position after this element
float nextYPosition = currentYPosition + childHeight +
paddingAfterChild;
-
- if (i <= algorithmState.lastTopStackIndex) {
+ if (nextYPosition >= bottomStackStart) {
// Case 1:
- // We are in the top Stack
- updateStateForTopStackChild(algorithmState, child,
- numberOfElementsCompletelyIn, i, childHeight, childViewState, scrollOffset);
- clampPositionToTopStackEnd(childViewState, childHeight);
-
- // check if we are overlapping with the bottom stack
- if (childViewState.yTranslation + childHeight + paddingAfterChild
- >= bottomStackStart && !mIsExpansionChanging && i != 0) {
- // we just collapse this element slightly
- int newSize = (int) Math.max(bottomStackStart - paddingAfterChild -
- childViewState.yTranslation, minHeight);
- childViewState.height = newSize;
- updateStateForChildTransitioningInBottom(algorithmState, bottomStackStart,
- child, childViewState.yTranslation, childViewState,
- childHeight);
- }
- clampPositionToBottomStackStart(childViewState, childViewState.height,
- minHeight, ambientState);
- } else if (nextYPosition >= bottomStackStart) {
- // Case 2:
// We are in the bottom stack.
if (currentYPosition >= bottomStackStart) {
// According to the regular scroll view we are fully translated out of the
@@ -455,36 +394,30 @@
childViewState, childHeight);
}
} else {
- // Case 3:
+ // Case 2:
// We are in the regular scroll area.
childViewState.location = StackViewState.LOCATION_MAIN_AREA;
- clampYTranslation(childViewState, childHeight, ambientState);
+ clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
+ ambientState);
}
- // The first card is always rendered.
- if (i == 0) {
- childViewState.hidden = false;
- childViewState.shadowAlpha = 1.0f;
- childViewState.yTranslation = Math.max(
- mFirstChildMinHeight - algorithmState.scrollY, 0);
- if (childViewState.yTranslation + childViewState.height
- > bottomPeekStart - mCollapseSecondCardPadding) {
- childViewState.height = (int) Math.max(
- bottomPeekStart - mCollapseSecondCardPadding
- - childViewState.yTranslation, mFirstChildMinHeight);
- }
- childViewState.location = StackViewState.LOCATION_FIRST_CARD;
+ if (i == 0 && ambientState.getScrollY() <= 0) {
+ // The first card can get into the bottom stack if it's the only one
+ // on the lockscreen which pushes it up. Let's make sure that doesn't happen and
+ // it stays at the top
+ childViewState.yTranslation = Math.max(0, childViewState.yTranslation);
+ }
+ currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
+ if (currentYPosition <= 0) {
+ childViewState.location = StackViewState.LOCATION_HIDDEN_TOP;
}
if (childViewState.location == StackViewState.LOCATION_UNKNOWN) {
Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
}
- currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
- yPositionInScrollView = yPositionInScrollViewAfterElement;
childViewState.yTranslation += ambientState.getTopPadding()
+ ambientState.getStackTranslation();
}
- updateHeadsUpStates(resultState, algorithmState, ambientState);
}
private int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
@@ -506,24 +439,27 @@
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!row.isHeadsUp()) {
break;
- } else if (topHeadsUpEntry == null) {
- topHeadsUpEntry = row;
}
StackViewState childState = resultState.getViewStateForView(row);
+ if (topHeadsUpEntry == null) {
+ topHeadsUpEntry = row;
+ childState.location = StackViewState.LOCATION_FIRST_HUN;
+ }
boolean isTopEntry = topHeadsUpEntry == row;
+ float unmodifiedEndLocation = childState.yTranslation + childState.height;
if (mIsExpanded) {
- // Ensure that the heads up is always visible even when scrolled off from the bottom
- float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height;
- childState.yTranslation = Math.min(childState.yTranslation,
- bottomPosition);
+ // Ensure that the heads up is always visible even when scrolled off
+ clampHunToTop(ambientState, row, childState);
+ clampHunToMaxTranslation(ambientState, row, childState);
}
if (row.isPinned()) {
childState.yTranslation = Math.max(childState.yTranslation, 0);
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
- if (!isTopEntry) {
+ StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
+ if (!isTopEntry && (!mIsExpanded
+ || unmodifiedEndLocation < topState.yTranslation + topState.height)) {
// Ensure that a headsUp doesn't vertically extend further than the heads-up at
// the top most z-position
- StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
childState.height = row.getIntrinsicHeight();
childState.yTranslation = topState.yTranslation + topState.height
- childState.height;
@@ -532,17 +468,23 @@
}
}
- /**
- * Clamp the yTranslation both up and down to valid positions.
- *
- * @param childViewState the view state of the child
- * @param minHeight the minimum height of this child
- */
- private void clampYTranslation(StackViewState childViewState, int minHeight,
- AmbientState ambientState) {
- clampPositionToBottomStackStart(childViewState, childViewState.height, minHeight,
- ambientState);
- clampPositionToTopStackEnd(childViewState, childViewState.height);
+ private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
+ StackViewState childState) {
+ float newTranslation = Math.max(ambientState.getTopPadding()
+ + ambientState.getStackTranslation(), childState.yTranslation);
+ childState.height = (int) Math.max(childState.height - (newTranslation
+ - childState.yTranslation), row.getMinHeight());
+ childState.yTranslation = newTranslation;
+ }
+
+ private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
+ StackViewState childState) {
+ float newTranslation;
+ float bottomPosition = ambientState.getMaxHeadsUpTranslation() - row.getMinHeight();
+ newTranslation = Math.min(childState.yTranslation, bottomPosition);
+ childState.height = (int) Math.max(childState.height
+ - (childState.yTranslation - newTranslation), row.getMinHeight());
+ childState.yTranslation = newTranslation;
}
/**
@@ -569,19 +511,6 @@
}
}
- /**
- * Clamp the yTranslation of the child up such that its end is at lest on the end of the top
- * stack.
- *
- * @param childViewState the view state of the child
- * @param childHeight the height of this child
- */
- private void clampPositionToTopStackEnd(StackViewState childViewState,
- int childHeight) {
- childViewState.yTranslation = Math.max(childViewState.yTranslation,
- mFirstChildMinHeight - childHeight);
- }
-
private int getMaxAllowedChildHeight(View child) {
if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
@@ -611,9 +540,6 @@
}
childViewState.yTranslation = transitioningPositionStart + offset - newHeight
- getPaddingAfterChild(algorithmState, child);
-
- // We want at least to be at the end of the top stack when collapsing
- clampPositionToTopStackEnd(childViewState, newHeight);
childViewState.location = StackViewState.LOCATION_MAIN_AREA;
}
@@ -642,177 +568,59 @@
}
childViewState.height = minHeight;
childViewState.yTranslation = currentYPosition - minHeight;
- clampPositionToTopStackEnd(childViewState, minHeight);
}
- private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
- ExpandableView child, int numberOfElementsCompletelyIn, int i, int childHeight,
- StackViewState childViewState, float scrollOffset) {
-
-
- // First we calculate the index relative to the current stack window of size at most
- // {@link #MAX_ITEMS_IN_TOP_STACK}
- int paddedIndex = i - 1
- - Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0);
- if (paddedIndex >= 0) {
-
- // We are currently visually entering the top stack
- float distanceToStack = (childHeight + getPaddingAfterChild(algorithmState, child))
- - algorithmState.scrolledPixelsTop;
- if (i == algorithmState.lastTopStackIndex
- && distanceToStack > (mTopStackTotalSize
- + getPaddingAfterChild(algorithmState, child))) {
-
- // Child is currently translating into stack but not yet inside slow down zone.
- // Handle it like the regular scrollview.
- childViewState.yTranslation = scrollOffset;
- } else {
- // Apply stacking logic.
- float numItemsBefore;
- if (i == algorithmState.lastTopStackIndex) {
- numItemsBefore = 1.0f
- - (distanceToStack / (mTopStackTotalSize
- + getPaddingAfterChild(algorithmState, child)));
- } else {
- numItemsBefore = algorithmState.itemsInTopStack - i;
- }
- // The end position of the current child
- float currentChildEndY = mFirstChildMinHeight + mTopStackTotalSize
- - mTopStackIndentationFunctor.getValue(numItemsBefore);
- childViewState.yTranslation = currentChildEndY - childHeight;
- }
- childViewState.location = StackViewState.LOCATION_TOP_STACK_PEEKING;
- } else {
- if (paddedIndex == -1) {
- childViewState.shadowAlpha = 1.0f - algorithmState.partialInTop;
- } else {
- // We are hidden behind the top card and faded out, so we can hide ourselves.
- childViewState.hidden = true;
- childViewState.shadowAlpha = 0.0f;
- }
- childViewState.yTranslation = mFirstChildMinHeight - childHeight;
- childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN;
- }
-
-
- }
/**
- * Find the number of items in the top stack and update the result state if needed.
+ * Update the height of the first child i.e clamp it to the bottom stack
*
- * @param resultState The result state to update if a height change of an child occurs
- * @param algorithmState The state in which the current pass of the algorithm is currently in
+ *
+
+ * @param child the child to update
+ * @param childViewState the viewstate of the child
+ * @param childHeight the height of the child
+ * @param ambientState The ambient state of the algorithm
*/
- private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
+ private void updateFirstChildHeight(ExpandableView child, StackViewState childViewState,
+ int childHeight, AmbientState ambientState) {
- // The y Position if the element would be in a regular scrollView
- float yPositionInScrollView = 0.0f;
- int childCount = algorithmState.visibleChildren.size();
- // find the number of elements in the top stack.
- for (int i = 0; i < childCount; i++) {
- ExpandableView child = algorithmState.visibleChildren.get(i);
- StackViewState childViewState = resultState.getViewStateForView(child);
- int childHeight = getMaxAllowedChildHeight(child);
- int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
- float yPositionInScrollViewAfterElement = yPositionInScrollView
- + childHeight
- + paddingAfterChild;
- if (yPositionInScrollView < algorithmState.scrollY) {
- if (i == 0 && algorithmState.scrollY <= mFirstChildMinHeight) {
-
- // The starting position of the bottom stack peek
- int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
- mCollapseSecondCardPadding;
- // Collapse and expand the first child while the shade is being expanded
- float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
- ? mFirstChildMaxHeight
- : childHeight;
- childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
- mFirstChildMinHeight);
- algorithmState.itemsInTopStack = 1.0f;
-
- } else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
- // According to the regular scroll view we are fully off screen
- algorithmState.itemsInTopStack += 1.0f;
- if (i == 0) {
- childViewState.height = child.getMinHeight();
- }
- } else {
- // According to the regular scroll view we are partially off screen
-
- // How much did we scroll into this child
- algorithmState.scrolledPixelsTop = algorithmState.scrollY
- - yPositionInScrollView;
- algorithmState.partialInTop = (algorithmState.scrolledPixelsTop) / (childHeight
- + paddingAfterChild);
-
- // Our element can be expanded, so this can get negative
- algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop);
- algorithmState.itemsInTopStack += algorithmState.partialInTop;
-
- if (i == 0) {
- // If it is expanded we have to collapse it to a new size
- float newSize = yPositionInScrollViewAfterElement
- - paddingAfterChild
- - algorithmState.scrollY + mFirstChildMinHeight;
- newSize = Math.max(mFirstChildMinHeight, newSize);
- algorithmState.itemsInTopStack = 1.0f;
- childViewState.height = (int) newSize;
- }
- algorithmState.lastTopStackIndex = i;
- break;
- }
- } else {
- algorithmState.lastTopStackIndex = i - 1;
- // We are already past the stack so we can end the loop
- break;
- }
- yPositionInScrollView = yPositionInScrollViewAfterElement;
- }
+ // The starting position of the bottom stack peek
+ int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
+ mCollapseSecondCardPadding + ambientState.getScrollY();
+ // Collapse and expand the first child while the shade is being expanded
+ float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
+ ? mFirstChildMaxHeight
+ : childHeight;
+ childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
+ child.getMinHeight());
}
/**
* Calculate the Z positions for all children based on the number of items in both stacks and
* save it in the resultState
- *
- * @param resultState The result state to update the zTranslation values
+ * @param resultState The result state to update the zTranslation values
* @param algorithmState The state in which the current pass of the algorithm is currently in
+ * @param ambientState The ambient state of the algorithm
*/
private void updateZValuesForState(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
- for (int i = 0; i < childCount; i++) {
- View child = algorithmState.visibleChildren.get(i);
+ int childrenOnTop = 0;
+ for (int i = childCount - 1; i >= 0; i--) {
+ ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState childViewState = resultState.getViewStateForView(child);
- if (i < algorithmState.itemsInTopStack) {
- float stackIndex = algorithmState.itemsInTopStack - i;
-
- // Ensure that the topmost item is a little bit higher than the rest when fully
- // scrolled, to avoid drawing errors when swiping it out
- float max = MAX_ITEMS_IN_TOP_STACK + (i == 0 ? 2.5f : 2);
- stackIndex = Math.min(stackIndex, max);
- if (i == 0 && algorithmState.itemsInTopStack < 2.0f) {
-
- // We only have the top item and an additional item in the top stack,
- // Interpolate the index from 0 to 2 while the second item is
- // translating in.
- stackIndex -= 1.0f;
- if (algorithmState.scrollY > mFirstChildMinHeight) {
-
- // Since there is a shadow treshhold, we cant just interpolate from 0 to
- // 2 but we interpolate from 0.1f to 2.0f when scrolled in. The jump in
- // height will not be noticable since we have padding in between.
- stackIndex = 0.1f + stackIndex * 1.9f;
- }
- }
- childViewState.zTranslation = mZBasicHeight
- + stackIndex * mZDistanceBetweenElements;
- } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
+ if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
+ // We are in the bottom stack
float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
- float translationZ = mZBasicHeight
+ childViewState.zTranslation = mZBasicHeight
- numItemsAbove * mZDistanceBetweenElements;
- childViewState.zTranslation = translationZ;
+ } else if (child.mustStayOnScreen()
+ && childViewState.yTranslation < ambientState.getTopPadding()
+ + ambientState.getStackTranslation()) {
+ // TODO; do this more cleanly
+ childrenOnTop++;
+ childViewState.zTranslation = mZBasicHeight
+ + childrenOnTop * mZDistanceBetweenElements;
} else {
childViewState.zTranslation = mZBasicHeight;
}
@@ -897,7 +705,6 @@
}
public void notifyChildrenChanged(final NotificationStackScrollLayout hostView) {
- mFirstChild = hostView.getFirstChildNotGone();
if (mIsExpansionChanging) {
hostView.post(new Runnable() {
@Override
@@ -922,26 +729,6 @@
public int scrollY;
/**
- * The quantity of items which are in the top stack.
- */
- public float itemsInTopStack;
-
- /**
- * how far in is the element currently transitioning into the top stack
- */
- public float partialInTop;
-
- /**
- * The number of pixels the last child in the top stack has scrolled in to the stack
- */
- public float scrolledPixelsTop;
-
- /**
- * The last item index which is in the top stack.
- */
- public int lastTopStackIndex;
-
- /**
* The quantity of items which are in the bottom stack.
*/
public float itemsInBottomStack;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
index 05fa27d..fa15195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
@@ -24,12 +24,11 @@
// These are flags such that we can create masks for filtering.
public static final int LOCATION_UNKNOWN = 0x00;
- public static final int LOCATION_FIRST_CARD = 0x01;
- public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
- public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
- public static final int LOCATION_MAIN_AREA = 0x08;
- public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
- public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
+ public static final int LOCATION_FIRST_HUN = 0x01;
+ public static final int LOCATION_HIDDEN_TOP = 0x02;
+ public static final int LOCATION_MAIN_AREA = 0x04;
+ public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
+ public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
/** The view isn't layouted at all. */
public static final int LOCATION_GONE = 0x40;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index 3e47d85..3c30410 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -28,6 +28,7 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
@@ -53,13 +54,17 @@
private static final int MAX_RUNNING_TASKS_COUNT = 10;
- private static final int STATE_NO_PIP = 0;
- private static final int STATE_PIP_OVERLAY = 1;
- private static final int STATE_PIP_MENU = 2;
+ public static final int STATE_NO_PIP = 0;
+ public static final int STATE_PIP_OVERLAY = 1;
+ public static final int STATE_PIP_MENU = 2;
private static final int TASK_ID_NO_PIP = -1;
private static final int INVALID_RESOURCE_TYPE = -1;
+ public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
+ public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
+ private int mSuspendPipResizingReason;
+
private Context mContext;
private IActivityManager mActivityManager;
private int mState = STATE_NO_PIP;
@@ -87,7 +92,8 @@
}
if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
- showPipOverlay(false);
+ // Set state to overlay so we show it when the pinned stack animation ends.
+ mState = STATE_PIP_OVERLAY;
launchPipOnboardingActivityIfNeeded();
}
};
@@ -105,6 +111,23 @@
movePipToFullscreen();
}
};
+ private final Runnable mOnPinnedStackAnimationEnded = new Runnable() {
+ @Override
+ public void run() {
+ if (mState == STATE_PIP_OVERLAY) {
+ showPipOverlay();
+ } else if (mState == STATE_PIP_MENU) {
+ showPipMenu();
+ }
+ }
+ };
+
+ private final Runnable mResizePinnedStackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ resizePinnedStack(mState);
+ }
+ };
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -164,7 +187,7 @@
if (!hasPipTasks()) {
startPip();
} else if (mState == STATE_PIP_OVERLAY) {
- showPipMenu();
+ resizePinnedStack(STATE_PIP_MENU);
}
}
@@ -210,11 +233,7 @@
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onMoveToFullscreen();
}
- try {
- mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true);
- } catch (RemoteException e) {
- Log.e(TAG, "moveTasksToFullscreenStack failed", e);
- }
+ resizePinnedStack(mState);
}
/**
@@ -222,25 +241,83 @@
* stack to the default PIP bound {@link com.android.internal.R.string
* .config_defaultPictureInPictureBounds}.
*/
- public void showPipOverlay(boolean resizeStack) {
+ private void showPipOverlay() {
if (DEBUG) Log.d(TAG, "showPipOverlay()");
mState = STATE_PIP_OVERLAY;
Intent intent = new Intent(mContext, PipOverlayActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchStackId(PINNED_STACK_ID);
- if (resizeStack) {
- options.setLaunchBounds(mPipBound);
- }
mContext.startActivity(intent, options.toBundle());
}
/**
+ * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
+ * @param reason The reason for suspending resizing operations on the Pip.
+ */
+ public void suspendPipResizing(int reason) {
+ if (DEBUG) Log.d(TAG,
+ "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+ mSuspendPipResizingReason |= reason;
+ }
+
+ /**
+ * Resumes resizing operation on the Pip that was previously suspended.
+ * @param reason The reason resizing operations on the Pip was suspended.
+ */
+ public void resumePipResizing(int reason) {
+ if ((mSuspendPipResizingReason & reason) == 0) {
+ return;
+ }
+ if (DEBUG) Log.d(TAG,
+ "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+ mSuspendPipResizingReason &= ~reason;
+ mHandler.post(mResizePinnedStackRunnable);
+ }
+
+ /**
+ * Resize the Pip to the appropriate size for the input state.
+ * @param state In Pip state also used to determine the new size for the Pip.
+ */
+ public void resizePinnedStack(int state) {
+ if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
+ mState = state;
+ Rect bounds;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onPipResizeAboutToStart();
+ }
+ switch (mState) {
+ case STATE_PIP_MENU:
+ bounds = mMenuModePipBound;
+ break;
+ case STATE_NO_PIP:
+ bounds = null;
+ break;
+ default:
+ bounds = mPipBound;
+ break;
+ }
+
+ if (mSuspendPipResizingReason != 0) {
+ if (DEBUG) Log.d(TAG,
+ "resizePinnedStack() deferring mSuspendPipResizingReason=" +
+ mSuspendPipResizingReason);
+ return;
+ }
+
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, bounds, true, true, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "showPipMenu failed", e);
+ }
+ }
+
+ /**
* Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
* stack to the centered PIP bound {@link com.android.internal.R.string
* .config_centeredPictureInPictureBounds}.
*/
- public void showPipMenu() {
+ private void showPipMenu() {
if (DEBUG) Log.d(TAG, "showPipMenu()");
mState = STATE_PIP_MENU;
for (int i = mListeners.size() - 1; i >= 0; --i) {
@@ -250,20 +327,13 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchStackId(PINNED_STACK_ID);
- options.setLaunchBounds(mMenuModePipBound);
mContext.startActivity(intent, options.toBundle());
}
- /**
- * Adds {@link Listener}.
- */
public void addListener(Listener listener) {
mListeners.add(listener);
}
- /**
- * Removes {@link Listener}.
- */
public void removeListener(Listener listener) {
mListeners.remove(listener);
}
@@ -338,32 +408,36 @@
@Override
public void onActivityPinned() throws RemoteException {
// Post the message back to the UI thread.
+ if (DEBUG) Log.d(TAG, "onActivityPinned()");
mHandler.post(mOnActivityPinnedRunnable);
}
@Override
public void onPinnedActivityRestartAttempt() {
// Post the message back to the UI thread.
+ if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
mHandler.post(mOnPinnedActivityRestartAttempt);
}
+
+ @Override
+ public void onPinnedStackAnimationEnded() {
+ if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
+ mHandler.post(mOnPinnedStackAnimationEnded);
+ }
}
/**
* A listener interface to receive notification on changes in PIP.
*/
public interface Listener {
- /**
- * Invoked when a PIPed activity is closed.
- */
+ /** Invoked when a PIPed activity is closed. */
void onPipActivityClosed();
- /**
- * Invoked when the PIP menu gets shown.
- */
+ /** Invoked when the PIP menu gets shown. */
void onShowPipMenu();
- /**
- * Invoked when the PIPed activity is returned back to the fullscreen.
- */
+ /** Invoked when the PIPed activity is returned back to the fullscreen. */
void onMoveToFullscreen();
+ /** Invoked when we are above to start resizing the Pip. */
+ void onPipResizeAboutToStart();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index 15c55f5..7e229d4 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -54,7 +54,7 @@
findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mPipManager.showPipOverlay(true);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
finish();
}
});
@@ -62,13 +62,15 @@
@Override
protected void onDestroy() {
- mPipManager.removeListener(this);
super.onDestroy();
+ mPipManager.removeListener(this);
+ mPipManager.resumePipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
}
@Override
public void onBackPressed() {
- mPipManager.showPipOverlay(true);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
finish();
}
@@ -84,4 +86,11 @@
public void onMoveToFullscreen() {
finish();
}
+
+ @Override
+ public void onPipResizeAboutToStart() {
+ finish();
+ mPipManager.suspendPipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
index a0b913a..6f71c92 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
@@ -62,4 +62,8 @@
public void onMoveToFullscreen() {
finish();
}
+
+ @Override
+ public void onPipResizeAboutToStart() {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index bc59a8c..b407935 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -19,8 +19,8 @@
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
-import android.view.View;
+import android.view.View;
import com.android.systemui.R;
/**
@@ -30,25 +30,37 @@
private static final String TAG = "PipOverlayActivity";
private static final boolean DEBUG = false;
- private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 2000;
+ private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 4000;
private final PipManager mPipManager = PipManager.getInstance();
private final Handler mHandler = new Handler();
+ private View mGuideOverlayView;
+ private final Runnable mHideGuideOverlayRunnable = new Runnable() {
+ public void run() {
+ mGuideOverlayView.setVisibility(View.INVISIBLE);
+ }
+ };
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.tv_pip_overlay);
+ mGuideOverlayView = findViewById(R.id.guide_overlay);
mPipManager.addListener(this);
- final View overlayView = findViewById(R.id.guide_overlay);
- // TODO: apply animation
- overlayView.setVisibility(View.VISIBLE);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- overlayView.setVisibility(View.INVISIBLE);
- }
- }, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mHandler.removeCallbacks(mHideGuideOverlayRunnable);
+ mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mHandler.removeCallbacks(mHideGuideOverlayRunnable);
+ finish();
}
@Override
@@ -56,6 +68,8 @@
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
mPipManager.removeListener(this);
+ mPipManager.resumePipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH);
}
@Override
@@ -72,4 +86,11 @@
public void onMoveToFullscreen() {
finish();
}
+
+ @Override
+ public void onPipResizeAboutToStart() {
+ finish();
+ mPipManager.suspendPipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH);
+ }
}
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index 6062137..383e25a 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -32,13 +33,16 @@
import android.os.IBinder;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
/**
* Find the best Service, and bind to it.
@@ -64,17 +68,21 @@
private final Runnable mNewServiceWork;
private final Handler mHandler;
- private Object mLock = new Object();
+ private final Object mLock = new Object();
- // all fields below synchronized on mLock
- private IBinder mBinder; // connected service
- private String mPackageName; // current best package
- private int mVersion = Integer.MIN_VALUE; // current best version
- /**
- * Whether the currently-connected service is multiuser-aware. This can change at run-time
- * when switching from one version of a service to another.
- */
- private boolean mIsMultiuser = false;
+ @GuardedBy("mLock")
+ private int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+ @GuardedBy("mLock")
+ private IBinder mBoundService;
+ @GuardedBy("mLock")
+ private ComponentName mBoundComponent;
+ @GuardedBy("mLock")
+ private String mBoundPackageName;
+ @GuardedBy("mLock")
+ private int mBoundVersion = Integer.MIN_VALUE;
+ @GuardedBy("mLock")
+ private int mBoundUserId = UserHandle.USER_NULL;
public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
List<String> initialPackageNames) {
@@ -84,7 +92,8 @@
String pkg = initialPackageNames.get(i);
try {
HashSet<Signature> set = new HashSet<Signature>();
- Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
+ Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.MATCH_SYSTEM_ONLY
+ | PackageManager.GET_SIGNATURES).signatures;
set.addAll(Arrays.asList(sigs));
sigSets.add(set);
} catch (NameNotFoundException e) {
@@ -108,7 +117,7 @@
// Whether to enable service overlay.
boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
- ArrayList<String> initialPackageNames = new ArrayList<String>();
+ ArrayList<String> initialPackageNames = new ArrayList<String>();
if (enableOverlay) {
// A list of package names used to create the signatures.
String[] pkgs = resources.getStringArray(initialPackageNamesResId);
@@ -126,20 +135,32 @@
mSignatureSets = getSignatureSets(context, initialPackageNames);
}
+ /**
+ * Start this watcher, including binding to the current best match and
+ * re-binding to any better matches down the road.
+ * <p>
+ * Note that if there are no matching encryption-aware services, we may not
+ * bind to a real service until after the current user is unlocked.
+ */
public boolean start() {
synchronized (mLock) {
- if (!bindBestPackageLocked(mServicePackageName)) return false;
+ bindBestPackageLocked(mServicePackageName, false);
}
// listen for user change
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL);
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- switchUser();
+ switchUser(userId);
+ } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ unlockUser(userId);
}
}
}, UserHandle.ALL, intentFilter, null, mHandler);
@@ -153,30 +174,36 @@
}
/**
- * Searches and binds to the best package, or do nothing
- * if the best package is already bound.
- * Only checks the named package, or checks all packages if it
- * is null.
- * Return true if a new package was found to bind to.
+ * Searches and binds to the best package, or do nothing if the best package
+ * is already bound, unless force rebinding is requested.
+ *
+ * @param justCheckThisPackage Only consider this package, or consider all
+ * packages if it is {@code null}.
+ * @param forceRebind Force a rebinding to the best package if it's already
+ * bound.
+ * @return {@code true} if a valid package was found to bind to.
*/
- private boolean bindBestPackageLocked(String justCheckThisPackage) {
+ private boolean bindBestPackageLocked(String justCheckThisPackage, boolean forceRebind) {
Intent intent = new Intent(mAction);
if (justCheckThisPackage != null) {
intent.setPackage(justCheckThisPackage);
}
- List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
- PackageManager.GET_META_DATA, UserHandle.USER_SYSTEM);
+ final List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
+ PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+ mCurrentUserId);
int bestVersion = Integer.MIN_VALUE;
- String bestPackage = null;
+ ComponentName bestComponent = null;
boolean bestIsMultiuser = false;
if (rInfos != null) {
for (ResolveInfo rInfo : rInfos) {
- String packageName = rInfo.serviceInfo.packageName;
+ final ComponentName component = rInfo.serviceInfo.getComponentName();
+ final String packageName = component.getPackageName();
// check signature
try {
PackageInfo pInfo;
- pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
if (!isSignatureMatch(pInfo.signatures)) {
Log.w(mTag, packageName + " resolves service " + mAction
+ ", but has wrong signature, ignoring");
@@ -196,9 +223,9 @@
isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
}
- if (version > mVersion) {
+ if (version > bestVersion) {
bestVersion = version;
- bestPackage = packageName;
+ bestComponent = component;
bestIsMultiuser = isMultiuser;
}
}
@@ -207,42 +234,53 @@
Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
(justCheckThisPackage == null ? ""
: "(" + justCheckThisPackage + ") "), rInfos.size(),
- (bestPackage == null ? "no new best package"
- : "new best package: " + bestPackage)));
+ (bestComponent == null ? "no new best component"
+ : "new best component: " + bestComponent)));
}
} else {
if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
}
- if (bestPackage != null) {
- bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
- return true;
+
+ if (bestComponent == null) {
+ Slog.w(mTag, "Odd, no component found for service " + mAction);
+ unbindLocked();
+ return false;
}
- return false;
+
+ final int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId;
+ final boolean alreadyBound = Objects.equals(bestComponent, mBoundComponent)
+ && bestVersion == mBoundVersion && userId == mBoundUserId;
+ if (forceRebind || !alreadyBound) {
+ unbindLocked();
+ bindToPackageLocked(bestComponent, bestVersion, userId);
+ }
+ return true;
}
private void unbindLocked() {
- String pkg;
- pkg = mPackageName;
- mPackageName = null;
- mVersion = Integer.MIN_VALUE;
- mIsMultiuser = false;
- if (pkg != null) {
- if (D) Log.d(mTag, "unbinding " + pkg);
+ ComponentName component;
+ component = mBoundComponent;
+ mBoundComponent = null;
+ mBoundPackageName = null;
+ mBoundVersion = Integer.MIN_VALUE;
+ mBoundUserId = UserHandle.USER_NULL;
+ if (component != null) {
+ if (D) Log.d(mTag, "unbinding " + component);
mContext.unbindService(this);
}
}
- private void bindToPackageLocked(String packageName, int version, boolean isMultiuser) {
- unbindLocked();
+ private void bindToPackageLocked(ComponentName component, int version, int userId) {
Intent intent = new Intent(mAction);
- intent.setPackage(packageName);
- mPackageName = packageName;
- mVersion = version;
- mIsMultiuser = isMultiuser;
- if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ") ("
- + (isMultiuser ? "multi" : "single") + "-user)");
- mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_NOT_VISIBLE, mIsMultiuser ? UserHandle.SYSTEM : UserHandle.CURRENT);
+ intent.setComponent(component);
+ mBoundComponent = component;
+ mBoundPackageName = component.getPackageName();
+ mBoundVersion = version;
+ mBoundUserId = userId;
+ if (D) Log.d(mTag, "binding " + component + " (v" + version + ") (u" + userId + ")");
+ mContext.bindServiceAsUser(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
+ new UserHandle(userId));
}
public static boolean isSignatureMatch(Signature[] signatures,
@@ -275,106 +313,92 @@
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
synchronized (mLock) {
- if (packageName.equals(mPackageName)) {
- // package updated, make sure to rebind
- unbindLocked();
- }
- // Need to check all packages because this method is also called when a
- // system app is uninstalled and the stock version in reinstalled.
- bindBestPackageLocked(null);
+ final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+ bindBestPackageLocked(null, forceRebind);
}
}
@Override
public void onPackageAdded(String packageName, int uid) {
synchronized (mLock) {
- if (packageName.equals(mPackageName)) {
- // package updated, make sure to rebind
- unbindLocked();
- }
- // check the new package is case it is better
- bindBestPackageLocked(null);
+ final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+ bindBestPackageLocked(null, forceRebind);
}
}
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
- if (packageName.equals(mPackageName)) {
- unbindLocked();
- // the currently bound package was removed,
- // need to search for a new package
- bindBestPackageLocked(null);
- }
+ final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+ bindBestPackageLocked(null, forceRebind);
}
}
@Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
synchronized (mLock) {
- if (packageName.equals(mPackageName)) {
- // service enabled or disabled, make sure to rebind
- unbindLocked();
- }
- // the service might be disabled, need to search for a new
- // package
- bindBestPackageLocked(null);
+ final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+ bindBestPackageLocked(null, forceRebind);
}
return super.onPackageChanged(packageName, uid, components);
}
};
@Override
- public void onServiceConnected(ComponentName name, IBinder binder) {
+ public void onServiceConnected(ComponentName component, IBinder binder) {
synchronized (mLock) {
- String packageName = name.getPackageName();
- if (packageName.equals(mPackageName)) {
- if (D) Log.d(mTag, packageName + " connected");
- mBinder = binder;
+ if (component.equals(mBoundComponent)) {
+ if (D) Log.d(mTag, component + " connected");
+ mBoundService = binder;
if (mHandler !=null && mNewServiceWork != null) {
mHandler.post(mNewServiceWork);
}
} else {
- Log.w(mTag, "unexpected onServiceConnected: " + packageName);
+ Log.w(mTag, "unexpected onServiceConnected: " + component);
}
}
}
@Override
- public void onServiceDisconnected(ComponentName name) {
+ public void onServiceDisconnected(ComponentName component) {
synchronized (mLock) {
- String packageName = name.getPackageName();
- if (D) Log.d(mTag, packageName + " disconnected");
+ if (D) Log.d(mTag, component + " disconnected");
- if (packageName.equals(mPackageName)) {
- mBinder = null;
+ if (component.equals(mBoundComponent)) {
+ mBoundService = null;
}
}
}
- public String getBestPackageName() {
+ public @Nullable String getBestPackageName() {
synchronized (mLock) {
- return mPackageName;
+ return mBoundPackageName;
}
}
public int getBestVersion() {
synchronized (mLock) {
- return mVersion;
+ return mBoundVersion;
}
}
- public IBinder getBinder() {
+ public @Nullable IBinder getBinder() {
synchronized (mLock) {
- return mBinder;
+ return mBoundService;
}
}
- public void switchUser() {
+ public void switchUser(int userId) {
synchronized (mLock) {
- if (!mIsMultiuser) {
- unbindLocked();
- bindBestPackageLocked(mServicePackageName);
+ mCurrentUserId = userId;
+ bindBestPackageLocked(mServicePackageName, false);
+ }
+ }
+
+ public void unlockUser(int userId) {
+ synchronized (mLock) {
+ if (userId == mCurrentUserId) {
+ bindBestPackageLocked(mServicePackageName, false);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 393edb6..104217a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1451,6 +1451,7 @@
static final int VR_MODE_CHANGE_MSG = 63;
static final int NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG = 64;
static final int NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG = 65;
+ static final int NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG = 66;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1979,6 +1980,20 @@
}
break;
}
+ case NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG: {
+ synchronized (ActivityManagerService.this) {
+ for (int i = mTaskStackListeners.beginBroadcast() - 1; i >= 0; i--) {
+ try {
+ // Make a one-way callback to the listener
+ mTaskStackListeners.getBroadcastItem(i).onPinnedStackAnimationEnded();
+ } catch (RemoteException e){
+ // Handled by the RemoteCallbackList
+ }
+ }
+ mTaskStackListeners.finishBroadcast();
+ }
+ break;
+ }
case NOTIFY_CLEARTEXT_NETWORK_MSG: {
final int uid = msg.arg1;
final byte[] firstPacket = (byte[]) msg.obj;
@@ -7161,8 +7176,8 @@
final Rect bounds = (mStackSupervisor.getStack(PINNED_STACK_ID) == null)
? mDefaultPinnedStackBounds : null;
- mStackSupervisor.moveActivityToStackLocked(
- r, PINNED_STACK_ID, "enterPictureInPicture", bounds);
+ mStackSupervisor.moveActivityToPinnedStackLocked(
+ r, "enterPictureInPicture", bounds);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -10722,6 +10737,12 @@
return;
}
+ // We're only interested in providers that are encryption unaware, and
+ // we don't care about uninstalled apps, since there's no way they're
+ // running at this point.
+ final int matchFlags = GET_PROVIDERS | MATCH_ENCRYPTION_UNAWARE
+ | MATCH_DEBUG_TRIAGED_MISSING;
+
synchronized (this) {
final int NP = mProcessNames.getMap().size();
for (int ip = 0; ip < NP; ip++) {
@@ -10736,8 +10757,7 @@
try {
final String pkgName = app.pkgList.keyAt(ig);
final PackageInfo pkgInfo = AppGlobals.getPackageManager()
- .getPackageInfo(pkgName,
- GET_PROVIDERS | MATCH_ENCRYPTION_UNAWARE, userId);
+ .getPackageInfo(pkgName, matchFlags, userId);
if (pkgInfo != null && !ArrayUtils.isEmpty(pkgInfo.providers)) {
for (ProviderInfo provInfo : pkgInfo.providers) {
Log.v(TAG, "Installing " + provInfo);
@@ -11052,6 +11072,16 @@
mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG).sendToTarget();
}
+ /** Notifies all listeners when the pinned stack animation ends. */
+ @Override
+ public void notifyPinnedStackAnimationEnded() {
+ synchronized (this) {
+ mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
+ mHandler.obtainMessage(
+ NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG).sendToTarget();
+ }
+ }
+
@Override
public void notifyCleartextNetwork(int uid, byte[] firstPacket) {
mHandler.obtainMessage(NOTIFY_CLEARTEXT_NETWORK_MSG, uid, 0, firstPacket).sendToTarget();
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 0bccffa..8560a9e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1587,6 +1587,33 @@
// determined individually unlike other stacks where the visibility or fullscreen
// status of an activity in a previous task affects other.
behindFullscreenActivity = stackVisibility == STACK_INVISIBLE;
+ } else if (mStackId == HOME_STACK_ID) {
+ if (task.isHomeTask()) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ // No other task in the home stack should be visible behind the home activity.
+ // Home activities is usually a translucent activity with the wallpaper behind
+ // them. However, when they don't have the wallpaper behind them, we want to
+ // show activities in the next application stack behind them vs. another
+ // task in the home stack like recents.
+ behindFullscreenActivity = true;
+ } else if (task.isRecentsTask()
+ && task.getTaskToReturnTo() == APPLICATION_ACTIVITY_TYPE) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Recents task returning to app: at " + task
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ // We don't want any other tasks in the home stack visible if the recents
+ // activity is going to be returning to an application activity type.
+ // We do this to preserve the visible order the user used to get into the
+ // recents activity. The recents activity is normally translucent and if it
+ // doesn't have the wallpaper behind it the next activity in the home stack
+ // shouldn't be visible when the home stack is brought to the front to display
+ // the recents activity from an app.
+ behindFullscreenActivity = true;
+ }
+
}
}
@@ -1687,33 +1714,7 @@
+ " behindFullscreenActivity=" + behindFullscreenActivity);
// At this point, nothing else needs to be shown in this task.
behindFullscreenActivity = true;
- } else if (isHomeStack()) {
- if (r.isHomeActivity()) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home activity: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- // No other activity in the home stack should be visible behind the home activity.
- // Home activities is usually a translucent activity with the wallpaper behind them.
- // However, when they don't have the wallpaper behind them, we want to show
- // activities in the next application stack behind them vs. another activity in the
- // home stack like recents.
- behindFullscreenActivity = true;
- } else if (r.isRecentsActivity()
- && task.getTaskToReturnTo() == APPLICATION_ACTIVITY_TYPE) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Recents activity returning to app: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
- // We don't want any other activities in the home stack visible if the recents
- // activity is going to be returning to an application activity type.
- // We do this to preserve the visible order the user used to get into the recents
- // activity. The recents activity is normally translucent and if it doesn't have
- // the wallpaper behind it the next activity in the home stack shouldn't be visible
- // when the home stack is brought to the front to display the recents activity from
- // an app.
- behindFullscreenActivity = true;
- }
- } else if (r.frontOfTask && task.isOverHomeStack()) {
+ } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
+ " stackInvisible=" + stackInvisible
+ " behindFullscreenActivity=" + behindFullscreenActivity);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0beef53..26108a3 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -148,6 +148,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ANIMATE;
import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
@@ -2321,36 +2322,44 @@
return false;
}
- moveActivityToStackLocked(r, PINNED_STACK_ID, "moveTopActivityToPinnedStack", null);
- mWindowManager.animateResizePinnedStack(bounds);
+ moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds);
return true;
}
- void moveActivityToStackLocked(ActivityRecord r, int stackId, String reason, Rect bounds) {
- final TaskRecord task = r.task;
- if (task.mActivities.size() == 1) {
- // There is only one activity in the task. So, we can just move the task over to the
- // stack without re-parenting the activity in a different task.
- moveTaskToStackLocked(
- task.taskId, stackId, ON_TOP, FORCE_FOCUS, reason, true /* animate */);
- } else {
- final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
- stack.moveActivityToStack(r);
+ void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds) {
+ mWindowManager.deferSurfaceLayout();
+ try {
+ final TaskRecord task = r.task;
+
+ // Need to make sure the pinned stack exist so we can resize it below...
+ final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+
+ // Resize the pinned stack to match the current size of the task the activity we are
+ // going to be moving is currently contained in. We do this to have the right starting
+ // animation bounds for the pinned stack to the desired bounds the caller wants.
+ resizeStackLocked(PINNED_STACK_ID, task.mBounds, null /* tempTaskBounds */,
+ null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
+ true /* allowResizeInDockedMode */);
+
+ if (task.mActivities.size() == 1) {
+ // There is only one activity in the task. So, we can just move the task over to
+ // the stack without re-parenting the activity in a different task.
+ moveTaskToStackLocked(
+ task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
+ } else {
+ stack.moveActivityToStack(r);
+ }
+ } finally {
+ mWindowManager.continueSurfaceLayout();
}
- if (bounds != null) {
- resizeStackLocked(stackId, bounds, null /* tempTaskBounds */,
- null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true);
- }
-
- // The task might have already been running and its visibility needs to be synchronized with
- // the visibility of the stack / windows.
+ // The task might have already been running and its visibility needs to be synchronized
+ // with the visibility of the stack / windows.
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- if (stackId == PINNED_STACK_ID) {
- mService.notifyActivityPinnedLocked();
- }
+ mWindowManager.animateResizePinnedStack(bounds);
+ mService.notifyActivityPinnedLocked();
}
void positionTaskInStackLocked(int taskId, int stackId, int position) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b360b89..28be456 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1001,7 +1001,7 @@
// If the activity is not focusable, we can't resume it, but still would like to
// make sure it becomes visible as it starts (this will also trigger entry
// animation). An example of this are PIP activities.
- mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
+ mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
} else {
mTargetStack.addRecentActivityLocked(mStartActivity);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 10f0977..98a7ead 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -933,10 +933,15 @@
}
return false;
}
+
boolean isHomeTask() {
return taskType == HOME_ACTIVITY_TYPE;
}
+ boolean isRecentsTask() {
+ return taskType == RECENTS_ACTIVITY_TYPE;
+ }
+
boolean isApplicationTask() {
return taskType == APPLICATION_ACTIVITY_TYPE;
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 4c269989..e5e86ac 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -1024,7 +1024,8 @@
final IPackageManager pm = AppGlobals.getPackageManager();
final ComponentName service = job.getService();
try {
- ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
+ ServiceInfo si = pm.getServiceInfo(service,
+ PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(uid));
if (si == null) {
throw new IllegalArgumentException("No such service " + service);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 21330db..36ddbcf 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3498,7 +3498,8 @@
try {
// TODO: it might be faster to return a boolean from package manager rather than the
// whole application info. Revisit and make the API change.
- ai = AppGlobals.getPackageManager().getApplicationInfo(pkg, 0, userId);
+ ai = AppGlobals.getPackageManager().getApplicationInfo(pkg,
+ PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
if (ai == null) {
Slog.w(TAG, "No application info for package " + pkg + " and user " + userId);
return false;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index bba0d40..383c1ab 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -578,7 +578,8 @@
ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
try {
- mPm.getPackageInfo(rule.component.getPackageName(), 0);
+ mPm.getPackageInfo(rule.component.getPackageName(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (PackageManager.NameNotFoundException e) {
newConfig.automaticRules.removeAt(i);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c8645b4..e47d514 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3427,6 +3427,18 @@
}
@Override
+ public @Nullable String getServicesSystemSharedLibraryPackageName() {
+ synchronized (mPackages) {
+ SharedLibraryEntry libraryEntry = mSharedLibraries.get(
+ PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES);
+ if (libraryEntry != null) {
+ return libraryEntry.apk;
+ }
+ }
+ return null;
+ }
+
+ @Override
public FeatureInfo[] getSystemAvailableFeatures() {
Collection<FeatureInfo> featSet;
synchronized (mPackages) {
@@ -8220,7 +8232,7 @@
*
* If {@code extractLibs} is true, native libraries are extracted from the app if required.
*/
- public void derivePackageAbi(PackageParser.Package pkg, File scanFile,
+ private void derivePackageAbi(PackageParser.Package pkg, File scanFile,
String cpuAbiOverride, boolean extractLibs)
throws PackageManagerException {
// TODO: We can probably be smarter about this stuff. For installed apps,
@@ -8301,16 +8313,17 @@
if (abi32 >= 0) {
final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
if (abi64 >= 0) {
- pkg.applicationInfo.secondaryCpuAbi = abi;
+ if (cpuAbiOverride == null && pkg.use32bitAbi) {
+ pkg.applicationInfo.secondaryCpuAbi = pkg.applicationInfo.primaryCpuAbi;
+ pkg.applicationInfo.primaryCpuAbi = abi;
+ } else {
+ pkg.applicationInfo.secondaryCpuAbi = abi;
+ }
} else {
pkg.applicationInfo.primaryCpuAbi = abi;
}
}
- if (cpuAbiOverride != null &&
- cpuAbiOverride.equals(pkg.applicationInfo.secondaryCpuAbi)) {
- pkg.applicationInfo.secondaryCpuAbi = pkg.applicationInfo.primaryCpuAbi;
- pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
- }
+
} else {
String[] abiList = (cpuAbiOverride != null) ?
new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
@@ -14396,7 +14409,6 @@
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
PackageSetting ps;
- int removeUser = -1;
synchronized (mPackages) {
ps = mSettings.mPackages.get(packageName);
@@ -14411,7 +14423,9 @@
Slog.d(TAG, "Uninstalled child package:" + packageName + " for user:"
+ ((user == null) ? UserHandle.USER_ALL : user));
}
- if (!clearPackageStateForUser(ps, removeUser, outInfo)) {
+ final int removedUserId = (user != null) ? user.getIdentifier()
+ : UserHandle.USER_ALL;
+ if (!clearPackageStateForUser(ps, removedUserId, outInfo)) {
return false;
}
markPackageUninstalledForUserLPw(ps, user);
@@ -14435,9 +14449,9 @@
if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
// Other user still have this package installed, so all
// we need to do is clear this user's data and save that
- // it is uninstalled.
- if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
- if (!clearPackageStateForUser(ps, removeUser, outInfo)) {
+ // it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
+ if (!clearPackageStateForUser(ps, user.getIdentifier(), outInfo)) {
return false;
}
scheduleWritePackageRestrictionsLocked(user);
@@ -14454,7 +14468,7 @@
// we need to do is clear this user's data and save that
// it is uninstalled.
if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
- if (!clearPackageStateForUser(ps, removeUser, outInfo)) {
+ if (!clearPackageStateForUser(ps, user.getIdentifier(), outInfo)) {
return false;
}
scheduleWritePackageRestrictionsLocked(user);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a8b72892..7244676 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -38,6 +38,7 @@
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
@@ -945,6 +946,13 @@
mDragResizing = false;
mService.requestTraversal();
}
+ if (mStackId == PINNED_STACK_ID) {
+ try {
+ mService.mActivityManager.notifyPinnedStackAnimationEnded();
+ } catch (RemoteException e) {
+ // I don't believe you...
+ }
+ }
}
@Override
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index c122c5a..f1cbb9a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -774,9 +774,9 @@
}
/**
- * Register a {@link PhoneAccount} for use by the system. When registering
- * {@link PhoneAccount}s, existing registrations will be overwritten if the
- * {@link PhoneAccountHandle} matches that of a {@link PhoneAccount} which is already
+ * Register a {@link PhoneAccount} for use by the system that will be stored in Device Encrypted
+ * storage. When registering {@link PhoneAccount}s, existing registrations will be overwritten
+ * if the {@link PhoneAccountHandle} matches that of a {@link PhoneAccount} which is already
* registered. Once registered, the {@link PhoneAccount} is listed to the user as an option
* when placing calls. The user may still need to enable the {@link PhoneAccount} within
* the phone app settings before the account is usable.
@@ -1166,11 +1166,16 @@
/**
* Registers a new incoming call. A {@link ConnectionService} should invoke this method when it
* has an incoming call. The specified {@link PhoneAccountHandle} must have been registered
- * with {@link #registerPhoneAccount}. Once invoked, this method will cause the system to bind
- * to the {@link ConnectionService} associated with the {@link PhoneAccountHandle} and request
- * additional information about the call (See
- * {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming call UI.
- *
+ * with {@link #registerPhoneAccount} and the user must have enabled the corresponding
+ * {@link PhoneAccount}. This can be checked using {@link #getPhoneAccount}. Once invoked, this
+ * method will cause the system to bind to the {@link ConnectionService} associated with the
+ * {@link PhoneAccountHandle} and request additional information about the call
+ * (See {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming
+ * call UI.
+ * <p>
+ * A {@link SecurityException} will be thrown if either the {@link PhoneAccountHandle} does not
+ * correspond to a registered {@link PhoneAccount} or the associated {@link PhoneAccount} is not
+ * currently enabled by the user.
* @param phoneAccount A {@link PhoneAccountHandle} registered with
* {@link #registerPhoneAccount}.
* @param extras A bundle that will be passed through to
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index ffb73f6..4e6d638 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -17,6 +17,7 @@
package android.test.mock;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -806,6 +807,12 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public @Nullable String getServicesSystemSharedLibraryPackageName() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public FeatureInfo[] getSystemAvailableFeatures() {
throw new UnsupportedOperationException();
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index f74b93a..88b6270 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -43,6 +43,9 @@
link/TableMerger.cpp \
link/XmlReferenceLinker.cpp \
process/SymbolTable.cpp \
+ proto/ProtoHelpers.cpp \
+ proto/TableProtoDeserializer.cpp \
+ proto/TableProtoSerializer.cpp \
unflatten/BinaryResourceParser.cpp \
unflatten/ResChunkPullParser.cpp \
util/BigBuffer.cpp \
@@ -67,13 +70,14 @@
xml/XmlPullParser.cpp \
xml/XmlUtil.cpp
+sources += Format.proto
+
testSources := \
compile/IdAssigner_test.cpp \
compile/PseudolocaleGenerator_test.cpp \
compile/Pseudolocalizer_test.cpp \
compile/XmlIdCollector_test.cpp \
filter/ConfigFilter_test.cpp \
- flatten/FileExportWriter_test.cpp \
flatten/TableFlattener_test.cpp \
flatten/XmlFlattener_test.cpp \
link/AutoVersioner_test.cpp \
@@ -83,7 +87,7 @@
link/TableMerger_test.cpp \
link/XmlReferenceLinker_test.cpp \
process/SymbolTable_test.cpp \
- unflatten/FileExportHeaderReader_test.cpp \
+ proto/TableProtoSerializer_test.cpp \
util/BigBuffer_test.cpp \
util/Maybe_test.cpp \
util/StringPiece_test.cpp \
@@ -105,6 +109,7 @@
toolSources := \
compile/Compile.cpp \
+ dump/Dump.cpp \
link/Link.cpp
hostLdLibs :=
@@ -119,6 +124,9 @@
libpng \
libbase
+hostSharedLibs := \
+ libprotobuf-cpp-lite
+
ifneq ($(strip $(USE_MINGW)),)
hostStaticLibs += libz
else
@@ -127,21 +135,23 @@
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
+protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST)
# ==========================================================
# Build the host static library: libaapt2
# ==========================================================
include $(CLEAR_VARS)
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
LOCAL_MODULE := libaapt2
LOCAL_SRC_FILES := $(sources)
LOCAL_STATIC_LIBRARIES += $(hostStaticLibs)
LOCAL_CFLAGS += $(cFlags)
LOCAL_CPPFLAGS += $(cppFlags)
+LOCAL_C_INCLUDES += $(protoIncludes)
include $(BUILD_HOST_STATIC_LIBRARY)
-
# ==========================================================
# Build the host tests: libaapt2_tests
# ==========================================================
@@ -152,9 +162,11 @@
LOCAL_SRC_FILES := $(testSources)
LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_SHARED_LIBRARIES += $(hostSharedLibs)
LOCAL_LDLIBS += $(hostLdLibs)
LOCAL_CFLAGS += $(cFlags)
LOCAL_CPPFLAGS += $(cppFlags)
+LOCAL_C_INCLUDES += $(protoIncludes)
include $(BUILD_HOST_NATIVE_TEST)
@@ -167,9 +179,11 @@
LOCAL_SRC_FILES := $(main) $(toolSources)
LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_SHARED_LIBRARIES += $(hostSharedLibs)
LOCAL_LDLIBS += $(hostLdLibs)
LOCAL_CFLAGS += $(cFlags)
LOCAL_CPPFLAGS += $(cppFlags)
+LOCAL_C_INCLUDES += $(protoIncludes)
include $(BUILD_HOST_EXECUTABLE)
diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto
new file mode 100644
index 0000000..d05425c
--- /dev/null
+++ b/tools/aapt2/Format.proto
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package aapt.pb;
+
+message ConfigDescription {
+ optional bytes data = 1;
+ optional string product = 2;
+}
+
+message StringPool {
+ optional bytes data = 1;
+}
+
+message CompiledFile {
+ message Symbol {
+ optional string resource_name = 1;
+ optional uint32 line_no = 2;
+ }
+
+ optional string resource_name = 1;
+ optional ConfigDescription config = 2;
+ optional string source_path = 3;
+ repeated Symbol exported_symbols = 4;
+}
+
+message ResourceTable {
+ optional StringPool string_pool = 1;
+ optional StringPool source_pool = 2;
+ optional StringPool symbol_pool = 3;
+ repeated Package packages = 4;
+}
+
+message Package {
+ optional uint32 package_id = 1;
+ optional string package_name = 2;
+ repeated Type types = 3;
+}
+
+message Type {
+ optional uint32 id = 1;
+ optional string name = 2;
+ repeated Entry entries = 3;
+}
+
+message SymbolStatus {
+ enum Visibility {
+ Unknown = 0;
+ Private = 1;
+ Public = 2;
+ }
+ optional Visibility visibility = 1;
+ optional Source source = 2;
+ optional string comment = 3;
+}
+
+message Entry {
+ optional uint32 id = 1;
+ optional string name = 2;
+ optional SymbolStatus symbol_status = 3;
+ repeated ConfigValue config_values = 4;
+}
+
+message ConfigValue {
+ optional ConfigDescription config = 1;
+ optional Value value = 2;
+}
+
+message Source {
+ optional uint32 path_idx = 1;
+ optional uint32 line_no = 2;
+ optional uint32 col_no = 3;
+}
+
+message Reference {
+ enum Type {
+ Ref = 0;
+ Attr = 1;
+ }
+ optional Type type = 1;
+ optional uint32 id = 2;
+ optional uint32 symbol_idx = 3;
+ optional bool private = 4;
+}
+
+message Id {
+}
+
+message String {
+ optional uint32 idx = 1;
+}
+
+message RawString {
+ optional uint32 idx = 1;
+}
+
+message FileReference {
+ optional uint32 path_idx = 1;
+}
+
+message Primitive {
+ optional uint32 type = 1;
+ optional uint32 data = 2;
+}
+
+message Attribute {
+ message Symbol {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference name = 3;
+ optional uint32 value = 4;
+ }
+ optional uint32 format_flags = 1;
+ optional int32 min_int = 2;
+ optional int32 max_int = 3;
+ repeated Symbol symbols = 4;
+}
+
+message Style {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference key = 3;
+ optional Item item = 4;
+ }
+
+ optional Reference parent = 1;
+ optional Source parent_source = 2;
+ repeated Entry entries = 3;
+}
+
+message Styleable {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference attr = 3;
+ }
+ repeated Entry entries = 1;
+}
+
+message Array {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Item item = 3;
+ }
+ repeated Entry entries = 1;
+}
+
+message Plural {
+ enum Arity {
+ Zero = 0;
+ One = 1;
+ Two = 2;
+ Few = 3;
+ Many = 4;
+ Other = 5;
+ }
+
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Arity arity = 3;
+ optional Item item = 4;
+ }
+ repeated Entry entries = 1;
+}
+
+message Item {
+ optional Reference ref = 1;
+ optional String str = 2;
+ optional RawString raw_str = 3;
+ optional FileReference file = 4;
+ optional Id id = 5;
+ optional Primitive prim = 6;
+}
+
+message CompoundValue {
+ optional Attribute attr = 1;
+ optional Style style = 2;
+ optional Styleable styleable = 3;
+ optional Array array = 4;
+ optional Plural plural = 5;
+}
+
+message Value {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional bool weak = 3;
+
+ optional Item item = 4;
+ optional CompoundValue compound_value = 5;
+}
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 248e7ad..a2fadd9 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -23,6 +23,7 @@
extern int compile(const std::vector<StringPiece>& args);
extern int link(const std::vector<StringPiece>& args);
+extern int dump(const std::vector<StringPiece>& args);
} // namespace aapt
@@ -41,12 +42,14 @@
return aapt::compile(args);
} else if (command == "link" || command == "l") {
return aapt::link(args);
+ } else if (command == "dump" || command == "d") {
+ return aapt::dump(args);
}
std::cerr << "unknown command '" << command << "'\n";
} else {
std::cerr << "no command specified\n";
}
- std::cerr << "\nusage: aapt2 [compile|link] ..." << std::endl;
+ std::cerr << "\nusage: aapt2 [compile|link|dump] ..." << std::endl;
return 1;
}
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 07f62af..74c48b0 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -51,6 +51,10 @@
}
bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
+ if (str.empty()) {
+ return false;
+ }
+
size_t offset = 0;
bool priv = false;
if (str.data()[0] == u'*') {
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 64ca971..a0fbcc6 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -45,7 +45,8 @@
* `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
* was present.
*/
-bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, bool* outPrivate);
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource,
+ bool* outPrivate = nullptr);
/*
* Returns true if the string was parsed as a reference (@[+][package:]type/name), with
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index c9f93e1..7425f97 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -58,6 +58,8 @@
EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv));
EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
EXPECT_TRUE(actualPriv);
+
+ EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece16(), &actual, &actualPriv));
}
TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index b93e6d8..ab9c792 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -19,7 +19,6 @@
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "util/Util.h"
-#include "flatten/ResourceTypeExtensions.h"
#include <androidfw/ResourceTypes.h>
#include <limits>
@@ -47,7 +46,7 @@
}
bool RawString::flatten(android::Res_value* outValue) const {
- outValue->dataType = ExtendedTypes::TYPE_RAW_STRING;
+ outValue->dataType = android::Res_value::TYPE_STRING;
outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
return true;
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 8e317db..dc2e28e 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -154,8 +154,8 @@
bool privateReference = false;
Reference();
- Reference(const ResourceNameRef& n, Type type = Type::kResource);
- Reference(const ResourceId& i, Type type = Type::kResource);
+ explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
+ explicit Reference(const ResourceId& i, Type type = Type::kResource);
bool flatten(android::Res_value* outValue) const override;
Reference* clone(StringPool* newPool) const override;
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index 94042e3..5493039 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -18,6 +18,7 @@
#define AAPT_VALUE_VISITOR_H
#include "ResourceValues.h"
+#include "ResourceTable.h"
namespace aapt {
@@ -140,6 +141,23 @@
return visitor.value;
}
+
+inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
+ for (auto& type : pkg->types) {
+ for (auto& entry : type->entries) {
+ for (auto& configValue : entry->values) {
+ configValue.value->accept(visitor);
+ }
+ }
+ }
+}
+
+inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) {
+ for (auto& pkg : table->packages) {
+ visitAllValuesInPackage(pkg.get(), visitor);
+ }
+}
+
} // namespace aapt
#endif // AAPT_VALUE_VISITOR_H
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 689ace6..1eefb82 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -24,15 +24,17 @@
#include "compile/PseudolocaleGenerator.h"
#include "compile/XmlIdCollector.h"
#include "flatten/Archive.h"
-#include "flatten/FileExportWriter.h"
-#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "proto/ProtoSerialize.h"
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/Util.h"
#include "xml/XmlDom.h"
#include "xml/XmlPullParser.h"
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/coded_stream.h>
+
#include <dirent.h>
#include <fstream>
#include <string>
@@ -232,34 +234,95 @@
}
}
- // Assign IDs to prepare the table for flattening.
- IdAssigner idAssigner;
- if (!idAssigner.consume(context, &table)) {
- return false;
- }
-
- // Flatten the table.
- BigBuffer buffer(1024);
- TableFlattenerOptions tableFlattenerOptions;
- tableFlattenerOptions.useExtendedChunks = true;
- TableFlattener flattener(&buffer, tableFlattenerOptions);
- if (!flattener.consume(context, &table)) {
- return false;
- }
-
+ // Create the file/zip entry.
if (!writer->startEntry(outputPath, 0)) {
context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
+
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
+ {
+ google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+
+ if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
}
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
+ if (!writer->finishEntry()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
+ return false;
+ }
+ return true;
+}
+
+static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
+ const BigBuffer& buffer, IArchiveWriter* writer,
+ IDiagnostics* diag) {
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Create the header.
+ std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
+
+ {
+ // The stream must be destroyed before we finish the entry, or else
+ // some data won't be flushed.
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+ // interface.
+ google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+ CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
+ for (const BigBuffer::Block& block : buffer) {
+ if (!outputStream.Write(block.buffer.get(), block.size)) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
+ }
+ }
+ }
+
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
+}
+
+static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
+ const android::FileMap& map, IArchiveWriter* writer,
+ IDiagnostics* diag) {
+ // Start the entry so we can write the header.
+ if (!writer->startEntry(outputPath, 0)) {
+ diag->error(DiagMessage(outputPath) << "failed to open file");
+ return false;
+ }
+
+ // Create the header.
+ std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
+
+ {
+ // The stream must be destroyed before we finish the entry, or else
+ // some data won't be flushed.
+ // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+ // interface.
+ google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+ CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
+ if (!outputStream.Write(map.getDataPtr(), map.getDataLength())) {
+ diag->error(DiagMessage(outputPath) << "failed to write data");
+ return false;
+ }
+ }
+
+ if (!writer->finishEntry()) {
+ diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+ return false;
+ }
+ return true;
}
static bool compileXml(IAaptContext* context, const CompileOptions& options,
@@ -267,7 +330,6 @@
const std::string& outputPath) {
std::unique_ptr<xml::XmlResource> xmlRes;
-
{
std::ifstream fin(pathData.source.path, std::ifstream::binary);
if (!fin) {
@@ -295,30 +357,18 @@
xmlRes->file.source = pathData.source;
BigBuffer buffer(1024);
- ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
-
XmlFlattenerOptions xmlFlattenerOptions;
xmlFlattenerOptions.keepRawValues = true;
- XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
+ XmlFlattener flattener(&buffer, xmlFlattenerOptions);
if (!flattener.consume(context, xmlRes.get())) {
return false;
}
- fileExportWriter.finish();
-
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+ if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer,
+ context->getDiagnostics())) {
return false;
}
-
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
-
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
+ return true;
}
static bool compilePng(IAaptContext* context, const CompileOptions& options,
@@ -330,8 +380,6 @@
resFile.config = pathData.config;
resFile.source = pathData.source;
- ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
-
{
std::ifstream fin(pathData.source.path, std::ifstream::binary);
if (!fin) {
@@ -340,26 +388,16 @@
}
Png png(context->getDiagnostics());
- if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
+ if (!png.process(pathData.source, &fin, &buffer, {})) {
return false;
}
}
- fileExportWriter.finish();
-
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+ if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
+ context->getDiagnostics())) {
return false;
}
-
- if (writer->writeEntry(buffer)) {
- if (writer->finishEntry()) {
- return true;
- }
- }
-
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
+ return true;
}
static bool compileFile(IAaptContext* context, const CompileOptions& options,
@@ -371,8 +409,6 @@
resFile.config = pathData.config;
resFile.source = pathData.source;
- ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
-
std::string errorStr;
Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
if (!f) {
@@ -380,35 +416,10 @@
return false;
}
- if (!writer->startEntry(outputPath, 0)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+ if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
+ context->getDiagnostics())) {
return false;
}
-
- // Manually set the size and don't call finish(). This is because we are not copying from
- // the buffer the entire file.
- fileExportWriter.getChunkHeader()->size =
- util::hostToDevice32(buffer.size() + f.value().getDataLength());
-
- if (!writer->writeEntry(buffer)) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
- }
-
- // Only write if we have something to write. This is because mmap fails with length of 0,
- // but we still want to compile the file to get the resource ID.
- if (f.value().getDataPtr() && f.value().getDataLength() > 0) {
- if (!writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
- }
- }
-
- if (!writer->finishEntry()) {
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
- }
-
return true;
}
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 80c6bbc..aa4a580 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -64,14 +64,12 @@
// Mark entry ID as taken.
if (!usedEntryIds.insert(entry->id.value()).second) {
// This ID existed before!
- ResourceNameRef nameRef =
- { package->name, type->type, entry->name };
- ResourceId takenId(package->id.value(), type->id.value(),
- entry->id.value());
+ ResourceNameRef nameRef(package->name, type->type, entry->name);
context->getDiagnostics()->error(DiagMessage()
<< "resource '" << nameRef << "' "
- << "has duplicate ID '"
- << takenId << "'");
+ << "has duplicate entry ID "
+ << std::hex << (int) entry->id.value()
+ << std::dec);
return false;
}
}
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
new file mode 100644
index 0000000..915fae8
--- /dev/null
+++ b/tools/aapt2/dump/Dump.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Debug.h"
+#include "Diagnostics.h"
+#include "Flags.h"
+#include "process/IResourceTableConsumer.h"
+#include "proto/ProtoSerialize.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <vector>
+
+namespace aapt {
+
+//struct DumpOptions {
+//
+//};
+
+void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t len,
+ const Source& source, IAaptContext* context) {
+ std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source,
+ context->getDiagnostics());
+ if (!file) {
+ return;
+ }
+
+ std::cout << "Resource: " << file->name << "\n"
+ << "Config: " << file->config << "\n"
+ << "Source: " << file->source << "\n";
+}
+
+void dumpCompiledTable(const pb::ResourceTable& pbTable, const Source& source,
+ IAaptContext* context) {
+ std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
+ context->getDiagnostics());
+ if (!table) {
+ return;
+ }
+
+ Debug::printTable(table.get());
+}
+
+void tryDumpFile(IAaptContext* context, const std::string& filePath) {
+ std::string err;
+ Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
+ if (!file) {
+ context->getDiagnostics()->error(DiagMessage(filePath) << err);
+ return;
+ }
+
+ android::FileMap* fileMap = &file.value();
+
+ // Try as a compiled table.
+ pb::ResourceTable pbTable;
+ if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) {
+ dumpCompiledTable(pbTable, Source(filePath), context);
+ return;
+ }
+
+ // Try as a compiled file.
+ CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength());
+ if (const pb::CompiledFile* pbFile = input.CompiledFile()) {
+ dumpCompiledFile(*pbFile, input.data(), input.size(), Source(filePath), context);
+ return;
+ }
+}
+
+class DumpContext : public IAaptContext {
+public:
+ IDiagnostics* getDiagnostics() override {
+ return &mDiagnostics;
+ }
+
+ NameMangler* getNameMangler() override {
+ abort();
+ return nullptr;
+ }
+
+ StringPiece16 getCompilationPackage() override {
+ return {};
+ }
+
+ uint8_t getPackageId() override {
+ return 0;
+ }
+
+ ISymbolTable* getExternalSymbols() override {
+ abort();
+ return nullptr;
+ }
+
+private:
+ StdErrDiagnostics mDiagnostics;
+};
+
+/**
+ * Entry point for dump command.
+ */
+int dump(const std::vector<StringPiece>& args) {
+ //DumpOptions options;
+ Flags flags = Flags();
+ if (!flags.parse("aapt2 dump", args, &std::cerr)) {
+ return 1;
+ }
+
+ DumpContext context;
+
+ for (const std::string& arg : flags.getArgs()) {
+ tryDumpFile(&context, arg);
+ }
+ return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index 6da1d2a..34c10ad 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -22,6 +22,7 @@
#include "util/Files.h"
#include "util/StringPiece.h"
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <fstream>
#include <memory>
#include <string>
@@ -40,13 +41,18 @@
size_t uncompressedSize;
};
-struct IArchiveWriter {
+struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
virtual ~IArchiveWriter() = default;
virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
virtual bool writeEntry(const BigBuffer& buffer) = 0;
virtual bool writeEntry(const void* data, size_t len) = 0;
virtual bool finishEntry() = 0;
+
+ // CopyingOutputStream implementations.
+ bool Write(const void* buffer, int size) override {
+ return writeEntry(buffer, size);
+ }
};
std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
diff --git a/tools/aapt2/flatten/FileExportWriter.h b/tools/aapt2/flatten/FileExportWriter.h
deleted file mode 100644
index 7688fa7..0000000
--- a/tools/aapt2/flatten/FileExportWriter.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_FLATTEN_FILEEXPORTWRITER_H
-#define AAPT_FLATTEN_FILEEXPORTWRITER_H
-
-#include "StringPool.h"
-
-#include "flatten/ResourceTypeExtensions.h"
-#include "flatten/ChunkWriter.h"
-#include "process/IResourceTableConsumer.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <utils/misc.h>
-
-namespace aapt {
-
-static ChunkWriter wrapBufferWithFileExportHeader(BigBuffer* buffer, ResourceFile* res) {
- ChunkWriter fileExportWriter(buffer);
- FileExport_header* fileExport = fileExportWriter.startChunk<FileExport_header>(
- RES_FILE_EXPORT_TYPE);
-
- ExportedSymbol* symbolRefs = nullptr;
- if (!res->exportedSymbols.empty()) {
- symbolRefs = fileExportWriter.nextBlock<ExportedSymbol>(
- res->exportedSymbols.size());
- }
- fileExport->exportedSymbolCount = util::hostToDevice32(res->exportedSymbols.size());
-
- StringPool symbolExportPool;
- memcpy(fileExport->magic, "AAPT", NELEM(fileExport->magic));
- fileExport->config = res->config;
- fileExport->config.swapHtoD();
- fileExport->name.index = util::hostToDevice32(symbolExportPool.makeRef(res->name.toString())
- .getIndex());
- fileExport->source.index = util::hostToDevice32(symbolExportPool.makeRef(util::utf8ToUtf16(
- res->source.path)).getIndex());
-
- for (const SourcedResourceName& name : res->exportedSymbols) {
- symbolRefs->name.index = util::hostToDevice32(symbolExportPool.makeRef(name.name.toString())
- .getIndex());
- symbolRefs->line = util::hostToDevice32(name.line);
- symbolRefs++;
- }
-
- StringPool::flattenUtf16(fileExportWriter.getBuffer(), symbolExportPool);
- return fileExportWriter;
-}
-
-} // namespace aapt
-
-#endif /* AAPT_FLATTEN_FILEEXPORTWRITER_H */
diff --git a/tools/aapt2/flatten/FileExportWriter_test.cpp b/tools/aapt2/flatten/FileExportWriter_test.cpp
deleted file mode 100644
index 32fc203..0000000
--- a/tools/aapt2/flatten/FileExportWriter_test.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Resource.h"
-
-#include "flatten/FileExportWriter.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-#include "test/Common.h"
-
-#include <gtest/gtest.h>
-
-namespace aapt {
-
-TEST(FileExportWriterTest, FlattenResourceFileDataWithNoExports) {
- ResourceFile resFile = {
- test::parseNameOrDie(u"@android:layout/main.xml"),
- test::parseConfigOrDie("sw600dp-v4"),
- Source{ "res/layout/main.xml" },
- };
-
- BigBuffer buffer(1024);
- ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
- *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
- writer.finish();
-
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
-
- // There should be more data (string pool) besides the header and our data.
- ASSERT_GT(buffer.size(), sizeof(FileExport_header) + sizeof(uint32_t));
-
- // Write at the end of this chunk is our data.
- uint32_t* val = (uint32_t*)(data.get() + buffer.size()) - 1;
- EXPECT_EQ(*val, 42u);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index 02bff2c..3e20ad6 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -22,208 +22,6 @@
namespace aapt {
/**
- * New android::ResChunk_header types defined
- * for AAPT to use.
- *
- * TODO(adamlesinski): Consider reserving these
- * enums in androidfw/ResourceTypes.h to avoid
- * future collisions.
- */
-enum {
- /**
- * A chunk that contains an entire file that
- * has been compiled.
- */
- RES_FILE_EXPORT_TYPE = 0x000c,
-
- RES_TABLE_PUBLIC_TYPE = 0x000d,
-
- /**
- * A chunk that holds the string pool
- * for source entries (path/to/source:line).
- */
- RES_TABLE_SOURCE_POOL_TYPE = 0x000e,
-
- /**
- * A chunk holding names of externally
- * defined symbols and offsets to where
- * they are referenced in the table.
- */
- RES_TABLE_SYMBOL_TABLE_TYPE = 0x000f,
-};
-
-/**
- * New resource types that are meant to only be used
- * by AAPT and will not end up on the device.
- */
-struct ExtendedTypes {
- enum {
- /**
- * A raw string value that hasn't had its escape sequences
- * processed nor whitespace removed.
- */
- TYPE_RAW_STRING = 0xfe,
- };
-};
-
-/**
- * New types for a ResTable_map.
- */
-struct ExtendedResTableMapTypes {
- enum {
- /**
- * Type that contains the source path of the next item in the map.
- */
- ATTR_SOURCE_PATH = Res_MAKEINTERNAL(0xffff),
-
- /**
- * Type that contains the source line of the next item in the map.
- */
- ATTR_SOURCE_LINE = Res_MAKEINTERNAL(0xfffe),
-
- /**
- * Type that contains the comment of the next item in the map.
- */
- ATTR_COMMENT = Res_MAKEINTERNAL(0xfffd)
- };
-};
-
-/**
- * Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool.
- */
-struct FileExport_header {
- android::ResChunk_header header;
-
- /**
- * MAGIC value. Must be 'AAPT' (0x41415054)
- */
- uint8_t magic[4];
-
- /**
- * Version of AAPT that built this file.
- */
- uint32_t version;
-
- /**
- * The resource name.
- */
- android::ResStringPool_ref name;
-
- /**
- * Configuration of this file.
- */
- android::ResTable_config config;
-
- /**
- * Original source path of this file.
- */
- android::ResStringPool_ref source;
-
- /**
- * Number of symbols exported by this file.
- */
- uint32_t exportedSymbolCount;
-};
-
-struct ExportedSymbol {
- android::ResStringPool_ref name;
- uint32_t line;
-};
-
-struct Public_header {
- android::ResChunk_header header;
-
- /**
- * The ID of the type this structure refers to.
- */
- uint8_t typeId;
-
- /**
- * Reserved. Must be 0.
- */
- uint8_t res0;
-
- /**
- * Reserved. Must be 0.
- */
- uint16_t res1;
-
- /**
- * Number of public entries.
- */
- uint32_t count;
-};
-
-/**
- * A structure representing source data for a resource entry.
- * Appears after an android::ResTable_entry or android::ResTable_map_entry.
- *
- * TODO(adamlesinski): This causes some issues when runtime code checks
- * the size of an android::ResTable_entry. It assumes it is an
- * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
- * which may not be true if this structure is present.
- */
-struct ResTable_entry_source {
- /**
- * File path reference.
- */
- android::ResStringPool_ref path;
-
- /**
- * Line number this resource was defined on.
- */
- uint32_t line;
-
- /**
- * Comment string reference.
- */
- android::ResStringPool_ref comment;
-};
-
-struct Public_entry {
- uint16_t entryId;
-
- enum : uint16_t {
- kUndefined = 0,
- kPublic = 1,
- kPrivate = 2,
- };
-
- uint16_t state;
- android::ResStringPool_ref key;
- ResTable_entry_source source;
-};
-
-/**
- * A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE.
- * Following the header are count number of SymbolTable_entry
- * structures, followed by an android::ResStringPool_header.
- */
-struct SymbolTable_header {
- android::ResChunk_header header;
-
- /**
- * Number of SymbolTable_entry structures following
- * this header.
- */
- uint32_t count;
-};
-
-struct SymbolTable_entry {
- /**
- * Offset from the beginning of the resource table
- * where the symbol entry is referenced.
- */
- uint32_t offset;
-
- /**
- * The index into the string pool where the name of this
- * symbol exists.
- */
- android::ResStringPool_ref name;
-};
-
-/**
* An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
* struct.
*/
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 26d7c2c..71ab3db 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -51,157 +51,49 @@
dst[i] = 0;
}
+static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
+ if (a.key.id) {
+ if (b.key.id) {
+ return a.key.id.value() < b.key.id.value();
+ }
+ return true;
+ } else if (!b.key.id) {
+ return a.key.name.value() < b.key.name.value();
+ }
+ return false;
+}
+
struct FlatEntry {
ResourceEntry* entry;
Value* value;
// The entry string pool index to the entry's name.
uint32_t entryKey;
-
- // The source string pool index to the source file path.
- uint32_t sourcePathKey;
- uint32_t sourceLine;
-
- // The source string pool index to the comment.
- uint32_t commentKey;
};
-class SymbolWriter {
+class MapFlattenVisitor : public RawValueVisitor {
public:
- struct Entry {
- StringPool::Ref name;
- size_t offset;
- };
-
- std::vector<Entry> symbols;
-
- explicit SymbolWriter(StringPool* pool) : mPool(pool) {
- }
-
- void addSymbol(const Reference& ref, size_t offset) {
- const ResourceName& name = ref.name.value();
- std::u16string fullName;
- if (ref.privateReference) {
- fullName += u"*";
- }
-
- if (!name.package.empty()) {
- fullName += name.package + u":";
- }
- fullName += toString(name.type).toString() + u"/" + name.entry;
- symbols.push_back(Entry{ mPool->makeRef(fullName), offset });
- }
-
- void shiftAllOffsets(size_t offset) {
- for (Entry& entry : symbols) {
- entry.offset += offset;
- }
- }
-
-private:
- StringPool* mPool;
-};
-
-struct MapFlattenVisitor : public RawValueVisitor {
using RawValueVisitor::visit;
- SymbolWriter* mSymbols;
- FlatEntry* mEntry;
- BigBuffer* mBuffer;
- StringPool* mSourcePool;
- StringPool* mCommentPool;
- bool mUseExtendedChunks;
-
- size_t mEntryCount = 0;
- const Reference* mParent = nullptr;
-
- MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
- StringPool* sourcePool, StringPool* commentPool,
- bool useExtendedChunks) :
- mSymbols(symbols), mEntry(entry), mBuffer(buffer), mSourcePool(sourcePool),
- mCommentPool(commentPool), mUseExtendedChunks(useExtendedChunks) {
- }
-
- void flattenKey(Reference* key, ResTable_map* outEntry) {
- if (!key->id || (key->privateReference && mUseExtendedChunks)) {
- assert(key->name && "reference must have a name");
-
- outEntry->name.ident = util::hostToDevice32(0);
- mSymbols->addSymbol(*key, (mBuffer->size() - sizeof(ResTable_map)) +
- offsetof(ResTable_map, name));
- } else {
- outEntry->name.ident = util::hostToDevice32(key->id.value().id);
- }
- }
-
- void flattenValue(Item* value, ResTable_map* outEntry) {
- bool privateRef = false;
- if (Reference* ref = valueCast<Reference>(value)) {
- privateRef = ref->privateReference && mUseExtendedChunks;
- if (!ref->id || privateRef) {
- assert(ref->name && "reference must have a name");
-
- mSymbols->addSymbol(*ref, (mBuffer->size() - sizeof(ResTable_map)) +
- offsetof(ResTable_map, value) + offsetof(Res_value, data));
- }
- }
-
- bool result = value->flatten(&outEntry->value);
- if (privateRef) {
- outEntry->value.data = 0;
- }
- assert(result && "flatten failed");
- }
-
- void flattenEntry(Reference* key, Item* value) {
- ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
- flattenKey(key, outEntry);
- flattenValue(value, outEntry);
- outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
- mEntryCount++;
- }
-
- void flattenMetaData(Value* value) {
- if (!mUseExtendedChunks) {
- return;
- }
-
- Reference key(ResourceId{ ExtendedResTableMapTypes::ATTR_SOURCE_PATH });
- StringPool::Ref sourcePathRef = mSourcePool->makeRef(
- util::utf8ToUtf16(value->getSource().path));
- BinaryPrimitive val(Res_value::TYPE_INT_DEC,
- static_cast<uint32_t>(sourcePathRef.getIndex()));
- flattenEntry(&key, &val);
-
- if (value->getSource().line) {
- key.id = ResourceId(ExtendedResTableMapTypes::ATTR_SOURCE_LINE);
- val.value.data = static_cast<uint32_t>(value->getSource().line.value());
- flattenEntry(&key, &val);
- }
-
- if (!value->getComment().empty()) {
- key.id = ResourceId(ExtendedResTableMapTypes::ATTR_COMMENT);
- StringPool::Ref commentRef = mCommentPool->makeRef(value->getComment());
- val.value.data = static_cast<uint32_t>(commentRef.getIndex());
- flattenEntry(&key, &val);
- }
+ MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) :
+ mOutEntry(outEntry), mBuffer(buffer) {
}
void visit(Attribute* attr) override {
{
- Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
+ Reference key = Reference(ResTable_map::ATTR_TYPE);
BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
flattenEntry(&key, &val);
}
if (attr->minInt != std::numeric_limits<int32_t>::min()) {
- Reference key(ResourceId{ ResTable_map::ATTR_MIN });
+ Reference key = Reference(ResTable_map::ATTR_MIN);
BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
flattenEntry(&key, &val);
}
if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
- Reference key(ResourceId{ ResTable_map::ATTR_MAX });
+ Reference key = Reference(ResTable_map::ATTR_MAX);
BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
flattenEntry(&key, &val);
}
@@ -212,22 +104,11 @@
}
}
- static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
- if (a.key.id) {
- if (b.key.id) {
- return a.key.id.value() < b.key.id.value();
- }
- return true;
- } else if (!b.key.id) {
- return a.key.name.value() < b.key.name.value();
- }
- return false;
- }
-
void visit(Style* style) override {
if (style->parent) {
- // Parents are treated a bit differently, so record the existence and move on.
- mParent = &style->parent.value();
+ const Reference& parentRef = style->parent.value();
+ assert(parentRef.id && "parent has no ID");
+ mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id);
}
// Sort the style.
@@ -235,7 +116,6 @@
for (Style::Entry& entry : style->entries) {
flattenEntry(&entry.key, entry.value.get());
- flattenMetaData(&entry.key);
}
}
@@ -243,8 +123,8 @@
for (auto& attrRef : styleable->entries) {
BinaryPrimitive val(Res_value{});
flattenEntry(&attrRef, &val);
- flattenMetaData(&attrRef);
}
+
}
void visit(Array* array) override {
@@ -253,7 +133,6 @@
flattenValue(item.get(), outEntry);
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
mEntryCount++;
- flattenMetaData(item.get());
}
}
@@ -297,18 +176,45 @@
Reference key(q);
flattenEntry(&key, plural->values[i].get());
- flattenMetaData(plural->values[i].get());
}
}
+
+ /**
+ * Call this after visiting a Value. This will finish any work that
+ * needs to be done to prepare the entry.
+ */
+ void finish() {
+ mOutEntry->count = util::hostToDevice32(mEntryCount);
+ }
+
+private:
+ void flattenKey(Reference* key, ResTable_map* outEntry) {
+ assert(key->id && "key has no ID");
+ outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+ }
+
+ void flattenValue(Item* value, ResTable_map* outEntry) {
+ bool result = value->flatten(&outEntry->value);
+ assert(result && "flatten failed");
+ }
+
+ void flattenEntry(Reference* key, Item* value) {
+ ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+ flattenKey(key, outEntry);
+ flattenValue(value, outEntry);
+ outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+ mEntryCount++;
+ }
+
+ ResTable_entry_ext* mOutEntry;
+ BigBuffer* mBuffer;
+ size_t mEntryCount = 0;
};
class PackageFlattener {
public:
- PackageFlattener(IDiagnostics* diag, TableFlattenerOptions options,
- ResourceTablePackage* package, SymbolWriter* symbolWriter,
- StringPool* sourcePool) :
- mDiag(diag), mOptions(options), mPackage(package), mSymbols(symbolWriter),
- mSourcePool(sourcePool) {
+ PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) :
+ mDiag(diag), mPackage(package) {
}
bool flattenPackage(BigBuffer* buffer) {
@@ -337,9 +243,6 @@
pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
- // Add the ResTable_package header/type/key strings to the offset.
- mSymbols->shiftAllOffsets(pkgWriter.size());
-
// Append the types.
buffer->appendBuffer(std::move(typeBuffer));
@@ -349,12 +252,9 @@
private:
IDiagnostics* mDiag;
- TableFlattenerOptions mOptions;
ResourceTablePackage* mPackage;
StringPool mTypePool;
StringPool mKeyPool;
- SymbolWriter* mSymbols;
- StringPool* mSourcePool;
template <typename T, bool IsItem>
T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
@@ -376,62 +276,24 @@
outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
}
- outEntry->key.index = util::hostToDevice32(entry->entryKey);
- outEntry->size = sizeof(T);
-
- if (mOptions.useExtendedChunks) {
- // Write the extra source block. This will be ignored by the Android runtime.
- ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
- sourceBlock->path.index = util::hostToDevice32(entry->sourcePathKey);
- sourceBlock->line = util::hostToDevice32(entry->sourceLine);
- sourceBlock->comment.index = util::hostToDevice32(entry->commentKey);
- outEntry->size += sizeof(*sourceBlock);
- }
-
outEntry->flags = util::hostToDevice16(outEntry->flags);
- outEntry->size = util::hostToDevice16(outEntry->size);
+ outEntry->key.index = util::hostToDevice32(entry->entryKey);
+ outEntry->size = util::hostToDevice16(sizeof(T));
return result;
}
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
if (Item* item = valueCast<Item>(entry->value)) {
writeEntry<ResTable_entry, true>(entry, buffer);
- bool privateRef = false;
- if (Reference* ref = valueCast<Reference>(entry->value)) {
- // If there is no ID or the reference is private and we allow extended chunks,
- // write out a 0 and mark the symbol table with the name of the reference.
- privateRef = (ref->privateReference && mOptions.useExtendedChunks);
- if (!ref->id || privateRef) {
- assert(ref->name && "reference must have at least a name");
- mSymbols->addSymbol(*ref, buffer->size() + offsetof(Res_value, data));
- }
- }
Res_value* outValue = buffer->nextBlock<Res_value>();
bool result = item->flatten(outValue);
assert(result && "flatten failed");
- if (privateRef) {
- // Force the value of 0 so we look up the symbol at unflatten time.
- outValue->data = 0;
- }
outValue->size = util::hostToDevice16(sizeof(*outValue));
} else {
- const size_t beforeEntry = buffer->size();
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
- MapFlattenVisitor visitor(mSymbols, entry, buffer, mSourcePool, mSourcePool,
- mOptions.useExtendedChunks);
+ MapFlattenVisitor visitor(outEntry, buffer);
entry->value->accept(&visitor);
- outEntry->count = util::hostToDevice32(visitor.mEntryCount);
- if (visitor.mParent) {
- const bool forceSymbol = visitor.mParent->privateReference &&
- mOptions.useExtendedChunks;
- if (!visitor.mParent->id || forceSymbol) {
- assert(visitor.mParent->name && "reference must have a name");
- mSymbols->addSymbol(*visitor.mParent,
- beforeEntry + offsetof(ResTable_entry_ext, parent));
- } else {
- outEntry->parent.ident = util::hostToDevice32(visitor.mParent->id.value().id);
- }
- }
+ visitor.finish();
}
return true;
}
@@ -480,7 +342,7 @@
std::vector<ResourceTableType*> collectAndSortTypes() {
std::vector<ResourceTableType*> sortedTypes;
for (auto& type : mPackage->types) {
- if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
+ if (type->type == ResourceType::kStyleable) {
// Styleables aren't real Resource Types, they are represented in the R.java
// file.
continue;
@@ -551,52 +413,6 @@
return true;
}
- bool flattenPublic(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
- BigBuffer* buffer) {
- ChunkWriter publicWriter(buffer);
- Public_header* publicHeader = publicWriter.startChunk<Public_header>(RES_TABLE_PUBLIC_TYPE);
- publicHeader->typeId = type->id.value();
-
- for (ResourceEntry* entry : *sortedEntries) {
- if (entry->symbolStatus.state != SymbolState::kUndefined) {
- // Write the public status of this entry.
- Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
- publicEntry->entryId = util::hostToDevice32(entry->id.value());
- publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
- entry->name).getIndex());
- publicEntry->source.path.index = util::hostToDevice32(mSourcePool->makeRef(
- util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
- if (entry->symbolStatus.source.line) {
- publicEntry->source.line = util::hostToDevice32(
- entry->symbolStatus.source.line.value());
- }
- publicEntry->source.comment.index = util::hostToDevice32(mSourcePool->makeRef(
- entry->symbolStatus.comment).getIndex());
-
- switch (entry->symbolStatus.state) {
- case SymbolState::kPrivate:
- publicEntry->state = Public_entry::kPrivate;
- break;
-
- case SymbolState::kPublic:
- publicEntry->state = Public_entry::kPublic;
- break;
-
- case SymbolState::kUndefined:
- publicEntry->state = Public_entry::kUndefined;
- break;
- }
-
- // Don't hostToDevice until the last step.
- publicHeader->count += 1;
- }
- }
-
- publicHeader->count = util::hostToDevice32(publicHeader->count);
- publicWriter.finish();
- return true;
- }
-
bool flattenTypes(BigBuffer* buffer) {
// Sort the types by their IDs. They will be inserted into the StringPool in this order.
std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
@@ -620,12 +436,6 @@
return false;
}
- if (mOptions.useExtendedChunks) {
- if (!flattenPublic(type, &sortedEntries, buffer)) {
- return false;
- }
- }
-
// The binary resource table lists resource entries for each configuration.
// We store them inverted, where a resource entry lists the values for each
// configuration available. Here we reverse this to match the binary table.
@@ -635,26 +445,8 @@
// Group values by configuration.
for (auto& configValue : entry->values) {
- Value* value = configValue.value.get();
-
- const StringPool::Ref sourceRef = mSourcePool->makeRef(
- util::utf8ToUtf16(value->getSource().path));
-
- uint32_t lineNumber = 0;
- if (value->getSource().line) {
- lineNumber = value->getSource().line.value();
- }
-
- const StringPool::Ref commentRef = mSourcePool->makeRef(value->getComment());
-
- configToEntryListMap[configValue.config]
- .push_back(FlatEntry{
- entry,
- value,
- keyIndex,
- (uint32_t) sourceRef.getIndex(),
- lineNumber,
- (uint32_t) commentRef.getIndex() });
+ configToEntryListMap[configValue.config].push_back(FlatEntry{
+ entry, configValue.value.get(), keyIndex });
}
}
@@ -692,86 +484,18 @@
// Flatten the values string pool.
StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
- // If we have a reference to a symbol that doesn't exist, we don't know its resource ID.
- // We encode the name of the symbol along with the offset of where to include the resource ID
- // once it is found.
- StringPool symbolPool;
- std::vector<SymbolWriter::Entry> symbolOffsets;
-
- // String pool holding the source paths of each value.
- StringPool sourcePool;
-
BigBuffer packageBuffer(1024);
// Flatten each package.
for (auto& package : table->packages) {
- const size_t beforePackageSize = packageBuffer.size();
-
- // All packages will share a single global symbol pool.
- SymbolWriter packageSymbolWriter(&symbolPool);
-
- PackageFlattener flattener(context->getDiagnostics(), mOptions, package.get(),
- &packageSymbolWriter, &sourcePool);
+ PackageFlattener flattener(context->getDiagnostics(), package.get());
if (!flattener.flattenPackage(&packageBuffer)) {
return false;
}
-
- // The symbols are offset only from their own Package start. Offset them from the
- // start of the packageBuffer.
- packageSymbolWriter.shiftAllOffsets(beforePackageSize);
-
- // Extract all the symbols to offset
- symbolOffsets.insert(symbolOffsets.end(),
- std::make_move_iterator(packageSymbolWriter.symbols.begin()),
- std::make_move_iterator(packageSymbolWriter.symbols.end()));
}
- SymbolTable_entry* symbolEntryData = nullptr;
- if (mOptions.useExtendedChunks) {
- if (!symbolOffsets.empty()) {
- // Sort the offsets so we can scan them linearly.
- std::sort(symbolOffsets.begin(), symbolOffsets.end(),
- [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
- return a.offset < b.offset;
- });
-
- // Write the Symbol header.
- ChunkWriter symbolWriter(tableWriter.getBuffer());
- SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
- RES_TABLE_SYMBOL_TABLE_TYPE);
- symbolHeader->count = util::hostToDevice32(symbolOffsets.size());
-
- symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(symbolOffsets.size());
- StringPool::flattenUtf8(symbolWriter.getBuffer(), symbolPool);
- symbolWriter.finish();
- }
-
- if (sourcePool.size() > 0) {
- // Write out source pool.
- ChunkWriter srcWriter(tableWriter.getBuffer());
- srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
- StringPool::flattenUtf8(srcWriter.getBuffer(), sourcePool);
- srcWriter.finish();
- }
- }
-
- const size_t beforePackagesSize = tableWriter.size();
-
// Finally merge all the packages into the main buffer.
tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
-
- // Update the offsets to their final values.
- if (symbolEntryData) {
- for (SymbolWriter::Entry& entry : symbolOffsets) {
- symbolEntryData->name.index = util::hostToDevice32(entry.name.getIndex());
-
- // The symbols were all calculated with the packageBuffer offset. We need to
- // add the beginning of the output buffer.
- symbolEntryData->offset = util::hostToDevice32(entry.offset + beforePackagesSize);
- symbolEntryData++;
- }
- }
-
tableWriter.finish();
return true;
}
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
index 901b129..0ab0197 100644
--- a/tools/aapt2/flatten/TableFlattener.h
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -24,28 +24,15 @@
class BigBuffer;
class ResourceTable;
-struct TableFlattenerOptions {
- /**
- * Specifies whether to output extended chunks, like
- * source information and missing symbol entries. Default
- * is false.
- *
- * Set this to true when emitting intermediate resource table.
- */
- bool useExtendedChunks = false;
-};
-
class TableFlattener : public IResourceTableConsumer {
public:
- TableFlattener(BigBuffer* buffer, TableFlattenerOptions options) :
- mBuffer(buffer), mOptions(options) {
+ TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {
}
bool consume(IAaptContext* context, ResourceTable* table) override;
private:
BigBuffer* mBuffer;
- TableFlattenerOptions mOptions;
};
} // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 7030603..39c4fd3 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -38,9 +38,7 @@
::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
BigBuffer buffer(1024);
- TableFlattenerOptions options = {};
- options.useExtendedChunks = true;
- TableFlattener flattener(&buffer, options);
+ TableFlattener flattener(&buffer);
if (!flattener.consume(mContext.get(), table)) {
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
}
@@ -54,9 +52,7 @@
::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) {
BigBuffer buffer(1024);
- TableFlattenerOptions options = {};
- options.useExtendedChunks = true;
- TableFlattener flattener(&buffer, options);
+ TableFlattener flattener(&buffer);
if (!flattener.consume(mContext.get(), table)) {
return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
}
@@ -210,58 +206,6 @@
Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
}
-TEST_F(TableFlattenerTest, FlattenUnlinkedTable) {
- std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
- .setPackageId(u"com.app.test", 0x7f)
- .addValue(u"@com.app.test:integer/one", ResourceId(0x7f020000),
- test::buildReference(u"@android:integer/foo"))
- .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f030000), test::StyleBuilder()
- .setParent(u"@android:style/Theme.Material")
- .addItem(u"@android:attr/background", {})
- .addItem(u"@android:attr/colorAccent",
- test::buildReference(u"@com.app.test:color/green"))
- .build())
- .build();
-
- {
- // Need access to stringPool to make RawString.
- Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
- style->entries[0].value = util::make_unique<RawString>(table->stringPool.makeRef(u"foo"));
- }
-
- ResourceTable finalTable;
- ASSERT_TRUE(flatten(table.get(), &finalTable));
-
- Reference* ref = test::getValue<Reference>(&finalTable, u"@com.app.test:integer/one");
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->name);
- EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@android:integer/foo"));
-
- Style* style = test::getValue<Style>(&finalTable, u"@com.app.test:style/Theme");
- ASSERT_NE(style, nullptr);
- AAPT_ASSERT_TRUE(style->parent);
- AAPT_ASSERT_TRUE(style->parent.value().name);
- EXPECT_EQ(style->parent.value().name.value(),
- test::parseNameOrDie(u"@android:style/Theme.Material"));
-
- ASSERT_EQ(2u, style->entries.size());
-
- AAPT_ASSERT_TRUE(style->entries[0].key.name);
- EXPECT_EQ(style->entries[0].key.name.value(),
- test::parseNameOrDie(u"@android:attr/background"));
- RawString* raw = valueCast<RawString>(style->entries[0].value.get());
- ASSERT_NE(raw, nullptr);
- EXPECT_EQ(*raw->value, u"foo");
-
- AAPT_ASSERT_TRUE(style->entries[1].key.name);
- EXPECT_EQ(style->entries[1].key.name.value(),
- test::parseNameOrDie(u"@android:attr/colorAccent"));
- ref = valueCast<Reference>(style->entries[1].value.get());
- ASSERT_NE(ref, nullptr);
- AAPT_ASSERT_TRUE(ref->name);
- EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
-}
-
TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
Attribute attr(false);
attr.typeMask = android::ResTable_map::TYPE_INTEGER;
@@ -284,33 +228,4 @@
EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
}
-TEST_F(TableFlattenerTest, FlattenSourceAndCommentsForChildrenOfCompoundValues) {
- Style style;
- Reference key(test::parseNameOrDie(u"@android:attr/foo"));
- key.id = ResourceId(0x01010000);
- key.setSource(Source("test").withLine(2));
- key.setComment(StringPiece16(u"comment"));
- style.entries.push_back(Style::Entry{ key, util::make_unique<Id>() });
-
- test::ResourceTableBuilder builder = test::ResourceTableBuilder();
- std::unique_ptr<ResourceTable> table = builder
- .setPackageId(u"android", 0x01)
- .addValue(u"@android:attr/foo", ResourceId(0x01010000),
- test::AttributeBuilder().build())
- .addValue(u"@android:style/foo", ResourceId(0x01020000),
- std::unique_ptr<Style>(style.clone(builder.getStringPool())))
- .build();
-
- ResourceTable result;
- ASSERT_TRUE(flatten(table.get(), &result));
-
- Style* actualStyle = test::getValue<Style>(&result, u"@android:style/foo");
- ASSERT_NE(nullptr, actualStyle);
- ASSERT_EQ(1u, actualStyle->entries.size());
-
- Reference* actualKey = &actualStyle->entries[0].key;
- EXPECT_EQ(key.getSource(), actualKey->getSource());
- EXPECT_EQ(key.getComment(), actualKey->getComment());
-}
-
} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index fd76e88..8e32179 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -19,6 +19,7 @@
#include "Flags.h"
#include "Locale.h"
#include "NameMangler.h"
+#include "ResourceUtils.h"
#include "compile/IdAssigner.h"
#include "filter/ConfigFilter.h"
#include "flatten/Archive.h"
@@ -35,12 +36,14 @@
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
+#include "proto/ProtoSerialize.h"
#include "unflatten/BinaryResourceParser.h"
-#include "unflatten/FileExportHeaderReader.h"
#include "util/Files.h"
#include "util/StringPiece.h"
#include "xml/XmlDom.h"
+#include <google/protobuf/io/coded_stream.h>
+
#include <fstream>
#include <sys/stat.h>
#include <vector>
@@ -144,6 +147,22 @@
return table;
}
+ std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
+ const void* data, size_t len) {
+ pb::ResourceTable pbTable;
+ if (!pbTable.ParseFromArray(data, len)) {
+ mContext->getDiagnostics()->error(DiagMessage(source) << "invalid compiled table");
+ return {};
+ }
+
+ std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
+ mContext->getDiagnostics());
+ if (!table) {
+ return {};
+ }
+ return table;
+ }
+
/**
* Inflates an XML file from the source path.
*/
@@ -161,18 +180,16 @@
const Source& source,
const void* data, size_t len,
IDiagnostics* diag) {
- std::string errorStr;
- ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
- if (offset < 0) {
- diag->error(DiagMessage(source) << errorStr);
+ CompiledFileInputStream inputStream(data, len);
+ if (!inputStream.CompiledFile()) {
+ diag->error(DiagMessage(source) << "invalid compiled file header");
return {};
}
- std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
- reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
- len - static_cast<size_t>(offset),
- diag,
- source);
+ const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
+ const size_t xmlDataLen = inputStream.size();
+
+ std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
if (!xmlRes) {
return {};
}
@@ -182,11 +199,16 @@
static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
const void* data, size_t len,
IDiagnostics* diag) {
- std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
- std::string errorStr;
- ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
- if (offset < 0) {
- diag->error(DiagMessage(source) << errorStr);
+ CompiledFileInputStream inputStream(data, len);
+ const pb::CompiledFile* pbFile = inputStream.CompiledFile();
+ if (!pbFile) {
+ diag->error(DiagMessage(source) << "invalid compiled file header");
+ return {};
+ }
+
+ std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source,
+ diag);
+ if (!resFile) {
return {};
}
return resFile;
@@ -214,16 +236,16 @@
return false;
}
- std::string errorStr;
- ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
- if (offset < 0) {
- mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
+ CompiledFileInputStream inputStream(data->data(), data->size());
+ if (!inputStream.CompiledFile()) {
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource())
+ << "invalid compiled file header");
return false;
}
if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
- if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
- data->size() - static_cast<size_t>(offset))) {
+ if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
+ inputStream.size())) {
if (writer->finishEntry()) {
return true;
}
@@ -307,9 +329,7 @@
bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
BigBuffer buffer(1024);
- TableFlattenerOptions options = {};
- options.useExtendedChunks = mOptions.staticLib;
- TableFlattener flattener(&buffer, options);
+ TableFlattener flattener(&buffer);
if (!flattener.consume(mContext, table)) {
return false;
}
@@ -445,8 +465,8 @@
return false;
}
- std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
- data->size());
+ std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), data->data(),
+ data->size());
if (!table) {
return false;
}
diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp
new file mode 100644
index 0000000..99981c5
--- /dev/null
+++ b/tools/aapt2/proto/ProtoHelpers.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "proto/ProtoHelpers.h"
+
+namespace aapt {
+
+void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool) {
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf8(&buffer, pool);
+
+ std::string* data = outPbPool->mutable_data();
+ data->reserve(buffer.size());
+
+ size_t offset = 0;
+ for (const BigBuffer::Block& block : buffer) {
+ data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size);
+ offset += block.size;
+ }
+}
+
+void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource) {
+ StringPool::Ref ref = srcPool->makeRef(util::utf8ToUtf16(source.path));
+ outPbSource->set_path_idx(static_cast<uint32_t>(ref.getIndex()));
+ if (source.line) {
+ outPbSource->set_line_no(static_cast<uint32_t>(source.line.value()));
+ }
+}
+
+void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
+ Source* outSource) {
+ if (pbSource.has_path_idx()) {
+ outSource->path = util::getString8(srcPool, pbSource.path_idx()).toString();
+ }
+
+ if (pbSource.has_line_no()) {
+ outSource->line = static_cast<size_t>(pbSource.line_no());
+ }
+}
+
+pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state) {
+ switch (state) {
+ case SymbolState::kPrivate: return pb::SymbolStatus_Visibility_Private;
+ case SymbolState::kPublic: return pb::SymbolStatus_Visibility_Public;
+ default: break;
+ }
+ return pb::SymbolStatus_Visibility_Unknown;
+}
+
+SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility) {
+ switch (pbVisibility) {
+ case pb::SymbolStatus_Visibility_Private: return SymbolState::kPrivate;
+ case pb::SymbolStatus_Visibility_Public: return SymbolState::kPublic;
+ default: break;
+ }
+ return SymbolState::kUndefined;
+}
+
+void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig) {
+ android::ResTable_config flatConfig = config;
+ flatConfig.size = sizeof(flatConfig);
+ flatConfig.swapHtoD();
+ outPbConfig->set_data(&flatConfig, sizeof(flatConfig));
+}
+
+bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
+ ConfigDescription* outConfig) {
+ if (!pbConfig.has_data()) {
+ return false;
+ }
+
+ const android::ResTable_config* config;
+ if (pbConfig.data().size() > sizeof(*config)) {
+ return false;
+ }
+
+ config = reinterpret_cast<const android::ResTable_config*>(pbConfig.data().data());
+ outConfig->copyFromDtoH(*config);
+ return true;
+}
+
+pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type) {
+ switch (type) {
+ case Reference::Type::kResource: return pb::Reference_Type_Ref;
+ case Reference::Type::kAttribute: return pb::Reference_Type_Attr;
+ default: break;
+ }
+ return pb::Reference_Type_Ref;
+}
+
+Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType) {
+ switch (pbType) {
+ case pb::Reference_Type_Ref: return Reference::Type::kResource;
+ case pb::Reference_Type_Attr: return Reference::Type::kAttribute;
+ default: break;
+ }
+ return Reference::Type::kResource;
+}
+
+pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx) {
+ switch (pluralIdx) {
+ case Plural::Zero: return pb::Plural_Arity_Zero;
+ case Plural::One: return pb::Plural_Arity_One;
+ case Plural::Two: return pb::Plural_Arity_Two;
+ case Plural::Few: return pb::Plural_Arity_Few;
+ case Plural::Many: return pb::Plural_Arity_Many;
+ default: break;
+ }
+ return pb::Plural_Arity_Other;
+}
+
+size_t deserializePluralEnumFromPb(pb::Plural_Arity arity) {
+ switch (arity) {
+ case pb::Plural_Arity_Zero: return Plural::Zero;
+ case pb::Plural_Arity_One: return Plural::One;
+ case pb::Plural_Arity_Two: return Plural::Two;
+ case pb::Plural_Arity_Few: return Plural::Few;
+ case pb::Plural_Arity_Many: return Plural::Many;
+ default: break;
+ }
+ return Plural::Other;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h
new file mode 100644
index 0000000..02e67f1
--- /dev/null
+++ b/tools/aapt2/proto/ProtoHelpers.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROTO_PROTOHELPERS_H
+#define AAPT_PROTO_PROTOHELPERS_H
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "Source.h"
+#include "StringPool.h"
+
+#include "proto/frameworks/base/tools/aapt2/Format.pb.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool);
+
+void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource);
+void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
+ Source* outSource);
+
+pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state);
+SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility);
+
+void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig);
+bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
+ ConfigDescription* outConfig);
+
+pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type);
+Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType);
+
+pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx);
+size_t deserializePluralEnumFromPb(pb::Plural_Arity arity);
+
+} // namespace aapt
+
+#endif /* AAPT_PROTO_PROTOHELPERS_H */
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
new file mode 100644
index 0000000..6e224ab
--- /dev/null
+++ b/tools/aapt2/proto/ProtoSerialize.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+
+#include "Diagnostics.h"
+#include "ResourceTable.h"
+#include "Source.h"
+#include "proto/ProtoHelpers.h"
+
+#include <android-base/macros.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+namespace aapt {
+
+std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table);
+std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
+ const Source& source,
+ IDiagnostics* diag);
+
+std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file);
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
+ const Source& source,
+ IDiagnostics* diag);
+
+class CompiledFileOutputStream : public google::protobuf::io::CopyingOutputStream {
+public:
+ CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
+ pb::CompiledFile* pbFile);
+ bool Write(const void* data, int size) override;
+ bool Finish();
+
+private:
+ bool ensureFileWritten();
+
+ google::protobuf::io::CodedOutputStream mOut;
+ pb::CompiledFile* mPbFile;
+
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
+};
+
+class CompiledFileInputStream {
+public:
+ CompiledFileInputStream(const void* data, size_t size);
+
+ const pb::CompiledFile* CompiledFile();
+
+ const void* data();
+ size_t size();
+
+private:
+ google::protobuf::io::CodedInputStream mIn;
+ std::unique_ptr<pb::CompiledFile> mPbFile;
+ const uint8_t* mData;
+ size_t mSize;
+
+ DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
new file mode 100644
index 0000000..1310aa6
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+#include "proto/ProtoHelpers.h"
+#include "proto/ProtoSerialize.h"
+#include "util/Comparators.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+namespace {
+
+class ReferenceIdToNameVisitor : public ValueVisitor {
+public:
+ using ValueVisitor::visit;
+
+ ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) :
+ mMapping(mapping) {
+ assert(mMapping);
+ }
+
+ void visit(Reference* reference) override {
+ if (!reference->id || !reference->id.value().isValid()) {
+ return;
+ }
+
+ ResourceId id = reference->id.value();
+ auto cacheIter = mMapping->find(id);
+ if (cacheIter != mMapping->end()) {
+ reference->name = cacheIter->second.toResourceName();
+ }
+ }
+
+private:
+ const std::map<ResourceId, ResourceNameRef>* mMapping;
+};
+
+class PackagePbDeserializer {
+public:
+ PackagePbDeserializer(const android::ResStringPool* valuePool,
+ const android::ResStringPool* sourcePool,
+ const android::ResStringPool* symbolPool,
+ const Source& source, IDiagnostics* diag) :
+ mValuePool(valuePool), mSourcePool(sourcePool), mSymbolPool(symbolPool),
+ mSource(source), mDiag(diag) {
+ }
+
+public:
+ bool deserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) {
+ Maybe<uint8_t> id;
+ if (pbPackage.has_package_id()) {
+ id = static_cast<uint8_t>(pbPackage.package_id());
+ }
+
+ std::map<ResourceId, ResourceNameRef> idIndex;
+
+ ResourceTablePackage* pkg = table->createPackage(
+ util::utf8ToUtf16(pbPackage.package_name()), id);
+ for (const pb::Type& pbType : pbPackage.types()) {
+ const ResourceType* resType = parseResourceType(util::utf8ToUtf16(pbType.name()));
+ if (!resType) {
+ mDiag->error(DiagMessage(mSource) << "unknown type '" << pbType.name() << "'");
+ return {};
+ }
+
+ ResourceTableType* type = pkg->findOrCreateType(*resType);
+
+ for (const pb::Entry& pbEntry : pbType.entries()) {
+ ResourceEntry* entry = type->findOrCreateEntry(util::utf8ToUtf16(pbEntry.name()));
+
+ // Deserialize the symbol status (public/private with source and comments).
+ if (pbEntry.has_symbol_status()) {
+ const pb::SymbolStatus& pbStatus = pbEntry.symbol_status();
+ if (pbStatus.has_source()) {
+ deserializeSourceFromPb(pbStatus.source(), *mSourcePool,
+ &entry->symbolStatus.source);
+ }
+
+ if (pbStatus.has_comment()) {
+ entry->symbolStatus.comment = util::utf8ToUtf16(pbStatus.comment());
+ }
+
+ SymbolState visibility = deserializeVisibilityFromPb(pbStatus.visibility());
+ entry->symbolStatus.state = visibility;
+
+ if (visibility == SymbolState::kPublic) {
+ // This is a public symbol, we must encode the ID now if there is one.
+ if (pbEntry.has_id()) {
+ entry->id = static_cast<uint16_t>(pbEntry.id());
+ }
+
+ if (type->symbolStatus.state != SymbolState::kPublic) {
+ // If the type has not been made public, do so now.
+ type->symbolStatus.state = SymbolState::kPublic;
+ if (pbType.has_id()) {
+ type->id = static_cast<uint8_t>(pbType.id());
+ }
+ }
+ } else if (visibility == SymbolState::kPrivate) {
+ if (type->symbolStatus.state == SymbolState::kUndefined) {
+ type->symbolStatus.state = SymbolState::kPrivate;
+ }
+ }
+ }
+
+ ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id());
+ if (resId.isValid()) {
+ idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name);
+ }
+
+ for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) {
+ const pb::ConfigDescription& pbConfig = pbConfigValue.config();
+
+ ConfigDescription config;
+ if (!deserializeConfigDescriptionFromPb(pbConfig, &config)) {
+ mDiag->error(DiagMessage(mSource) << "invalid configuration");
+ return {};
+ }
+
+ auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
+ config, cmp::lessThanConfig);
+ if (iter != entry->values.end() && iter->config == config) {
+ // Duplicate config.
+ mDiag->error(DiagMessage(mSource) << "duplicate configuration");
+ return {};
+ }
+
+ std::unique_ptr<Value> value = deserializeValueFromPb(pbConfigValue.value(),
+ config,
+ &table->stringPool);
+ if (!value) {
+ return {};
+ }
+ entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
+ }
+ }
+ }
+
+ ReferenceIdToNameVisitor visitor(&idIndex);
+ visitAllValuesInPackage(pkg, &visitor);
+ return true;
+ }
+
+private:
+ std::unique_ptr<Item> deserializeItemFromPb(const pb::Item& pbItem,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ if (pbItem.has_ref()) {
+ const pb::Reference& pbRef = pbItem.ref();
+ std::unique_ptr<Reference> ref = util::make_unique<Reference>();
+ if (!deserializeReferenceFromPb(pbRef, ref.get())) {
+ return {};
+ }
+ return std::move(ref);
+
+ } else if (pbItem.has_prim()) {
+ const pb::Primitive& pbPrim = pbItem.prim();
+ android::Res_value prim = {};
+ prim.dataType = static_cast<uint8_t>(pbPrim.type());
+ prim.data = pbPrim.data();
+ return util::make_unique<BinaryPrimitive>(prim);
+
+ } else if (pbItem.has_id()) {
+ return util::make_unique<Id>();
+
+ } else if (pbItem.has_str()) {
+ const uint32_t idx = pbItem.str().idx();
+ StringPiece16 str = util::getString(*mValuePool, idx);
+
+ const android::ResStringPool_span* spans = mValuePool->styleAt(idx);
+ if (spans && spans->name.index != android::ResStringPool_span::END) {
+ StyleString styleStr = { str.toString() };
+ while (spans->name.index != android::ResStringPool_span::END) {
+ styleStr.spans.push_back(Span{
+ util::getString(*mValuePool, spans->name.index).toString(),
+ spans->firstChar,
+ spans->lastChar
+ });
+ spans++;
+ }
+ return util::make_unique<StyledString>(
+ pool->makeRef(styleStr, StringPool::Context{ 1, config }));
+ }
+ return util::make_unique<String>(
+ pool->makeRef(str, StringPool::Context{ 1, config }));
+
+ } else if (pbItem.has_raw_str()) {
+ const uint32_t idx = pbItem.raw_str().idx();
+ StringPiece16 str = util::getString(*mValuePool, idx);
+ return util::make_unique<RawString>(
+ pool->makeRef(str, StringPool::Context{ 1, config }));
+
+ } else if (pbItem.has_file()) {
+ const uint32_t idx = pbItem.file().path_idx();
+ StringPiece16 str = util::getString(*mValuePool, idx);
+ return util::make_unique<FileReference>(
+ pool->makeRef(str, StringPool::Context{ 0, config }));
+
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown item");
+ }
+ return {};
+ }
+
+ std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue,
+ const ConfigDescription& config,
+ StringPool* pool) {
+ const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false;
+
+ std::unique_ptr<Value> value;
+ if (pbValue.has_item()) {
+ value = deserializeItemFromPb(pbValue.item(), config, pool);
+ if (!value) {
+ return {};
+ }
+
+ } else if (pbValue.has_compound_value()) {
+ const pb::CompoundValue pbCompoundValue = pbValue.compound_value();
+ if (pbCompoundValue.has_attr()) {
+ const pb::Attribute& pbAttr = pbCompoundValue.attr();
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+ attr->typeMask = pbAttr.format_flags();
+ for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
+ Attribute::Symbol symbol;
+ deserializeItemCommon(pbSymbol, &symbol.symbol);
+ if (!deserializeReferenceFromPb(pbSymbol.name(), &symbol.symbol)) {
+ return {};
+ }
+ symbol.value = pbSymbol.value();
+ attr->symbols.push_back(std::move(symbol));
+ }
+ value = std::move(attr);
+
+ } else if (pbCompoundValue.has_style()) {
+ const pb::Style& pbStyle = pbCompoundValue.style();
+ std::unique_ptr<Style> style = util::make_unique<Style>();
+ if (pbStyle.has_parent()) {
+ style->parent = Reference();
+ if (!deserializeReferenceFromPb(pbStyle.parent(), &style->parent.value())) {
+ return {};
+ }
+
+ if (pbStyle.has_parent_source()) {
+ Source parentSource;
+ deserializeSourceFromPb(pbStyle.parent_source(), *mSourcePool,
+ &parentSource);
+ style->parent.value().setSource(std::move(parentSource));
+ }
+ }
+
+ for (const pb::Style_Entry& pbEntry : pbStyle.entries()) {
+ Style::Entry entry;
+ deserializeItemCommon(pbEntry, &entry.key);
+ if (!deserializeReferenceFromPb(pbEntry.key(), &entry.key)) {
+ return {};
+ }
+
+ entry.value = deserializeItemFromPb(pbEntry.item(), config, pool);
+ if (!entry.value) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, entry.value.get());
+ style->entries.push_back(std::move(entry));
+ }
+ value = std::move(style);
+
+ } else if (pbCompoundValue.has_styleable()) {
+ const pb::Styleable& pbStyleable = pbCompoundValue.styleable();
+ std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+ for (const pb::Styleable_Entry& pbEntry : pbStyleable.entries()) {
+ Reference attrRef;
+ deserializeItemCommon(pbEntry, &attrRef);
+ deserializeReferenceFromPb(pbEntry.attr(), &attrRef);
+ styleable->entries.push_back(std::move(attrRef));
+ }
+ value = std::move(styleable);
+
+ } else if (pbCompoundValue.has_array()) {
+ const pb::Array& pbArray = pbCompoundValue.array();
+ std::unique_ptr<Array> array = util::make_unique<Array>();
+ for (const pb::Array_Entry& pbEntry : pbArray.entries()) {
+ std::unique_ptr<Item> item = deserializeItemFromPb(pbEntry.item(), config,
+ pool);
+ if (!item) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, item.get());
+ array->items.push_back(std::move(item));
+ }
+ value = std::move(array);
+
+ } else if (pbCompoundValue.has_plural()) {
+ const pb::Plural& pbPlural = pbCompoundValue.plural();
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ for (const pb::Plural_Entry& pbEntry : pbPlural.entries()) {
+ size_t pluralIdx = deserializePluralEnumFromPb(pbEntry.arity());
+ plural->values[pluralIdx] = deserializeItemFromPb(pbEntry.item(), config,
+ pool);
+ if (!plural->values[pluralIdx]) {
+ return {};
+ }
+
+ deserializeItemCommon(pbEntry, plural->values[pluralIdx].get());
+ }
+ value = std::move(plural);
+
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown compound value");
+ return {};
+ }
+ } else {
+ mDiag->error(DiagMessage(mSource) << "unknown value");
+ return {};
+ }
+
+ assert(value && "forgot to set value");
+
+ value->setWeak(isWeak);
+ deserializeItemCommon(pbValue, value.get());
+ return value;
+ }
+
+ bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) {
+ outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type());
+ outRef->privateReference = pbRef.private_();
+
+ if (!pbRef.has_id() && !pbRef.has_symbol_idx()) {
+ return false;
+ }
+
+ if (pbRef.has_id()) {
+ outRef->id = ResourceId(pbRef.id());
+ }
+
+ if (pbRef.has_symbol_idx()) {
+ StringPiece16 strSymbol = util::getString(*mSymbolPool, pbRef.symbol_idx());
+ ResourceNameRef nameRef;
+ if (!ResourceUtils::parseResourceName(strSymbol, &nameRef, nullptr)) {
+ mDiag->error(DiagMessage(mSource) << "invalid reference name '"
+ << strSymbol << "'");
+ return false;
+ }
+
+ outRef->name = nameRef.toResourceName();
+ }
+ return true;
+ }
+
+ template <typename T>
+ void deserializeItemCommon(const T& pbItem, Value* outValue) {
+ if (pbItem.has_source()) {
+ Source source;
+ deserializeSourceFromPb(pbItem.source(), *mSourcePool, &source);
+ outValue->setSource(std::move(source));
+ }
+
+ if (pbItem.has_comment()) {
+ outValue->setComment(util::utf8ToUtf16(pbItem.comment()));
+ }
+ }
+
+private:
+ const android::ResStringPool* mValuePool;
+ const android::ResStringPool* mSourcePool;
+ const android::ResStringPool* mSymbolPool;
+ const Source mSource;
+ IDiagnostics* mDiag;
+};
+
+} // namespace
+
+std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
+ const Source& source,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+
+ if (!pbTable.has_string_pool()) {
+ diag->error(DiagMessage(source) << "no string pool found");
+ return {};
+ }
+
+ android::ResStringPool valuePool;
+ android::status_t result = valuePool.setTo(pbTable.string_pool().data().data(),
+ pbTable.string_pool().data().size());
+ if (result != android::NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid string pool");
+ return {};
+ }
+
+ android::ResStringPool sourcePool;
+ if (pbTable.has_source_pool()) {
+ result = sourcePool.setTo(pbTable.source_pool().data().data(),
+ pbTable.source_pool().data().size());
+ if (result != android::NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid source pool");
+ return {};
+ }
+ }
+
+ android::ResStringPool symbolPool;
+ if (pbTable.has_symbol_pool()) {
+ result = symbolPool.setTo(pbTable.symbol_pool().data().data(),
+ pbTable.symbol_pool().data().size());
+ if (result != android::NO_ERROR) {
+ diag->error(DiagMessage(source) << "invalid symbol pool");
+ return {};
+ }
+ }
+
+ PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag);
+ for (const pb::Package& pbPackage : pbTable.packages()) {
+ if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) {
+ return {};
+ }
+ }
+ return table;
+}
+
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
+ const Source& source,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
+
+ ResourceNameRef nameRef;
+
+ // Need to create an lvalue here so that nameRef can point to something real.
+ std::u16string utf16Name = util::utf8ToUtf16(pbFile.resource_name());
+ if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) {
+ diag->error(DiagMessage(source) << "invalid resource name in compiled file header: "
+ << pbFile.resource_name());
+ return {};
+ }
+ file->name = nameRef.toResourceName();
+ file->source.path = pbFile.source_path();
+ deserializeConfigDescriptionFromPb(pbFile.config(), &file->config);
+
+ for (const pb::CompiledFile_Symbol& pbSymbol : pbFile.exported_symbols()) {
+ // Need to create an lvalue here so that nameRef can point to something real.
+ utf16Name = util::utf8ToUtf16(pbSymbol.resource_name());
+ if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) {
+ diag->error(DiagMessage(source) << "invalid resource name for exported symbol in "
+ "compiled file header: "
+ << pbFile.resource_name());
+ return {};
+ }
+ file->exportedSymbols.push_back(
+ SourcedResourceName{ nameRef.toResourceName(), pbSymbol.line_no() });
+ }
+ return file;
+}
+
+CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) :
+ mIn(static_cast<const uint8_t*>(data), size), mPbFile(),
+ mData(static_cast<const uint8_t*>(data)), mSize(size) {
+}
+
+const pb::CompiledFile* CompiledFileInputStream::CompiledFile() {
+ if (!mPbFile) {
+ std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
+ uint64_t pbSize = 0u;
+ if (!mIn.ReadLittleEndian64(&pbSize)) {
+ return nullptr;
+ }
+ mIn.PushLimit(static_cast<int>(pbSize));
+ if (!pbFile->ParsePartialFromCodedStream(&mIn)) {
+ return nullptr;
+ }
+
+ const size_t padding = 4 - (pbSize & 0x03);
+ mData += sizeof(uint64_t) + pbSize + padding;
+ mSize -= sizeof(uint64_t) + pbSize + padding;
+ mPbFile = std::move(pbFile);
+ }
+ return mPbFile.get();
+}
+
+const void* CompiledFileInputStream::data() {
+ if (!mPbFile) {
+ if (!CompiledFile()) {
+ return nullptr;
+ }
+ }
+ return mData;
+}
+
+size_t CompiledFileInputStream::size() {
+ if (!mPbFile) {
+ if (!CompiledFile()) {
+ return 0;
+ }
+ }
+ return mSize;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
new file mode 100644
index 0000000..4a2176d
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Resource.h"
+#include "ResourceTable.h"
+#include "StringPool.h"
+#include "ValueVisitor.h"
+#include "proto/ProtoHelpers.h"
+#include "proto/ProtoSerialize.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+namespace {
+
+class PbSerializerVisitor : public RawValueVisitor {
+public:
+ using RawValueVisitor::visit;
+
+ /**
+ * Constructor to use when expecting to serialize any value.
+ */
+ PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Value* outPbValue) :
+ mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(outPbValue),
+ mOutPbItem(nullptr) {
+ }
+
+ /**
+ * Constructor to use when expecting to serialize an Item.
+ */
+ PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Item* outPbItem) :
+ mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(nullptr),
+ mOutPbItem(outPbItem) {
+ }
+
+ void visit(Reference* ref) override {
+ serializeReferenceToPb(*ref, getPbItem()->mutable_ref());
+ }
+
+ void visit(String* str) override {
+ getPbItem()->mutable_str()->set_idx(str->value.getIndex());
+ }
+
+ void visit(StyledString* str) override {
+ getPbItem()->mutable_str()->set_idx(str->value.getIndex());
+ }
+
+ void visit(FileReference* file) override {
+ getPbItem()->mutable_file()->set_path_idx(file->path.getIndex());
+ }
+
+ void visit(Id* id) override {
+ getPbItem()->mutable_id();
+ }
+
+ void visit(RawString* rawStr) override {
+ getPbItem()->mutable_raw_str()->set_idx(rawStr->value.getIndex());
+ }
+
+ void visit(BinaryPrimitive* prim) override {
+ android::Res_value val = {};
+ prim->flatten(&val);
+
+ pb::Primitive* pbPrim = getPbItem()->mutable_prim();
+ pbPrim->set_type(val.dataType);
+ pbPrim->set_data(val.data);
+ }
+
+ void visitItem(Item* item) override {
+ assert(false && "unimplemented item");
+ }
+
+ void visit(Attribute* attr) override {
+ pb::Attribute* pbAttr = getPbCompoundValue()->mutable_attr();
+ pbAttr->set_format_flags(attr->typeMask);
+ pbAttr->set_min_int(attr->minInt);
+ pbAttr->set_max_int(attr->maxInt);
+
+ for (auto& symbol : attr->symbols) {
+ pb::Attribute_Symbol* pbSymbol = pbAttr->add_symbols();
+ serializeItemCommonToPb(symbol.symbol, pbSymbol);
+ serializeReferenceToPb(symbol.symbol, pbSymbol->mutable_name());
+ pbSymbol->set_value(symbol.value);
+ }
+ }
+
+ void visit(Style* style) override {
+ pb::Style* pbStyle = getPbCompoundValue()->mutable_style();
+ if (style->parent) {
+ serializeReferenceToPb(style->parent.value(), pbStyle->mutable_parent());
+ serializeSourceToPb(style->parent.value().getSource(),
+ mSourcePool,
+ pbStyle->mutable_parent_source());
+ }
+
+ for (Style::Entry& entry : style->entries) {
+ pb::Style_Entry* pbEntry = pbStyle->add_entries();
+ serializeReferenceToPb(entry.key, pbEntry->mutable_key());
+
+ pb::Item* pbItem = pbEntry->mutable_item();
+ serializeItemCommonToPb(*entry.value, pbEntry);
+ PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem);
+ entry.value->accept(&subVisitor);
+ }
+ }
+
+ void visit(Styleable* styleable) override {
+ pb::Styleable* pbStyleable = getPbCompoundValue()->mutable_styleable();
+ for (Reference& entry : styleable->entries) {
+ pb::Styleable_Entry* pbEntry = pbStyleable->add_entries();
+ serializeItemCommonToPb(entry, pbEntry);
+ serializeReferenceToPb(entry, pbEntry->mutable_attr());
+ }
+ }
+
+ void visit(Array* array) override {
+ pb::Array* pbArray = getPbCompoundValue()->mutable_array();
+ for (auto& value : array->items) {
+ pb::Array_Entry* pbEntry = pbArray->add_entries();
+ serializeItemCommonToPb(*value, pbEntry);
+ PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbEntry->mutable_item());
+ value->accept(&subVisitor);
+ }
+ }
+
+ void visit(Plural* plural) override {
+ pb::Plural* pbPlural = getPbCompoundValue()->mutable_plural();
+ const size_t count = plural->values.size();
+ for (size_t i = 0; i < count; i++) {
+ if (!plural->values[i]) {
+ // No plural value set here.
+ continue;
+ }
+
+ pb::Plural_Entry* pbEntry = pbPlural->add_entries();
+ pbEntry->set_arity(serializePluralEnumToPb(i));
+ pb::Item* pbElement = pbEntry->mutable_item();
+ serializeItemCommonToPb(*plural->values[i], pbEntry);
+ PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbElement);
+ plural->values[i]->accept(&subVisitor);
+ }
+ }
+
+private:
+ pb::Item* getPbItem() {
+ if (mOutPbValue) {
+ return mOutPbValue->mutable_item();
+ }
+ return mOutPbItem;
+ }
+
+ pb::CompoundValue* getPbCompoundValue() {
+ assert(mOutPbValue);
+ return mOutPbValue->mutable_compound_value();
+ }
+
+ template <typename T>
+ void serializeItemCommonToPb(const Item& item, T* pbItem) {
+ serializeSourceToPb(item.getSource(), mSourcePool, pbItem->mutable_source());
+ if (!item.getComment().empty()) {
+ pbItem->set_comment(util::utf16ToUtf8(item.getComment()));
+ }
+ }
+
+ void serializeReferenceToPb(const Reference& ref, pb::Reference* pbRef) {
+ if (ref.id) {
+ pbRef->set_id(ref.id.value().id);
+ } else if (ref.name) {
+ StringPool::Ref symbolRef = mSymbolPool->makeRef(ref.name.value().toString());
+ pbRef->set_symbol_idx(static_cast<uint32_t>(symbolRef.getIndex()));
+ }
+ pbRef->set_private_(ref.privateReference);
+ pbRef->set_type(serializeReferenceTypeToPb(ref.referenceType));
+ }
+
+ StringPool* mSourcePool;
+ StringPool* mSymbolPool;
+ pb::Value* mOutPbValue;
+ pb::Item* mOutPbItem;
+};
+
+} // 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->stringPool.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;
+ });
+ table->stringPool.prune();
+
+ std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>();
+ serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool());
+
+ StringPool sourcePool, symbolPool;
+
+ for (auto& package : table->packages) {
+ pb::Package* pbPackage = pbTable->add_packages();
+ if (package->id) {
+ pbPackage->set_package_id(package->id.value());
+ }
+ pbPackage->set_package_name(util::utf16ToUtf8(package->name));
+
+ for (auto& type : package->types) {
+ pb::Type* pbType = pbPackage->add_types();
+ if (type->id) {
+ pbType->set_id(type->id.value());
+ }
+ pbType->set_name(util::utf16ToUtf8(toString(type->type)));
+
+ for (auto& entry : type->entries) {
+ pb::Entry* pbEntry = pbType->add_entries();
+ if (entry->id) {
+ pbEntry->set_id(entry->id.value());
+ }
+ pbEntry->set_name(util::utf16ToUtf8(entry->name));
+
+ // Write the SymbolStatus struct.
+ pb::SymbolStatus* pbStatus = pbEntry->mutable_symbol_status();
+ pbStatus->set_visibility(serializeVisibilityToPb(entry->symbolStatus.state));
+ serializeSourceToPb(entry->symbolStatus.source, &sourcePool,
+ pbStatus->mutable_source());
+ pbStatus->set_comment(util::utf16ToUtf8(entry->symbolStatus.comment));
+
+ for (auto& configValue : entry->values) {
+ pb::ConfigValue* pbConfigValue = pbEntry->add_config_values();
+ serializeConfig(configValue.config, pbConfigValue->mutable_config());
+
+ pb::Value* pbValue = pbConfigValue->mutable_value();
+ serializeSourceToPb(configValue.value->getSource(), &sourcePool,
+ pbValue->mutable_source());
+ if (!configValue.value->getComment().empty()) {
+ pbValue->set_comment(util::utf16ToUtf8(configValue.value->getComment()));
+ }
+
+ if (configValue.value->isWeak()) {
+ pbValue->set_weak(true);
+ }
+
+ PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue);
+ configValue.value->accept(&visitor);
+ }
+ }
+ }
+ }
+
+ serializeStringPoolToPb(sourcePool, pbTable->mutable_source_pool());
+ serializeStringPoolToPb(symbolPool, pbTable->mutable_symbol_pool());
+ return pbTable;
+}
+
+std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file) {
+ std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
+ pbFile->set_resource_name(util::utf16ToUtf8(file.name.toString()));
+ pbFile->set_source_path(file.source.path);
+ serializeConfig(file.config, pbFile->mutable_config());
+
+ for (const SourcedResourceName& exported : file.exportedSymbols) {
+ pb::CompiledFile_Symbol* pbSymbol = pbFile->add_exported_symbols();
+ pbSymbol->set_resource_name(util::utf16ToUtf8(exported.name.toString()));
+ pbSymbol->set_line_no(exported.line);
+ }
+ return pbFile;
+}
+
+CompiledFileOutputStream::CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
+ pb::CompiledFile* pbFile) :
+ mOut(out), mPbFile(pbFile) {
+}
+
+bool CompiledFileOutputStream::ensureFileWritten() {
+ if (mPbFile) {
+ const uint64_t pbSize = mPbFile->ByteSize();
+ mOut.WriteLittleEndian64(pbSize);
+ mPbFile->SerializeWithCachedSizes(&mOut);
+ const size_t padding = 4 - (pbSize & 0x03);
+ if (padding > 0) {
+ uint32_t zero = 0u;
+ mOut.WriteRaw(&zero, padding);
+ }
+ mPbFile = nullptr;
+ }
+ return !mOut.HadError();
+}
+
+bool CompiledFileOutputStream::Write(const void* data, int size) {
+ if (!ensureFileWritten()) {
+ return false;
+ }
+ mOut.WriteRaw(data, size);
+ return !mOut.HadError();
+}
+
+bool CompiledFileOutputStream::Finish() {
+ return ensureFileWritten();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
new file mode 100644
index 0000000..1061b8f
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourceTable.h"
+#include "proto/ProtoSerialize.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(TableProtoSerializer, SerializeSinglePackage) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.a", 0x7f)
+ .addFileReference(u"@com.app.a:layout/main", ResourceId(0x7f020000),
+ u"res/layout/main.xml")
+ .addReference(u"@com.app.a:layout/other", ResourceId(0x7f020001),
+ u"@com.app.a:layout/main")
+ .addString(u"@com.app.a:string/text", {}, u"hi")
+ .addValue(u"@com.app.a:id/foo", {}, util::make_unique<Id>())
+ .build();
+
+ Symbol publicSymbol;
+ publicSymbol.state = SymbolState::kPublic;
+ ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@com.app.a:layout/main"),
+ ResourceId(0x7f020000),
+ publicSymbol, context->getDiagnostics()));
+
+ Id* id = test::getValue<Id>(table.get(), u"@com.app.a:id/foo");
+ ASSERT_NE(nullptr, id);
+
+ // Make a plural.
+ std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef(u"one"));
+ ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:plurals/hey"),
+ ConfigDescription{}, std::move(plural),
+ context->getDiagnostics()));
+
+ std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get());
+ ASSERT_NE(nullptr, pbTable);
+
+ std::unique_ptr<ResourceTable> newTable = deserializeTableFromPb(*pbTable,
+ Source{ "test" },
+ context->getDiagnostics());
+ ASSERT_NE(nullptr, newTable);
+
+ Id* newId = test::getValue<Id>(newTable.get(), u"@com.app.a:id/foo");
+ ASSERT_NE(nullptr, newId);
+ EXPECT_EQ(id->isWeak(), newId->isWeak());
+
+ Maybe<ResourceTable::SearchResult> result = newTable->findResource(
+ test::parseNameOrDie(u"@com.app.a:layout/main"));
+ AAPT_ASSERT_TRUE(result);
+ EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state);
+ EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
+}
+
+TEST(TableProtoSerializer, SerializeFileHeader) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+ ResourceFile f;
+ f.config = test::parseConfigOrDie("hdpi-v9");
+ f.name = test::parseNameOrDie(u"@com.app.a:layout/main");
+ f.source.path = "res/layout-hdpi-v9/main.xml";
+ f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie(u"@+id/unchecked"), 23u });
+
+ const std::string expectedData = "1234";
+
+ std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+
+ std::string outputStr;
+ {
+ google::protobuf::io::StringOutputStream outStream(&outputStr);
+ CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
+
+ ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
+ ASSERT_TRUE(outFileStream.Finish());
+ }
+
+ CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
+ const pb::CompiledFile* newPbFile = inFileStream.CompiledFile();
+ ASSERT_NE(nullptr, newPbFile);
+
+ std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source{ "test" },
+ context->getDiagnostics());
+ ASSERT_NE(nullptr, file);
+
+ std::string actualData((const char*)inFileStream.data(), inFileStream.size());
+ EXPECT_EQ(expectedData, actualData);
+ EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(inFileStream.data()) & 0x03);
+
+ ASSERT_EQ(1u, file->exportedSymbols.size());
+ EXPECT_EQ(test::parseNameOrDie(u"@+id/unchecked"), file->exportedSymbols[0].name);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 6b7a63cf..3417703 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -19,8 +19,6 @@
#include "ResourceValues.h"
#include "Source.h"
#include "ValueVisitor.h"
-
-#include "flatten/ResourceTypeExtensions.h"
#include "unflatten/BinaryResourceParser.h"
#include "unflatten/ResChunkPullParser.h"
#include "util/Util.h"
@@ -36,6 +34,8 @@
using namespace android;
+namespace {
+
/*
* Visitor that converts a reference's resource ID to a resource name,
* given a mapping from resource ID to resource name.
@@ -66,6 +66,8 @@
}
};
+} // namespace
+
BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
const Source& source, const void* data, size_t len) :
mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) {
@@ -97,106 +99,6 @@
return !error;
}
-Maybe<Reference> BinaryResourceParser::getSymbol(const void* data) {
- if (!mSymbolEntries || mSymbolEntryCount == 0) {
- return {};
- }
-
- if ((uintptr_t) data < (uintptr_t) mData) {
- return {};
- }
-
- // We only support 32 bit offsets right now.
- const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData;
- if (offset > std::numeric_limits<uint32_t>::max()) {
- return {};
- }
-
- for (size_t i = 0; i < mSymbolEntryCount; i++) {
- if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) {
- // This offset is a symbol!
- const StringPiece16 str = util::getString(
- mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
-
- ResourceNameRef nameRef;
- bool privateRef = false;
- if (!ResourceUtils::parseResourceName(str, &nameRef, &privateRef)) {
- return {};
- }
-
- // Since we scan the symbol table in order, we can start looking for the
- // next symbol from this point.
- mSymbolEntryCount -= i + 1;
- mSymbolEntries += i + 1;
-
- Reference ref(nameRef);
- ref.privateReference = privateRef;
- return Maybe<Reference>(std::move(ref));
- }
- }
- return {};
-}
-
-/**
- * Parses the SymbolTable_header, which is present on non-final resource tables
- * after the compile phase.
- *
- * | SymbolTable_header |
- * |--------------------|
- * |SymbolTable_entry 0 |
- * |SymbolTable_entry 1 |
- * | ... |
- * |SymbolTable_entry n |
- * |--------------------|
- *
- */
-bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
- const SymbolTable_header* header = convertTo<SymbolTable_header>(chunk);
- if (!header) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt SymbolTable_header");
- return false;
- }
-
- const uint32_t entrySizeBytes =
- util::deviceToHost32(header->count) * sizeof(SymbolTable_entry);
- if (entrySizeBytes > getChunkDataLen(&header->header)) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "SymbolTable_header data section too long");
- return false;
- }
-
- mSymbolEntries = (const SymbolTable_entry*) getChunkData(&header->header);
- mSymbolEntryCount = util::deviceToHost32(header->count);
-
- // Skip over the symbol entries and parse the StringPool chunk that should be next.
- ResChunkPullParser parser(getChunkData(&header->header) + entrySizeBytes,
- getChunkDataLen(&header->header) - entrySizeBytes);
- if (!ResChunkPullParser::isGoodEvent(parser.next())) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "failed to parse chunk in SymbolTable: "
- << parser.getLastError());
- return false;
- }
-
- const ResChunk_header* nextChunk = parser.getChunk();
- if (util::deviceToHost16(nextChunk->type) != android::RES_STRING_POOL_TYPE) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "expected string pool in SymbolTable but got "
- << "chunk of type "
- << (int) util::deviceToHost16(nextChunk->type));
- return false;
- }
-
- if (mSymbolPool.setTo(nextChunk, util::deviceToHost32(nextChunk->size)) != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt string pool in SymbolTable: "
- << mSymbolPool.getError());
- return false;
- }
- return true;
-}
-
/**
* Parses the resource table, which contains all the packages, types, and entries.
*/
@@ -230,24 +132,6 @@
}
break;
- case RES_TABLE_SYMBOL_TABLE_TYPE:
- if (!parseSymbolTable(parser.getChunk())) {
- return false;
- }
- break;
-
- case RES_TABLE_SOURCE_POOL_TYPE: {
- status_t err = mSourcePool.setTo(getChunkData(parser.getChunk()),
- getChunkDataLen(parser.getChunk()));
- if (err != NO_ERROR) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt source string pool in ResTable: "
- << mSourcePool.getError());
- return false;
- }
- break;
- }
-
case android::RES_TABLE_PACKAGE_TYPE:
if (!parsePackage(parser.getChunk())) {
return false;
@@ -350,12 +234,6 @@
}
break;
- case RES_TABLE_PUBLIC_TYPE:
- if (!parsePublic(package, parser.getChunk())) {
- return false;
- }
- break;
-
default:
mContext->getDiagnostics()
->warn(DiagMessage(mSource)
@@ -375,97 +253,7 @@
// Now go through the table and change local resource ID references to
// symbolic references.
ReferenceIdToNameVisitor visitor(&mIdIndex);
- for (auto& package : mTable->packages) {
- for (auto& type : package->types) {
- for (auto& entry : type->entries) {
- for (auto& configValue : entry->values) {
- configValue.value->accept(&visitor);
- }
- }
- }
- }
- return true;
-}
-
-bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package,
- const ResChunk_header* chunk) {
- const Public_header* header = convertTo<Public_header>(chunk);
- if (!header) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "corrupt Public_header chunk");
- return false;
- }
-
- if (header->typeId == 0) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "invalid type ID "
- << (int) header->typeId);
- return false;
- }
-
- StringPiece16 typeStr16 = util::getString(mTypePool, header->typeId - 1);
- const ResourceType* parsedType = parseResourceType(typeStr16);
- if (!parsedType) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "invalid type '" << typeStr16 << "'");
- return false;
- }
-
- const uintptr_t chunkEnd = (uintptr_t) chunk + util::deviceToHost32(chunk->size);
- const Public_entry* entry = (const Public_entry*) getChunkData(&header->header);
- for (uint32_t i = 0; i < util::deviceToHost32(header->count); i++) {
- if ((uintptr_t) entry + sizeof(*entry) > chunkEnd) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "Public_entry data section is too long");
- return false;
- }
-
- const ResourceId resId(package->id.value(), header->typeId,
- util::deviceToHost16(entry->entryId));
-
- const ResourceName name(package->name, *parsedType,
- util::getString(mKeyPool, entry->key.index).toString());
-
- Symbol symbol;
- if (mSourcePool.getError() == NO_ERROR) {
- symbol.source.path = util::utf16ToUtf8(util::getString(
- mSourcePool, util::deviceToHost32(entry->source.path.index)));
- symbol.source.line = util::deviceToHost32(entry->source.line);
- }
-
- StringPiece16 comment = util::getString(mSourcePool,
- util::deviceToHost32(entry->source.comment.index));
- if (!comment.empty()) {
- symbol.comment = comment.toString();
- }
-
- switch (util::deviceToHost16(entry->state)) {
- case Public_entry::kPrivate:
- symbol.state = SymbolState::kPrivate;
- break;
-
- case Public_entry::kPublic:
- symbol.state = SymbolState::kPublic;
- break;
-
- case Public_entry::kUndefined:
- symbol.state = SymbolState::kUndefined;
- break;
- }
-
- if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, mContext->getDiagnostics())) {
- return false;
- }
-
- // Add this resource name->id mapping to the index so
- // that we can resolve all ID references to name references.
- auto cacheIter = mIdIndex.find(resId);
- if (cacheIter == mIdIndex.end()) {
- mIdIndex.insert({ resId, name });
- }
-
- entry++;
- }
+ visitAllValuesInTable(mTable, &visitor);
return true;
}
@@ -545,25 +333,12 @@
const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
std::unique_ptr<Value> resourceValue;
- const ResTable_entry_source* sourceBlock = nullptr;
-
if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
- if (util::deviceToHost32(mapEntry->size) - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
- const uint8_t* data = (const uint8_t*) mapEntry;
- data += util::deviceToHost32(mapEntry->size) - sizeof(*sourceBlock);
- sourceBlock = (const ResTable_entry_source*) data;
- }
// TODO(adamlesinski): Check that the entry count is valid.
resourceValue = parseMapEntry(name, config, mapEntry);
} else {
- if (util::deviceToHost32(entry->size) - sizeof(*entry) == sizeof(*sourceBlock)) {
- const uint8_t* data = (const uint8_t*) entry;
- data += util::deviceToHost32(entry->size) - sizeof(*sourceBlock);
- sourceBlock = (const ResTable_entry_source*) data;
- }
-
const Res_value* value = (const Res_value*)(
(const uint8_t*) entry + util::deviceToHost32(entry->size));
resourceValue = parseValue(name, config, value, entry->flags);
@@ -577,30 +352,6 @@
return false;
}
- Source source = mSource;
- if (sourceBlock) {
- StringPiece path = util::getString8(mSourcePool,
- util::deviceToHost32(sourceBlock->path.index));
- if (!path.empty()) {
- source.path = path.toString();
- }
- source.line = util::deviceToHost32(sourceBlock->line);
-
- if (Style* style = valueCast<Style>(resourceValue.get())) {
- // The parent's source is the same as the resource itself, set it here.
- if (style->parent) {
- style->parent.value().setSource(source);
- }
- }
- }
-
- StringPiece16 comment = util::getString(mSourcePool,
- util::deviceToHost32(sourceBlock->comment.index));
- if (!comment.empty()) {
- resourceValue->setComment(comment);
- }
-
- resourceValue->setSource(source);
if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
mContext->getDiagnostics())) {
return false;
@@ -674,26 +425,15 @@
const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
Reference::Type::kResource : Reference::Type::kAttribute;
- if (data != 0) {
- // This is a normal reference.
- return util::make_unique<Reference>(data, type);
+ if (data == 0) {
+ // A reference of 0, must be the magic @null reference.
+ Res_value nullType = {};
+ nullType.dataType = Res_value::TYPE_REFERENCE;
+ return util::make_unique<BinaryPrimitive>(nullType);
}
- // This reference has an invalid ID. Check if it is an unresolved symbol.
- if (Maybe<Reference> ref = getSymbol(&value->data)) {
- ref.value().referenceType = type;
- return util::make_unique<Reference>(std::move(ref.value()));
- }
-
- // This is not an unresolved symbol, so it must be the magic @null reference.
- Res_value nullType = {};
- nullType.dataType = Res_value::TYPE_REFERENCE;
- return util::make_unique<BinaryPrimitive>(nullType);
- }
-
- if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
- return util::make_unique<RawString>(mTable->stringPool.makeRef(
- util::getString(mValuePool, data), StringPool::Context{ 1, config }));
+ // This is a normal reference.
+ return util::make_unique<Reference>(data, type);
}
// Treat this as a raw binary primitive.
@@ -712,8 +452,6 @@
return parseAttr(name, config, map);
case ResourceType::kArray:
return parseArray(name, config, map);
- case ResourceType::kStyleable:
- return parseStyleable(name, config, map);
case ResourceType::kPlurals:
return parsePlural(name, config, map);
default:
@@ -727,51 +465,23 @@
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Style> style = util::make_unique<Style>();
- if (util::deviceToHost32(map->parent.ident) == 0) {
- // The parent is either not set or it is an unresolved symbol.
- // Check to see if it is a symbol.
- style->parent = getSymbol(&map->parent.ident);
-
- } else {
- // The parent is a regular reference to a resource.
+ if (util::deviceToHost32(map->parent.ident) != 0) {
+ // The parent is a regular reference to a resource.
style->parent = Reference(util::deviceToHost32(map->parent.ident));
}
for (const ResTable_map& mapEntry : map) {
if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
- if (style->entries.empty()) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "out-of-sequence meta data in style");
- return {};
- }
- collectMetaData(mapEntry, &style->entries.back().key);
continue;
}
- style->entries.emplace_back();
- Style::Entry& styleEntry = style->entries.back();
-
- if (util::deviceToHost32(mapEntry.name.ident) == 0) {
- // The map entry's key (attribute) is not set. This must be
- // a symbol reference, so resolve it.
- Maybe<Reference> symbol = getSymbol(&mapEntry.name.ident);
- if (!symbol) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "unresolved style attribute");
- return {};
- }
- styleEntry.key = std::move(symbol.value());
-
- } else {
- // The map entry's key (attribute) is a regular reference.
- styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
- }
-
- // Parse the attribute's value.
+ Style::Entry styleEntry;
+ styleEntry.key = Reference(util::deviceToHost32(mapEntry.name.ident));
styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
if (!styleEntry.value) {
return {};
}
+ style->entries.push_back(std::move(styleEntry));
}
return style;
}
@@ -807,22 +517,7 @@
if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
Attribute::Symbol symbol;
symbol.value = util::deviceToHost32(mapEntry.value.data);
- if (util::deviceToHost32(mapEntry.name.ident) == 0) {
- // The map entry's key (id) is not set. This must be
- // a symbol reference, so resolve it.
- Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
- if (!ref) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "unresolved attribute symbol");
- return {};
- }
- symbol.symbol = std::move(ref.value());
-
- } else {
- // The map entry's key (id) is a regular reference.
- symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
- }
-
+ symbol.symbol = Reference(util::deviceToHost32(mapEntry.name.ident));
attr->symbols.push_back(std::move(symbol));
}
}
@@ -831,115 +526,26 @@
return attr;
}
-static bool isMetaDataEntry(const ResTable_map& mapEntry) {
- switch (util::deviceToHost32(mapEntry.name.ident)) {
- case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
- case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
- case ExtendedResTableMapTypes::ATTR_COMMENT:
- return true;
- }
- return false;
-}
-
-bool BinaryResourceParser::collectMetaData(const ResTable_map& mapEntry, Value* value) {
- switch (util::deviceToHost32(mapEntry.name.ident)) {
- case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
- value->setSource(Source(util::getString8(mSourcePool,
- util::deviceToHost32(mapEntry.value.data))));
- return true;
- break;
-
- case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
- value->setSource(value->getSource().withLine(util::deviceToHost32(mapEntry.value.data)));
- return true;
- break;
-
- case ExtendedResTableMapTypes::ATTR_COMMENT:
- value->setComment(util::getString(mSourcePool, util::deviceToHost32(mapEntry.value.data)));
- return true;
- break;
- }
- return false;
-}
-
std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Array> array = util::make_unique<Array>();
- Source source;
for (const ResTable_map& mapEntry : map) {
- if (isMetaDataEntry(mapEntry)) {
- if (array->items.empty()) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "out-of-sequence meta data in array");
- return {};
- }
- collectMetaData(mapEntry, array->items.back().get());
- continue;
- }
-
array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
}
return array;
}
-std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
- const ConfigDescription& config,
- const ResTable_map_entry* map) {
- std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
- for (const ResTable_map& mapEntry : map) {
- if (isMetaDataEntry(mapEntry)) {
- if (styleable->entries.empty()) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "out-of-sequence meta data in styleable");
- return {};
- }
- collectMetaData(mapEntry, &styleable->entries.back());
- continue;
- }
-
- if (util::deviceToHost32(mapEntry.name.ident) == 0) {
- // The map entry's key (attribute) is not set. This must be
- // a symbol reference, so resolve it.
- Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
- if (!ref) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "unresolved styleable symbol");
- return {};
- }
- styleable->entries.emplace_back(std::move(ref.value()));
-
- } else {
- // The map entry's key (attribute) is a regular reference.
- styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
- }
- }
- return styleable;
-}
-
std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- Item* lastEntry = nullptr;
for (const ResTable_map& mapEntry : map) {
- if (isMetaDataEntry(mapEntry)) {
- if (!lastEntry) {
- mContext->getDiagnostics()->error(DiagMessage(mSource)
- << "out-of-sequence meta data in plural");
- return {};
- }
- collectMetaData(mapEntry, lastEntry);
- continue;
- }
-
std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
if (!item) {
return {};
}
- lastEntry = item.get();
-
switch (util::deviceToHost32(mapEntry.name.ident)) {
case ResTable_map::ATTR_ZERO:
plural->values[Plural::Zero] = std::move(item);
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 0745a59..12bc13d 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -55,14 +55,8 @@
bool parse();
private:
- // Helper method to retrieve the symbol name for a given table offset specified
- // as a pointer.
- Maybe<Reference> getSymbol(const void* data);
-
bool parseTable(const android::ResChunk_header* chunk);
- bool parseSymbolTable(const android::ResChunk_header* chunk);
bool parsePackage(const android::ResChunk_header* chunk);
- bool parsePublic(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
bool parseTypeSpec(const android::ResChunk_header* chunk);
bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
@@ -87,10 +81,6 @@
const ConfigDescription& config,
const android::ResTable_map_entry* map);
- std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
- const ConfigDescription& config,
- const android::ResTable_map_entry* map);
-
/**
* If the mapEntry is a special type that denotes meta data (source, comment), then it is
* read and added to the Value.
@@ -106,23 +96,6 @@
const void* mData;
const size_t mDataLen;
- // The array of symbol entries. Each element points to an offset
- // in the table and an index into the symbol table string pool.
- const SymbolTable_entry* mSymbolEntries = nullptr;
-
- // Number of symbol entries.
- size_t mSymbolEntryCount = 0;
-
- // The symbol table string pool. Holds the names of symbols
- // referenced in this table but not defined nor resolved to an
- // ID.
- android::ResStringPool mSymbolPool;
-
- // The source string pool. Resource entries may have an extra
- // field that points into this string pool, which denotes where
- // the resource was parsed from originally.
- android::ResStringPool mSourcePool;
-
// The standard value string pool for resource values.
android::ResStringPool mValuePool;
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader.h b/tools/aapt2/unflatten/FileExportHeaderReader.h
deleted file mode 100644
index e552ea1..0000000
--- a/tools/aapt2/unflatten/FileExportHeaderReader.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H
-#define AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H
-
-#include "ResChunkPullParser.h"
-#include "Resource.h"
-#include "ResourceUtils.h"
-
-#include "flatten/ResourceTypeExtensions.h"
-#include "util/StringPiece.h"
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-static ssize_t parseFileExportHeaderImpl(const void* data, const size_t len,
- const FileExport_header** outFileExport,
- const ExportedSymbol** outExportedSymbolIndices,
- android::ResStringPool* outStringPool,
- std::string* outError) {
- ResChunkPullParser parser(data, len);
- if (!ResChunkPullParser::isGoodEvent(parser.next())) {
- if (outError) *outError = parser.getLastError();
- return -1;
- }
-
- if (util::deviceToHost16(parser.getChunk()->type) != RES_FILE_EXPORT_TYPE) {
- if (outError) *outError = "no FileExport_header found";
- return -1;
- }
-
- const FileExport_header* fileExport = convertTo<FileExport_header>(parser.getChunk());
- if (!fileExport) {
- if (outError) *outError = "corrupt FileExport_header";
- return -1;
- }
-
- if (memcmp(fileExport->magic, "AAPT", sizeof(fileExport->magic)) != 0) {
- if (outError) *outError = "invalid magic value";
- return -1;
- }
-
- const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
-
- // Verify that we have enough space for all those symbols.
- size_t dataLen = getChunkDataLen(&fileExport->header);
- if (exportedSymbolCount > dataLen / sizeof(ExportedSymbol)) {
- if (outError) *outError = "too many symbols";
- return -1;
- }
-
- const size_t symbolIndicesSize = exportedSymbolCount * sizeof(ExportedSymbol);
-
- const void* strPoolData = getChunkData(&fileExport->header) + symbolIndicesSize;
- const size_t strPoolDataLen = dataLen - symbolIndicesSize;
- if (outStringPool->setTo(strPoolData, strPoolDataLen, false) != android::NO_ERROR) {
- if (outError) *outError = "corrupt string pool";
- return -1;
- }
-
- *outFileExport = fileExport;
- *outExportedSymbolIndices = (const ExportedSymbol*) getChunkData(
- &fileExport->header);
- return util::deviceToHost16(fileExport->header.headerSize) + symbolIndicesSize +
- outStringPool->bytes();
-}
-
-static ssize_t getWrappedDataOffset(const void* data, size_t len, std::string* outError) {
- const FileExport_header* header = nullptr;
- const ExportedSymbol* entries = nullptr;
- android::ResStringPool pool;
- return parseFileExportHeaderImpl(data, len, &header, &entries, &pool, outError);
-}
-
-/**
- * Reads the FileExport_header and populates outRes with the values in that header.
- */
-static ssize_t unwrapFileExportHeader(const void* data, size_t len, ResourceFile* outRes,
- std::string* outError) {
-
- const FileExport_header* fileExport = nullptr;
- const ExportedSymbol* entries = nullptr;
- android::ResStringPool symbolPool;
- const ssize_t offset = parseFileExportHeaderImpl(data, len, &fileExport, &entries, &symbolPool,
- outError);
- if (offset < 0) {
- return offset;
- }
-
- const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
- outRes->exportedSymbols.clear();
- outRes->exportedSymbols.reserve(exportedSymbolCount);
-
- for (size_t i = 0; i < exportedSymbolCount; i++) {
- const StringPiece16 str = util::getString(symbolPool,
- util::deviceToHost32(entries[i].name.index));
- StringPiece16 packageStr, typeStr, entryStr;
- ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
- const ResourceType* resType = parseResourceType(typeStr);
- if (!resType || entryStr.empty()) {
- if (outError) {
- std::stringstream errorStr;
- errorStr << "invalid exported symbol at index="
- << util::deviceToHost32(entries[i].name.index)
- << " (" << str << ")";
- *outError = errorStr.str();
- }
- return -1;
- }
-
- outRes->exportedSymbols.push_back(SourcedResourceName{
- ResourceName{ packageStr.toString(), *resType, entryStr.toString() },
- util::deviceToHost32(entries[i].line) });
- }
-
- const StringPiece16 str = util::getString(symbolPool,
- util::deviceToHost32(fileExport->name.index));
- StringPiece16 packageStr, typeStr, entryStr;
- ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
- const ResourceType* resType = parseResourceType(typeStr);
- if (!resType || entryStr.empty()) {
- if (outError) {
- std::stringstream errorStr;
- errorStr << "invalid resource name at index="
- << util::deviceToHost32(fileExport->name.index)
- << " (" << str << ")";
- *outError = errorStr.str();
- }
- return -1;
- }
-
- outRes->name = ResourceName{ packageStr.toString(), *resType, entryStr.toString() };
- outRes->source.path = util::utf16ToUtf8(
- util::getString(symbolPool, util::deviceToHost32(fileExport->source.index)));
- outRes->config.copyFromDtoH(fileExport->config);
- return offset;
-}
-
-} // namespace aapt
-
-#endif /* AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H */
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
deleted file mode 100644
index a76c83b..0000000
--- a/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Resource.h"
-
-#include "flatten/FileExportWriter.h"
-#include "unflatten/FileExportHeaderReader.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-#include "test/Common.h"
-
-#include <gtest/gtest.h>
-
-namespace aapt {
-
-TEST(FileExportHeaderReaderTest, ReadHeaderWithNoSymbolExports) {
- ResourceFile resFile = {
- test::parseNameOrDie(u"@android:layout/main.xml"),
- test::parseConfigOrDie("sw600dp-v4"),
- Source{ "res/layout/main.xml" },
- };
-
- BigBuffer buffer(1024);
- ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
- *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
- writer.finish();
-
- std::unique_ptr<uint8_t[]> data = util::copy(buffer);
-
- ResourceFile actualResFile;
-
- ssize_t offset = unwrapFileExportHeader(data.get(), buffer.size(), &actualResFile, nullptr);
- ASSERT_GT(offset, 0);
-
- EXPECT_EQ(offset, getWrappedDataOffset(data.get(), buffer.size(), nullptr));
-
- EXPECT_EQ(actualResFile.config, test::parseConfigOrDie("sw600dp-v4"));
- EXPECT_EQ(actualResFile.name, test::parseNameOrDie(u"@android:layout/main.xml"));
- EXPECT_EQ(actualResFile.source.path, "res/layout/main.xml");
-
- EXPECT_EQ(*(uint32_t*)(data.get() + offset), 42u);
-}
-
-} // namespace aapt
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 037ce57..5c20dfa 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -308,6 +308,11 @@
}
@Override
+ public String getServicesSystemSharedLibraryPackageName() {
+ return null;
+ }
+
+ @Override
public FeatureInfo[] getSystemAvailableFeatures() {
return new FeatureInfo[0];
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 4a86c59..31da670 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -332,6 +332,7 @@
public static class InformationElement {
public static final int EID_SSID = 0;
public static final int EID_BSS_LOAD = 11;
+ public static final int EID_RSN = 48;
public static final int EID_HT_OPERATION = 61;
public static final int EID_INTERWORKING = 107;
public static final int EID_ROAMING_CONSORTIUM = 111;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index a0dbd85..362738e 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -101,6 +101,8 @@
/** @hide */
public static final String CA_CERT_KEY = "ca_cert";
/** @hide */
+ public static final String CA_PATH_KEY = "ca_path";
+ /** @hide */
public static final String ENGINE_KEY = "engine";
/** @hide */
public static final String ENGINE_ID_KEY = "engine_id";
@@ -625,6 +627,33 @@
mCaCerts = null;
}
+ /**
+ * Set the ca_path directive on wpa_supplicant.
+ *
+ * From wpa_supplicant documentation:
+ *
+ * Directory path for CA certificate files (PEM). This path may contain
+ * multiple CA certificates in OpenSSL format. Common use for this is to
+ * point to system trusted CA list which is often installed into directory
+ * like /etc/ssl/certs. If configured, these certificates are added to the
+ * list of trusted CAs. ca_cert may also be included in that case, but it is
+ * not required.
+ * @param domain The path for CA certificate files
+ * @hide
+ */
+ public void setCaPath(String path) {
+ setFieldValue(CA_PATH_KEY, path);
+ }
+
+ /**
+ * Get the domain_suffix_match value. See setDomSuffixMatch.
+ * @return The path for CA certificate files.
+ * @hide
+ */
+ public String getCaPath() {
+ return getFieldValue(CA_PATH_KEY, "");
+ }
+
/** Set Client certificate alias.
*
* <p> See the {@link android.security.KeyChain} for details on installing or choosing