Merge "Use abstractions to allow FullscreenUserSwitcher take advantage of SystemUIOverlayWindow." into rvc-dev
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 05c6611..49adaa8 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -939,6 +939,12 @@
}
}
+ void runIdleMaintenance() {
+ synchronized (mBlobsLock) {
+ handleIdleMaintenanceLocked();
+ }
+ }
+
@GuardedBy("mBlobsLock")
private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
@@ -1408,9 +1414,7 @@
private class LocalService extends BlobStoreManagerInternal {
@Override
public void onIdleMaintenance() {
- synchronized (mBlobsLock) {
- handleIdleMaintenanceLocked();
- }
+ runIdleMaintenance();
}
}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
index d58294b..72af323 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -44,6 +44,8 @@
return runClearAllBlobs(pw);
case "delete-blob":
return runDeleteBlob(pw);
+ case "idle-maintenance":
+ return runIdleMaintenance(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -84,6 +86,11 @@
return 0;
}
+ private int runIdleMaintenance(PrintWriter pw) {
+ mService.runIdleMaintenance();
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -112,6 +119,8 @@
pw.println(" --expiry: Expiry time of the blob to delete, in milliseconds.");
pw.println(" --label: Label of the blob to delete.");
pw.println(" --tag: Tag of the blob to delete.");
+ pw.println("idle-maintenance");
+ pw.println(" Run idle maintenance which takes care of removing stale data.");
pw.println();
}
diff --git a/api/current.txt b/api/current.txt
index 563665f..1cf6832 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9924,8 +9924,11 @@
field public static final String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
field public static final String EXTRA_SIZE = "android.content.extra.SIZE";
field public static final String EXTRA_TOTAL_COUNT = "android.content.extra.TOTAL_COUNT";
+ field public static final int NOTIFY_DELETE = 16; // 0x10
+ field public static final int NOTIFY_INSERT = 4; // 0x4
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
+ field public static final int NOTIFY_UPDATE = 8; // 0x8
field public static final String QUERY_ARG_GROUP_COLUMNS = "android:query-arg-group-columns";
field public static final String QUERY_ARG_LIMIT = "android:query-arg-limit";
field public static final String QUERY_ARG_OFFSET = "android:query-arg-offset";
@@ -12967,9 +12970,13 @@
ctor public ContentObserver(android.os.Handler);
method public boolean deliverSelfNotifications();
method @Deprecated public final void dispatchChange(boolean);
- method public final void dispatchChange(boolean, android.net.Uri);
+ method public final void dispatchChange(boolean, @Nullable android.net.Uri);
+ method public final void dispatchChange(boolean, @Nullable android.net.Uri, int);
+ method public final void dispatchChange(boolean, @NonNull Iterable<android.net.Uri>, int);
method public void onChange(boolean);
- method public void onChange(boolean, android.net.Uri);
+ method public void onChange(boolean, @Nullable android.net.Uri);
+ method public void onChange(boolean, @Nullable android.net.Uri, int);
+ method public void onChange(boolean, @NonNull Iterable<android.net.Uri>, int);
}
public interface CrossProcessCursor extends android.database.Cursor {
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index d6c6c39..6e0bd06 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -231,6 +231,7 @@
fprintf(out, " -l list available sections\n");
fprintf(out, " -p privacy spec, LOCAL, EXPLICIT or AUTOMATIC. Default AUTOMATIC.\n");
fprintf(out, " -r REASON human readable description of why the report is taken.\n");
+ fprintf(out, " -z gzip the incident report, i.e. pipe the output through gzip.\n");
fprintf(out, "\n");
fprintf(out, "and one of these destinations:\n");
fprintf(out, " -b (default) print the report to stdout (in proto format)\n");
@@ -255,7 +256,7 @@
// Parse the args
int opt;
- while ((opt = getopt(argc, argv, "bhdlp:r:s:u")) != -1) {
+ while ((opt = getopt(argc, argv, "bhdlp:r:s:uz")) != -1) {
switch (opt) {
case 'h':
usage(stdout);
@@ -302,6 +303,9 @@
destination = DEST_BROADCAST;
receiverArg = optarg;
break;
+ case 'z':
+ args.setGzip(true);
+ break;
default:
usage(stderr);
return 1;
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index aa40f85..ad25342 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -35,10 +35,12 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <time.h>
+#include <wait.h>
namespace android {
namespace os {
@@ -51,6 +53,8 @@
* frameworks/base/core/proto/android/os/incident.proto
*/
const int FIELD_ID_METADATA = 2;
+// Args for exec gzip
+static const char* GZIP[] = {"/system/bin/gzip", NULL};
IncidentMetadata_Destination privacy_policy_to_dest(uint8_t privacyPolicy) {
switch (privacyPolicy) {
@@ -142,7 +146,8 @@
mListener(listener),
mFd(fd),
mIsStreaming(fd >= 0),
- mStatus(NO_ERROR) {
+ mStatus(OK),
+ mZipPid(-1) {
}
ReportRequest::~ReportRequest() {
@@ -153,7 +158,14 @@
}
bool ReportRequest::ok() {
- return mFd >= 0 && mStatus == NO_ERROR;
+ if (mStatus != OK) {
+ return false;
+ }
+ if (!args.gzip()) {
+ return mFd >= 0;
+ }
+ // Send a blank signal to check if mZipPid is alive
+ return mZipPid > 0 && kill(mZipPid, 0) == 0;
}
bool ReportRequest::containsSection(int sectionId) const {
@@ -161,10 +173,45 @@
}
void ReportRequest::closeFd() {
- if (mIsStreaming && mFd >= 0) {
+ if (!mIsStreaming) {
+ return;
+ }
+ if (mFd >= 0) {
close(mFd);
mFd = -1;
}
+ if (mZipPid > 0) {
+ mZipPipe.close();
+ // Gzip may take some time.
+ status_t err = wait_child(mZipPid, /* timeout_ms= */ 10 * 1000);
+ if (err != 0) {
+ ALOGW("[ReportRequest] abnormal child process: %s", strerror(-err));
+ }
+ }
+}
+
+int ReportRequest::getFd() {
+ return mZipPid > 0 ? mZipPipe.writeFd().get() : mFd;
+}
+
+status_t ReportRequest::initGzipIfNecessary() {
+ if (!mIsStreaming || !args.gzip()) {
+ return OK;
+ }
+ if (!mZipPipe.init()) {
+ ALOGE("[ReportRequest] Failed to setup pipe for gzip");
+ mStatus = -errno;
+ return mStatus;
+ }
+ int status = 0;
+ pid_t pid = fork_execute_cmd((char* const*)GZIP, mZipPipe.readFd().release(), mFd, &status);
+ if (pid < 0 || status != 0) {
+ mStatus = status;
+ return mStatus;
+ }
+ mZipPid = pid;
+ mFd = -1;
+ return OK;
}
// ================================================================================
@@ -562,6 +609,13 @@
reportId = (spec.tv_sec) * 1000 + spec.tv_nsec;
}
+ mBatch->forEachStreamingRequest([](const sp<ReportRequest>& request) {
+ status_t err = request->initGzipIfNecessary();
+ if (err != 0) {
+ ALOGW("Error forking gzip: %s", strerror(err));
+ }
+ });
+
// Write the incident report headers - each request gets its own headers. It's different
// from the other top-level fields in IncidentReport that are the sections where the rest
// is all shared data (although with their own individual privacy filtering).
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index cbc8b13..bd47a23 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -15,6 +15,7 @@
*/
#pragma once
+#include "incidentd_util.h"
#include "FdBuffer.h"
#include "WorkDirectory.h"
@@ -63,10 +64,12 @@
sp<IIncidentReportStatusListener> getListener() { return mListener; }
- int getFd() { return mFd; }
+ int getFd();
int setPersistedFd(int fd);
+ status_t initGzipIfNecessary();
+
void closeFd();
private:
@@ -74,6 +77,8 @@
int mFd;
bool mIsStreaming;
status_t mStatus;
+ pid_t mZipPid;
+ Fpipe mZipPipe;
};
// ================================================================================
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 9963533..1944d6e 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -16,10 +16,10 @@
#include "Log.h"
-#include "WorkDirectory.h"
-
+#include "incidentd_util.h"
#include "proto_util.h"
#include "PrivacyFilter.h"
+#include "WorkDirectory.h"
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <private/android_filesystem_config.h>
@@ -68,6 +68,9 @@
/** metadata field id in IncidentProto */
const int FIELD_ID_INCIDENT_METADATA = 2;
+// Args for exec gzip
+static const char* GZIP[] = {"/system/bin/gzip", NULL};
+
/**
* Read a protobuf from disk into the message.
*/
@@ -292,6 +295,7 @@
report->set_cls(args.receiverCls());
report->set_privacy_policy(args.getPrivacyPolicy());
report->set_all_sections(args.all());
+ report->set_gzip(args.gzip());
for (int section: args.sections()) {
report->add_section(section);
}
@@ -417,6 +421,24 @@
return BAD_VALUE;
}
+ pid_t zipPid = 0;
+ if (args.gzip()) {
+ Fpipe zipPipe;
+ if (!zipPipe.init()) {
+ ALOGE("[ReportFile] Failed to setup pipe for gzip");
+ close(writeFd);
+ return -errno;
+ }
+ int status = 0;
+ zipPid = fork_execute_cmd((char* const*)GZIP, zipPipe.readFd().release(), writeFd, &status);
+ close(writeFd);
+ if (zipPid < 0 || status != 0) {
+ ALOGE("[ReportFile] Failed to fork and exec gzip");
+ return status;
+ }
+ writeFd = zipPipe.writeFd().release();
+ }
+
status_t err;
for (const auto& report : mEnvelope.report()) {
@@ -437,6 +459,13 @@
}
close(writeFd);
+ if (zipPid > 0) {
+ status_t err = wait_child(zipPid, /* timeout_ms= */ 10 * 1000);
+ if (err != 0) {
+ ALOGE("[ReportFile] abnormal child process: %s", strerror(-err));
+ }
+ return err;
+ }
return NO_ERROR;
}
@@ -621,7 +650,7 @@
map<string,WorkDirectoryEntry> files;
get_directory_contents_locked(&files, 0);
-
+
for (map<string,WorkDirectoryEntry>::iterator it = files.begin();
it != files.end(); it++) {
sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs,
@@ -815,6 +844,7 @@
out->setAll(report.all_sections());
out->setReceiverPkg(report.pkg());
out->setReceiverCls(report.cls());
+ out->setGzip(report.gzip());
const int sectionCount = report.section_size();
for (int i = 0; i < sectionCount; i++) {
diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp
index dfaf893..2649fb9 100644
--- a/cmds/incidentd/src/incidentd_util.cpp
+++ b/cmds/incidentd/src/incidentd_util.cpp
@@ -18,6 +18,7 @@
#include "incidentd_util.h"
+#include <fcntl.h>
#include <sys/prctl.h>
#include <wait.h>
@@ -64,28 +65,52 @@
unique_fd& Fpipe::writeFd() { return mWrite; }
-pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output) {
- // fork used in multithreaded environment, avoid adding unnecessary code in child process
+pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status) {
+ int in = -1;
+ if (input != nullptr) {
+ in = input->readFd().release();
+ // Auto close write end of the input pipe on exec to prevent leaking fd in child process
+ fcntl(input->writeFd().get(), F_SETFD, FD_CLOEXEC);
+ }
+ int out = output->writeFd().release();
+ // Auto close read end of the output pipe on exec
+ fcntl(output->readFd().get(), F_SETFD, FD_CLOEXEC);
+ return fork_execute_cmd(argv, in, out, status);
+}
+
+pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status) {
+ int dummy_status = 0;
+ if (status == nullptr) {
+ status = &dummy_status;
+ }
+ *status = 0;
pid_t pid = fork();
+ if (pid < 0) {
+ *status = -errno;
+ return -1;
+ }
if (pid == 0) {
- if (input != NULL && (TEMP_FAILURE_RETRY(dup2(input->readFd().get(), STDIN_FILENO)) < 0 ||
- !input->close())) {
+ // In child
+ if (in >= 0 && (TEMP_FAILURE_RETRY(dup2(in, STDIN_FILENO)) < 0 || close(in))) {
ALOGW("Failed to dup2 stdin.");
_exit(EXIT_FAILURE);
}
- if (TEMP_FAILURE_RETRY(dup2(output->writeFd().get(), STDOUT_FILENO)) < 0 ||
- !output->close()) {
+ if (TEMP_FAILURE_RETRY(dup2(out, STDOUT_FILENO)) < 0 || close(out)) {
ALOGW("Failed to dup2 stdout.");
_exit(EXIT_FAILURE);
}
- /* make sure the child dies when incidentd dies */
+ // Make sure the child dies when incidentd dies
prctl(PR_SET_PDEATHSIG, SIGKILL);
execvp(argv[0], argv);
_exit(errno); // always exits with failure if any
}
- // close the fds used in child process.
- if (input != NULL) input->readFd().reset();
- output->writeFd().reset();
+ // In parent
+ if ((in >= 0 && close(in) < 0) || close(out) < 0) {
+ ALOGW("Failed to close pd. Killing child process");
+ *status = -errno;
+ kill_child(pid);
+ return -1;
+ }
return pid;
}
@@ -120,9 +145,6 @@
}
// ================================================================================
-const int WAIT_MAX = 5;
-const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
-
static status_t statusCode(int status) {
if (WIFSIGNALED(status)) {
VLOG("return by signal: %s", strerror(WTERMSIG(status)));
@@ -134,25 +156,64 @@
return NO_ERROR;
}
+static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
+ sigset_t child_mask, old_mask;
+ sigemptyset(&child_mask);
+ sigaddset(&child_mask, SIGCHLD);
+
+ if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
+ ALOGW("sigprocmask failed: %s", strerror(errno));
+ return false;
+ }
+
+ timespec ts;
+ ts.tv_sec = timeout_ms / 1000;
+ ts.tv_nsec = (timeout_ms % 1000) * 1000000;
+ int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
+ int saved_errno = errno;
+
+ // Set the signals back the way they were.
+ if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
+ ALOGW("sigprocmask failed: %s", strerror(errno));
+ if (ret == 0) {
+ return false;
+ }
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ if (errno == EAGAIN) {
+ errno = ETIMEDOUT;
+ } else {
+ ALOGW("sigtimedwait failed: %s", strerror(errno));
+ }
+ return false;
+ }
+
+ pid_t child_pid = waitpid(pid, status, WNOHANG);
+ if (child_pid == pid) {
+ return true;
+ }
+ if (child_pid == -1) {
+ ALOGW("waitpid failed: %s", strerror(errno));
+ } else {
+ ALOGW("Waiting for pid %d, got pid %d instead", pid, child_pid);
+ }
+ return false;
+}
+
status_t kill_child(pid_t pid) {
int status;
- VLOG("try to kill child process %d", pid);
kill(pid, SIGKILL);
if (waitpid(pid, &status, 0) == -1) return -1;
return statusCode(status);
}
-status_t wait_child(pid_t pid) {
+status_t wait_child(pid_t pid, int timeout_ms) {
int status;
- bool died = false;
- // wait for child to report status up to 1 seconds
- for (int loop = 0; !died && loop < WAIT_MAX; loop++) {
- if (waitpid(pid, &status, WNOHANG) == pid) died = true;
- // sleep for 0.2 second
- nanosleep(&WAIT_INTERVAL_NS, NULL);
+ if (waitpid_with_timeout(pid, timeout_ms, &status)) {
+ return statusCode(status);
}
- if (!died) return kill_child(pid);
- return statusCode(status);
+ return kill_child(pid);
}
} // namespace incidentd
diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h
index cc30768..a54993fe 100644
--- a/cmds/incidentd/src/incidentd_util.h
+++ b/cmds/incidentd/src/incidentd_util.h
@@ -56,11 +56,24 @@
};
/**
- * Forks and exec a command with two pipes, one connects stdin for input,
- * one connects stdout for output. It returns the pid of the child.
- * Input pipe can be NULL to indicate child process doesn't read stdin.
+ * Forks and exec a command with two pipes and returns the pid of the child, or -1 when it fails.
+ *
+ * input connects stdin for input. output connects stdout for output. input can be nullptr to
+ * indicate that child process doesn't read stdin. This function will close in and out fds upon
+ * success. If status is not NULL, the status information will be stored in the int to which it
+ * points.
*/
-pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output);
+pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output, int* status = nullptr);
+
+/**
+ * Forks and exec a command that reads from in fd and writes to out fd and returns the pid of the
+ * child, or -1 when it fails.
+ *
+ * in can be -1 to indicate that child process doesn't read stdin. This function will close in and
+ * out fds upon success. If status is not NULL, the status information will be stored in the int
+ * to which it points.
+ */
+pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status = nullptr);
/**
* Grabs varargs from stack and stores them in heap with NULL-terminated array.
@@ -76,7 +89,7 @@
* Methods to wait or kill child process, return exit status code.
*/
status_t kill_child(pid_t pid);
-status_t wait_child(pid_t pid);
+status_t wait_child(pid_t pid, int timeout_ms = 1000);
status_t start_detached_thread(const function<void ()>& func);
diff --git a/cmds/incidentd/src/report_file.proto b/cmds/incidentd/src/report_file.proto
index 7563da2..85fd2da 100644
--- a/cmds/incidentd/src/report_file.proto
+++ b/cmds/incidentd/src/report_file.proto
@@ -65,6 +65,11 @@
* the given client.
*/
optional bool share_approved = 8;
+
+ /**
+ * Whether the report is gzipped.
+ */
+ optional bool gzip = 9;
}
/**
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 0c3a49a9..a2fee8b 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -122,7 +122,6 @@
"libbinder_ndk",
"libincident",
"liblog",
- "libstatssocket",
"statsd-aidl-ndk_platform",
],
}
@@ -215,7 +214,10 @@
type: "lite",
},
- shared_libs: ["libgtest_prod"],
+ shared_libs: [
+ "libgtest_prod",
+ "libstatssocket",
+ ],
apex_available: [
"com.android.os.statsd",
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index c7f42cb..ae786aa 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -629,7 +629,10 @@
/** @hide */
@IntDef(flag = true, prefix = { "NOTIFY_" }, value = {
NOTIFY_SYNC_TO_NETWORK,
- NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS
+ NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS,
+ NOTIFY_INSERT,
+ NOTIFY_UPDATE,
+ NOTIFY_DELETE
})
@Retention(RetentionPolicy.SOURCE)
public @interface NotifyFlags {}
@@ -651,6 +654,36 @@
public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 1<<1;
/**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of an {@link ContentProvider#insert} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_INSERT = 1 << 2;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of an {@link ContentProvider#update} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_UPDATE = 1 << 3;
+
+ /**
+ * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+ * by a {@link ContentProvider} to indicate that this notification is the
+ * result of a {@link ContentProvider#delete} call.
+ * <p>
+ * Sending these detailed flags are optional, but providers are strongly
+ * recommended to send them.
+ */
+ public static final int NOTIFY_DELETE = 1 << 4;
+
+ /**
* No exception, throttled by app standby normally.
* @hide
*/
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index 69ca581..ede264d 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -16,11 +16,17 @@
package android.database;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver.NotifyFlags;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
+import java.util.Arrays;
+
/**
* Receives call backs for changes to content.
* Must be implemented by objects which are added to a {@link ContentObservable}.
@@ -101,12 +107,10 @@
* This method is called when a content change occurs.
* Includes the changed content Uri when available.
* <p>
- * Subclasses should override this method to handle content changes.
- * To ensure correct operation on older versions of the framework that
- * did not provide a Uri argument, applications should also implement
- * the {@link #onChange(boolean)} overload of this method whenever they
- * implement the {@link #onChange(boolean, Uri)} overload.
- * </p><p>
+ * Subclasses should override this method to handle content changes. To
+ * ensure correct operation on older versions of the framework that did not
+ * provide richer arguments, applications should implement all overloads.
+ * <p>
* Example implementation:
* <pre><code>
* // Implement the onChange(boolean) method to delegate the change notification to
@@ -126,38 +130,63 @@
* </p>
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
+ * @param uri The Uri of the changed content.
*/
- public void onChange(boolean selfChange, Uri uri) {
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
onChange(selfChange);
}
/**
- * Dispatches a change notification to the observer. Includes the changed
- * content Uri when available and also the user whose content changed.
+ * This method is called when a content change occurs. Includes the changed
+ * content Uri when available.
+ * <p>
+ * Subclasses should override this method to handle content changes. To
+ * ensure correct operation on older versions of the framework that did not
+ * provide richer arguments, applications should implement all overloads.
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
- * @param userId The user whose content changed. Can be either a specific
- * user or {@link UserHandle#USER_ALL}.
- *
- * @hide
+ * @param uri The Uri of the changed content.
+ * @param flags Flags indicating details about this change.
*/
- public void onChange(boolean selfChange, Uri uri, int userId) {
+ public void onChange(boolean selfChange, @Nullable Uri uri, @NotifyFlags int flags) {
onChange(selfChange, uri);
}
/**
+ * This method is called when a content change occurs. Includes the changed
+ * content Uris when available.
+ * <p>
+ * Subclasses should override this method to handle content changes. To
+ * ensure correct operation on older versions of the framework that did not
+ * provide richer arguments, applications should implement all overloads.
+ *
+ * @param selfChange True if this is a self-change notification.
+ * @param uris The Uris of the changed content.
+ * @param flags Flags indicating details about this change.
+ */
+ public void onChange(boolean selfChange, @NonNull Iterable<Uri> uris, @NotifyFlags int flags) {
+ for (Uri uri : uris) {
+ onChange(selfChange, uri, flags);
+ }
+ }
+
+ /** @hide */
+ public void onChange(boolean selfChange, @NonNull Iterable<Uri> uris, @NotifyFlags int flags,
+ @UserIdInt int userId) {
+ onChange(selfChange, uris, flags);
+ }
+
+ /**
* Dispatches a change notification to the observer.
* <p>
- * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
- * then a call to the {@link #onChange} method is posted to the handler's message queue.
- * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
- * </p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
*
- * @param selfChange True if this is a self-change notification.
- *
- * @deprecated Use {@link #dispatchChange(boolean, Uri)} instead.
+ * @deprecated Callers should migrate towards using a richer overload that
+ * provides more details about the change, such as
+ * {@link #dispatchChange(boolean, Iterable, int)}.
*/
@Deprecated
public final void dispatchChange(boolean selfChange) {
@@ -165,57 +194,66 @@
}
/**
- * Dispatches a change notification to the observer.
- * Includes the changed content Uri when available.
+ * Dispatches a change notification to the observer. Includes the changed
+ * content Uri when available.
* <p>
- * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
- * then a call to the {@link #onChange} method is posted to the handler's message queue.
- * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
- * </p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
+ * @param uri The Uri of the changed content.
*/
- public final void dispatchChange(boolean selfChange, Uri uri) {
- dispatchChange(selfChange, uri, UserHandle.getCallingUserId());
+ public final void dispatchChange(boolean selfChange, @Nullable Uri uri) {
+ dispatchChange(selfChange, Arrays.asList(uri), 0, UserHandle.getCallingUserId());
}
/**
* Dispatches a change notification to the observer. Includes the changed
- * content Uri when available and also the user whose content changed.
+ * content Uri when available.
* <p>
- * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
- * then a call to the {@link #onChange} method is posted to the handler's message queue.
- * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
- * </p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
*
* @param selfChange True if this is a self-change notification.
- * @param uri The Uri of the changed content, or null if unknown.
- * @param userId The user whose content changed.
+ * @param uri The Uri of the changed content.
+ * @param flags Flags indicating details about this change.
*/
- private void dispatchChange(boolean selfChange, Uri uri, int userId) {
- if (mHandler == null) {
- onChange(selfChange, uri, userId);
- } else {
- mHandler.post(new NotificationRunnable(selfChange, uri, userId));
- }
+ public final void dispatchChange(boolean selfChange, @Nullable Uri uri,
+ @NotifyFlags int flags) {
+ dispatchChange(selfChange, Arrays.asList(uri), flags, UserHandle.getCallingUserId());
}
+ /**
+ * Dispatches a change notification to the observer. Includes the changed
+ * content Uris when available.
+ * <p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver}
+ * constructor, then a call to the {@link #onChange} method is posted to the
+ * handler's message queue. Otherwise, the {@link #onChange} method is
+ * invoked immediately on this thread.
+ *
+ * @param selfChange True if this is a self-change notification.
+ * @param uris The Uri of the changed content.
+ * @param flags Flags indicating details about this change.
+ */
+ public final void dispatchChange(boolean selfChange, @NonNull Iterable<Uri> uris,
+ @NotifyFlags int flags) {
+ dispatchChange(selfChange, uris, flags, UserHandle.getCallingUserId());
+ }
- private final class NotificationRunnable implements Runnable {
- private final boolean mSelfChange;
- private final Uri mUri;
- private final int mUserId;
-
- public NotificationRunnable(boolean selfChange, Uri uri, int userId) {
- mSelfChange = selfChange;
- mUri = uri;
- mUserId = userId;
- }
-
- @Override
- public void run() {
- ContentObserver.this.onChange(mSelfChange, mUri, mUserId);
+ /** @hide */
+ public final void dispatchChange(boolean selfChange, @NonNull Iterable<Uri> uris,
+ @NotifyFlags int flags, @UserIdInt int userId) {
+ if (mHandler == null) {
+ onChange(selfChange, uris, flags, userId);
+ } else {
+ mHandler.post(() -> {
+ onChange(selfChange, uris, flags, userId);
+ });
}
}
@@ -228,9 +266,16 @@
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
+ // This is kept intact purely for apps using hidden APIs, to
+ // redirect to the updated implementation
+ onChangeEtc(selfChange, new Uri[] { uri }, 0, userId);
+ }
+
+ @Override
+ public void onChangeEtc(boolean selfChange, Uri[] uris, int flags, int userId) {
ContentObserver contentObserver = mContentObserver;
if (contentObserver != null) {
- contentObserver.dispatchChange(selfChange, uri, userId);
+ contentObserver.dispatchChange(selfChange, Arrays.asList(uris), flags, userId);
}
}
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 02eddf2..1855dd2 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -16,9 +16,14 @@
package android.database;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver.NotifyFlags;
import android.net.Uri;
import android.os.*;
+import java.util.ArrayList;
+
/**
* Wraps a BulkCursor around an existing Cursor making it remotable.
@@ -76,9 +81,18 @@
}
@Override
- public void onChange(boolean selfChange, Uri uri) {
+ public void onChange(boolean selfChange, @NonNull Iterable<Uri> uris,
+ @NotifyFlags int flags, @UserIdInt int userId) {
+ // Since we deliver changes from the most-specific to least-specific
+ // overloads, we only need to redirect from the most-specific local
+ // method to the most-specific remote method
+
+ final ArrayList<Uri> asList = new ArrayList<>();
+ uris.forEach(asList::add);
+ final Uri[] asArray = asList.toArray(new Uri[asList.size()]);
+
try {
- mRemote.onChange(selfChange, uri, android.os.Process.myUid());
+ mRemote.onChangeEtc(selfChange, asArray, flags, userId);
} catch (RemoteException ex) {
// Do nothing, the far side is dead
}
diff --git a/core/java/android/database/IContentObserver.aidl b/core/java/android/database/IContentObserver.aidl
index 6235566..19284cf 100644
--- a/core/java/android/database/IContentObserver.aidl
+++ b/core/java/android/database/IContentObserver.aidl
@@ -22,8 +22,7 @@
/**
* @hide
*/
-interface IContentObserver
-{
+interface IContentObserver {
/**
* This method is called when an update occurs to the cursor that is being
* observed. selfUpdate is true if the update was caused by a call to
@@ -31,4 +30,11 @@
*/
@UnsupportedAppUsage
oneway void onChange(boolean selfUpdate, in Uri uri, int userId);
+
+ /**
+ * This method is called when an update occurs to the cursor that is being
+ * observed. selfUpdate is true if the update was caused by a call to
+ * commit on the cursor that is being observed.
+ */
+ oneway void onChangeEtc(boolean selfUpdate, in Uri[] uri, int flags, int userId);
}
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 223f920..de274c0 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -559,6 +559,37 @@
}
}
+ private static class CleanupAppliedPayloadCallback extends IUpdateEngineCallback.Stub {
+ private int mErrorCode = ErrorCodeConstants.ERROR;
+ private boolean mCompleted = false;
+ private Object mLock = new Object();
+ private int getResult() {
+ synchronized (mLock) {
+ while (!mCompleted) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException ex) {
+ // do nothing, just wait again.
+ }
+ }
+ return mErrorCode;
+ }
+ }
+
+ @Override
+ public void onStatusUpdate(int status, float percent) {
+ }
+
+ @Override
+ public void onPayloadApplicationComplete(int errorCode) {
+ synchronized (mLock) {
+ mErrorCode = errorCode;
+ mCompleted = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
/**
* Cleanup files used by the previous update and free up space after the
* device has been booted successfully into the new build.
@@ -590,8 +621,10 @@
@WorkerThread
@ErrorCode
public int cleanupAppliedPayload() {
+ CleanupAppliedPayloadCallback callback = new CleanupAppliedPayloadCallback();
try {
- return mUpdateEngine.cleanupSuccessfulUpdate();
+ mUpdateEngine.cleanupSuccessfulUpdate(callback);
+ return callback.getResult();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 92ac425..f07f1ce 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -87,6 +87,7 @@
if (mSourceControl == control) {
return;
}
+ final InsetsSourceControl lastControl = mSourceControl;
mSourceControl = control;
// We are loosing control
@@ -97,25 +98,27 @@
mState.getSource(getType()).setVisible(
mController.getLastDispatchedState().getSource(getType()).isVisible());
applyLocalVisibilityOverride();
- return;
- }
-
- // We are gaining control, and need to run an animation since previous state didn't match
- if (mRequestedVisible != mState.getSource(mType).isVisible()) {
- if (mRequestedVisible) {
- showTypes[0] |= toPublicType(getType());
+ } else {
+ // We are gaining control, and need to run an animation since previous state
+ // didn't match
+ if (mRequestedVisible != mState.getSource(mType).isVisible()) {
+ if (mRequestedVisible) {
+ showTypes[0] |= toPublicType(getType());
+ } else {
+ hideTypes[0] |= toPublicType(getType());
+ }
} else {
- hideTypes[0] |= toPublicType(getType());
+ // We are gaining control, but don't need to run an animation.
+ // However make sure that the leash visibility is still up to date.
+ if (applyLocalVisibilityOverride()) {
+ mController.notifyVisibilityChanged();
+ }
+ applyHiddenToControl();
}
- return;
}
-
- // We are gaining control, but don't need to run an animation. However make sure that the
- // leash visibility is still up to date.
- if (applyLocalVisibilityOverride()) {
- mController.notifyVisibilityChanged();
+ if (lastControl != null) {
+ lastControl.release();
}
- applyHiddenToControl();
}
@VisibleForTesting
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7be3e6a..29ba56a 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -94,6 +94,12 @@
dest.writeParcelable(mSurfacePosition, 0 /* flags*/);
}
+ public void release() {
+ if (mLeash != null) {
+ mLeash.release();
+ }
+ }
+
public static final @android.annotation.NonNull Creator<InsetsSourceControl> CREATOR
= new Creator<InsetsSourceControl>() {
public InsetsSourceControl createFromParcel(Parcel in) {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 31e4555..6761435 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -27,6 +27,7 @@
#include "DamageAccumulator.h"
#include "pipeline/skia/SkiaDisplayList.h"
#endif
+#include "utils/FatVector.h"
#include "utils/MathUtils.h"
#include "utils/StringUtils.h"
#include "utils/TraceUtils.h"
@@ -36,7 +37,6 @@
#include <atomic>
#include <sstream>
#include <string>
-#include <ui/FatVector.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index c0ec217..d55e5b0 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -27,8 +27,6 @@
#include <androidfw/ResourceTypes.h>
-#include <ui/FatVector.h>
-
#include "AnimatorManager.h"
#include "CanvasTransform.h"
#include "Debug.h"
@@ -37,6 +35,7 @@
#include "RenderProperties.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaLayer.h"
+#include "utils/FatVector.h"
#include <vector>
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index a2fef1e..0ce04a2 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -29,9 +29,9 @@
#include <hwui/MinikinSkia.h>
#include <hwui/Typeface.h>
+#include <utils/FatVector.h>
#include <minikin/FontFamily.h>
#include <minikin/LocaleList.h>
-#include <ui/FatVector.h>
#include <memory>
@@ -104,7 +104,7 @@
static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
jint weight, jint italic) {
- FatVector<SkFontArguments::Axis, 2> skiaAxes;
+ uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
for (const auto& axis : builder->axes) {
skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
}
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 5714cd1..7e8f8d8 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -28,8 +28,8 @@
#include <hwui/MinikinSkia.h>
#include <hwui/Typeface.h>
+#include <utils/FatVector.h>
#include <minikin/FontFamily.h>
-#include <ui/FatVector.h>
#include <memory>
@@ -93,7 +93,7 @@
sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
release_global_ref, reinterpret_cast<void*>(fontRef)));
- FatVector<SkFontArguments::Axis, 2> skiaAxes;
+ uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
for (const auto& axis : builder->axes) {
skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
}
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
index d669f84..cfc0f9b 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h
@@ -21,7 +21,7 @@
#include <SkCanvas.h>
#include <SkDrawable.h>
-#include <ui/FatVector.h>
+#include <utils/FatVector.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 206b58f..cae3e3b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -27,6 +27,7 @@
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
#include "utils/TimeUtils.h"
#include "utils/TraceUtils.h"
@@ -39,8 +40,6 @@
#include <utils/Mutex.h>
#include <thread>
-#include <ui/FatVector.h>
-
namespace android {
namespace uirenderer {
namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index ba70afc..a5355fc 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -23,13 +23,13 @@
#include <GrContext.h>
#include <GrTypes.h>
#include <android/sync.h>
-#include <ui/FatVector.h>
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
#include "Properties.h"
#include "RenderThread.h"
#include "renderstate/RenderState.h"
+#include "utils/FatVector.h"
#include "utils/TraceUtils.h"
namespace android {
diff --git a/libs/hwui/tests/unit/FatVectorTests.cpp b/libs/hwui/tests/unit/FatVectorTests.cpp
index 6585a62..8523e6c 100644
--- a/libs/hwui/tests/unit/FatVectorTests.cpp
+++ b/libs/hwui/tests/unit/FatVectorTests.cpp
@@ -15,7 +15,7 @@
*/
#include <gtest/gtest.h>
-#include <ui/FatVector.h>
+#include <utils/FatVector.h>
#include <tests/common/TestUtils.h>
diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h
new file mode 100644
index 0000000..49f1984
--- /dev/null
+++ b/libs/hwui/utils/FatVector.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 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 ANDROID_FAT_VECTOR_H
+#define ANDROID_FAT_VECTOR_H
+
+#include "utils/Macros.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <utils/Log.h>
+#include <type_traits>
+
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+template <typename T, size_t SIZE>
+class InlineStdAllocator {
+public:
+ struct Allocation {
+ PREVENT_COPY_AND_ASSIGN(Allocation);
+
+ public:
+ Allocation(){};
+ // char array instead of T array, so memory is uninitialized, with no destructors run
+ char array[sizeof(T) * SIZE];
+ bool inUse = false;
+ };
+
+ typedef T value_type; // needed to implement std::allocator
+ typedef T* pointer; // needed to implement std::allocator
+
+ explicit InlineStdAllocator(Allocation& allocation) : mAllocation(allocation) {}
+ InlineStdAllocator(const InlineStdAllocator& other) : mAllocation(other.mAllocation) {}
+ ~InlineStdAllocator() {}
+
+ T* allocate(size_t num, const void* = 0) {
+ if (!mAllocation.inUse && num <= SIZE) {
+ mAllocation.inUse = true;
+ return (T*)mAllocation.array;
+ } else {
+ return (T*)malloc(num * sizeof(T));
+ }
+ }
+
+ void deallocate(pointer p, size_t num) {
+ if (p == (T*)mAllocation.array) {
+ mAllocation.inUse = false;
+ } else {
+ // 'free' instead of delete here - destruction handled separately
+ free(p);
+ }
+ }
+ Allocation& mAllocation;
+};
+
+/**
+ * std::vector with SIZE elements preallocated into an internal buffer.
+ *
+ * Useful for avoiding the cost of malloc in cases where only SIZE or
+ * fewer elements are needed in the common case.
+ */
+template <typename T, size_t SIZE>
+class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> {
+public:
+ FatVector()
+ : std::vector<T, InlineStdAllocator<T, SIZE>>(
+ InlineStdAllocator<T, SIZE>(mAllocation)) {
+ this->reserve(SIZE);
+ }
+
+ explicit FatVector(size_t capacity) : FatVector() { this->resize(capacity); }
+
+private:
+ typename InlineStdAllocator<T, SIZE>::Allocation mAllocation;
+};
+
+} // namespace uirenderer
+} // namespace android
+
+#endif // ANDROID_FAT_VECTOR_H
diff --git a/libs/incident/include_priv/android/os/IncidentReportArgs.h b/libs/incident/include_priv/android/os/IncidentReportArgs.h
index 0e61590..ec3aabb 100644
--- a/libs/incident/include_priv/android/os/IncidentReportArgs.h
+++ b/libs/incident/include_priv/android/os/IncidentReportArgs.h
@@ -53,6 +53,7 @@
void setReceiverPkg(const string& pkg);
void setReceiverCls(const string& cls);
void addHeader(const vector<uint8_t>& headerProto);
+ void setGzip(bool gzip);
inline bool all() const { return mAll; }
bool containsSection(int section, bool specific) const;
@@ -61,6 +62,7 @@
inline const string& receiverPkg() const { return mReceiverPkg; }
inline const string& receiverCls() const { return mReceiverCls; }
inline const vector<vector<uint8_t>>& headers() const { return mHeaders; }
+ inline bool gzip() const {return mGzip; }
void merge(const IncidentReportArgs& that);
@@ -71,6 +73,7 @@
int mPrivacyPolicy;
string mReceiverPkg;
string mReceiverCls;
+ bool mGzip;
};
}
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index 9d8a983..db495cf 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -26,7 +26,8 @@
IncidentReportArgs::IncidentReportArgs()
:mSections(),
mAll(false),
- mPrivacyPolicy(-1)
+ mPrivacyPolicy(-1),
+ mGzip(false)
{
}
@@ -36,7 +37,8 @@
mAll(that.mAll),
mPrivacyPolicy(that.mPrivacyPolicy),
mReceiverPkg(that.mReceiverPkg),
- mReceiverCls(that.mReceiverCls)
+ mReceiverCls(that.mReceiverCls),
+ mGzip(that.mGzip)
{
}
@@ -93,6 +95,11 @@
return err;
}
+ err = out->writeInt32(mGzip);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
return NO_ERROR;
}
@@ -149,6 +156,15 @@
mReceiverPkg = String8(in->readString16()).string();
mReceiverCls = String8(in->readString16()).string();
+ int32_t gzip;
+ err = in->readInt32(&gzip);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ if (gzip != 0) {
+ mGzip = gzip;
+ }
+
return OK;
}
@@ -193,6 +209,12 @@
mHeaders.push_back(headerProto);
}
+void
+IncidentReportArgs::setGzip(bool gzip)
+{
+ mGzip = gzip;
+}
+
bool
IncidentReportArgs::containsSection(int section, bool specific) const
{
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 9c942a5..8674047 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -27,6 +27,9 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
@@ -72,11 +75,14 @@
val aRank = a.ranking.rank
val bRank = b.ranking.rank
- val aIsPeople = a.isPeopleNotification()
- val bIsPeople = b.isPeopleNotification()
+ val aPersonType = a.getPeopleNotificationType()
+ val bPersonType = b.getPeopleNotificationType()
- val aIsImportantPeople = a.isImportantPeopleNotification()
- val bIsImportantPeople = b.isImportantPeopleNotification()
+ val aIsPeople = aPersonType == TYPE_PERSON
+ val bIsPeople = bPersonType == TYPE_PERSON
+
+ val aIsImportantPeople = aPersonType == TYPE_IMPORTANT_PERSON
+ val bIsImportantPeople = bPersonType == TYPE_IMPORTANT_PERSON
val aMedia = isImportantMedia(a)
val bMedia = isImportantMedia(b)
@@ -165,7 +171,7 @@
) {
if (usePeopleFiltering && isHeadsUp) {
entry.bucket = BUCKET_HEADS_UP
- } else if (usePeopleFiltering && entry.isPeopleNotification()) {
+ } else if (usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON) {
entry.bucket = BUCKET_PEOPLE
} else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) {
entry.bucket = BUCKET_ALERTING
@@ -198,11 +204,8 @@
}
}
- private fun NotificationEntry.isPeopleNotification() =
- peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)
-
- private fun NotificationEntry.isImportantPeopleNotification() =
- peopleNotificationIdentifier.isImportantPeopleNotification(sbn, ranking)
+ private fun NotificationEntry.getPeopleNotificationType() =
+ peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
private fun NotificationEntry.isHighPriority() =
highPriorityProvider.isHighPriority(this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index 0d9beae..df3609b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -103,8 +103,8 @@
}
private boolean isPeopleNotification(NotificationEntry entry) {
- return mPeopleNotificationIdentifier.isPeopleNotification(
- entry.getSbn(), entry.getRanking());
+ return mPeopleNotificationIdentifier.getPeopleNotificationType(
+ entry.getSbn(), entry.getRanking()) != PeopleNotificationIdentifier.TYPE_NON_PERSON;
}
private boolean hasUserSetImportance(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
index 3007198..3af6ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.people
-import android.app.PendingIntent
import android.graphics.drawable.Drawable
/**
@@ -45,10 +44,11 @@
/** `Model` for a single "Person" in PeopleHub. */
data class PersonModel(
val key: PersonKey,
+ val userId: Int,
+ // TODO: these should live in the ViewModel
val name: CharSequence,
val avatar: Drawable,
- val clickRunnable: Runnable,
- val userId: Int
+ val clickRunnable: Runnable
)
/** Unique identifier for a Person in PeopleHub. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 360bf96..2fbd3ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -20,9 +20,7 @@
import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
-import android.content.pm.ShortcutInfo
import android.content.pm.UserInfo
-import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.UserManager
import android.service.notification.NotificationListenerService
@@ -45,6 +43,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.policy.ExtensionController
import java.util.ArrayDeque
import java.util.concurrent.Executor
@@ -79,7 +78,7 @@
override fun extractPerson(sbn: StatusBarNotification) =
plugin?.extractPerson(sbn)?.run {
- PersonModel(key, name, avatar, clickRunnable, sbn.user.identifier)
+ PersonModel(key, sbn.user.identifier, name, avatar, clickRunnable)
}
override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
@@ -90,26 +89,35 @@
@Singleton
class PeopleHubDataSourceImpl @Inject constructor(
- private val notificationEntryManager: NotificationEntryManager,
- private val extractor: NotificationPersonExtractor,
- private val userManager: UserManager,
- private val launcherApps: LauncherApps,
- private val packageManager: PackageManager,
- private val c: Context,
- private val notificationListener: NotificationListener,
- @Background private val bgExecutor: Executor,
- @Main private val mainExecutor: Executor,
- private val notifLockscreenUserMgr: NotificationLockscreenUserManager,
- private val peopleNotificationIdentifier: PeopleNotificationIdentifier
- ) : DataSource<PeopleHubModel> {
+ private val notificationEntryManager: NotificationEntryManager,
+ private val extractor: NotificationPersonExtractor,
+ private val userManager: UserManager,
+ launcherApps: LauncherApps,
+ packageManager: PackageManager,
+ context: Context,
+ private val notificationListener: NotificationListener,
+ @Background private val bgExecutor: Executor,
+ @Main private val mainExecutor: Executor,
+ private val notifLockscreenUserMgr: NotificationLockscreenUserManager,
+ private val peopleNotificationIdentifier: PeopleNotificationIdentifier
+) : DataSource<PeopleHubModel> {
private var userChangeSubscription: Subscription? = null
private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
- val context: Context = c.applicationContext
- val iconFactory = ConversationIconFactory(context, launcherApps, packageManager,
- IconDrawableFactory.newInstance(context), context.resources.getDimensionPixelSize(
- R.dimen.notification_guts_conversation_icon_size))
+
+ private val iconFactory = run {
+ val appContext = context.applicationContext
+ ConversationIconFactory(
+ appContext,
+ launcherApps,
+ packageManager,
+ IconDrawableFactory.newInstance(appContext),
+ appContext.resources.getDimensionPixelSize(
+ R.dimen.notification_guts_conversation_icon_size
+ )
+ )
+ }
private val notificationEntryListener = object : NotificationEntryListener {
override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry)
@@ -206,7 +214,8 @@
}
private fun NotificationEntry.extractPerson(): PersonModel? {
- if (!peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)) {
+ val type = peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
+ if (type == TYPE_NON_PERSON) {
return null
}
val clickRunnable = Runnable { notificationListener.unsnoozeNotification(key) }
@@ -215,23 +224,34 @@
?: extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
?: extras.getString(Notification.EXTRA_TITLE)
?: return null
- val drawable = ranking.shortcutInfo?.getIcon(iconFactory, sbn, ranking)
- ?: iconFactory.getConversationDrawable(extractAvatarFromRow(this), sbn.packageName,
- sbn.uid, ranking.channel.isImportantConversation)
-
- return PersonModel(key, name, drawable, clickRunnable, sbn.user.identifier)
+ val drawable = ranking.getIcon(iconFactory, sbn)
+ ?: iconFactory.getConversationDrawable(
+ extractAvatarFromRow(this),
+ sbn.packageName,
+ sbn.uid,
+ ranking.channel.isImportantConversation
+ )
+ return PersonModel(key, sbn.user.identifier, name, drawable, clickRunnable)
}
- private fun ShortcutInfo.getIcon(iconFactory: ConversationIconFactory,
- sbn: StatusBarNotification,
- ranking: NotificationListenerService.Ranking): Drawable? {
- return iconFactory.getConversationDrawable(ranking.shortcutInfo, sbn.packageName, sbn.uid,
- ranking.channel.isImportantConversation)
- }
+ private fun NotificationListenerService.Ranking.getIcon(
+ iconFactory: ConversationIconFactory,
+ sbn: StatusBarNotification
+ ): Drawable? =
+ shortcutInfo?.let { shortcutInfo ->
+ iconFactory.getConversationDrawable(
+ shortcutInfo,
+ sbn.packageName,
+ sbn.uid,
+ channel.isImportantConversation
+ )
+ }
- private fun NotificationEntry.extractPersonKey(): PersonKey? =
- // TODO migrate to shortcut id when snoozing is conversation wide
- if (peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)) key else null
+ private fun NotificationEntry.extractPersonKey(): PersonKey? {
+ // TODO migrate to shortcut id when snoozing is conversation wide
+ val type = peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
+ return if (type != TYPE_NON_PERSON) key else null
+ }
}
private fun NotificationLockscreenUserManager.registerListener(
@@ -303,4 +323,3 @@
?.drawable
}
?.firstOrNull()
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index e15fa2e..597bdb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -16,24 +16,97 @@
package com.android.systemui.statusbar.notification.people
+import android.annotation.IntDef
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.statusbar.phone.NotificationGroupManager
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.math.max
interface PeopleNotificationIdentifier {
- fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking): Boolean
- fun isImportantPeopleNotification(sbn: StatusBarNotification, ranking: Ranking): Boolean
+
+ /**
+ * Identifies if the given notification can be classified as a "People" notification.
+ *
+ * @return [TYPE_NON_PERSON] if not a people notification, [TYPE_PERSON] if a standard people
+ * notification, and [TYPE_IMPORTANT_PERSON] if an "important" people notification.
+ */
+ @PeopleNotificationType
+ fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int
+
+ companion object {
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(prefix = ["TYPE_"], value = [TYPE_NON_PERSON, TYPE_PERSON, TYPE_IMPORTANT_PERSON])
+ annotation class PeopleNotificationType
+
+ const val TYPE_NON_PERSON = 0
+ const val TYPE_PERSON = 1
+ const val TYPE_IMPORTANT_PERSON = 2
+ }
}
@Singleton
class PeopleNotificationIdentifierImpl @Inject constructor(
- private val personExtractor: NotificationPersonExtractor
+ private val personExtractor: NotificationPersonExtractor,
+ private val groupManager: NotificationGroupManager
) : PeopleNotificationIdentifier {
- override fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking) =
- ranking.isConversation || personExtractor.isPersonNotification(sbn)
+ @PeopleNotificationType
+ override fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int =
+ when (val type = ranking.personTypeInfo) {
+ TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
+ else -> {
+ when (val type = upperBound(type, extractPersonTypeInfo(sbn))) {
+ TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
+ else -> upperBound(type, getPeopleTypeOfSummary(sbn))
+ }
+ }
+ }
- override fun isImportantPeopleNotification(sbn: StatusBarNotification, ranking: Ranking) =
- isPeopleNotification(sbn, ranking) && ranking.channel.isImportantConversation
-}
\ No newline at end of file
+ /**
+ * Given two [PeopleNotificationType]s, determine the upper bound. Used to constrain a
+ * notification to a type given multiple signals, i.e. notification groups, where each child
+ * has a [PeopleNotificationType] that is used to constrain the summary.
+ */
+ @PeopleNotificationType
+ private fun upperBound(
+ @PeopleNotificationType type: Int,
+ @PeopleNotificationType other: Int
+ ): Int =
+ max(type, other)
+
+ private val Ranking.personTypeInfo
+ get() = when {
+ channel.isImportantConversation -> TYPE_IMPORTANT_PERSON
+ isConversation -> TYPE_PERSON
+ else -> TYPE_NON_PERSON
+ }
+
+ private fun extractPersonTypeInfo(sbn: StatusBarNotification) =
+ if (personExtractor.isPersonNotification(sbn)) TYPE_PERSON else TYPE_NON_PERSON
+
+ private fun getPeopleTypeOfSummary(statusBarNotification: StatusBarNotification): Int {
+ if (!groupManager.isSummaryOfGroup(statusBarNotification)) {
+ return TYPE_NON_PERSON
+ }
+
+ val childTypes = groupManager.getLogicalChildren(statusBarNotification)
+ ?.asSequence()
+ ?.map { getPeopleNotificationType(it.sbn, it.ranking) }
+ ?: return TYPE_NON_PERSON
+
+ var groupType = TYPE_NON_PERSON
+ for (childType in childTypes) {
+ groupType = upperBound(groupType, childType)
+ if (groupType == TYPE_IMPORTANT_PERSON)
+ break
+ }
+ return groupType
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 8ee2f50..42a7c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -332,6 +332,10 @@
// Put it at the end of the list.
peopleHeaderTarget = lastNotifIndex;
}
+ // Offset the target to account for the current position of the people header.
+ if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) {
+ peopleHeaderTarget--;
+ }
}
// Add headers in reverse order to preserve indices
@@ -459,6 +463,11 @@
return mPeopleHubView;
}
+ @VisibleForTesting
+ void setPeopleHubVisible(boolean visible) {
+ mPeopleHubVisible = visible;
+ }
+
private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onLocaleListChanged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index e4865b6..78e9b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -20,6 +20,9 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.TYPE_NON_PERSON;
+import static com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.TYPE_PERSON;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -60,8 +63,9 @@
final NotificationEntry entry = new NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
- .thenReturn(false);
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+ .thenReturn(TYPE_NON_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -76,8 +80,9 @@
.setNotification(notification)
.setImportance(IMPORTANCE_LOW)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
- .thenReturn(true);
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+ .thenReturn(TYPE_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -92,8 +97,9 @@
final NotificationEntry entry = new NotificationEntryBuilder()
.setNotification(notification)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
- .thenReturn(false);
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+ .thenReturn(TYPE_NON_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -109,8 +115,9 @@
.setNotification(notification)
.setImportance(IMPORTANCE_LOW)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
- .thenReturn(false);
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+ .thenReturn(TYPE_NON_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -126,8 +133,9 @@
.setNotification(notification)
.setImportance(IMPORTANCE_MIN)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
- .thenReturn(false);
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+ .thenReturn(TYPE_NON_PERSON);
// THEN it does NOT have high priority
assertFalse(mHighPriorityProvider.isHighPriority(entry));
@@ -149,8 +157,9 @@
.setNotification(notification)
.setChannel(channel)
.build();
- when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
- .thenReturn(true);
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
+ .thenReturn(TYPE_PERSON);
// THEN it does NOT have high priority
assertFalse(mHighPriorityProvider.isHighPriority(entry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 5a6f888..cdf0f2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -32,6 +32,8 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
@@ -166,10 +168,8 @@
.setOverrideGroupKey("")
.build()
- whenever(personNotificationIdentifier.isImportantPeopleNotification(a.sbn, a.ranking))
- .thenReturn(true)
- whenever(personNotificationIdentifier.isPeopleNotification(a.sbn, a.ranking))
- .thenReturn(true)
+ whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+ .thenReturn(TYPE_IMPORTANT_PERSON)
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
@@ -188,10 +188,8 @@
whenever(it.isHeadsUp).thenReturn(true)
}
- whenever(personNotificationIdentifier.isImportantPeopleNotification(a.sbn, a.ranking))
- .thenReturn(false)
- whenever(personNotificationIdentifier.isPeopleNotification(a.sbn, a.ranking))
- .thenReturn(false)
+ whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+ .thenReturn(TYPE_PERSON)
assertEquals(listOf(b, a), rankingManager.updateRanking(null, listOf(a, b), "test"))
}
@@ -213,10 +211,8 @@
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
- whenever(personNotificationIdentifier.isImportantPeopleNotification(a.sbn, a.ranking))
- .thenReturn(false)
- whenever(personNotificationIdentifier.isPeopleNotification(a.sbn, a.ranking))
- .thenReturn(true)
+ whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
+ .thenReturn(TYPE_PERSON)
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
@@ -231,10 +227,8 @@
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
- whenever(personNotificationIdentifier.isImportantPeopleNotification(b.sbn, b.ranking))
- .thenReturn(true)
- whenever(personNotificationIdentifier.isPeopleNotification(b.sbn, b.ranking))
- .thenReturn(true)
+ whenever(personNotificationIdentifier.getPeopleNotificationType(b.sbn, b.ranking))
+ .thenReturn(TYPE_IMPORTANT_PERSON)
assertEquals(
listOf(b, a),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index b1288f8..79fa436 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.people
-import android.app.PendingIntent
-import android.content.Intent
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.view.View
@@ -151,7 +149,7 @@
clickRunnable: Runnable,
userId: Int = 0
): PersonModel =
- PersonModel(id, name, mock(Drawable::class.java), clickRunnable, userId)
+ PersonModel(id, userId, name, mock(Drawable::class.java), clickRunnable)
private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 2c9fd2c..5d0349d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -76,6 +77,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -84,12 +86,11 @@
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
-import java.util.concurrent.CountDownLatch;
-
/**
* Functional tests for notification inflation from {@link NotificationEntryManager}.
*/
@SmallTest
+@Ignore("Flaking")
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NotificationEntryManagerInflationTest extends SysuiTestCase {
@@ -131,7 +132,6 @@
private NotificationEntryManager mEntryManager;
private NotificationRowBinderImpl mRowBinder;
private Handler mHandler;
- private CountDownLatch mCountDownLatch;
@Before
public void setUp() {
@@ -305,7 +305,9 @@
verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
NotificationEntry entry = entryCaptor.getValue();
- waitForInflation();
+ // Wait for inflation
+ // row inflation, system notification, remote views, contracted view
+ waitForMessages(4);
// THEN the notification has its row inflated
assertNotNull(entry.getRow());
@@ -332,7 +334,7 @@
NotificationEntry.class);
verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
NotificationEntry entry = entryCaptor.getValue();
- waitForInflation();
+ waitForMessages(4);
Mockito.reset(mEntryListener);
Mockito.reset(mPresenter);
@@ -340,7 +342,9 @@
// WHEN the notification is updated
mEntryManager.updateNotification(mSbn, mRankingMap);
- waitForInflation();
+ // Wait for inflation
+ // remote views, contracted view
+ waitForMessages(2);
// THEN the notification has its row and inflated
assertNotNull(entry.getRow());
@@ -353,31 +357,32 @@
verify(mPresenter).updateNotificationViews();
}
- private void waitForInflation() {
+ /**
+ * Wait for a certain number of messages to finish before continuing, timing out if they never
+ * occur.
+ *
+ * As part of the inflation pipeline, the main thread is forced to deal with several callbacks
+ * due to the nature of the API used (generally because they're {@link android.os.AsyncTask}
+ * callbacks). In order, these are
+ *
+ * 1) Callback after row inflation. See {@link RowInflaterTask}.
+ * 2) Callback checking if row is system notification. See
+ * {@link ExpandableNotificationRow#setEntry}
+ * 3) Callback after remote views are created. See
+ * {@link NotificationContentInflater.AsyncInflationTask}.
+ * 4-6) Callback after each content view is inflated/rebound from remote view. See
+ * {@link NotificationContentInflater#applyRemoteView} and {@link InflationFlag}.
+ *
+ * Depending on the test, only some of these will be necessary. For example, generally, not
+ * every content view is inflated or the row may not be inflated if one already exists.
+ *
+ * Currently, the burden is on the developer to figure these out until we have a much more
+ * test-friendly way of executing inflation logic (i.e. pass in an executor).
+ */
+ private void waitForMessages(int numMessages) {
mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
- final CountDownLatch latch = new CountDownLatch(1);
- NotificationEntryListener inflationListener = new NotificationEntryListener() {
- @Override
- public void onEntryInflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onEntryReinflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onInflationError(StatusBarNotification notification, Exception exception) {
- latch.countDown();
- }
- };
- mEntryManager.addNotificationEntryListener(inflationListener);
- while (latch.getCount() != 0) {
- TestableLooper.get(this).processMessages(1);
- }
+ TestableLooper.get(this).processMessages(numMessages);
mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
- mEntryManager.removeNotificationEntryListener(inflationListener);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index c64dd09..a263a72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -315,6 +315,24 @@
verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
}
+ @Test
+ public void testPeopleFiltering_keepPeopleHeaderWhenSectionEmpty() {
+ mSectionsManager.setPeopleHubVisible(true);
+ enablePeopleFiltering();
+
+ setStackState(
+ ChildType.PEOPLE_HEADER,
+ ChildType.ALERTING_HEADER,
+ ChildType.ALERTING,
+ ChildType.GENTLE_HEADER,
+ ChildType.GENTLE
+ );
+ mSectionsManager.updateSectionBoundaries();
+
+ verify(mNssl, never()).removeView(mSectionsManager.getPeopleHeaderView());
+ verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
+ }
+
private void enablePeopleFiltering() {
when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 74c1e63..61b18ee 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -83,6 +83,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
/**
* {@hide}
@@ -399,15 +400,22 @@
public void notifyChange(Uri[] uris, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags, int userHandle,
int targetSdkVersion, String callingPackage) {
+ final ObserverCollector collector = new ObserverCollector();
for (Uri uri : uris) {
notifyChange(uri, observer, observerWantsSelfNotifications, flags, userHandle,
- targetSdkVersion, callingPackage);
+ targetSdkVersion, callingPackage, collector);
+ }
+ final long token = clearCallingIdentity();
+ try {
+ collector.dispatch();
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
public void notifyChange(Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags, int userHandle,
- int targetSdkVersion, String callingPackage) {
+ int targetSdkVersion, String callingPackage, ObserverCollector collector) {
if (DEBUG) Slog.d(TAG, "Notifying update of " + uri + " for user " + userHandle
+ " from observer " + observer + ", flags " + Integer.toHexString(flags));
@@ -442,22 +450,9 @@
// process rather than the caller's process. We will restore this before returning.
long identityToken = clearCallingIdentity();
try {
- ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
- flags, userHandle, calls);
- }
- final int numCalls = calls.size();
- for (int i = 0; i < numCalls; i++) {
- // Immediately dispatch notifications to foreground apps that
- // are important to the user; all other background observers are
- // delayed to avoid stampeding
- final ObserverCall oc = calls.get(i);
- if (oc.mProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- oc.run();
- } else {
- BackgroundThread.getHandler().postDelayed(oc, BACKGROUND_OBSERVER_DELAY);
- }
+ flags, userHandle, collector);
}
if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
SyncManager syncManager = getSyncManager();
@@ -487,40 +482,84 @@
}
}
- public void notifyChange(Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork,
- String callingPackage) {
- notifyChange(uri, observer, observerWantsSelfNotifications,
- syncToNetwork ? ContentResolver.NOTIFY_SYNC_TO_NETWORK : 0,
- UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT, callingPackage);
- }
-
- /** {@hide} */
+ /**
+ * Collection of detected change notifications that should be delivered.
+ * <p>
+ * To help reduce Binder transaction overhead, this class clusters together
+ * multiple {@link Uri} where all other arguments are identical.
+ */
@VisibleForTesting
- public static final class ObserverCall implements Runnable {
- final IContentObserver mObserver;
- final boolean mSelfChange;
- final Uri mUri;
- final int mUserId;
- final int mProcState;
+ public static class ObserverCollector {
+ private final ArrayMap<Key, List<Uri>> collected = new ArrayMap<>();
- ObserverCall(IContentObserver observer, boolean selfChange, Uri uri, int userId,
- int procState) {
- mObserver = observer;
- mSelfChange = selfChange;
- mUri = uri;
- mUserId = userId;
- mProcState = procState;
+ private static class Key {
+ final IContentObserver observer;
+ final int uid;
+ final boolean selfChange;
+ final int flags;
+ final int userId;
+
+ Key(IContentObserver observer, int uid, boolean selfChange, int flags, int userId) {
+ this.observer = observer;
+ this.uid = uid;
+ this.selfChange = selfChange;
+ this.flags = flags;
+ this.userId = userId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Key)) {
+ return false;
+ }
+ final Key other = (Key) o;
+ return Objects.equals(observer, other.observer)
+ && (uid == other.uid)
+ && (selfChange == other.selfChange)
+ && (flags == other.flags)
+ && (userId == other.userId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(observer, uid, selfChange, flags, userId);
+ }
}
- @Override
- public void run() {
- try {
- mObserver.onChange(mSelfChange, mUri, mUserId);
- if (DEBUG) Slog.d(TAG, "Notified " + mObserver + " of update at " + mUri);
- } catch (RemoteException ignored) {
- // We already have a death observer that will clean up if the
- // remote process dies
+ public void collect(IContentObserver observer, int uid, boolean selfChange, Uri uri,
+ int flags, int userId) {
+ final Key key = new Key(observer, uid, selfChange, flags, userId);
+ List<Uri> value = collected.get(key);
+ if (value == null) {
+ value = new ArrayList<>();
+ collected.put(key, value);
+ }
+ value.add(uri);
+ }
+
+ public void dispatch() {
+ for (int i = 0; i < collected.size(); i++) {
+ final Key key = collected.keyAt(i);
+ final List<Uri> value = collected.valueAt(i);
+
+ final Runnable task = () -> {
+ try {
+ key.observer.onChangeEtc(key.selfChange,
+ value.toArray(new Uri[value.size()]), key.flags, key.userId);
+ } catch (RemoteException ignored) {
+ }
+ };
+
+ // Immediately dispatch notifications to foreground apps that
+ // are important to the user; all other background observers are
+ // delayed to avoid stampeding
+ final int procState = LocalServices.getService(ActivityManagerInternal.class)
+ .getUidProcessState(key.uid);
+ if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ task.run();
+ } else {
+ BackgroundThread.getHandler().postDelayed(task, BACKGROUND_OBSERVER_DELAY);
+ }
}
}
}
@@ -1455,10 +1494,6 @@
}
}
- public static final int INSERT_TYPE = 0;
- public static final int UPDATE_TYPE = 1;
- public static final int DELETE_TYPE = 2;
-
private String mName;
private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
@@ -1588,7 +1623,7 @@
private void collectMyObserversLocked(Uri uri, boolean leaf, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
- int targetUserHandle, ArrayList<ObserverCall> calls) {
+ int targetUserHandle, ObserverCollector collector) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
@@ -1628,10 +1663,8 @@
if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
+ " flags=" + Integer.toHexString(flags)
+ " desc=" + entry.notifyForDescendants);
- final int procState = LocalServices.getService(ActivityManagerInternal.class)
- .getUidProcessState(entry.uid);
- calls.add(new ObserverCall(entry.observer, selfChange, uri,
- targetUserHandle, procState));
+ collector.collect(entry.observer, entry.uid, selfChange, uri, flags,
+ targetUserHandle);
}
}
}
@@ -1641,21 +1674,21 @@
*/
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
- int targetUserHandle, ArrayList<ObserverCall> calls) {
+ int targetUserHandle, ObserverCollector collector) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
- flags, targetUserHandle, calls);
+ flags, targetUserHandle, collector);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
+ segment);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
- flags, targetUserHandle, calls);
+ flags, targetUserHandle, collector);
}
int N = mChildren.size();
@@ -1664,7 +1697,7 @@
if (segment == null || node.mName.equals(segment)) {
// We found the child,
node.collectObserversLocked(uri, index + 1, observer,
- observerWantsSelfNotifications, flags, targetUserHandle, calls);
+ observerWantsSelfNotifications, flags, targetUserHandle, collector);
if (segment != null) {
break;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 571f582..1869a46 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2480,6 +2480,11 @@
void onSessionCreated(IInputMethod method, IInputMethodSession session,
InputChannel channel) {
synchronized (mMethodMap) {
+ if (mUserSwitchHandlerTask != null) {
+ // We have a pending user-switching task so it's better to just ignore this session.
+ channel.dispose();
+ return;
+ }
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index acb9900..7a30211 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7628,6 +7628,11 @@
}
@Override
+ boolean canCreateRemoteAnimationTarget() {
+ return true;
+ }
+
+ @Override
void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
Rect outSurfaceInsets) {
final WindowState win = findMainWindow();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 0912b2e..6a47c9e 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -446,7 +446,7 @@
siblings.add(current);
boolean canPromote = true;
- if (parent == null) {
+ if (parent == null || !parent.canCreateRemoteAnimationTarget()) {
canPromote = false;
} else {
// In case a descendant of the parent belongs to the other group, we cannot promote
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index faa6bac8..88d4fb3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3121,6 +3121,11 @@
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
+ @Override
+ boolean canCreateRemoteAnimationTarget() {
+ return true;
+ }
+
WindowState getTopVisibleAppMainWindow() {
final ActivityRecord activity = getTopVisibleActivity();
return activity != null ? activity.findMainWindow() : null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b12c698..10d34b5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2196,6 +2196,10 @@
return null;
}
+ boolean canCreateRemoteAnimationTarget() {
+ return false;
+ }
+
boolean okToDisplay() {
final DisplayContent dc = getDisplayContent();
return dc != null && dc.okToDisplay();
diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
index 891ca74..0e4d2be 100644
--- a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
@@ -16,30 +16,52 @@
package com.android.server.content;
-import java.util.ArrayList;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.ContentResolver;
import android.database.ContentObserver;
+import android.database.IContentObserver;
import android.net.Uri;
+import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
-import com.android.server.content.ContentService.ObserverCall;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.content.ContentService.ObserverCollector;
import com.android.server.content.ContentService.ObserverNode;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+import java.util.Arrays;
+
/**
* atest FrameworksServicesTests:com.android.server.content.ObserverNodeTest
*/
-@SmallTest
-public class ObserverNodeTest extends AndroidTestCase {
- static class TestObserver extends ContentObserver {
+@RunWith(AndroidJUnit4.class)
+public class ObserverNodeTest {
+ static class TestObserver extends ContentObserver {
public TestObserver() {
super(new Handler(Looper.getMainLooper()));
}
}
+ @Test
public void testUri() {
final int myUserHandle = UserHandle.myUserId();
@@ -65,15 +87,15 @@
0, 0, myUserHandle);
}
- ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
-
for (int i = nums.length - 1; i >=0; --i) {
- root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, calls);
- assertEquals(nums[i], calls.size());
- calls.clear();
+ final ObserverCollector collector = mock(ObserverCollector.class);
+ root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, collector);
+ verify(collector, times(nums[i])).collect(
+ any(), anyInt(), anyBoolean(), any(), anyInt(), anyInt());
}
}
+ @Test
public void testUriNotNotify() {
final int myUserHandle = UserHandle.myUserId();
@@ -95,12 +117,67 @@
0, 0, myUserHandle);
}
- ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
-
for (int i = uris.length - 1; i >=0; --i) {
- root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, calls);
- assertEquals(nums[i], calls.size());
- calls.clear();
+ final ObserverCollector collector = mock(ObserverCollector.class);
+ root.collectObserversLocked(uris[i], 0, null, false, 0, myUserHandle, collector);
+ verify(collector, times(nums[i])).collect(
+ any(), anyInt(), anyBoolean(), any(), anyInt(), anyInt());
+ }
+ }
+
+ @Test
+ public void testCluster() throws Exception {
+ final int myUserHandle = UserHandle.myUserId();
+
+ // Assume everything is foreground during our test
+ final ActivityManagerInternal ami = mock(ActivityManagerInternal.class);
+ when(ami.getUidProcessState(anyInt()))
+ .thenReturn(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, ami);
+
+ final IContentObserver observer = mock(IContentObserver.class);
+ when(observer.asBinder()).thenReturn(new Binder());
+
+ final ObserverNode root = new ObserverNode("");
+ root.addObserverLocked(Uri.parse("content://authority/"), observer,
+ true, root, 0, 1000, myUserHandle);
+
+ final ObserverCollector collector = new ObserverCollector();
+ root.collectObserversLocked(Uri.parse("content://authority/1"), 0, null, false,
+ 0, myUserHandle, collector);
+ root.collectObserversLocked(Uri.parse("content://authority/1"), 0, null, false,
+ ContentResolver.NOTIFY_INSERT, myUserHandle, collector);
+ root.collectObserversLocked(Uri.parse("content://authority/2"), 0, null, false,
+ ContentResolver.NOTIFY_INSERT, myUserHandle, collector);
+ root.collectObserversLocked(Uri.parse("content://authority/2"), 0, null, false,
+ ContentResolver.NOTIFY_UPDATE, myUserHandle, collector);
+ collector.dispatch();
+
+ // We should only cluster when all other arguments are equal
+ verify(observer).onChangeEtc(eq(false), argThat(new UriSetMatcher(
+ Uri.parse("content://authority/1"))),
+ eq(0), anyInt());
+ verify(observer).onChangeEtc(eq(false), argThat(new UriSetMatcher(
+ Uri.parse("content://authority/1"),
+ Uri.parse("content://authority/2"))),
+ eq(ContentResolver.NOTIFY_INSERT), anyInt());
+ verify(observer).onChangeEtc(eq(false), argThat(new UriSetMatcher(
+ Uri.parse("content://authority/2"))),
+ eq(ContentResolver.NOTIFY_UPDATE), anyInt());
+ }
+
+ private static class UriSetMatcher implements ArgumentMatcher<Uri[]> {
+ private final ArraySet<Uri> uris;
+
+ public UriSetMatcher(Uri... uris) {
+ this.uris = new ArraySet<>(Arrays.asList(uris));
+ }
+
+ @Override
+ public boolean matches(Uri[] uris) {
+ final ArraySet<Uri> test = new ArraySet<>(Arrays.asList(uris));
+ return this.uris.equals(test);
}
}
}
diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
index 8ccf007..de2f5d9 100644
--- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -242,6 +242,8 @@
* to scan to find the network, see the "DPP Connection Status Object"
* section in the specification for the format, and Table E-4 in
* IEEE Std 802.11-2016 - Global operating classes for more details.
+ * The sparse array key is the Global Operating class, and the value
+ * is an integer array of Wi-Fi channels.
* @param operatingClassArray Array of bands the Enrollee supports as expressed as the Global
* Operating Class, see Table E-4 in IEEE Std 802.11-2016 - Global
* operating classes.