Merge "Reduce jank during pinned stack animation" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 649d7fa..b9f932b 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
@@ -34045,6 +34045,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();
@@ -34069,6 +34070,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);
@@ -40661,6 +40663,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();
@@ -43164,6 +43181,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);
@@ -43215,6 +43233,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();
@@ -43340,6 +43359,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 3c3054e..b404bb0 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
@@ -36528,6 +36528,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();
@@ -36552,6 +36553,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);
@@ -43413,6 +43415,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();
@@ -45916,6 +45933,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);
@@ -45967,6 +45985,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();
@@ -46093,6 +46112,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 8fbcff9..9f8b8a8 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
@@ -34060,6 +34060,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();
@@ -34084,6 +34085,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);
@@ -40678,6 +40680,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();
@@ -43181,6 +43198,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);
@@ -43232,6 +43250,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();
@@ -43357,6 +43376,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/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 9a88f2c..33fd1db 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1842,13 +1842,10 @@
* @see UiAutomation
*/
public UiAutomation getUiAutomation() {
- if (mUiAutomationConnection != null) {
- if (mUiAutomation == null) {
- return getUiAutomation(0);
- }
- return mUiAutomation;
+ if ((mUiAutomation == null) || (mUiAutomation.isDestroyed())) {
+ return getUiAutomation(0);
}
- return null;
+ return mUiAutomation;
}
/**
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/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/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/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/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..0ae3e89 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;
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/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 3c21a21..73d0f72 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -219,8 +219,8 @@
void onStackRestored(boolean restored, boolean external) {}
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;
}
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/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index d21b157..b6c27b5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -53,9 +53,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,11 +92,13 @@
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;
@@ -155,9 +155,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 +180,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).
@@ -240,6 +222,13 @@
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);
@@ -432,41 +421,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 +454,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 +758,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 +1254,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 +1297,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/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/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 417fd24..6ba2146 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -24,6 +24,7 @@
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 +33,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 +194,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();
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 3faa8f4..6af492c 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -73,7 +73,6 @@
extraValuesList,
COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
EMPTY_ARGS,
- /* heuristic */ false,
COLUMN_DEVICE_ID);
database.setTransactionSuccessful();
return changed;
@@ -92,16 +91,13 @@
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:
@@ -120,7 +116,6 @@
extraValuesList,
COLUMN_PARENT_DOCUMENT_ID + "=?",
strings(parentDocumentId),
- heuristic,
mapColumn);
database.setTransactionSuccessful();
@@ -137,16 +132,13 @@
* @param documents List of document information.
*/
synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
- final boolean heuristic;
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:
@@ -163,17 +155,13 @@
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);
@@ -209,11 +197,6 @@
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)));
-
// Set all documents as invalidated.
final ContentValues values = new ContentValues();
values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
@@ -245,7 +228,6 @@
* @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.
*/
private boolean putDocuments(
@@ -253,7 +235,6 @@
@Nullable ContentValues[] rootExtraValuesList,
String selection,
String[] args,
- boolean heuristic,
String mappingKey) {
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
boolean added = false;
@@ -285,7 +266,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 +274,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.
@@ -334,84 +312,10 @@
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();
-
boolean changed = false;
// Delete all invalidated rows that cannot be mapped.
@@ -421,58 +325,10 @@
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 {
database.endTransaction();
}
}
-
- /**
- * 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.
- */
- 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();
- }
- }
-
- /**
- * 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";
- }
}
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/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index a49dc67..a75012e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -310,14 +310,14 @@
assertEquals(3, cursor.getCount());
cursor.moveToNext();
assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
- assertTrue(isNull(cursor, COLUMN_STORAGE_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));
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
@@ -333,7 +333,7 @@
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
@@ -387,7 +387,7 @@
assertEquals(4, cursor.getCount());
cursor.moveToPosition(3);
- assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
@@ -406,7 +406,7 @@
assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
@@ -544,7 +544,7 @@
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
- assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+ assertEquals(201, getInt(cursor, COLUMN_OBJECT_HANDLE));
cursor.close();
}
}
@@ -626,11 +626,12 @@
final Cursor cursor = mDatabase.queryRootDocuments(columns);
assertEquals(2, cursor.getCount());
cursor.moveToNext();
- assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+ // One reuses exisitng document ID 1.
+ assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.moveToNext();
- assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+ assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
cursor.close();
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/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 3b49d37..9d4f425 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -240,11 +240,11 @@
/**
* Dismisses recents if we are already visible and the intent is to toggle the recents view.
*/
- boolean dismissRecentsToFocusedTask() {
+ boolean dismissRecentsToFocusedTask(int logCategory) {
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
// If we have a focused Task, launch that Task now
- if (mRecentsView.launchFocusedTask()) return true;
+ if (mRecentsView.launchFocusedTask(logCategory)) return true;
}
return false;
}
@@ -270,7 +270,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
// If we have a focused Task, launch that Task now
- if (mRecentsView.launchFocusedTask()) return true;
+ if (mRecentsView.launchFocusedTask(0 /* logCategory */)) return true;
// If none of the other cases apply, then just go Home
dismissRecentsToHome(true /* animateTaskViews */);
return true;
@@ -360,7 +360,7 @@
mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
@Override
public void run() {
- dismissRecentsToFocusedTask();
+ dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT);
}
});
@@ -634,7 +634,7 @@
// Focus the next task
EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
- MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
+ MetricsLogger.action(this, MetricsEvent.OVERVIEW_PAGE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index f7ebd94..82e7861 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -66,8 +66,9 @@
*/
public int getInitialFocusTaskIndex(int numTasks) {
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchedFromAppWithThumbnail) {
- if (debugFlags.isFastToggleRecentsEnabled()) {
+ if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
// If fast toggling, focus the front most task so that the next tap will focus the
// N-1 task
return numTasks - 1;
@@ -76,7 +77,7 @@
// If coming from another app, focus the next task
return numTasks - 2;
} else {
- if (debugFlags.isFastToggleRecentsEnabled()) {
+ if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
// If fast toggling, defer focusing until the next tap (which will automatically
// focus the front most task)
return -1;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index 5eeda72..d7b9b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -161,7 +161,7 @@
ssp.startActivityFromRecents(v.getContext(), task.key.id, task.title,
ActivityOptions.makeBasic());
- MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
+ MetricsLogger.action(v.getContext(), MetricsEvent.OVERVIEW_SELECT,
task.key.getComponent().toString());
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
index acad0ea..3d1ea8e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
@@ -21,6 +21,7 @@
import android.support.v7.widget.helper.ItemTouchHelper;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
@@ -72,6 +73,8 @@
// Keep track of deletions by swiping within history
MetricsLogger.histogram(mContext, "overview_task_dismissed_source",
Constants.Metrics.DismissSourceHistorySwipeGesture);
+ MetricsLogger.action(mContext, MetricsEvent.OVERVIEW_DISMISS,
+ taskRow.task.key.getComponent().toString());
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 4deea54..52043f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -62,6 +62,8 @@
}
};
+ public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
+
/**
* @return the first parent walking up the view hierarchy that has the given class type.
*
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index d4624f5..42aaa97 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -248,13 +248,18 @@
}
/** Launches the focused task from the first stack if possible */
- public boolean launchFocusedTask() {
+ public boolean launchFocusedTask(int logEvent) {
if (mTaskStackView != null) {
Task task = mTaskStackView.getFocusedTask();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
INVALID_STACK_ID, false));
+
+ if (logEvent != 0) {
+ MetricsLogger.action(getContext(), logEvent,
+ task.key.getComponent().toString());
+ }
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 7eaa193..76972d7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -23,6 +23,7 @@
import android.graphics.Path;
import android.graphics.RectF;
import android.view.View;
+import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.Interpolators;
@@ -34,6 +35,7 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -81,9 +83,18 @@
private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR =
new PathInterpolator(0.4f, 0, 1f, 1f);
+ private static final PathInterpolator FOCUS_NEXT_TASK_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0, 0, 1f);
+ private static final PathInterpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR =
+ new PathInterpolator(0, 0, 0, 1f);
+ private static final PathInterpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0, 0.2f, 1f);
+
private TaskStackView mStackView;
private TaskViewTransform mTmpTransform = new TaskViewTransform();
+ private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>();
+ private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>();
public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
mStackView = stackView;
@@ -418,4 +429,92 @@
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
}
+
+ /**
+ * Starts the animation to focus the next {@link TaskView} when paging through recents.
+ *
+ * @return whether or not this will trigger a scroll in the stack
+ */
+ public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask,
+ boolean requestViewFocus) {
+ TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
+ TaskStackViewScroller stackScroller = mStackView.getScroller();
+ TaskStack stack = mStackView.getStack();
+
+ final float newScroll = stackLayout.getStackScrollForTask(newFocusedTask);
+ boolean willScrollToFront = newScroll > stackScroller.getStackScroll();
+ boolean willScroll = Float.compare(newScroll, stackScroller.getStackScroll()) != 0;
+
+ // Get the current set of task transforms
+ ArrayList<Task> stackTasks = stack.getStackTasks();
+ mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
+
+ // Pick up the newly visible views after the scroll
+ mStackView.bindVisibleTaskViews(newScroll);
+
+ // Update the internal state
+ stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
+ stackScroller.setStackScroll(newScroll, null /* animation */);
+ mStackView.cancelDeferredTaskViewLayoutAnimation();
+
+ // Get the final set of task transforms
+ mStackView.getLayoutTaskTransforms(newScroll, stackTasks, mTmpFinalTaskTransforms);
+
+ // Focus the task view
+ TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
+ newFocusedTaskView.setFocusedState(true, requestViewFocus);
+
+ // Setup the end listener to return all the hidden views to the view pool after the
+ // focus animation
+ AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStackView.bindVisibleTaskViews(newScroll);
+ }
+ };
+
+ List<TaskView> taskViews = mStackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView);
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+
+ if (mStackView.isIgnoredTask(task)) {
+ continue;
+ }
+
+ int taskIndex = stackTasks.indexOf(task);
+ TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
+ TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
+
+ // Update the task to the initial state (for the newly picked up tasks)
+ mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
+
+ int duration;
+ Interpolator interpolator;
+ if (willScrollToFront) {
+ duration = Math.max(100, 100 + ((i - 1) * 50));
+ interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
+ } else {
+ if (i < newFocusTaskViewIndex) {
+ duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50);
+ interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
+ } else if (i > newFocusTaskViewIndex) {
+ duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50));
+ interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR;
+ } else {
+ duration = 200;
+ interpolator = FOCUS_NEXT_TASK_INTERPOLATOR;
+ }
+ }
+
+ AnimationProps anim = new AnimationProps()
+ .setDuration(AnimationProps.BOUNDS, duration)
+ .setInterpolator(AnimationProps.BOUNDS, interpolator)
+ .setListener(endListener);
+ mStackView.updateTaskViewToTransform(tv, toTransform, anim);
+ }
+ return willScroll;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index bd37c3b..19ac1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -457,7 +457,7 @@
launchTaskIndex - 1));
}
} else {
- float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height();
+ float offsetPct = (float) (mTaskRect.height() / 3) / mStackRect.height();
float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX)));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index bb74de4..fb3515a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,6 +20,8 @@
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
@@ -41,8 +43,12 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
@@ -113,6 +119,7 @@
private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
+ LayoutInflater mInflater;
TaskStack mStack;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScroller mStackScroller;
@@ -142,16 +149,15 @@
boolean mScreenPinningEnabled;
// The stable stack bounds are the full bounds that we were measured with from RecentsView
- Rect mStableStackBounds = new Rect();
+ private Rect mStableStackBounds = new Rect();
// The current stack bounds are dynamic and may change as the user drags and drops
- Rect mStackBounds = new Rect();
+ private Rect mStackBounds = new Rect();
- int[] mTmpVisibleRange = new int[2];
- Rect mTmpRect = new Rect();
- ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
- List<TaskView> mTmpTaskViews = new ArrayList<>();
- TaskViewTransform mTmpTransform = new TaskViewTransform();
- LayoutInflater mInflater;
+ private int[] mTmpVisibleRange = new int[2];
+ private Rect mTmpRect = new Rect();
+ private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
+ private List<TaskView> mTmpTaskViews = new ArrayList<>();
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
@@ -396,6 +402,7 @@
int frontMostVisibleIndex = -1;
int backMostVisibleIndex = -1;
boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
+ boolean targetScrollIsInFront = targetStackScroll > curStackScroll;
// We can reuse the task transforms where possible to reduce object allocation
Utilities.matchTaskListSize(tasks, taskTransforms);
@@ -439,7 +446,7 @@
frontMostVisibleIndex = i;
}
backMostVisibleIndex = i;
- } else {
+ } else if (!targetScrollIsInFront) {
if (backMostVisibleIndex != -1) {
// We've reached the end of the visible range, so going down the rest of the
// stack, we can just reset the transforms accordingly
@@ -531,7 +538,7 @@
}
// Skip the invisible non-freeform stack tasks
- if (i > visibleStackRange[0] && !task.isFreeformTask()) {
+ if (!task.isFreeformTask() && !transform.visible) {
continue;
}
@@ -671,12 +678,20 @@
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
- mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null);
+ mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null,
+ true /* forceUpdate */);
transform.visible = true;
}
}
/**
+ * Cancels the next deferred task view layout.
+ */
+ void cancelDeferredTaskViewLayoutAnimation() {
+ mDeferredTaskViewLayoutAnimation = null;
+ }
+
+ /**
* Cancels all {@link TaskView} animations.
*
* @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
@@ -716,7 +731,7 @@
TaskView frontTv = null;
int clipBottom = 0;
- if (mIgnoreTasks.contains(tv.getTask().key)) {
+ if (isIgnoredTask(tv.getTask())) {
// For each of the ignore tasks, update the translationZ of its TaskView to be
// between the translationZ of the tasks immediately underneath it
if (prevVisibleTv != null) {
@@ -804,15 +819,15 @@
}
/**
- * Sets the focused task to the provided (bounded taskIndex).
+ * Sets the focused task to the provided (bounded focusTaskIndex).
*
* @return whether or not the stack will scroll as a part of this focus change
*/
- private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
- final boolean requestViewFocus, final int timerIndicatorDuration) {
+ private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
+ boolean requestViewFocus, int timerIndicatorDuration) {
// Find the next task to focus
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
- Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
+ Math.max(0, Math.min(mStack.getTaskCount() - 1, focusTaskIndex)) : -1;
final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
mStack.getStackTasks().get(newFocusedTaskIndex) : null;
@@ -830,7 +845,6 @@
}
boolean willScroll = false;
-
mFocusedTask = newFocusedTask;
if (newFocusedTask != null) {
@@ -845,33 +859,20 @@
}
}
- Runnable focusTaskRunnable = new Runnable() {
- @Override
- public void run() {
- final TaskView tv = getChildViewForTask(newFocusedTask);
- if (tv != null) {
- tv.setFocusedState(true, requestViewFocus);
- }
- }
- };
-
if (scrollToTask) {
// Cancel any running enter animations at this point when we scroll or change focus
if (!mEnterAnimationComplete) {
cancelAllTaskViewAnimations();
}
- // TODO: Center the newly focused task view, only if not freeform
- float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
- if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
- mStackScroller.animateScroll(newScroll, focusTaskRunnable);
- willScroll = true;
- } else {
- focusTaskRunnable.run();
- }
- mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
+ willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask,
+ requestViewFocus);
} else {
- focusTaskRunnable.run();
+ // Focus the task view
+ TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask);
+ if (newFocusedTaskView != null) {
+ newFocusedTaskView.setFocusedState(true, requestViewFocus);
+ }
}
}
return willScroll;
@@ -1276,7 +1277,7 @@
Task task = tasks.get(i);
// Ignore deleting tasks
- if (mIgnoreTasks.contains(task.key)) {
+ if (isIgnoredTask(task)) {
if (i == tasks.size() - 1) {
isFrontMostTask.value = true;
}
@@ -1390,7 +1391,7 @@
}
@Override
- public void prepareViewToEnterPool(TaskView tv) {
+ public void onReturnViewToPool(TaskView tv) {
final Task task = tv.getTask();
// Report that this tasks's data is no longer being used
@@ -1411,7 +1412,7 @@
}
@Override
- public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
+ public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
// Find the index where this task should be placed in the stack
int taskIndex = mStack.indexOfStackTask(task);
int insertIndex = findTaskViewInsertIndex(task, taskIndex);
@@ -1601,6 +1602,9 @@
public final void onBusEvent(TaskViewDismissedEvent event) {
removeTaskViewFromStack(event.taskView, event.task);
EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
+
+ MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
+ event.task.key.getComponent().toString());
}
public final void onBusEvent(FocusNextTaskViewEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index c641d75..d1bce55 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -191,21 +191,27 @@
stopScroller();
stopBoundScrollAnimation();
- mFinalAnimatedScroll = newScroll;
- mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
- mScrollAnimator.setDuration(mContext.getResources().getInteger(
- R.integer.recents_animate_task_stack_scroll_duration));
- mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- mScrollAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (postRunnable != null) {
- postRunnable.run();
+ if (Float.compare(mStackScrollP, newScroll) != 0) {
+ mFinalAnimatedScroll = newScroll;
+ mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
+ mScrollAnimator.setDuration(mContext.getResources().getInteger(
+ R.integer.recents_animate_task_stack_scroll_duration));
+ mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ mScrollAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (postRunnable != null) {
+ postRunnable.run();
+ }
+ mScrollAnimator.removeAllListeners();
}
- mScrollAnimator.removeAllListeners();
+ });
+ mScrollAnimator.start();
+ } else {
+ if (postRunnable != null) {
+ postRunnable.run();
}
- });
- mScrollAnimator.start();
+ }
}
/** Aborts any current stack scrolls */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index d6680fd..5d1bb66 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -34,6 +34,7 @@
import android.view.animation.PathInterpolator;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
@@ -58,8 +59,6 @@
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
private static final int INACTIVE_POINTER_ID = -1;
-
- private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator();
private static final Interpolator STACK_TRANSFORM_INTERPOLATOR =
new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f);
@@ -230,6 +229,8 @@
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
+
+ MetricsLogger.action(mSv.getContext(), MetricsEvent.OVERVIEW_SCROLL);
}
}
if (mIsScrolling) {
@@ -542,8 +543,8 @@
mTmpTransform.copyFrom(fromTransform);
// We only really need to interpolate the bounds, progress and translation
- mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect,
- toTransform.rect));
+ mTmpTransform.rect.set(Utilities.RECTF_EVALUATOR.evaluate(dismissFraction,
+ fromTransform.rect, toTransform.rect));
mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction;
mTmpTransform.translationZ = fromTransform.translationZ +
(toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 439d96f..850e36e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -38,6 +38,8 @@
import android.view.ViewOutlineProvider;
import android.view.animation.AccelerateInterpolator;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
@@ -585,6 +587,9 @@
}
EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
screenPinningRequested));
+
+ MetricsLogger.action(v.getContext(), MetricsEvent.OVERVIEW_SELECT,
+ mTask.key.getComponent().toString());
}
/**** View.OnLongClickListener Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
index 31fbd3e..a287fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -29,8 +29,8 @@
/* An interface to the consumer of a view pool */
public interface ViewPoolConsumer<V, T> {
public V createView(Context context);
- public void prepareViewToEnterPool(V v);
- public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
+ public void onReturnViewToPool(V v);
+ public void onPickUpViewFromPool(V v, T prepareData, boolean isNewView);
public boolean hasPreferredData(V v, T preferredData);
}
@@ -46,7 +46,7 @@
/** Returns a view into the pool */
void returnViewToPool(V v) {
- mViewCreator.prepareViewToEnterPool(v);
+ mViewCreator.onReturnViewToPool(v);
mPool.push(v);
}
@@ -73,7 +73,7 @@
v = mPool.pop();
}
}
- mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
+ mViewCreator.onPickUpViewFromPool(v, prepareData, isNewView);
return v;
}
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/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 5abd1d5..dd6d6f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -35,6 +35,8 @@
import android.widget.SeekBar;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -51,6 +53,7 @@
private SeekBar mSeekBar;
private Notification.Topic mTopic;
private INotificationManager mINotificationManager;
+ private int mStartingImportance;
public NotificationGuts(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -103,6 +106,7 @@
void bindImportance(final StatusBarNotification sbn, final ExpandableNotificationRow row,
final int importance) {
+ mStartingImportance = importance;
mINotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mTopic = sbn.getNotification().getTopic() == null
@@ -151,6 +155,7 @@
}
updateTitleAndSummary(progress);
if (fromUser) {
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_MODIFY_IMPORTANCE_SLIDER);
if (appUsesTopics) {
mApplyToTopic.setVisibility(View.VISIBLE);
mApplyToTopic.setText(
@@ -205,6 +210,8 @@
void saveImportance(final StatusBarNotification sbn) {
int progress = mSeekBar.getProgress();
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
+ progress - mStartingImportance);
try {
mINotificationManager.setImportance(sbn.getPackageName(), sbn.getUid(),
mApplyToTopic.isChecked() ? mTopic : null, progress);
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/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 3327ec4..3f3f851 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -318,10 +318,10 @@
OVERVIEW_HISTORY = 275;
// Logged when the user pages through overview.
- ACTION_OVERVIEW_PAGE = 276;
+ OVERVIEW_PAGE = 276;
// Logged when the user launches a task from overview.
- ACTION_OVERVIEW_SELECT = 277;
+ OVERVIEW_SELECT = 277;
// Logged when the user views the emergency info.
ACTION_VIEW_EMERGENCY_INFO = 278;
@@ -353,5 +353,21 @@
// Logged when the user undocks a previously docked window by long pressing recents while in
// docked mode.
ACTION_WINDOW_UNDOCK_LONGPRESS = 286;
+
+ // Logged when the user scrolls through overview manually
+ OVERVIEW_SCROLL = 287;
+
+ // Logged when the overview times out automatically selecting an app
+ OVERVIEW_SELECT_TIMEOUT = 288;
+
+ // Logged when a user dismisses a task in overview
+ OVERVIEW_DISMISS = 289;
+
+ // Logged when the user modifying the notification importance slider.
+ ACTION_MODIFY_IMPORTANCE_SLIDER = 290;
+
+ // Logged when the user saves a modification to notification importance. Negative numbers
+ // indicate the user lowered the importance; positive means they increased it.
+ ACTION_SAVE_IMPORTANCE = 291;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c35a73a..4be6833 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -52,7 +52,6 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -654,7 +653,7 @@
userState.mUiAutomationFlags = flags;
userState.mIsAccessibilityEnabled = true;
userState.mInstalledServices.add(accessibilityServiceInfo);
- if (userState.isUiAutomationSuppressingOtherServices()) {
+ if ((flags & UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) == 0) {
// Set the temporary state.
userState.mIsTouchExplorationEnabled = false;
userState.mIsEnhancedWebAccessibilityEnabled = false;
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 0413667..104217a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10737,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++) {
@@ -10751,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);
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 bf8e8fb..d2bcf13 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -39,6 +39,7 @@
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
+import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_NONE;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -2001,6 +2002,9 @@
@Override
public void setImportanceFromAssistant(INotificationListener token, String key,
int importance, CharSequence explanation) throws RemoteException {
+ if (importance == IMPORTANCE_NONE) {
+ throw new IllegalArgumentException("blocking not allowed: key=" + key);
+ }
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
@@ -3480,7 +3484,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/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 6c338c1..9b5fde0 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -262,6 +262,7 @@
grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId);
grantRuntimePermissionsLPw(setupPackage, CONTACTS_PERMISSIONS, userId);
grantRuntimePermissionsLPw(setupPackage, LOCATION_PERMISSIONS, userId);
+ grantRuntimePermissionsLPw(setupPackage, CAMERA_PERMISSIONS, userId);
}
// Camera
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c8645b4..04e4a8c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8220,7 +8220,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 +8301,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 +14397,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 +14411,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 +14437,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 +14456,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/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/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/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
index 611ed15..9fc1706 100644
--- a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
+++ b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
@@ -36,15 +36,15 @@
mMoTree = moTree;
}
- public String getmBaseUri() {
+ public String getBaseUri() {
return mBaseUri;
}
- public String getmUrn() {
+ public String getUrn() {
return mUrn;
}
- public String getmMoTree() {
+ public String getMoTree() {
return mMoTree;
}
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