Merge "We wanted actual protection level, not flags."
diff --git a/api/current.txt b/api/current.txt
index 8b4b499..870c020 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -283,7 +283,6 @@
field public static final int allowBackup = 16843392; // 0x1010280
field public static final int allowClearUserData = 16842757; // 0x1010005
field public static final int allowEmbedded = 16843765; // 0x10103f5
- field public static final int allowForceDark = 16844172; // 0x101058c
field public static final int allowParallelSyncs = 16843570; // 0x1010332
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
@@ -638,6 +637,7 @@
field public static final int fontVariationSettings = 16844144; // 0x1010570
field public static final int fontWeight = 16844083; // 0x1010533
field public static final int footerDividersEnabled = 16843311; // 0x101022f
+ field public static final int forceDarkAllowed = 16844172; // 0x101058c
field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521
field public static final int foreground = 16843017; // 0x1010109
field public static final int foregroundGravity = 16843264; // 0x1010200
@@ -36802,6 +36802,7 @@
ctor public MediaStore();
method public static android.net.Uri getDocumentUri(android.content.Context, android.net.Uri);
method public static android.net.Uri getMediaScannerUri();
+ method public static android.net.Uri getMediaUri(android.content.Context, android.net.Uri);
method public static java.lang.String getVersion(android.content.Context);
field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
@@ -48519,6 +48520,7 @@
method public final boolean isFocusableInTouchMode();
method public boolean isFocused();
method public final boolean isFocusedByDefault();
+ method public boolean isForceDarkAllowed();
method public boolean isHapticFeedbackEnabled();
method public boolean isHardwareAccelerated();
method public boolean isHorizontalFadingEdgeEnabled();
@@ -48705,6 +48707,7 @@
method public void setFocusable(int);
method public void setFocusableInTouchMode(boolean);
method public void setFocusedByDefault(boolean);
+ method public void setForceDarkAllowed(boolean);
method public void setForeground(android.graphics.drawable.Drawable);
method public void setForegroundGravity(int);
method public void setForegroundTintList(android.content.res.ColorStateList);
diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto
index 453bf7e..e33bd8c 100644
--- a/cmds/statsd/src/atom_field_options.proto
+++ b/cmds/statsd/src/atom_field_options.proto
@@ -64,10 +64,22 @@
optional StateField option = 1 [default = STATE_FIELD_UNSET];
}
+// Used to generate StatsLog.write APIs.
+enum LogMode {
+ MODE_UNSET = 0;
+ // Log fields as their actual types e.g., all primary data types.
+ // Or fields that are hardcoded in stats_log_api_gen tool e.g., AttributionNode
+ MODE_AUTOMATIC = 1;
+ // Log fields in their proto binary format. These fields will not be parsed in statsd
+ MODE_BYTES = 2;
+}
+
extend google.protobuf.FieldOptions {
// Flags to decorate an atom that presents a state change.
optional StateAtomFieldOption state_field_option = 50000;
// Flags to decorate the uid fields in an atom.
optional bool is_uid = 50001 [default = false];
+
+ optional LogMode log_mode = 50002 [default = MODE_AUTOMATIC];
}
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 805e583..2498d9f 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -25,15 +25,16 @@
#include <utils/Log.h>
#include <utils/SystemClock.h>
+using android::util::AtomsInfo;
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FIXED64;
using android::util::FIELD_TYPE_FLOAT;
using android::util::FIELD_TYPE_INT32;
using android::util::FIELD_TYPE_INT64;
-using android::util::FIELD_TYPE_UINT64;
-using android::util::FIELD_TYPE_FIXED64;
using android::util::FIELD_TYPE_MESSAGE;
using android::util::FIELD_TYPE_STRING;
+using android::util::FIELD_TYPE_UINT64;
using android::util::ProtoOutputStream;
namespace android {
@@ -294,8 +295,9 @@
// }
//
//
-void writeFieldValueTreeToStreamHelper(const std::vector<FieldValue>& dims, size_t* index,
- int depth, int prefix, ProtoOutputStream* protoOutput) {
+void writeFieldValueTreeToStreamHelper(int tagId, const std::vector<FieldValue>& dims,
+ size_t* index, int depth, int prefix,
+ ProtoOutputStream* protoOutput) {
size_t count = dims.size();
while (*index < count) {
const auto& dim = dims[*index];
@@ -319,9 +321,31 @@
case FLOAT:
protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value);
break;
- case STRING:
- protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
+ case STRING: {
+ bool isBytesField = false;
+ // Bytes field is logged via string format in log_msg format. So here we check
+ // if this string field is a byte field.
+ std::map<int, std::vector<int>>::const_iterator itr;
+ if (depth == 0 && (itr = AtomsInfo::kBytesFieldAtoms.find(tagId)) !=
+ AtomsInfo::kBytesFieldAtoms.end()) {
+ const std::vector<int>& bytesFields = itr->second;
+ for (int bytesField : bytesFields) {
+ if (bytesField == fieldNum) {
+ // This is a bytes field
+ isBytesField = true;
+ break;
+ }
+ }
+ }
+ if (isBytesField) {
+ protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum,
+ (const char*)dim.mValue.str_value.c_str(),
+ dim.mValue.str_value.length());
+ } else {
+ protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
+ }
break;
+ }
case STORAGE:
protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum,
(const char*)dim.mValue.storage_value.data(),
@@ -342,7 +366,7 @@
}
// Directly jump to the leaf value because the repeated position field is implied
// by the position of the sub msg in the parent field.
- writeFieldValueTreeToStreamHelper(dims, index, valueDepth,
+ writeFieldValueTreeToStreamHelper(tagId, dims, index, valueDepth,
dim.mField.getPrefix(valueDepth), protoOutput);
if (msg_token != 0) {
protoOutput->end(msg_token);
@@ -359,7 +383,7 @@
uint64_t atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | tagId);
size_t index = 0;
- writeFieldValueTreeToStreamHelper(values, &index, 0, 0, protoOutput);
+ writeFieldValueTreeToStreamHelper(tagId, values, &index, 0, 0, protoOutput);
protoOutput->end(atomToken);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9d3c5c6..4756bf4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1425,60 +1425,10 @@
PrintWriter pw = new FastPrintWriter(
new FileOutputStream(pfd.getFileDescriptor()));
PrintWriterPrinter printer = new PrintWriterPrinter(pw);
- SQLiteDebug.dump(printer, args);
-
- if (isSystem) {
- dumpDatabaseFileSizes(pw, Environment.getDataSystemDirectory(), true);
- dumpDatabaseFileSizes(pw, Environment.getDataSystemDeDirectory(), true);
- dumpDatabaseFileSizes(pw, Environment.getDataSystemCeDirectory(), true);
- } else {
- Context context = getApplication();
- if (context != null) {
- dumpDatabaseFileSizes(pw,
- getDatabasesDir(context.createDeviceProtectedStorageContext()),
- false);
- dumpDatabaseFileSizes(pw,
- getDatabasesDir(context.createCredentialProtectedStorageContext()),
- false);
- }
- }
+ SQLiteDebug.dump(printer, args, isSystem);
pw.flush();
}
- private void dumpDatabaseFileSizes(PrintWriter pw, File dir, boolean isSystem) {
- final File[] files = dir.listFiles();
- if (files == null || files.length == 0) {
- return;
- }
- Arrays.sort(files, (a, b) -> a.getName().compareTo(b.getName()));
-
- boolean needHeader = true;
- for (File f : files) {
- if (isSystem) {
- // If it's the system server, the directory contains other files too, so
- // filter by file extensions.
- // (If it's an app, just print all files because they may not use *.db
- // extension.)
- final String name = f.getName();
- if (!(name.endsWith(".db") || name.endsWith(".db-wal")
- || name.endsWith(".db-journal"))) {
- continue;
- }
- }
- if (needHeader) {
- pw.println();
- pw.println("Database files in " + dir.getAbsolutePath() + ":");
- needHeader = false;
- }
-
- pw.print(" ");
- pw.print(f.getName());
- pw.print(" ");
- pw.print(f.length());
- pw.println(" bytes");
- }
- }
-
@Override
public void dumpDbInfo(final ParcelFileDescriptor pfd, final String[] args) {
if (mSystemThread) {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 599c2d2..a2a6b9b 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -36,11 +36,9 @@
import android.database.Cursor;
import android.database.IContentObserver;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.ImageDecoder;
import android.graphics.ImageDecoder.ImageInfo;
import android.graphics.ImageDecoder.Source;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -55,7 +53,6 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -3255,4 +3252,13 @@
}
});
}
+
+ /** {@hide} */
+ public static void onDbCorruption(String tag, String message, Throwable stacktrace) {
+ try {
+ getContentService().onDbCorruption(tag, message, Log.getStackTraceString(stacktrace));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index a55dd31..1d02375 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -185,4 +185,6 @@
Bundle getCache(in String packageName, in Uri key, int userId);
void resetTodayStats();
+
+ void onDbCorruption(String tag, String message, String stacktrace);
}
diff --git a/core/java/android/database/DefaultDatabaseErrorHandler.java b/core/java/android/database/DefaultDatabaseErrorHandler.java
index 7fa2b40..cf019e1 100755
--- a/core/java/android/database/DefaultDatabaseErrorHandler.java
+++ b/core/java/android/database/DefaultDatabaseErrorHandler.java
@@ -15,14 +15,14 @@
*/
package android.database;
-import java.io.File;
-import java.util.List;
-
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import android.util.Pair;
+import java.io.File;
+import java.util.List;
+
/**
* Default class used to define the action to take when database corruption is reported
* by sqlite.
@@ -52,6 +52,7 @@
*/
public void onCorruption(SQLiteDatabase dbObj) {
Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
+ SQLiteDatabase.wipeDetected(dbObj.getPath(), "corruption");
// is the corruption detected even before database could be 'opened'?
if (!dbObj.isOpen()) {
@@ -99,7 +100,7 @@
}
Log.e(TAG, "deleting the database file: " + fileName);
try {
- SQLiteDatabase.deleteDatabase(new File(fileName));
+ SQLiteDatabase.deleteDatabase(new File(fileName), /*removeCheckFile=*/ false);
} catch (Exception e) {
/* print warning and ignore exception */
Log.w(TAG, "delete failed: " + e.getMessage());
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 5c4f16a..20505ca 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -34,6 +34,7 @@
import dalvik.system.CloseGuard;
import java.io.File;
+import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -414,6 +415,10 @@
final String newLocale = mConfiguration.locale.toString();
nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
+ if (!mConfiguration.isInMemoryDb()) {
+ checkDatabaseWiped();
+ }
+
// If the database is read-only, we cannot modify the android metadata table
// or existing indexes.
if (mIsReadOnlyConnection) {
@@ -449,6 +454,36 @@
}
}
+ private void checkDatabaseWiped() {
+ if (!SQLiteGlobal.checkDbWipe()) {
+ return;
+ }
+ try {
+ final File checkFile = new File(mConfiguration.path
+ + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX);
+
+ final boolean hasMetadataTable = executeForLong(
+ "SELECT count(*) FROM sqlite_master"
+ + " WHERE type='table' AND name='android_metadata'", null, null) > 0;
+ final boolean hasCheckFile = checkFile.exists();
+
+ if (!mIsReadOnlyConnection && !hasCheckFile) {
+ // Create the check file, unless it's a readonly connection,
+ // in which case we can't create the metadata table anyway.
+ checkFile.createNewFile();
+ }
+
+ if (!hasMetadataTable && hasCheckFile) {
+ // Bad. The DB is gone unexpectedly.
+ SQLiteDatabase.wipeDetected(mConfiguration.path, "unknown");
+ }
+
+ } catch (RuntimeException | IOException ex) {
+ SQLiteDatabase.wtfAsSystemServer(TAG,
+ "Unexpected exception while checking for wipe", ex);
+ }
+ }
+
// Called by SQLiteConnectionPool only.
void reconfigure(SQLiteDatabaseConfiguration configuration) {
mOnlyAllowReadOnlyOperations = false;
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 3ee348b..dbc1766 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -24,6 +24,7 @@
import android.os.OperationCanceledException;
import android.os.SystemClock;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.PrefixPrinter;
import android.util.Printer;
@@ -34,6 +35,7 @@
import dalvik.system.CloseGuard;
import java.io.Closeable;
+import java.io.File;
import java.util.ArrayList;
import java.util.Map;
import java.util.WeakHashMap;
@@ -1105,9 +1107,12 @@
* @param printer The printer to receive the dump, not null.
* @param verbose True to dump more verbose information.
*/
- public void dump(Printer printer, boolean verbose) {
+ public void dump(Printer printer, boolean verbose, ArraySet<String> directories) {
Printer indentedPrinter = PrefixPrinter.create(printer, " ");
synchronized (mLock) {
+ if (directories != null) {
+ directories.add(new File(mConfiguration.path).getParent());
+ }
printer.println("Connection pool for " + mConfiguration.path + ":");
printer.println(" Open: " + mIsOpen);
printer.println(" Max connections: " + mMaxConnectionPoolSize);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index eb5c720..f9c2c3e 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -22,6 +22,8 @@
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
@@ -34,6 +36,7 @@
import android.os.OperationCanceledException;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
@@ -45,9 +48,14 @@
import java.io.File;
import java.io.FileFilter;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -808,6 +816,12 @@
* @return True if the database was successfully deleted.
*/
public static boolean deleteDatabase(@NonNull File file) {
+ return deleteDatabase(file, /*removeCheckFile=*/ true);
+ }
+
+
+ /** @hide */
+ public static boolean deleteDatabase(@NonNull File file, boolean removeCheckFile) {
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
@@ -818,6 +832,9 @@
deleted |= new File(file.getPath() + "-shm").delete();
deleted |= new File(file.getPath() + "-wal").delete();
+ // This file is not a standard SQLite file, so don't update the deleted flag.
+ new File(file.getPath() + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX).delete();
+
File dir = file.getParentFile();
if (dir != null) {
final String prefix = file.getName() + "-mj";
@@ -2170,21 +2187,61 @@
* Dump detailed information about all open databases in the current process.
* Used by bug report.
*/
- static void dumpAll(Printer printer, boolean verbose) {
+ static void dumpAll(Printer printer, boolean verbose, boolean isSystem) {
+ // Use this ArraySet to collect file paths.
+ final ArraySet<String> directories = new ArraySet<>();
+
for (SQLiteDatabase db : getActiveDatabases()) {
- db.dump(printer, verbose);
+ db.dump(printer, verbose, isSystem, directories);
+ }
+
+ // Dump DB files in the directories.
+ if (directories.size() > 0) {
+ final String[] dirs = directories.toArray(new String[directories.size()]);
+ Arrays.sort(dirs);
+ for (String dir : dirs) {
+ dumpDatabaseDirectory(printer, new File(dir), isSystem);
+ }
}
}
- private void dump(Printer printer, boolean verbose) {
+ private void dump(Printer printer, boolean verbose, boolean isSystem, ArraySet directories) {
synchronized (mLock) {
if (mConnectionPoolLocked != null) {
printer.println("");
- mConnectionPoolLocked.dump(printer, verbose);
+ mConnectionPoolLocked.dump(printer, verbose, directories);
}
}
}
+ private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
+ pw.println("");
+ pw.println("Database files in " + dir.getAbsolutePath() + ":");
+ final File[] files = dir.listFiles();
+ if (files == null || files.length == 0) {
+ pw.println(" [none]");
+ return;
+ }
+ Arrays.sort(files, (a, b) -> a.getName().compareTo(b.getName()));
+
+ for (File f : files) {
+ if (isSystem) {
+ // If called within the system server, the directory contains other files too, so
+ // filter by file extensions.
+ // (If it's an app, just print all files because they may not use *.db
+ // extension.)
+ final String name = f.getName();
+ if (!(name.endsWith(".db") || name.endsWith(".db-wal")
+ || name.endsWith(".db-journal")
+ || name.endsWith(SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX))) {
+ continue;
+ }
+ }
+ pw.println(String.format(" %-40s %7db %s", f.getName(), f.length(),
+ SQLiteDatabase.getFileTimestamps(f.getAbsolutePath())));
+ }
+ }
+
/**
* Returns list of full pathnames of all attached databases including the main database
* by executing 'pragma database_list' on the database.
@@ -2611,7 +2668,7 @@
return this;
}
- /**
+ /**w
* Sets <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>
* .
* @return
@@ -2646,5 +2703,34 @@
@Retention(RetentionPolicy.SOURCE)
public @interface DatabaseOpenFlags {}
+ /** @hide */
+ public static void wipeDetected(String filename, String reason) {
+ wtfAsSystemServer(TAG, "DB wipe detected:"
+ + " package=" + ActivityThread.currentPackageName()
+ + " reason=" + reason
+ + " file=" + filename
+ + " " + getFileTimestamps(filename)
+ + " checkfile " + getFileTimestamps(filename + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX),
+ new Throwable("STACKTRACE"));
+ }
+
+ /** @hide */
+ public static String getFileTimestamps(String path) {
+ try {
+ BasicFileAttributes attr = Files.readAttributes(
+ FileSystems.getDefault().getPath(path), BasicFileAttributes.class);
+ return "ctime=" + attr.creationTime()
+ + " mtime=" + attr.lastModifiedTime()
+ + " atime=" + attr.lastAccessTime();
+ } catch (IOException e) {
+ return "[unable to obtain timestamp]";
+ }
+ }
+
+ /** @hide */
+ static void wtfAsSystemServer(String tag, String message, Throwable stacktrace) {
+ Log.e(tag, message, stacktrace);
+ ContentResolver.onDbCorruption(tag, message, stacktrace);
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 1c66204..f220205 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -189,6 +189,11 @@
* @param args Command-line arguments supplied to dumpsys dbinfo
*/
public static void dump(Printer printer, String[] args) {
+ dump(printer, args, false);
+ }
+
+ /** @hide */
+ public static void dump(Printer printer, String[] args, boolean isSystem) {
boolean verbose = false;
for (String arg : args) {
if (arg.equals("-v")) {
@@ -196,6 +201,6 @@
}
}
- SQLiteDatabase.dumpAll(printer, verbose);
+ SQLiteDatabase.dumpAll(printer, verbose, isSystem);
}
}
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 67e5f65..ff286fd 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -42,6 +42,9 @@
/** @hide */
public static final String SYNC_MODE_FULL = "FULL";
+ /** @hide */
+ static final String WIPE_CHECK_FILE_SUFFIX = "-wipecheck";
+
private static final Object sLock = new Object();
private static int sDefaultPageSize;
@@ -181,4 +184,8 @@
com.android.internal.R.integer.db_wal_truncate_size));
}
+ /** @hide */
+ public static boolean checkDbWipe() {
+ return true;
+ }
}
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 5de89e3..56da719 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -130,30 +130,42 @@
* {@link PendingIntent} through a {@link BroadcastReceiver}, and maps an {@link Intent} to a
* {@link ContextHubClientCallback}.
*
- * @param intent The PendingIntent to register for this client
- * @param nanoAppId the unique ID of the nanoapp to receive events for
+ * @param pendingIntent the PendingIntent to register for this client
+ * @param nanoAppId the unique ID of the nanoapp to receive events for
* @return true on success, false otherwise
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public boolean registerIntent(@NonNull PendingIntent intent, long nanoAppId) {
- // TODO: Implement this
- return false;
+ public boolean registerIntent(@NonNull PendingIntent pendingIntent, long nanoAppId) {
+ Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
+
+ try {
+ return mClientProxy.registerIntent(pendingIntent, nanoAppId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* Unregisters an intent previously registered via {@link #registerIntent(PendingIntent, long)}.
* If this intent has not been registered for this client, this method returns false.
*
+ * @param pendingIntent the PendingIntent to unregister
+ *
* @return true on success, false otherwise
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public boolean unregisterIntent(@NonNull PendingIntent intent) {
- // TODO: Implement this
- return false;
+ public boolean unregisterIntent(@NonNull PendingIntent pendingIntent) {
+ Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
+
+ try {
+ return mClientProxy.unregisterIntent(pendingIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6541ecd..b0b77f3 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -800,22 +800,22 @@
* through {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} or
* equivalent at an earlier time.
*
- * @param intent the intent that is associated with a client
- * @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
- * @param executor the executor to invoke the callback
+ * @param pendingIntent the PendingIntent that has been registered with a client
+ * @param hubInfo the hub to attach this client to
+ * @param callback the notification callback to register
+ * @param executor the executor to invoke the callback
* @return the registered client object
*
- * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or the intent
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
* was not associated with a client
* @throws IllegalStateException if there were too many registered clients at the service
- * @throws NullPointerException if intent, hubInfo, callback, or executor is null
+ * @throws NullPointerException if pendingIntent, hubInfo, callback, or executor is null
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@NonNull public ContextHubClient createClient(
- @NonNull PendingIntent intent, @NonNull ContextHubInfo hubInfo,
+ @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
@NonNull ContextHubClientCallback callback,
@NonNull @CallbackExecutor Executor executor) {
// TODO: Implement this
@@ -823,26 +823,27 @@
}
/**
- * Equivalent to {@link #createClient(Intent, ContextHubInfo, ContextHubClientCallback,
+ * Equivalent to {@link #createClient(PendingIntent, ContextHubInfo, ContextHubClientCallback,
* Executor)} with the executor using the main thread's Looper.
*
- * @param intent the intent that is associated with a client
- * @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
+ * @param pendingIntent the PendingIntent that has been registered with a client
+ * @param hubInfo the hub to attach this client to
+ * @param callback the notification callback to register
* @return the registered client object
*
- * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or the intent
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
* was not associated with a client
* @throws IllegalStateException if there were too many registered clients at the service
- * @throws NullPointerException if intent, hubInfo, or callback is null
+ * @throws NullPointerException if pendingIntent, hubInfo, or callback is null
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@NonNull public ContextHubClient createClient(
- @NonNull PendingIntent intent, @NonNull ContextHubInfo hubInfo,
+ @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
@NonNull ContextHubClientCallback callback) {
- return createClient(intent, hubInfo, callback, new HandlerExecutor(Handler.getMain()));
+ return createClient(
+ pendingIntent, hubInfo, callback, new HandlerExecutor(Handler.getMain()));
}
/**
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index d81126a..7559cd5 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -16,6 +16,7 @@
package android.hardware.location;
+import android.app.PendingIntent;
import android.hardware.location.NanoAppMessage;
/**
@@ -28,4 +29,10 @@
// Closes the connection with the Context Hub
void close();
+
+ // Registers a PendingIntent with the client
+ boolean registerIntent(in PendingIntent intent, long nanoAppId);
+
+ // Unregisters a PendingIntent from the client
+ boolean unregisterIntent(in PendingIntent intent);
}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 954d18a..67e52aa 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -731,6 +731,8 @@
public static final String EXTRA_PARENT_URI = "parentUri";
/** {@hide} */
public static final String EXTRA_URI = "uri";
+ /** {@hide} */
+ public static final String EXTRA_URI_PERMISSIONS = "uriPermissions";
/**
* @see #createWebLinkIntent(ContentResolver, Uri, Bundle)
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f5660b9..57f33f0 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -46,9 +46,6 @@
import com.android.internal.annotations.GuardedBy;
-import libcore.io.IoUtils;
-
-import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -82,6 +79,11 @@
*/
public static final String RETRANSLATE_CALL = "update_titles";
+ /** {@hide} */
+ public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
+ /** {@hide} */
+ public static final String GET_MEDIA_URI_CALL = "get_media_uri";
+
/**
* This is for internal use by the media scanner only.
* Name of the (optional) Uri parameter that determines whether to skip deleting
@@ -2275,84 +2277,62 @@
}
/**
- * Gets a URI backed by a {@link DocumentsProvider} that points to the same media
- * file as the specified mediaUri. This allows apps who have permissions to access
- * media files in Storage Access Framework to perform file operations through that
- * on media files.
+ * Return a {@link DocumentsProvider} Uri that is an equivalent to the given
+ * {@link MediaStore} Uri.
* <p>
- * Note: this method doesn't grant any URI permission. Callers need to obtain
- * permission before calling this method. One way to obtain permission is through
- * a 3-step process:
- * <ol>
- * <li>Call {@link android.os.storage.StorageManager#getStorageVolume(File)} to
- * obtain the {@link android.os.storage.StorageVolume} of a media file;</li>
+ * This allows apps with Storage Access Framework permissions to convert
+ * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
+ * to the same underlying item. Note that this method doesn't grant any new
+ * permissions; callers must already hold permissions obtained with
+ * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
*
- * <li>Invoke the intent returned by
- * {@link android.os.storage.StorageVolume#createAccessIntent(String)} to
- * obtain the access of the volume or one of its specific subdirectories;</li>
- *
- * <li>Check whether permission is granted and take persistent permission.</li>
- * </ol>
- * @param mediaUri the media URI which document URI is requested
- * @return the document URI
+ * @param mediaUri The {@link MediaStore} Uri to convert.
+ * @return An equivalent {@link DocumentsProvider} Uri. Returns {@code null}
+ * if no equivalent was found.
+ * @see #getMediaUri(Context, Uri)
*/
public static Uri getDocumentUri(Context context, Uri mediaUri) {
+ final ContentResolver resolver = context.getContentResolver();
+ final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
- try {
- final ContentResolver resolver = context.getContentResolver();
-
- final String path = getFilePath(resolver, mediaUri);
- final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
-
- return getDocumentUri(resolver, path, uriPermissions);
+ try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
+ final Bundle in = new Bundle();
+ in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
+ in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
+ return out.getParcelable(DocumentsContract.EXTRA_URI);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
- private static String getFilePath(ContentResolver resolver, Uri mediaUri)
- throws RemoteException {
+ /**
+ * Return a {@link MediaStore} Uri that is an equivalent to the given
+ * {@link DocumentsProvider} Uri.
+ * <p>
+ * This allows apps with Storage Access Framework permissions to convert
+ * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
+ * to the same underlying item. Note that this method doesn't grant any new
+ * permissions; callers must already hold permissions obtained with
+ * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
+ *
+ * @param documentUri The {@link DocumentsProvider} Uri to convert.
+ * @return An equivalent {@link MediaStore} Uri. Returns {@code null} if no
+ * equivalent was found.
+ * @see #getDocumentUri(Context, Uri)
+ */
+ public static Uri getMediaUri(Context context, Uri documentUri) {
+ final ContentResolver resolver = context.getContentResolver();
+ final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
- try (ContentProviderClient client =
- resolver.acquireUnstableContentProviderClient(AUTHORITY)) {
- final Cursor c = client.query(
- mediaUri,
- new String[]{ MediaColumns.DATA },
- null, /* selection */
- null, /* selectionArg */
- null /* sortOrder */);
-
- final String path;
- try {
- if (c.getCount() == 0) {
- throw new IllegalStateException("Not found media file under URI: " + mediaUri);
- }
-
- if (!c.moveToFirst()) {
- throw new IllegalStateException("Failed to move cursor to the first item.");
- }
-
- path = c.getString(0);
- } finally {
- IoUtils.closeQuietly(c);
- }
-
- return path;
- }
- }
-
- private static Uri getDocumentUri(
- ContentResolver resolver, String path, List<UriPermission> uriPermissions)
- throws RemoteException {
-
- try (ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
- DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY)) {
+ try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
final Bundle in = new Bundle();
- in.putParcelableList(
- DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY + ".extra.uriPermissions",
- uriPermissions);
- final Bundle out = client.call("getDocumentId", path, in);
+ in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
+ in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+ final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
return out.getParcelable(DocumentsContract.EXTRA_URI);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index f17a458..f3cb376 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -897,7 +897,7 @@
HwuiContext(boolean isWideColorGamut) {
mRenderNode = RenderNode.create("HwuiCanvas", null);
mRenderNode.setClipToBounds(false);
- mRenderNode.setAllowForceDark(false);
+ mRenderNode.setForceDarkAllowed(false);
mIsWideColorGamut = isWideColorGamut;
mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject,
isWideColorGamut);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c829182..1157b28 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5542,6 +5542,10 @@
break;
case com.android.internal.R.styleable.View_accessibilityHeading:
setAccessibilityHeading(a.getBoolean(attr, false));
+ break;
+ case R.styleable.View_forceDarkAllowed:
+ mRenderNode.setForceDarkAllowed(a.getBoolean(attr, true));
+ break;
}
}
@@ -15286,11 +15290,9 @@
* If a theme is isLightTheme="false", then force dark is globally disabled for that theme.
*
* @param allow Whether or not to allow force dark.
- *
- * @hide
*/
- public void setAllowForceDark(boolean allow) {
- if (mRenderNode.setAllowForceDark(allow)) {
+ public void setForceDarkAllowed(boolean allow) {
+ if (mRenderNode.setForceDarkAllowed(allow)) {
// Currently toggling force-dark requires a new display list push to apply
// TODO: Make it not clobber the display list so this is just a damageSelf() instead
invalidate();
@@ -15298,15 +15300,13 @@
}
/**
- * See {@link #setAllowForceDark(boolean)}
+ * See {@link #setForceDarkAllowed(boolean)}
*
* @return true if force dark is allowed (default), false if it is disabled
- *
- * @hide
*/
@ViewDebug.ExportedProperty(category = "drawing")
- public boolean getAllowForceDark() {
- return mRenderNode.getAllowForceDark();
+ public boolean isForceDarkAllowed() {
+ return mRenderNode.isForceDarkAllowed();
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7da31eb..170c783 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1096,15 +1096,21 @@
private void updateForceDarkMode() {
if (mAttachInfo.mThreadedRenderer == null) return;
- boolean nightMode = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
- TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
- boolean isLightTheme = a.getBoolean(R.styleable.Theme_isLightTheme, false);
- a.recycle();
+ boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
- boolean changed = mAttachInfo.mThreadedRenderer.setForceDark(nightMode);
- changed |= mAttachInfo.mThreadedRenderer.getRootNode().setAllowForceDark(isLightTheme);
+ // Allow debug.hwui.force_dark to override the target SDK check
+ if (useAutoDark && !SystemProperties.getBoolean("debug.hwui.force_dark", false)) {
+ useAutoDark = mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q;
+ }
- if (changed) {
+ if (useAutoDark) {
+ TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
+ useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
+ && a.getBoolean(R.styleable.Theme_forceDarkAllowed, true);
+ a.recycle();
+ }
+
+ if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {
// TODO: Don't require regenerating all display lists to apply this setting
invalidateWorld(mView);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index e370e11..e9acdc3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -737,45 +737,67 @@
return false;
}
- private static IInputMethodManager getIInputMethodManager() throws ServiceNotFoundException {
- if (!isInEditMode()) {
- return IInputMethodManager.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
- }
- // If InputMethodManager is running for layoutlib, stub out IPCs into IMMS.
- final Class<IInputMethodManager> c = IInputMethodManager.class;
- return (IInputMethodManager) Proxy.newProxyInstance(c.getClassLoader(),
- new Class[]{c}, (proxy, method, args) -> {
- final Class<?> returnType = method.getReturnType();
- if (returnType == boolean.class) {
- return false;
- } else if (returnType == int.class) {
- return 0;
- } else if (returnType == long.class) {
- return 0L;
- } else if (returnType == short.class) {
- return 0;
- } else if (returnType == char.class) {
- return 0;
- } else if (returnType == byte.class) {
- return 0;
- } else if (returnType == float.class) {
- return 0f;
- } else if (returnType == double.class) {
- return 0.0;
- } else {
- return null;
- }
- });
+ @NonNull
+ private static InputMethodManager createInstance(int displayId, Looper looper) {
+ return isInEditMode() ? createStubInstance(displayId, looper)
+ : createRealInstance(displayId, looper);
}
- InputMethodManager(int displayId, Looper looper) throws ServiceNotFoundException {
- mService = getIInputMethodManager();
+ @NonNull
+ private static InputMethodManager createRealInstance(int displayId, Looper looper) {
+ final IInputMethodManager service;
+ try {
+ service = IInputMethodManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ final InputMethodManager imm = new InputMethodManager(service, displayId, looper);
+ try {
+ service.addClient(imm.mClient, imm.mIInputContext, displayId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return imm;
+ }
+
+ @NonNull
+ private static InputMethodManager createStubInstance(int displayId, Looper looper) {
+ // If InputMethodManager is running for layoutlib, stub out IPCs into IMMS.
+ final Class<IInputMethodManager> c = IInputMethodManager.class;
+ final IInputMethodManager stubInterface =
+ (IInputMethodManager) Proxy.newProxyInstance(c.getClassLoader(),
+ new Class[]{c}, (proxy, method, args) -> {
+ final Class<?> returnType = method.getReturnType();
+ if (returnType == boolean.class) {
+ return false;
+ } else if (returnType == int.class) {
+ return 0;
+ } else if (returnType == long.class) {
+ return 0L;
+ } else if (returnType == short.class) {
+ return 0;
+ } else if (returnType == char.class) {
+ return 0;
+ } else if (returnType == byte.class) {
+ return 0;
+ } else if (returnType == float.class) {
+ return 0f;
+ } else if (returnType == double.class) {
+ return 0.0;
+ } else {
+ return null;
+ }
+ });
+ return new InputMethodManager(stubInterface, displayId, looper);
+ }
+
+ private InputMethodManager(IInputMethodManager service, int displayId, Looper looper) {
+ mService = service;
mMainLooper = looper;
mH = new H(looper);
mDisplayId = displayId;
- mIInputContext = new ControlledInputConnectionWrapper(looper,
- mDummyInputConnection, this);
+ mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection, this);
}
/**
@@ -785,7 +807,7 @@
* @return {@link InputMethodManager} instance
* @hide
*/
- @Nullable
+ @NonNull
public static InputMethodManager forContext(Context context) {
final int displayId = context.getDisplayId();
// For better backward compatibility, we always use Looper.getMainLooper() for the default
@@ -795,7 +817,7 @@
return forContextInternal(displayId, looper);
}
- @Nullable
+ @NonNull
private static InputMethodManager forContextInternal(int displayId, Looper looper) {
final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY;
synchronized (sLock) {
@@ -803,12 +825,7 @@
if (instance != null) {
return instance;
}
- try {
- instance = new InputMethodManager(displayId, looper);
- instance.mService.addClient(instance.mClient, instance.mIInputContext, displayId);
- } catch (ServiceNotFoundException | RemoteException e) {
- throw new IllegalStateException(e);
- }
+ instance = createInstance(displayId, looper);
// For backward compatibility, store the instance also to sInstance for default display.
if (sInstance == null && isDefaultDisplay) {
sInstance = instance;
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 97896f0..b799728 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -69,6 +69,7 @@
private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
private static final boolean PROFILE_DRAWING = false;
+ private static final float LINE_FADE_ALPHA_MULTIPLIER = 3.5f;
private final CellState[][] mCellStates;
private final int mDotSize;
@@ -1170,9 +1171,9 @@
float centerX = getCenterXForColumn(cell.column);
float centerY = getCenterYForRow(cell.row);
if (i != 0) {
- // Set this line segment to slowly fade over the next second.
+ // Set this line segment to fade away animated.
int lineFadeVal = (int) Math.min((elapsedRealtime -
- mLineFadeStart[i])/2f, 255f);
+ mLineFadeStart[i]) * LINE_FADE_ALPHA_MULTIPLIER, 255f);
CellState state = mCellStates[cell.row][cell.column];
currentPath.rewind();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 32cf2e8..a25c998 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1141,6 +1141,15 @@
<!-- Alpha value of the spot shadow projected by elevated views, between 0 and 1. -->
<attr name="spotShadowAlpha" format="float" />
+
+ <!-- <p>Whether or not the force dark feature is allowed to be applied to this theme.
+ <p>Setting this to false will disable the auto-dark feature on everything this
+ theme is applied to along with anything drawn by any children of views using
+ this theme.
+ <p>Setting this to true will allow this view to be automatically made dark, however
+ a value of 'true' will not override any 'false' value in its parent chain nor will
+ it prevent any 'false' in any of its children. -->
+ <attr name="forceDarkAllowed" format="boolean" />
</declare-styleable>
<!-- **************************************************************** -->
@@ -3116,8 +3125,13 @@
{@link android.R.attr#ambientShadowAlpha} theme attribute. -->
<attr name="outlineAmbientShadowColor" format="color" />
- <!-- Whether to allow the rendering system to force this View to render as light-on-dark. -->
- <attr name="allowForceDark" format="boolean" />
+ <!-- <p>Whether or not the force dark feature is allowed to be applied to this View.
+ <p>Setting this to false will disable the auto-dark feature on this View draws
+ including any descendants.
+ <p>Setting this to true will allow this view to be automatically made dark, however
+ a value of 'true' will not override any 'false' value in its parent chain nor will
+ it prevent any 'false' in any of its children. -->
+ <attr name="forceDarkAllowed" format="boolean" />
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fd688a7..b790829 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2908,7 +2908,7 @@
<public name="opticalInsetTop" />
<public name="opticalInsetRight" />
<public name="opticalInsetBottom" />
- <public name="allowForceDark" />
+ <public name="forceDarkAllowed" />
<public name="supportsAmbientMode" />
<!-- @hide For use by platform and tools only. Developers should not specify this value. -->
<public name="usesNonSdkApi" />
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 364fb04..632edfa 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1294,7 +1294,7 @@
final RenderNode node = RenderNode.create("BitmapTemporary", null);
node.setLeftTopRightBottom(0, 0, width, height);
node.setClipToBounds(false);
- node.setAllowForceDark(false);
+ node.setForceDarkAllowed(false);
final RecordingCanvas canvas = node.start(width, height);
if (source.getWidth() != width || source.getHeight() != height) {
canvas.scale(width / (float) source.getWidth(),
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 60641d8..b61488c 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -931,16 +931,16 @@
* @param allow Whether or not to allow force dark.
* @return true If the value has changed, false otherwise.
*/
- public boolean setAllowForceDark(boolean allow) {
+ public boolean setForceDarkAllowed(boolean allow) {
return nSetAllowForceDark(mNativeRenderNode, allow);
}
/**
- * See {@link #setAllowForceDark(boolean)}
+ * See {@link #setForceDarkAllowed(boolean)}
*
* @return true if force dark is allowed (default), false if it is disabled
*/
- public boolean getAllowForceDark() {
+ public boolean isForceDarkAllowed() {
return nGetAllowForceDark(mNativeRenderNode);
}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index b10ed8d..3ec595d 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -3023,7 +3023,7 @@
/**
* Registers a callback for the notification of stream events.
* This callback can only be registered for instances operating in offloaded mode
- * (see {@link #Builder.setOffloadedPlayback(boolean)} and
+ * (see {@link AudioTrack.Builder#setOffloadedPlayback(boolean)} and
* {@link AudioManager#isOffloadedPlaybackSupported(AudioFormat)} for more details).
* @param executor {@link Executor} to handle the callbacks.
* @param eventCallback the callback to receive the stream event notifications.
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 1072f95..484dbcc 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -17,6 +17,10 @@
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
+ <!-- Stub that allows MediaProvider to make incoming calls -->
+ <path-permission
+ android:path="/media_internal"
+ android:permission="android.permission.WRITE_MEDIA_STORAGE" />
</provider>
<receiver android:name=".MountReceiver">
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 62207c5..4e52ff6d 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -37,6 +37,7 @@
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Path;
import android.provider.DocumentsContract.Root;
+import android.provider.MediaStore;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
@@ -606,11 +607,16 @@
}
break;
}
- case "getDocumentId": {
- final String path = arg;
- final List<UriPermission> accessUriPermissions =
- extras.getParcelableArrayList(AUTHORITY + ".extra.uriPermissions");
+ case MediaStore.GET_DOCUMENT_URI_CALL: {
+ // All callers must go through MediaProvider
+ getContext().enforceCallingPermission(
+ android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
+ final Uri fileUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+ final List<UriPermission> accessUriPermissions = extras
+ .getParcelableArrayList(DocumentsContract.EXTRA_URI_PERMISSIONS);
+
+ final String path = fileUri.getPath();
try {
final Bundle out = new Bundle();
final Uri uri = getDocumentUri(path, accessUriPermissions);
@@ -619,7 +625,22 @@
} catch (FileNotFoundException e) {
throw new IllegalStateException("File in " + path + " is not found.", e);
}
+ }
+ case MediaStore.GET_MEDIA_URI_CALL: {
+ // All callers must go through MediaProvider
+ getContext().enforceCallingPermission(
+ android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
+ final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+ final String docId = DocumentsContract.getDocumentId(documentUri);
+ try {
+ final Bundle out = new Bundle();
+ final Uri uri = Uri.fromFile(getFileForDocId(docId));
+ out.putParcelable(DocumentsContract.EXTRA_URI, uri);
+ return out;
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
}
default:
Log.w(TAG, "unknown method passed to call(): " + method);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index a3862eb..a543d17 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -102,7 +102,8 @@
findViewById(R.id.key9)
},
new View[]{
- null, findViewById(R.id.key0), findViewById(R.id.key_enter)
+ findViewById(R.id.delete_button), findViewById(R.id.key0),
+ findViewById(R.id.key_enter)
},
new View[]{
null, mEcaView, null
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 3007b6e..c844496 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -207,11 +207,11 @@
mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mOverlay.setAlpha(0);
- mOverlay.setAllowForceDark(false);
+ mOverlay.setForceDarkAllowed(false);
mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mBottomOverlay.setAlpha(0);
- mBottomOverlay.setAllowForceDark(false);
+ mBottomOverlay.setForceDarkAllowed(false);
updateViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index fe0a7c7..2e280d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertFalse;
import android.os.Handler;
-import android.os.Looper;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -38,6 +37,7 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -51,6 +51,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@Ignore("b/118400112")
public class NonPhoneDependencyTest extends SysuiTestCase {
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationListContainer mListContainer;
@@ -69,6 +70,7 @@
}
@Test
+ @Ignore("b/118400112")
public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
mDependency.injectMockDependency(ShadeController.class);
NotificationEntryManager entryManager = Dependency.get(NotificationEntryManager.class);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 5698fdf..5ed6263 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1615,6 +1615,15 @@
}
@Override
+ public void onDbCorruption(String tag, String message, String stacktrace) {
+ Slog.e(tag, message);
+ Slog.e(tag, "at " + stacktrace);
+
+ // TODO: Figure out a better way to report it. b/117886381
+ Slog.wtf(tag, message);
+ }
+
+ @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 99a04d7..1d0ab8f 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -16,6 +16,7 @@
package com.android.server.location;
+import android.app.PendingIntent;
import android.content.Context;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.IContexthub;
@@ -138,6 +139,27 @@
}
/**
+ * @param intent the intent to register
+ * @param nanoAppId the ID of the nanoapp to send events for
+ * @return true on success, false otherwise
+ */
+ @Override
+ public boolean registerIntent(PendingIntent intent, long nanoAppId) {
+ // TODO: Implement this
+ return false;
+ }
+
+ /**
+ * @param intent the intent to unregister
+ * @return true on success, false otherwise
+ */
+ @Override
+ public boolean unregisterIntent(PendingIntent intent) {
+ // TODO: Implement this
+ return false;
+ }
+
+ /**
* Closes the connection for this client with the service.
*/
@Override
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index d631f35..f06643d 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -28,7 +28,7 @@
/* these formats are already compressed, or don't compress well */
static const char* kNoCompressExt[] = {
- ".jpg", ".jpeg", ".png", ".gif",
+ ".jpg", ".jpeg", ".png", ".gif", ".opus",
".wav", ".mp2", ".mp3", ".ogg", ".aac",
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index d1f42f8..257043b 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -47,7 +47,8 @@
fields(that.fields),
primaryFields(that.primaryFields),
exclusiveField(that.exclusiveField),
- uidField(that.uidField) {}
+ uidField(that.uidField),
+ binaryFields(that.binaryFields) {}
AtomDecl::AtomDecl(int c, const string& n, const string& m)
:code(c),
@@ -119,6 +120,9 @@
} else if (field->message_type()->full_name() ==
"android.os.statsd.KeyValuePair") {
return JAVA_TYPE_KEY_VALUE_PAIR;
+ } else if (field->options().GetExtension(os::statsd::log_mode) ==
+ os::statsd::LogMode::MODE_BYTES) {
+ return JAVA_TYPE_BYTE_ARRAY;
} else {
return JAVA_TYPE_OBJECT;
}
@@ -188,6 +192,8 @@
for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
it != fields.end(); it++) {
const FieldDescriptor *field = it->second;
+ bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
+ os::statsd::LogMode::MODE_BYTES;
java_type_t javaType = java_type(field);
@@ -197,17 +203,24 @@
continue;
} else if (javaType == JAVA_TYPE_OBJECT &&
atomDecl->code < PULL_ATOM_START_ID) {
- // Allow attribution chain, but only at position 1.
- print_error(field,
- "Message type not allowed for field in pushed atoms: %s\n",
- field->name().c_str());
- errorCount++;
- continue;
- } else if (javaType == JAVA_TYPE_BYTE_ARRAY) {
- print_error(field, "Raw bytes type not allowed for field: %s\n",
- field->name().c_str());
- errorCount++;
- continue;
+ // Allow attribution chain, but only at position 1.
+ print_error(field,
+ "Message type not allowed for field in pushed atoms: %s\n",
+ field->name().c_str());
+ errorCount++;
+ continue;
+ } else if (javaType == JAVA_TYPE_BYTE_ARRAY && !isBinaryField) {
+ print_error(field, "Raw bytes type not allowed for field: %s\n",
+ field->name().c_str());
+ errorCount++;
+ continue;
+ }
+
+ if (isBinaryField && javaType != JAVA_TYPE_BYTE_ARRAY) {
+ print_error(field, "Cannot mark field %s as bytes.\n",
+ field->name().c_str());
+ errorCount++;
+ continue;
}
}
@@ -233,6 +246,8 @@
it != fields.end(); it++) {
const FieldDescriptor *field = it->second;
java_type_t javaType = java_type(field);
+ bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
+ os::statsd::LogMode::MODE_BYTES;
AtomField atField(field->name(), javaType);
// Generate signature for pushed atoms
@@ -241,8 +256,10 @@
// All enums are treated as ints when it comes to function signatures.
signature->push_back(JAVA_TYPE_INT);
collate_enums(*field->enum_type(), &atField);
+ } else if (javaType == JAVA_TYPE_OBJECT && isBinaryField) {
+ signature->push_back(JAVA_TYPE_BYTE_ARRAY);
} else {
- signature->push_back(javaType);
+ signature->push_back(javaType);
}
}
if (javaType == JAVA_TYPE_ENUM) {
@@ -287,6 +304,10 @@
errorCount++;
}
}
+ // Binary field validity is already checked above.
+ if (isBinaryField) {
+ atomDecl->binaryFields.push_back(it->first);
+ }
}
return errorCount;
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 31b8b07..450b305 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -89,6 +89,8 @@
int uidField = 0;
+ vector<int> binaryFields;
+
AtomDecl();
AtomDecl(const AtomDecl& that);
AtomDecl(int code, const string& name, const string& message);
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 56c8428..1ef34b9 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -66,6 +66,8 @@
return "double";
case JAVA_TYPE_STRING:
return "char const*";
+ case JAVA_TYPE_BYTE_ARRAY:
+ return "char const*";
default:
return "UNKNOWN";
}
@@ -88,6 +90,8 @@
return "double";
case JAVA_TYPE_STRING:
return "java.lang.String";
+ case JAVA_TYPE_BYTE_ARRAY:
+ return "byte[]";
default:
return "UNKNOWN";
}
@@ -198,13 +202,40 @@
}
fprintf(out, " return options;\n");
- fprintf(out, " }\n");
+ fprintf(out, "}\n");
fprintf(out,
"const std::map<int, StateAtomFieldOptions> "
"AtomsInfo::kStateAtomsFieldOptions = "
"getStateAtomFieldOptions();\n");
+ fprintf(out,
+ "static std::map<int, std::vector<int>> "
+ "getBinaryFieldAtoms() {\n");
+ fprintf(out, " std::map<int, std::vector<int>> options;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->binaryFields.size() == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding binary fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+
+ for (const auto& field : atom->binaryFields) {
+ fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
+ make_constant_name(atom->name).c_str(), field);
+ }
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, "}\n");
+
+ fprintf(out,
+ "const std::map<int, std::vector<int>> "
+ "AtomsInfo::kBytesFieldAtoms = "
+ "getBinaryFieldAtoms();\n");
fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
@@ -664,6 +695,9 @@
fprintf(out,
" const static std::map<int, StateAtomFieldOptions> "
"kStateAtomsFieldOptions;\n");
+ fprintf(out,
+ " const static std::map<int, std::vector<int>> "
+ "kBytesFieldAtoms;");
fprintf(out, "};\n");
fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
@@ -698,6 +732,8 @@
fprintf(out, ", android.os.WorkSource workSource");
} else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", SparseArray<Object> value_map");
+ } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
+ fprintf(out, ", byte[] %s", field->name.c_str());
} else {
fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
}
@@ -890,6 +926,8 @@
return "jdouble";
case JAVA_TYPE_STRING:
return "jstring";
+ case JAVA_TYPE_BYTE_ARRAY:
+ return "jbyteArray";
default:
return "UNKNOWN";
}
@@ -942,6 +980,9 @@
case JAVA_TYPE_KEY_VALUE_PAIR:
result += "_KeyValuePairs";
break;
+ case JAVA_TYPE_BYTE_ARRAY:
+ result += "_bytes";
+ break;
default:
result += "_UNKNOWN";
break;
@@ -967,6 +1008,8 @@
return "D";
case JAVA_TYPE_STRING:
return "Ljava/lang/String;";
+ case JAVA_TYPE_BYTE_ARRAY:
+ return "[B";
default:
return "UNKNOWN";
}
@@ -1081,6 +1124,25 @@
fprintf(out, " } else {\n");
fprintf(out, " str%d = NULL;\n", argIndex);
fprintf(out, " }\n");
+ } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+ hadStringOrChain = true;
+ fprintf(out, " jbyte* jbyte_array%d;\n", argIndex);
+ fprintf(out, " const char* str%d;\n", argIndex);
+ fprintf(out, " if (arg%d != NULL) {\n", argIndex);
+ fprintf(out,
+ " jbyte_array%d = "
+ "env->GetByteArrayElements(arg%d, NULL);\n",
+ argIndex, argIndex);
+ fprintf(out,
+ " str%d = "
+ "reinterpret_cast<char*>(env->GetByteArrayElements(arg%"
+ "d, NULL));\n",
+ argIndex, argIndex);
+ fprintf(out, " } else {\n");
+ fprintf(out, " jbyte_array%d = NULL;\n", argIndex);
+ fprintf(out, " str%d = NULL;\n", argIndex);
+ fprintf(out, " }\n");
+
} else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
hadStringOrChain = true;
for (auto chainField : attributionDecl.fields) {
@@ -1154,7 +1216,10 @@
} else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) {
fprintf(out, ", int32_t_map, int64_t_map, string_map, float_map");
} else {
- const char *argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
+ const char* argName = (*arg == JAVA_TYPE_STRING ||
+ *arg == JAVA_TYPE_BYTE_ARRAY)
+ ? "str"
+ : "arg";
fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
}
argIndex++;
@@ -1171,6 +1236,13 @@
fprintf(out, " env->ReleaseStringUTFChars(arg%d, str%d);\n",
argIndex, argIndex);
fprintf(out, " }\n");
+ } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+ fprintf(out, " if (str%d != NULL) { \n", argIndex);
+ fprintf(out,
+ " env->ReleaseByteArrayElements(arg%d, "
+ "jbyte_array%d, 0);\n",
+ argIndex, argIndex);
+ fprintf(out, " }\n");
} else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
for (auto chainField : attributionDecl.fields) {
if (chainField.javaType == JAVA_TYPE_INT) {
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
index f635974..3be87d9 100644
--- a/tools/stats_log_api_gen/test.proto
+++ b/tools/stats_log_api_gen/test.proto
@@ -109,6 +109,28 @@
oneof event { BadAttributionNodePositionAtom bad = 1; }
}
+message GoodEventWithBinaryFieldAtom {
+ oneof event { GoodBinaryFieldAtom field1 = 1; }
+}
+
+message ComplexField {
+ optional string str = 1;
+}
+
+message GoodBinaryFieldAtom {
+ optional int32 field1 = 1;
+ optional ComplexField bf = 2 [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
+message BadEventWithBinaryFieldAtom {
+ oneof event { BadBinaryFieldAtom field1 = 1; }
+}
+
+message BadBinaryFieldAtom {
+ optional int32 field1 = 1;
+ optional ComplexField bf = 2;
+}
+
message BadStateAtoms {
oneof event {
BadStateAtom1 bad1 = 1;
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index 1936d96..ad3bffac 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -212,5 +212,19 @@
EXPECT_EQ(0, errorCount);
}
+TEST(CollationTest, PassOnGoodBinaryFieldAtom) {
+ Atoms atoms;
+ int errorCount =
+ collate_atoms(GoodEventWithBinaryFieldAtom::descriptor(), &atoms);
+ EXPECT_EQ(0, errorCount);
+}
+
+TEST(CollationTest, FailOnBadBinaryFieldAtom) {
+ Atoms atoms;
+ int errorCount =
+ collate_atoms(BadEventWithBinaryFieldAtom::descriptor(), &atoms);
+ EXPECT_TRUE(errorCount > 0);
+}
+
} // namespace stats_log_api_gen
} // namespace android
\ No newline at end of file